• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from fontTools.ttLib import TTFont, tagToXML
2from io import StringIO
3import os
4import sys
5import re
6import contextlib
7import pytest
8
9try:
10    import unicodedata2
11except ImportError:
12    if sys.version_info[:2] < (3, 6):
13        unicodedata2 = None
14    else:
15        # on 3.6 the built-in unicodedata is the same as unicodedata2 backport
16        import unicodedata
17        unicodedata2 = unicodedata
18
19
20# Font files in data/*.{o,t}tf; output gets compared to data/*.ttx.*
21TESTS = {
22    "aots/base.otf":                                ('CFF ', 'cmap', 'head',
23                                                     'hhea', 'hmtx', 'maxp',
24                                                     'name', 'OS/2', 'post'),
25    "aots/classdef1_font1.otf":                     ('GSUB',),
26    "aots/classdef1_font2.otf":                     ('GSUB',),
27    "aots/classdef1_font3.otf":                     ('GSUB',),
28    "aots/classdef1_font4.otf":                     ('GSUB',),
29    "aots/classdef2_font1.otf":                     ('GSUB',),
30    "aots/classdef2_font2.otf":                     ('GSUB',),
31    "aots/classdef2_font3.otf":                     ('GSUB',),
32    "aots/classdef2_font4.otf":                     ('GSUB',),
33    "aots/cmap0_font1.otf":                         ('cmap',),
34    "aots/cmap10_font1.otf":                        ('cmap',),
35    "aots/cmap10_font2.otf":                        ('cmap',),
36    "aots/cmap12_font1.otf":                        ('cmap',),
37    "aots/cmap14_font1.otf":                        ('cmap',),
38    "aots/cmap2_font1.otf":                         ('cmap',),
39    "aots/cmap4_font1.otf":                         ('cmap',),
40    "aots/cmap4_font2.otf":                         ('cmap',),
41    "aots/cmap4_font3.otf":                         ('cmap',),
42    "aots/cmap4_font4.otf":                         ('cmap',),
43    "aots/cmap6_font1.otf":                         ('cmap',),
44    "aots/cmap6_font2.otf":                         ('cmap',),
45    "aots/cmap8_font1.otf":                         ('cmap',),
46    "aots/cmap_composition_font1.otf":              ('cmap',),
47    "aots/cmap_subtableselection_font1.otf":        ('cmap',),
48    "aots/cmap_subtableselection_font2.otf":        ('cmap',),
49    "aots/cmap_subtableselection_font3.otf":        ('cmap',),
50    "aots/cmap_subtableselection_font4.otf":        ('cmap',),
51    "aots/cmap_subtableselection_font5.otf":        ('cmap',),
52    "aots/gpos1_1_lookupflag_f1.otf":               ('GDEF', 'GPOS'),
53    "aots/gpos1_1_simple_f1.otf":                   ('GPOS',),
54    "aots/gpos1_1_simple_f2.otf":                   ('GPOS',),
55    "aots/gpos1_1_simple_f3.otf":                   ('GPOS',),
56    "aots/gpos1_1_simple_f4.otf":                   ('GPOS',),
57    "aots/gpos1_2_font1.otf":                       ('GPOS',),
58    "aots/gpos1_2_font2.otf":                       ('GDEF', 'GPOS'),
59    "aots/gpos2_1_font6.otf":                       ('GPOS',),
60    "aots/gpos2_1_font7.otf":                       ('GPOS',),
61    "aots/gpos2_1_lookupflag_f1.otf":               ('GDEF', 'GPOS'),
62    "aots/gpos2_1_lookupflag_f2.otf":               ('GDEF', 'GPOS'),
63    "aots/gpos2_1_next_glyph_f1.otf":               ('GPOS',),
64    "aots/gpos2_1_next_glyph_f2.otf":               ('GPOS',),
65    "aots/gpos2_1_simple_f1.otf":                   ('GPOS',),
66    "aots/gpos2_2_font1.otf":                       ('GPOS',),
67    "aots/gpos2_2_font2.otf":                       ('GDEF', 'GPOS'),
68    "aots/gpos2_2_font3.otf":                       ('GDEF', 'GPOS'),
69    "aots/gpos2_2_font4.otf":                       ('GPOS',),
70    "aots/gpos2_2_font5.otf":                       ('GPOS',),
71    "aots/gpos3_font1.otf":                         ('GPOS',),
72    "aots/gpos3_font2.otf":                         ('GDEF', 'GPOS'),
73    "aots/gpos3_font3.otf":                         ('GDEF', 'GPOS'),
74    "aots/gpos4_lookupflag_f1.otf":                 ('GDEF', 'GPOS'),
75    "aots/gpos4_lookupflag_f2.otf":                 ('GDEF', 'GPOS'),
76    "aots/gpos4_multiple_anchors_1.otf":            ('GDEF', 'GPOS'),
77    "aots/gpos4_simple_1.otf":                      ('GDEF', 'GPOS'),
78    "aots/gpos5_font1.otf":                         ('GDEF', 'GPOS', 'GSUB'),
79    "aots/gpos6_font1.otf":                         ('GDEF', 'GPOS'),
80    "aots/gpos7_1_font1.otf":                       ('GPOS',),
81    "aots/gpos9_font1.otf":                         ('GPOS',),
82    "aots/gpos9_font2.otf":                         ('GPOS',),
83    "aots/gpos_chaining1_boundary_f1.otf":          ('GDEF', 'GPOS'),
84    "aots/gpos_chaining1_boundary_f2.otf":          ('GDEF', 'GPOS'),
85    "aots/gpos_chaining1_boundary_f3.otf":          ('GDEF', 'GPOS'),
86    "aots/gpos_chaining1_boundary_f4.otf":          ('GDEF', 'GPOS'),
87    "aots/gpos_chaining1_lookupflag_f1.otf":        ('GDEF', 'GPOS'),
88    "aots/gpos_chaining1_multiple_subrules_f1.otf": ('GDEF', 'GPOS'),
89    "aots/gpos_chaining1_multiple_subrules_f2.otf": ('GDEF', 'GPOS'),
90    "aots/gpos_chaining1_next_glyph_f1.otf":        ('GDEF', 'GPOS'),
91    "aots/gpos_chaining1_simple_f1.otf":            ('GDEF', 'GPOS'),
92    "aots/gpos_chaining1_simple_f2.otf":            ('GDEF', 'GPOS'),
93    "aots/gpos_chaining1_successive_f1.otf":        ('GDEF', 'GPOS'),
94    "aots/gpos_chaining2_boundary_f1.otf":          ('GDEF', 'GPOS'),
95    "aots/gpos_chaining2_boundary_f2.otf":          ('GDEF', 'GPOS'),
96    "aots/gpos_chaining2_boundary_f3.otf":          ('GDEF', 'GPOS'),
97    "aots/gpos_chaining2_boundary_f4.otf":          ('GDEF', 'GPOS'),
98    "aots/gpos_chaining2_lookupflag_f1.otf":        ('GDEF', 'GPOS'),
99    "aots/gpos_chaining2_multiple_subrules_f1.otf": ('GDEF', 'GPOS'),
100    "aots/gpos_chaining2_multiple_subrules_f2.otf": ('GDEF', 'GPOS'),
101    "aots/gpos_chaining2_next_glyph_f1.otf":        ('GDEF', 'GPOS'),
102    "aots/gpos_chaining2_simple_f1.otf":            ('GDEF', 'GPOS'),
103    "aots/gpos_chaining2_simple_f2.otf":            ('GDEF', 'GPOS'),
104    "aots/gpos_chaining2_successive_f1.otf":        ('GDEF', 'GPOS'),
105    "aots/gpos_chaining3_boundary_f1.otf":          ('GDEF', 'GPOS'),
106    "aots/gpos_chaining3_boundary_f2.otf":          ('GDEF', 'GPOS'),
107    "aots/gpos_chaining3_boundary_f3.otf":          ('GDEF', 'GPOS'),
108    "aots/gpos_chaining3_boundary_f4.otf":          ('GDEF', 'GPOS'),
109    "aots/gpos_chaining3_lookupflag_f1.otf":        ('GDEF', 'GPOS'),
110    "aots/gpos_chaining3_next_glyph_f1.otf":        ('GDEF', 'GPOS'),
111    "aots/gpos_chaining3_simple_f1.otf":            ('GDEF', 'GPOS'),
112    "aots/gpos_chaining3_simple_f2.otf":            ('GDEF', 'GPOS'),
113    "aots/gpos_chaining3_successive_f1.otf":        ('GDEF', 'GPOS'),
114    "aots/gpos_context1_boundary_f1.otf":           ('GDEF', 'GPOS'),
115    "aots/gpos_context1_boundary_f2.otf":           ('GDEF', 'GPOS'),
116    "aots/gpos_context1_expansion_f1.otf":          ('GDEF', 'GPOS'),
117    "aots/gpos_context1_lookupflag_f1.otf":         ('GDEF', 'GPOS'),
118    "aots/gpos_context1_lookupflag_f2.otf":         ('GDEF', 'GPOS'),
119    "aots/gpos_context1_multiple_subrules_f1.otf":  ('GDEF', 'GPOS'),
120    "aots/gpos_context1_multiple_subrules_f2.otf":  ('GDEF', 'GPOS'),
121    "aots/gpos_context1_next_glyph_f1.otf":         ('GDEF', 'GPOS'),
122    "aots/gpos_context1_simple_f1.otf":             ('GDEF', 'GPOS'),
123    "aots/gpos_context1_simple_f2.otf":             ('GDEF', 'GPOS'),
124    "aots/gpos_context1_successive_f1.otf":         ('GDEF', 'GPOS'),
125    "aots/gpos_context2_boundary_f1.otf":           ('GDEF', 'GPOS'),
126    "aots/gpos_context2_boundary_f2.otf":           ('GDEF', 'GPOS'),
127    "aots/gpos_context2_classes_f1.otf":            ('GDEF', 'GPOS'),
128    "aots/gpos_context2_classes_f2.otf":            ('GDEF', 'GPOS'),
129    "aots/gpos_context2_expansion_f1.otf":          ('GDEF', 'GPOS'),
130    "aots/gpos_context2_lookupflag_f1.otf":         ('GDEF', 'GPOS'),
131    "aots/gpos_context2_lookupflag_f2.otf":         ('GDEF', 'GPOS'),
132    "aots/gpos_context2_multiple_subrules_f1.otf":  ('GDEF', 'GPOS'),
133    "aots/gpos_context2_multiple_subrules_f2.otf":  ('GDEF', 'GPOS'),
134    "aots/gpos_context2_next_glyph_f1.otf":         ('GDEF', 'GPOS'),
135    "aots/gpos_context2_simple_f1.otf":             ('GDEF', 'GPOS'),
136    "aots/gpos_context2_simple_f2.otf":             ('GDEF', 'GPOS'),
137    "aots/gpos_context2_successive_f1.otf":         ('GDEF', 'GPOS'),
138    "aots/gpos_context3_boundary_f1.otf":           ('GDEF', 'GPOS'),
139    "aots/gpos_context3_boundary_f2.otf":           ('GDEF', 'GPOS'),
140    "aots/gpos_context3_lookupflag_f1.otf":         ('GDEF', 'GPOS'),
141    "aots/gpos_context3_lookupflag_f2.otf":         ('GDEF', 'GPOS'),
142    "aots/gpos_context3_next_glyph_f1.otf":         ('GDEF', 'GPOS'),
143    "aots/gpos_context3_simple_f1.otf":             ('GDEF', 'GPOS'),
144    "aots/gpos_context3_successive_f1.otf":         ('GDEF', 'GPOS'),
145    "aots/gsub1_1_lookupflag_f1.otf":               ('GDEF', 'GSUB'),
146    "aots/gsub1_1_modulo_f1.otf":                   ('GSUB',),
147    "aots/gsub1_1_simple_f1.otf":                   ('GSUB',),
148    "aots/gsub1_2_lookupflag_f1.otf":               ('GDEF', 'GSUB'),
149    "aots/gsub1_2_simple_f1.otf":                   ('GSUB',),
150    "aots/gsub2_1_lookupflag_f1.otf":               ('GDEF', 'GSUB'),
151    "aots/gsub2_1_multiple_sequences_f1.otf":       ('GSUB',),
152    "aots/gsub2_1_simple_f1.otf":                   ('GSUB',),
153    "aots/gsub3_1_lookupflag_f1.otf":               ('GDEF', 'GSUB'),
154    "aots/gsub3_1_multiple_f1.otf":                 ('GSUB',),
155    "aots/gsub3_1_simple_f1.otf":                   ('GSUB',),
156    "aots/gsub4_1_lookupflag_f1.otf":               ('GDEF', 'GSUB'),
157    "aots/gsub4_1_multiple_ligatures_f1.otf":       ('GSUB',),
158    "aots/gsub4_1_multiple_ligatures_f2.otf":       ('GSUB',),
159    "aots/gsub4_1_multiple_ligsets_f1.otf":         ('GSUB',),
160    "aots/gsub4_1_simple_f1.otf":                   ('GSUB',),
161    "aots/gsub7_font1.otf":                         ('GSUB',),
162    "aots/gsub7_font2.otf":                         ('GSUB',),
163    "aots/gsub_chaining1_boundary_f1.otf":          ('GDEF', 'GSUB'),
164    "aots/gsub_chaining1_boundary_f2.otf":          ('GDEF', 'GSUB'),
165    "aots/gsub_chaining1_boundary_f3.otf":          ('GDEF', 'GSUB'),
166    "aots/gsub_chaining1_boundary_f4.otf":          ('GDEF', 'GSUB'),
167    "aots/gsub_chaining1_lookupflag_f1.otf":        ('GDEF', 'GSUB'),
168    "aots/gsub_chaining1_multiple_subrules_f1.otf": ('GDEF', 'GSUB'),
169    "aots/gsub_chaining1_multiple_subrules_f2.otf": ('GDEF', 'GSUB'),
170    "aots/gsub_chaining1_next_glyph_f1.otf":        ('GDEF', 'GSUB'),
171    "aots/gsub_chaining1_simple_f1.otf":            ('GDEF', 'GSUB'),
172    "aots/gsub_chaining1_simple_f2.otf":            ('GDEF', 'GSUB'),
173    "aots/gsub_chaining1_successive_f1.otf":        ('GDEF', 'GSUB'),
174    "aots/gsub_chaining2_boundary_f1.otf":          ('GDEF', 'GSUB'),
175    "aots/gsub_chaining2_boundary_f2.otf":          ('GDEF', 'GSUB'),
176    "aots/gsub_chaining2_boundary_f3.otf":          ('GDEF', 'GSUB'),
177    "aots/gsub_chaining2_boundary_f4.otf":          ('GDEF', 'GSUB'),
178    "aots/gsub_chaining2_lookupflag_f1.otf":        ('GDEF', 'GSUB'),
179    "aots/gsub_chaining2_multiple_subrules_f1.otf": ('GDEF', 'GSUB'),
180    "aots/gsub_chaining2_multiple_subrules_f2.otf": ('GDEF', 'GSUB'),
181    "aots/gsub_chaining2_next_glyph_f1.otf":        ('GDEF', 'GSUB'),
182    "aots/gsub_chaining2_simple_f1.otf":            ('GDEF', 'GSUB'),
183    "aots/gsub_chaining2_simple_f2.otf":            ('GDEF', 'GSUB'),
184    "aots/gsub_chaining2_successive_f1.otf":        ('GDEF', 'GSUB'),
185    "aots/gsub_chaining3_boundary_f1.otf":          ('GDEF', 'GSUB'),
186    "aots/gsub_chaining3_boundary_f2.otf":          ('GDEF', 'GSUB'),
187    "aots/gsub_chaining3_boundary_f3.otf":          ('GDEF', 'GSUB'),
188    "aots/gsub_chaining3_boundary_f4.otf":          ('GDEF', 'GSUB'),
189    "aots/gsub_chaining3_lookupflag_f1.otf":        ('GDEF', 'GSUB'),
190    "aots/gsub_chaining3_next_glyph_f1.otf":        ('GDEF', 'GSUB'),
191    "aots/gsub_chaining3_simple_f1.otf":            ('GDEF', 'GSUB'),
192    "aots/gsub_chaining3_simple_f2.otf":            ('GDEF', 'GSUB'),
193    "aots/gsub_chaining3_successive_f1.otf":        ('GDEF', 'GSUB'),
194    "aots/gsub_context1_boundary_f1.otf":           ('GDEF', 'GSUB'),
195    "aots/gsub_context1_boundary_f2.otf":           ('GDEF', 'GSUB'),
196    "aots/gsub_context1_expansion_f1.otf":          ('GDEF', 'GSUB'),
197    "aots/gsub_context1_lookupflag_f1.otf":         ('GDEF', 'GSUB'),
198    "aots/gsub_context1_lookupflag_f2.otf":         ('GDEF', 'GSUB'),
199    "aots/gsub_context1_multiple_subrules_f1.otf":  ('GDEF', 'GSUB'),
200    "aots/gsub_context1_multiple_subrules_f2.otf":  ('GDEF', 'GSUB'),
201    "aots/gsub_context1_next_glyph_f1.otf":         ('GDEF', 'GSUB'),
202    "aots/gsub_context1_simple_f1.otf":             ('GDEF', 'GSUB'),
203    "aots/gsub_context1_simple_f2.otf":             ('GDEF', 'GSUB'),
204    "aots/gsub_context1_successive_f1.otf":         ('GDEF', 'GSUB'),
205    "aots/gsub_context2_boundary_f1.otf":           ('GDEF', 'GSUB'),
206    "aots/gsub_context2_boundary_f2.otf":           ('GDEF', 'GSUB'),
207    "aots/gsub_context2_classes_f1.otf":            ('GDEF', 'GSUB'),
208    "aots/gsub_context2_classes_f2.otf":            ('GDEF', 'GSUB'),
209    "aots/gsub_context2_expansion_f1.otf":          ('GDEF', 'GSUB'),
210    "aots/gsub_context2_lookupflag_f1.otf":         ('GDEF', 'GSUB'),
211    "aots/gsub_context2_lookupflag_f2.otf":         ('GDEF', 'GSUB'),
212    "aots/gsub_context2_multiple_subrules_f1.otf":  ('GDEF', 'GSUB'),
213    "aots/gsub_context2_multiple_subrules_f2.otf":  ('GDEF', 'GSUB'),
214    "aots/gsub_context2_next_glyph_f1.otf":         ('GDEF', 'GSUB'),
215    "aots/gsub_context2_simple_f1.otf":             ('GDEF', 'GSUB'),
216    "aots/gsub_context2_simple_f2.otf":             ('GDEF', 'GSUB'),
217    "aots/gsub_context2_successive_f1.otf":         ('GDEF', 'GSUB'),
218    "aots/gsub_context3_boundary_f1.otf":           ('GDEF', 'GSUB'),
219    "aots/gsub_context3_boundary_f2.otf":           ('GDEF', 'GSUB'),
220    "aots/gsub_context3_lookupflag_f1.otf":         ('GDEF', 'GSUB'),
221    "aots/gsub_context3_lookupflag_f2.otf":         ('GDEF', 'GSUB'),
222    "aots/gsub_context3_next_glyph_f1.otf":         ('GDEF', 'GSUB'),
223    "aots/gsub_context3_simple_f1.otf":             ('GDEF', 'GSUB'),
224    "aots/gsub_context3_successive_f1.otf":         ('GDEF', 'GSUB'),
225    "aots/lookupflag_ignore_attach_f1.otf":         ('GDEF', 'GSUB'),
226    "aots/lookupflag_ignore_base_f1.otf":           ('GDEF', 'GSUB'),
227    "aots/lookupflag_ignore_combination_f1.otf":    ('GDEF', 'GSUB'),
228    "aots/lookupflag_ignore_ligatures_f1.otf":      ('GDEF', 'GSUB'),
229    "aots/lookupflag_ignore_marks_f1.otf":          ('GDEF', 'GSUB'),
230    "graphite/graphite_tests.ttf":                  ('Silf', 'Glat', 'Feat', 'Sill'),
231}
232
233
234TEST_REQUIREMENTS = {
235    "aots/cmap4_font4.otf":                         ("unicodedata2",),
236}
237
238
239ttLibVersion_RE = re.compile(r' ttLibVersion=".*"')
240
241
242def getpath(testfile):
243    path = os.path.dirname(__file__)
244    return os.path.join(path, "data", testfile)
245
246
247def read_expected_ttx(testfile, tableTag):
248    name = os.path.splitext(testfile)[0]
249    xml_expected_path = getpath("%s.ttx.%s" % (name, tagToXML(tableTag)))
250    with open(xml_expected_path, 'r', encoding="utf-8") as xml_file:
251        xml_expected = ttLibVersion_RE.sub('', xml_file.read())
252    return xml_expected
253
254
255def dump_ttx(font, tableTag):
256    f = StringIO()
257    font.saveXML(f, tables=[tableTag])
258    return ttLibVersion_RE.sub('', f.getvalue())
259
260
261def load_ttx(ttx):
262    f = StringIO()
263    f.write(ttx)
264    f.seek(0)
265    font = TTFont()
266    font.importXML(f)
267    return font
268
269
270@contextlib.contextmanager
271def open_font(testfile):
272    font = TTFont(getpath(testfile))
273    try:
274        yield font
275    finally:
276        font.close()
277
278
279def _skip_if_requirement_missing(testfile):
280    if testfile in TEST_REQUIREMENTS:
281        for req in TEST_REQUIREMENTS[testfile]:
282            if globals()[req] is None:
283                pytest.skip('%s not installed' % req)
284
285
286def test_xml_from_binary(testfile, tableTag):
287    """Check XML from decompiled object."""
288    _skip_if_requirement_missing(testfile)
289
290    xml_expected = read_expected_ttx(testfile, tableTag)
291
292    with open_font(testfile) as font:
293        xml_from_binary = dump_ttx(font, tableTag)
294
295    assert xml_expected == xml_from_binary
296
297
298def test_xml_from_xml(testfile, tableTag):
299    """Check XML from object read from XML."""
300    _skip_if_requirement_missing(testfile)
301
302    xml_expected = read_expected_ttx(testfile, tableTag)
303
304    font = load_ttx(xml_expected)
305    name = os.path.splitext(testfile)[0]
306    setupfile = getpath("%s.ttx.%s.setup" % (name, tagToXML(tableTag)))
307    if os.path.exists(setupfile):
308#        import pdb; pdb.set_trace()
309        font.importXML(setupfile)
310    xml_from_xml = dump_ttx(font, tableTag)
311
312    assert xml_expected == xml_from_xml
313
314
315def pytest_generate_tests(metafunc):
316    # http://doc.pytest.org/en/latest/parametrize.html#basic-pytest-generate-tests-example
317    fixturenames = metafunc.fixturenames
318    argnames = ("testfile", "tableTag")
319    if all(fn in fixturenames for fn in argnames):
320        argvalues = [(testfile, tableTag)
321                     for testfile, tableTags in sorted(TESTS.items())
322                     for tableTag in tableTags]
323        metafunc.parametrize(argnames, argvalues)
324
325
326if __name__ == '__main__':
327    sys.exit(pytest.main(sys.argv))
328