fonttools/Tests/mtiLib/mti_test.py
2023-11-03 10:25:15 +00:00

518 lines
12 KiB
Python

from fontTools.misc.xmlWriter import XMLWriter
from fontTools.ttLib import TTFont
from fontTools.feaLib.lookupDebugInfo import LOOKUP_DEBUG_ENV_VAR
from fontTools import mtiLib
import difflib
from io import StringIO
import os
import sys
import pytest
@pytest.fixture(autouse=True)
def set_lookup_debug_env_var(monkeypatch):
monkeypatch.setenv(LOOKUP_DEBUG_ENV_VAR, "1")
class MtiTest:
GLYPH_ORDER = [
".notdef",
"a",
"b",
"pakannada",
"phakannada",
"vakannada",
"pevowelkannada",
"phevowelkannada",
"vevowelkannada",
"uvowelsignkannada",
"uuvowelsignkannada",
"uvowelsignaltkannada",
"uuvowelsignaltkannada",
"uuvowelsignsinh",
"uvowelsignsinh",
"rakarsinh",
"zero",
"one",
"two",
"three",
"four",
"five",
"six",
"seven",
"eight",
"nine",
"slash",
"fraction",
"A",
"B",
"C",
"fi",
"fl",
"breve",
"acute",
"uniFB01",
"ffi",
"grave",
"commaacent",
"dotbelow",
"dotabove",
"cedilla",
"commaaccent",
"Acircumflex",
"V",
"T",
"acircumflex",
"Aacute",
"Agrave",
"O",
"Oacute",
"Ograve",
"Ocircumflex",
"aacute",
"agrave",
"aimatrabindigurmukhi",
"aimatragurmukhi",
"aimatratippigurmukhi",
"aumatrabindigurmukhi",
"aumatragurmukhi",
"bindigurmukhi",
"eematrabindigurmukhi",
"eematragurmukhi",
"eematratippigurmukhi",
"oomatrabindigurmukhi",
"oomatragurmukhi",
"oomatratippigurmukhi",
"lagurmukhi",
"lanuktagurmukhi",
"nagurmukhi",
"nanuktagurmukhi",
"ngagurmukhi",
"nganuktagurmukhi",
"nnagurmukhi",
"nnanuktagurmukhi",
"tthagurmukhi",
"tthanuktagurmukhi",
"bsuperior",
"isuperior",
"vsuperior",
"wsuperior",
"periodsuperior",
"osuperior",
"tsuperior",
"dollarsuperior",
"fsuperior",
"gsuperior",
"zsuperior",
"dsuperior",
"psuperior",
"hsuperior",
"oesuperior",
"aesuperior",
"centsuperior",
"esuperior",
"lsuperior",
"qsuperior",
"csuperior",
"asuperior",
"commasuperior",
"xsuperior",
"egravesuperior",
"usuperior",
"rsuperior",
"nsuperior",
"ssuperior",
"msuperior",
"jsuperior",
"ysuperior",
"ksuperior",
"guilsinglright",
"guilsinglleft",
"uniF737",
"uniE11C",
"uniE11D",
"uniE11A",
"uni2077",
"uni2087",
"uniE11B",
"uniE119",
"uniE0DD",
"uniE0DE",
"uniF736",
"uniE121",
"uniE122",
"uniE11F",
"uni2076",
"uni2086",
"uniE120",
"uniE11E",
"uniE0DB",
"uniE0DC",
"uniF733",
"uniE12B",
"uniE12C",
"uniE129",
"uni00B3",
"uni2083",
"uniE12A",
"uniE128",
"uniF732",
"uniE133",
"uniE134",
"uniE131",
"uni00B2",
"uni2082",
"uniE132",
"uniE130",
"uniE0F9",
"uniF734",
"uniE0D4",
"uniE0D5",
"uniE0D2",
"uni2074",
"uni2084",
"uniE0D3",
"uniE0D1",
"uniF730",
"uniE13D",
"uniE13E",
"uniE13A",
"uni2070",
"uni2080",
"uniE13B",
"uniE139",
"uniE13C",
"uniF739",
"uniE0EC",
"uniE0ED",
"uniE0EA",
"uni2079",
"uni2089",
"uniE0EB",
"uniE0E9",
"uniF735",
"uniE0CD",
"uniE0CE",
"uniE0CB",
"uni2075",
"uni2085",
"uniE0CC",
"uniE0CA",
"uniF731",
"uniE0F3",
"uniE0F4",
"uniE0F1",
"uni00B9",
"uni2081",
"uniE0F2",
"uniE0F0",
"uniE0F8",
"uniF738",
"uniE0C0",
"uniE0C1",
"uniE0BE",
"uni2078",
"uni2088",
"uniE0BF",
"uniE0BD",
"I",
"Ismall",
"t",
"i",
"f",
"IJ",
"J",
"IJsmall",
"Jsmall",
"tt",
"ij",
"j",
"ffb",
"ffh",
"h",
"ffk",
"k",
"ffl",
"l",
"fft",
"fb",
"ff",
"fh",
"fj",
"fk",
"ft",
"janyevoweltelugu",
"kassevoweltelugu",
"jaivoweltelugu",
"nyasubscripttelugu",
"kaivoweltelugu",
"ssasubscripttelugu",
"bayi1",
"jeemi1",
"kafi1",
"ghafi1",
"laami1",
"kafm1",
"ghafm1",
"laamm1",
"rayf2",
"reyf2",
"yayf2",
"zayf2",
"fayi1",
"ayehf2",
"hamzayeharabf2",
"hamzayehf2",
"yehf2",
"ray",
"rey",
"zay",
"yay",
"dal",
"del",
"zal",
"rayf1",
"reyf1",
"yayf1",
"zayf1",
"ayehf1",
"hamzayeharabf1",
"hamzayehf1",
"yehf1",
"dal1",
"del1",
"zal1",
"onehalf",
"onehalf.alt",
"onequarter",
"onequarter.alt",
"threequarters",
"threequarters.alt",
"AlefSuperiorNS",
"DammaNS",
"DammaRflxNS",
"DammatanNS",
"Fatha2dotsNS",
"FathaNS",
"FathatanNS",
"FourDotsAboveNS",
"HamzaAboveNS",
"MaddaNS",
"OneDotAbove2NS",
"OneDotAboveNS",
"ShaddaAlefNS",
"ShaddaDammaNS",
"ShaddaDammatanNS",
"ShaddaFathatanNS",
"ShaddaKasraNS",
"ShaddaKasratanNS",
"ShaddaNS",
"SharetKafNS",
"SukunNS",
"ThreeDotsDownAboveNS",
"ThreeDotsUpAboveNS",
"TwoDotsAboveNS",
"TwoDotsVerticalAboveNS",
"UltapeshNS",
"WaslaNS",
"AinIni.12m_MeemFin.02",
"AinIni_YehBarreeFin",
"AinMed_YehBarreeFin",
"BehxIni_MeemFin",
"BehxIni_NoonGhunnaFin",
"BehxIni_RehFin",
"BehxIni_RehFin.b",
"BehxMed_MeemFin.py",
"BehxMed_NoonGhunnaFin",
"BehxMed_NoonGhunnaFin.cup",
"BehxMed_RehFin",
"BehxMed_RehFin.cup",
"BehxMed_YehxFin",
"FehxMed_YehBarreeFin",
"HahIni_YehBarreeFin",
"KafIni_YehBarreeFin",
"KafMed.12_YehxFin.01",
"KafMed_MeemFin",
"KafMed_YehBarreeFin",
"LamAlefFin",
"LamAlefFin.cup",
"LamAlefFin.cut",
"LamAlefFin.short",
"LamAlefSep",
"LamIni_MeemFin",
"LamIni_YehBarreeFin",
"LamMed_MeemFin",
"LamMed_MeemFin.b",
"LamMed_YehxFin",
"LamMed_YehxFin.cup",
"TahIni_YehBarreeFin",
"null",
"CR",
"space",
"exclam",
"quotedbl",
"numbersign",
]
# Feature files in data/*.txt; output gets compared to data/*.ttx.
TESTS = {
None: ("mti/cmap",),
"cmap": ("mti/cmap",),
"GSUB": (
"featurename-backward",
"featurename-forward",
"lookupnames-backward",
"lookupnames-forward",
"mixed-toplevels",
"mti/scripttable",
"mti/chainedclass",
"mti/chainedcoverage",
"mti/chained-glyph",
"mti/gsubalternate",
"mti/gsubligature",
"mti/gsubmultiple",
"mti/gsubreversechanined",
"mti/gsubsingle",
),
"GPOS": (
"mti/scripttable",
"mti/chained-glyph",
"mti/gposcursive",
"mti/gposkernset",
"mti/gposmarktobase",
"mti/gpospairclass",
"mti/gpospairglyph",
"mti/gpossingle",
"mti/mark-to-ligature",
),
"GDEF": (
"mti/gdefattach",
"mti/gdefclasses",
"mti/gdefligcaret",
"mti/gdefmarkattach",
"mti/gdefmarkfilter",
),
}
# TODO:
# https://github.com/Monotype/OpenType_Table_Source/issues/12
#
# 'mti/featuretable'
# 'mti/contextclass'
# 'mti/contextcoverage'
# 'mti/context-glyph'
@staticmethod
def getpath(testfile):
path, _ = os.path.split(__file__)
return os.path.join(path, "data", testfile)
def expect_ttx(self, expected_ttx, actual_ttx, fromfile=None, tofile=None):
expected = [l + "\n" for l in expected_ttx.split("\n")]
actual = [l + "\n" for l in actual_ttx.split("\n")]
if actual != expected:
sys.stderr.write("\n")
for line in difflib.unified_diff(
expected, actual, fromfile=fromfile, tofile=tofile
):
sys.stderr.write(line)
pytest.fail("TTX output is different from expected")
@classmethod
def create_font(celf):
font = TTFont()
font.setGlyphOrder(celf.GLYPH_ORDER)
return font
def check_mti_file(self, name, tableTag=None):
xml_expected_path = self.getpath(
"%s.ttx" % name + ("." + tableTag if tableTag is not None else "")
)
with open(xml_expected_path, "rt", encoding="utf-8") as xml_expected_file:
xml_expected = xml_expected_file.read()
font = self.create_font()
with open(self.getpath("%s.txt" % name), "rt", encoding="utf-8") as f:
table = mtiLib.build(f, font, tableTag=tableTag)
if tableTag is not None:
assert tableTag == table.tableTag
tableTag = table.tableTag
# Make sure it compiles.
blob = table.compile(font)
# Make sure it decompiles.
decompiled = table.__class__()
decompiled.decompile(blob, font)
# XML from built object.
writer = XMLWriter(StringIO())
writer.begintag(tableTag)
writer.newline()
table.toXML(writer, font)
writer.endtag(tableTag)
writer.newline()
xml_built = writer.file.getvalue()
# XML from decompiled object.
writer = XMLWriter(StringIO())
writer.begintag(tableTag)
writer.newline()
decompiled.toXML(writer, font)
writer.endtag(tableTag)
writer.newline()
xml_binary = writer.file.getvalue()
self.expect_ttx(xml_binary, xml_built, fromfile="decompiled", tofile="built")
self.expect_ttx(
xml_expected, xml_built, fromfile=xml_expected_path, tofile="built"
)
from fontTools.misc import xmlReader
f = StringIO()
f.write(xml_expected)
f.seek(0)
font2 = TTFont()
font2.setGlyphOrder(font.getGlyphOrder())
reader = xmlReader.XMLReader(f, font2)
reader.read(rootless=True)
# XML from object read from XML.
writer = XMLWriter(StringIO())
writer.begintag(tableTag)
writer.newline()
font2[tableTag].toXML(writer, font)
writer.endtag(tableTag)
writer.newline()
xml_fromxml = writer.file.getvalue()
self.expect_ttx(
xml_expected, xml_fromxml, fromfile=xml_expected_path, tofile="fromxml"
)
def generate_mti_file_test(name, tableTag=None):
return lambda self: self.check_mti_file(
os.path.join(*name.split("/")), tableTag=tableTag
)
for tableTag, tests in MtiTest.TESTS.items():
for name in tests:
setattr(
MtiTest,
"test_MtiFile_%s%s" % (name, "_" + tableTag if tableTag else ""),
generate_mti_file_test(name, tableTag=tableTag),
)
if __name__ == "__main__":
if len(sys.argv) > 1:
from fontTools.mtiLib import main
font = MtiTest.create_font()
sys.exit(main(sys.argv[1:], font))
sys.exit(pytest.main(sys.argv))