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))