• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from fontTools.misc.xmlWriter import XMLWriter
2from fontTools.ttLib import TTFont
3from fontTools import mtiLib
4import difflib
5from io import StringIO
6import os
7import sys
8import unittest
9
10
11class MtiTest(unittest.TestCase):
12
13    GLYPH_ORDER = ['.notdef',
14        'a', 'b', 'pakannada', 'phakannada', 'vakannada', 'pevowelkannada',
15        'phevowelkannada', 'vevowelkannada', 'uvowelsignkannada', 'uuvowelsignkannada',
16        'uvowelsignaltkannada', 'uuvowelsignaltkannada', 'uuvowelsignsinh',
17        'uvowelsignsinh', 'rakarsinh', 'zero', 'one', 'two', 'three', 'four', 'five',
18        'six', 'seven', 'eight', 'nine', 'slash', 'fraction', 'A', 'B', 'C', 'fi',
19        'fl', 'breve', 'acute', 'uniFB01', 'ffi', 'grave', 'commaacent', 'dotbelow',
20        'dotabove', 'cedilla', 'commaaccent', 'Acircumflex', 'V', 'T', 'acircumflex',
21        'Aacute', 'Agrave', 'O', 'Oacute', 'Ograve', 'Ocircumflex', 'aacute', 'agrave',
22        'aimatrabindigurmukhi', 'aimatragurmukhi', 'aimatratippigurmukhi',
23        'aumatrabindigurmukhi', 'aumatragurmukhi', 'bindigurmukhi',
24        'eematrabindigurmukhi', 'eematragurmukhi', 'eematratippigurmukhi',
25        'oomatrabindigurmukhi', 'oomatragurmukhi', 'oomatratippigurmukhi',
26        'lagurmukhi', 'lanuktagurmukhi', 'nagurmukhi', 'nanuktagurmukhi',
27        'ngagurmukhi', 'nganuktagurmukhi', 'nnagurmukhi', 'nnanuktagurmukhi',
28        'tthagurmukhi', 'tthanuktagurmukhi', 'bsuperior', 'isuperior', 'vsuperior',
29        'wsuperior', 'periodsuperior', 'osuperior', 'tsuperior', 'dollarsuperior',
30        'fsuperior', 'gsuperior', 'zsuperior', 'dsuperior', 'psuperior', 'hsuperior',
31        'oesuperior', 'aesuperior', 'centsuperior', 'esuperior', 'lsuperior',
32        'qsuperior', 'csuperior', 'asuperior', 'commasuperior', 'xsuperior',
33        'egravesuperior', 'usuperior', 'rsuperior', 'nsuperior', 'ssuperior',
34        'msuperior', 'jsuperior', 'ysuperior', 'ksuperior', 'guilsinglright',
35        'guilsinglleft', 'uniF737', 'uniE11C', 'uniE11D', 'uniE11A', 'uni2077',
36        'uni2087', 'uniE11B', 'uniE119', 'uniE0DD', 'uniE0DE', 'uniF736', 'uniE121',
37        'uniE122', 'uniE11F', 'uni2076', 'uni2086', 'uniE120', 'uniE11E', 'uniE0DB',
38        'uniE0DC', 'uniF733', 'uniE12B', 'uniE12C', 'uniE129', 'uni00B3', 'uni2083',
39        'uniE12A', 'uniE128', 'uniF732', 'uniE133', 'uniE134', 'uniE131', 'uni00B2',
40        'uni2082', 'uniE132', 'uniE130', 'uniE0F9', 'uniF734', 'uniE0D4', 'uniE0D5',
41        'uniE0D2', 'uni2074', 'uni2084', 'uniE0D3', 'uniE0D1', 'uniF730', 'uniE13D',
42        'uniE13E', 'uniE13A', 'uni2070', 'uni2080', 'uniE13B', 'uniE139', 'uniE13C',
43        'uniF739', 'uniE0EC', 'uniE0ED', 'uniE0EA', 'uni2079', 'uni2089', 'uniE0EB',
44        'uniE0E9', 'uniF735', 'uniE0CD', 'uniE0CE', 'uniE0CB', 'uni2075', 'uni2085',
45        'uniE0CC', 'uniE0CA', 'uniF731', 'uniE0F3', 'uniE0F4', 'uniE0F1', 'uni00B9',
46        'uni2081', 'uniE0F2', 'uniE0F0', 'uniE0F8', 'uniF738', 'uniE0C0', 'uniE0C1',
47        'uniE0BE', 'uni2078', 'uni2088', 'uniE0BF', 'uniE0BD', 'I', 'Ismall', 't', 'i',
48        'f', 'IJ', 'J', 'IJsmall', 'Jsmall', 'tt', 'ij', 'j', 'ffb', 'ffh', 'h', 'ffk',
49        'k', 'ffl', 'l', 'fft', 'fb', 'ff', 'fh', 'fj', 'fk', 'ft', 'janyevoweltelugu',
50        'kassevoweltelugu', 'jaivoweltelugu', 'nyasubscripttelugu', 'kaivoweltelugu',
51        'ssasubscripttelugu', 'bayi1', 'jeemi1', 'kafi1', 'ghafi1', 'laami1', 'kafm1',
52        'ghafm1', 'laamm1', 'rayf2', 'reyf2', 'yayf2', 'zayf2', 'fayi1', 'ayehf2',
53        'hamzayeharabf2', 'hamzayehf2', 'yehf2', 'ray', 'rey', 'zay', 'yay', 'dal',
54        'del', 'zal', 'rayf1', 'reyf1', 'yayf1', 'zayf1', 'ayehf1', 'hamzayeharabf1',
55        'hamzayehf1', 'yehf1', 'dal1', 'del1', 'zal1', 'onehalf', 'onehalf.alt',
56        'onequarter', 'onequarter.alt', 'threequarters', 'threequarters.alt',
57        'AlefSuperiorNS', 'DammaNS', 'DammaRflxNS', 'DammatanNS', 'Fatha2dotsNS',
58        'FathaNS', 'FathatanNS', 'FourDotsAboveNS', 'HamzaAboveNS', 'MaddaNS',
59        'OneDotAbove2NS', 'OneDotAboveNS', 'ShaddaAlefNS', 'ShaddaDammaNS',
60        'ShaddaDammatanNS', 'ShaddaFathatanNS', 'ShaddaKasraNS', 'ShaddaKasratanNS',
61        'ShaddaNS', 'SharetKafNS', 'SukunNS', 'ThreeDotsDownAboveNS',
62        'ThreeDotsUpAboveNS', 'TwoDotsAboveNS', 'TwoDotsVerticalAboveNS', 'UltapeshNS',
63        'WaslaNS', 'AinIni.12m_MeemFin.02', 'AinIni_YehBarreeFin',
64        'AinMed_YehBarreeFin', 'BehxIni_MeemFin', 'BehxIni_NoonGhunnaFin',
65        'BehxIni_RehFin', 'BehxIni_RehFin.b', 'BehxMed_MeemFin.py',
66        'BehxMed_NoonGhunnaFin', 'BehxMed_NoonGhunnaFin.cup', 'BehxMed_RehFin',
67        'BehxMed_RehFin.cup', 'BehxMed_YehxFin', 'FehxMed_YehBarreeFin',
68        'HahIni_YehBarreeFin', 'KafIni_YehBarreeFin', 'KafMed.12_YehxFin.01',
69        'KafMed_MeemFin', 'KafMed_YehBarreeFin', 'LamAlefFin', 'LamAlefFin.cup',
70        'LamAlefFin.cut', 'LamAlefFin.short', 'LamAlefSep', 'LamIni_MeemFin',
71        'LamIni_YehBarreeFin', 'LamMed_MeemFin', 'LamMed_MeemFin.b', 'LamMed_YehxFin',
72        'LamMed_YehxFin.cup', 'TahIni_YehBarreeFin', 'null', 'CR', 'space',
73        'exclam', 'quotedbl', 'numbersign',
74    ]
75
76    # Feature files in data/*.txt; output gets compared to data/*.ttx.
77    TESTS = {
78        None: (
79            'mti/cmap',
80        ),
81        'cmap': (
82            'mti/cmap',
83        ),
84        'GSUB': (
85            'featurename-backward',
86            'featurename-forward',
87            'lookupnames-backward',
88            'lookupnames-forward',
89            'mixed-toplevels',
90
91            'mti/scripttable',
92            'mti/chainedclass',
93            'mti/chainedcoverage',
94            'mti/chained-glyph',
95            'mti/gsubalternate',
96            'mti/gsubligature',
97            'mti/gsubmultiple',
98            'mti/gsubreversechanined',
99            'mti/gsubsingle',
100        ),
101        'GPOS': (
102            'mti/scripttable',
103            'mti/chained-glyph',
104            'mti/gposcursive',
105            'mti/gposkernset',
106            'mti/gposmarktobase',
107            'mti/gpospairclass',
108            'mti/gpospairglyph',
109            'mti/gpossingle',
110            'mti/mark-to-ligature',
111        ),
112        'GDEF': (
113            'mti/gdefattach',
114            'mti/gdefclasses',
115            'mti/gdefligcaret',
116            'mti/gdefmarkattach',
117            'mti/gdefmarkfilter',
118        ),
119    }
120    # TODO:
121    # https://github.com/Monotype/OpenType_Table_Source/issues/12
122    #
123    #        'mti/featuretable'
124    #        'mti/contextclass'
125    #        'mti/contextcoverage'
126    #        'mti/context-glyph'
127
128    def __init__(self, methodName):
129        unittest.TestCase.__init__(self, methodName)
130        # Python 3 renamed assertRaisesRegexp to assertRaisesRegex,
131        # and fires deprecation warnings if a program uses the old name.
132        if not hasattr(self, "assertRaisesRegex"):
133            self.assertRaisesRegex = self.assertRaisesRegexp
134
135    def setUp(self):
136        pass
137
138    def tearDown(self):
139        pass
140
141    @staticmethod
142    def getpath(testfile):
143        path, _ = os.path.split(__file__)
144        return os.path.join(path, "data", testfile)
145
146    def expect_ttx(self, expected_ttx, actual_ttx, fromfile=None, tofile=None):
147        expected = [l+'\n' for l in expected_ttx.split('\n')]
148        actual = [l+'\n' for l in actual_ttx.split('\n')]
149        if actual != expected:
150            sys.stderr.write('\n')
151            for line in difflib.unified_diff(
152                    expected, actual, fromfile=fromfile, tofile=tofile):
153                sys.stderr.write(line)
154            self.fail("TTX output is different from expected")
155
156    @classmethod
157    def create_font(celf):
158        font = TTFont()
159        font.setGlyphOrder(celf.GLYPH_ORDER)
160        return font
161
162    def check_mti_file(self, name, tableTag=None):
163
164        xml_expected_path = self.getpath("%s.ttx" % name + ('.'+tableTag if tableTag is not None else ''))
165        with open(xml_expected_path, 'rt', encoding="utf-8") as xml_expected_file:
166            xml_expected = xml_expected_file.read()
167
168        font = self.create_font()
169
170        with open(self.getpath("%s.txt" % name), 'rt', encoding="utf-8") as f:
171            table = mtiLib.build(f, font, tableTag=tableTag)
172
173        if tableTag is not None:
174            self.assertEqual(tableTag, table.tableTag)
175        tableTag = table.tableTag
176
177        # Make sure it compiles.
178        blob = table.compile(font)
179
180        # Make sure it decompiles.
181        decompiled = table.__class__()
182        decompiled.decompile(blob, font)
183
184        # XML from built object.
185        writer = XMLWriter(StringIO())
186        writer.begintag(tableTag); writer.newline()
187        table.toXML(writer, font)
188        writer.endtag(tableTag); writer.newline()
189        xml_built = writer.file.getvalue()
190
191        # XML from decompiled object.
192        writer = XMLWriter(StringIO())
193        writer.begintag(tableTag); writer.newline()
194        decompiled.toXML(writer, font)
195        writer.endtag(tableTag); writer.newline()
196        xml_binary = writer.file.getvalue()
197
198        self.expect_ttx(xml_binary,   xml_built, fromfile='decompiled',      tofile='built')
199        self.expect_ttx(xml_expected, xml_built, fromfile=xml_expected_path, tofile='built')
200
201        from fontTools.misc import xmlReader
202        f = StringIO()
203        f.write(xml_expected)
204        f.seek(0)
205        font2 = TTFont()
206        font2.setGlyphOrder(font.getGlyphOrder())
207        reader = xmlReader.XMLReader(f, font2)
208        reader.read(rootless=True)
209
210        # XML from object read from XML.
211        writer = XMLWriter(StringIO())
212        writer.begintag(tableTag); writer.newline()
213        font2[tableTag].toXML(writer, font)
214        writer.endtag(tableTag); writer.newline()
215        xml_fromxml = writer.file.getvalue()
216
217        self.expect_ttx(xml_expected, xml_fromxml, fromfile=xml_expected_path, tofile='fromxml')
218
219def generate_mti_file_test(name, tableTag=None):
220    return lambda self: self.check_mti_file(os.path.join(*name.split('/')), tableTag=tableTag)
221
222
223for tableTag,tests in MtiTest.TESTS.items():
224    for name in tests:
225        setattr(MtiTest, "test_MtiFile_%s%s" % (name, '_'+tableTag if tableTag else ''),
226                generate_mti_file_test(name, tableTag=tableTag))
227
228
229if __name__ == "__main__":
230    if len(sys.argv) > 1:
231        from fontTools.mtiLib import main
232        font = MtiTest.create_font()
233        sys.exit(main(sys.argv[1:], font))
234    sys.exit(unittest.main())
235