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