1from __future__ import print_function, division, absolute_import 2from __future__ import unicode_literals 3from fontTools.misc.py23 import * 4from fontTools.voltLib import ast 5from fontTools.voltLib.error import VoltLibError 6from fontTools.voltLib.parser import Parser 7import unittest 8 9 10class ParserTest(unittest.TestCase): 11 def __init__(self, methodName): 12 unittest.TestCase.__init__(self, methodName) 13 # Python 3 renamed assertRaisesRegexp to assertRaisesRegex, 14 # and fires deprecation warnings if a program uses the old name. 15 if not hasattr(self, "assertRaisesRegex"): 16 self.assertRaisesRegex = self.assertRaisesRegexp 17 18 def assertSubEqual(self, sub, glyph_ref, replacement_ref): 19 glyphs = [[g.glyph for g in v] for v in sub.mapping.keys()] 20 replacement = [[g.glyph for g in v] for v in sub.mapping.values()] 21 22 self.assertEqual(glyphs, glyph_ref) 23 self.assertEqual(replacement, replacement_ref) 24 25 def test_def_glyph_base(self): 26 [def_glyph] = self.parse( 27 'DEF_GLYPH ".notdef" ID 0 TYPE BASE END_GLYPH' 28 ).statements 29 self.assertEqual((def_glyph.name, def_glyph.id, def_glyph.unicode, 30 def_glyph.type, def_glyph.components), 31 (".notdef", 0, None, "BASE", None)) 32 33 def test_def_glyph_base_with_unicode(self): 34 [def_glyph] = self.parse( 35 'DEF_GLYPH "space" ID 3 UNICODE 32 TYPE BASE END_GLYPH' 36 ).statements 37 self.assertEqual((def_glyph.name, def_glyph.id, def_glyph.unicode, 38 def_glyph.type, def_glyph.components), 39 ("space", 3, [0x0020], "BASE", None)) 40 41 def test_def_glyph_base_with_unicodevalues(self): 42 [def_glyph] = self.parse( 43 'DEF_GLYPH "CR" ID 2 UNICODEVALUES "U+0009" ' 44 'TYPE BASE END_GLYPH' 45 ).statements 46 self.assertEqual((def_glyph.name, def_glyph.id, def_glyph.unicode, 47 def_glyph.type, def_glyph.components), 48 ("CR", 2, [0x0009], "BASE", None)) 49 50 def test_def_glyph_base_with_mult_unicodevalues(self): 51 [def_glyph] = self.parse( 52 'DEF_GLYPH "CR" ID 2 UNICODEVALUES "U+0009,U+000D" ' 53 'TYPE BASE END_GLYPH' 54 ).statements 55 self.assertEqual((def_glyph.name, def_glyph.id, def_glyph.unicode, 56 def_glyph.type, def_glyph.components), 57 ("CR", 2, [0x0009, 0x000D], "BASE", None)) 58 59 def test_def_glyph_base_with_empty_unicodevalues(self): 60 [def_glyph] = self.parse( 61 'DEF_GLYPH "i.locl" ID 269 UNICODEVALUES "" ' 62 'TYPE BASE END_GLYPH' 63 ).statements 64 self.assertEqual((def_glyph.name, def_glyph.id, def_glyph.unicode, 65 def_glyph.type, def_glyph.components), 66 ("i.locl", 269, None, "BASE", None)) 67 68 def test_def_glyph_base_2_components(self): 69 [def_glyph] = self.parse( 70 'DEF_GLYPH "glyphBase" ID 320 TYPE BASE COMPONENTS 2 END_GLYPH' 71 ).statements 72 self.assertEqual((def_glyph.name, def_glyph.id, def_glyph.unicode, 73 def_glyph.type, def_glyph.components), 74 ("glyphBase", 320, None, "BASE", 2)) 75 76 def test_def_glyph_ligature_2_components(self): 77 [def_glyph] = self.parse( 78 'DEF_GLYPH "f_f" ID 320 TYPE LIGATURE COMPONENTS 2 END_GLYPH' 79 ).statements 80 self.assertEqual((def_glyph.name, def_glyph.id, def_glyph.unicode, 81 def_glyph.type, def_glyph.components), 82 ("f_f", 320, None, "LIGATURE", 2)) 83 84 def test_def_glyph_mark(self): 85 [def_glyph] = self.parse( 86 'DEF_GLYPH "brevecomb" ID 320 TYPE MARK END_GLYPH' 87 ).statements 88 self.assertEqual((def_glyph.name, def_glyph.id, def_glyph.unicode, 89 def_glyph.type, def_glyph.components), 90 ("brevecomb", 320, None, "MARK", None)) 91 92 def test_def_glyph_component(self): 93 [def_glyph] = self.parse( 94 'DEF_GLYPH "f.f_f" ID 320 TYPE COMPONENT END_GLYPH' 95 ).statements 96 self.assertEqual((def_glyph.name, def_glyph.id, def_glyph.unicode, 97 def_glyph.type, def_glyph.components), 98 ("f.f_f", 320, None, "COMPONENT", None)) 99 100 def test_def_glyph_no_type(self): 101 [def_glyph] = self.parse( 102 'DEF_GLYPH "glyph20" ID 20 END_GLYPH' 103 ).statements 104 self.assertEqual((def_glyph.name, def_glyph.id, def_glyph.unicode, 105 def_glyph.type, def_glyph.components), 106 ("glyph20", 20, None, None, None)) 107 108 def test_def_glyph_case_sensitive(self): 109 def_glyphs = self.parse( 110 'DEF_GLYPH "A" ID 3 UNICODE 65 TYPE BASE END_GLYPH\n' 111 'DEF_GLYPH "a" ID 4 UNICODE 97 TYPE BASE END_GLYPH\n' 112 ).statements 113 self.assertEqual((def_glyphs[0].name, def_glyphs[0].id, 114 def_glyphs[0].unicode, def_glyphs[0].type, 115 def_glyphs[0].components), 116 ("A", 3, [0x41], "BASE", None)) 117 self.assertEqual((def_glyphs[1].name, def_glyphs[1].id, 118 def_glyphs[1].unicode, def_glyphs[1].type, 119 def_glyphs[1].components), 120 ("a", 4, [0x61], "BASE", None)) 121 122 def test_def_group_glyphs(self): 123 [def_group] = self.parse( 124 'DEF_GROUP "aaccented"\n' 125 'ENUM GLYPH "aacute" GLYPH "abreve" GLYPH "acircumflex" ' 126 'GLYPH "adieresis" GLYPH "ae" GLYPH "agrave" GLYPH "amacron" ' 127 'GLYPH "aogonek" GLYPH "aring" GLYPH "atilde" END_ENUM\n' 128 'END_GROUP\n' 129 ).statements 130 self.assertEqual((def_group.name, def_group.enum.glyphSet()), 131 ("aaccented", 132 ("aacute", "abreve", "acircumflex", "adieresis", 133 "ae", "agrave", "amacron", "aogonek", "aring", 134 "atilde"))) 135 136 def test_def_group_groups(self): 137 [group1, group2, test_group] = self.parse( 138 'DEF_GROUP "Group1"\n' 139 'ENUM GLYPH "a" GLYPH "b" GLYPH "c" GLYPH "d" END_ENUM\n' 140 'END_GROUP\n' 141 'DEF_GROUP "Group2"\n' 142 'ENUM GLYPH "e" GLYPH "f" GLYPH "g" GLYPH "h" END_ENUM\n' 143 'END_GROUP\n' 144 'DEF_GROUP "TestGroup"\n' 145 'ENUM GROUP "Group1" GROUP "Group2" END_ENUM\n' 146 'END_GROUP\n' 147 ).statements 148 groups = [g.group for g in test_group.enum.enum] 149 self.assertEqual((test_group.name, groups), 150 ("TestGroup", ["Group1", "Group2"])) 151 152 def test_def_group_groups_not_yet_defined(self): 153 [group1, test_group1, test_group2, test_group3, group2] = \ 154 self.parse( 155 'DEF_GROUP "Group1"\n' 156 'ENUM GLYPH "a" GLYPH "b" GLYPH "c" GLYPH "d" END_ENUM\n' 157 'END_GROUP\n' 158 'DEF_GROUP "TestGroup1"\n' 159 'ENUM GROUP "Group1" GROUP "Group2" END_ENUM\n' 160 'END_GROUP\n' 161 'DEF_GROUP "TestGroup2"\n' 162 'ENUM GROUP "Group2" END_ENUM\n' 163 'END_GROUP\n' 164 'DEF_GROUP "TestGroup3"\n' 165 'ENUM GROUP "Group2" GROUP "Group1" END_ENUM\n' 166 'END_GROUP\n' 167 'DEF_GROUP "Group2"\n' 168 'ENUM GLYPH "e" GLYPH "f" GLYPH "g" GLYPH "h" END_ENUM\n' 169 'END_GROUP\n' 170 ).statements 171 groups = [g.group for g in test_group1.enum.enum] 172 self.assertEqual( 173 (test_group1.name, groups), 174 ("TestGroup1", ["Group1", "Group2"])) 175 groups = [g.group for g in test_group2.enum.enum] 176 self.assertEqual( 177 (test_group2.name, groups), 178 ("TestGroup2", ["Group2"])) 179 groups = [g.group for g in test_group3.enum.enum] 180 self.assertEqual( 181 (test_group3.name, groups), 182 ("TestGroup3", ["Group2", "Group1"])) 183 184 # def test_def_group_groups_undefined(self): 185 # with self.assertRaisesRegex( 186 # VoltLibError, 187 # r'Group "Group2" is used but undefined.'): 188 # [group1, test_group, group2] = self.parse( 189 # 'DEF_GROUP "Group1"\n' 190 # 'ENUM GLYPH "a" GLYPH "b" GLYPH "c" GLYPH "d" END_ENUM\n' 191 # 'END_GROUP\n' 192 # 'DEF_GROUP "TestGroup"\n' 193 # 'ENUM GROUP "Group1" GROUP "Group2" END_ENUM\n' 194 # 'END_GROUP\n' 195 # ).statements 196 197 def test_def_group_glyphs_and_group(self): 198 [def_group1, def_group2] = self.parse( 199 'DEF_GROUP "aaccented"\n' 200 'ENUM GLYPH "aacute" GLYPH "abreve" GLYPH "acircumflex" ' 201 'GLYPH "adieresis" GLYPH "ae" GLYPH "agrave" GLYPH "amacron" ' 202 'GLYPH "aogonek" GLYPH "aring" GLYPH "atilde" END_ENUM\n' 203 'END_GROUP\n' 204 'DEF_GROUP "KERN_lc_a_2ND"\n' 205 'ENUM GLYPH "a" GROUP "aaccented" END_ENUM\n' 206 'END_GROUP' 207 ).statements 208 items = def_group2.enum.enum 209 self.assertEqual((def_group2.name, items[0].glyphSet(), items[1].group), 210 ("KERN_lc_a_2ND", ("a",), "aaccented")) 211 212 def test_def_group_range(self): 213 def_group = self.parse( 214 'DEF_GLYPH "a" ID 163 UNICODE 97 TYPE BASE END_GLYPH\n' 215 'DEF_GLYPH "agrave" ID 194 UNICODE 224 TYPE BASE END_GLYPH\n' 216 'DEF_GLYPH "aacute" ID 195 UNICODE 225 TYPE BASE END_GLYPH\n' 217 'DEF_GLYPH "acircumflex" ID 196 UNICODE 226 TYPE BASE END_GLYPH\n' 218 'DEF_GLYPH "atilde" ID 197 UNICODE 227 TYPE BASE END_GLYPH\n' 219 'DEF_GLYPH "c" ID 165 UNICODE 99 TYPE BASE END_GLYPH\n' 220 'DEF_GLYPH "ccaron" ID 209 UNICODE 269 TYPE BASE END_GLYPH\n' 221 'DEF_GLYPH "ccedilla" ID 210 UNICODE 231 TYPE BASE END_GLYPH\n' 222 'DEF_GLYPH "cdotaccent" ID 210 UNICODE 267 TYPE BASE END_GLYPH\n' 223 'DEF_GROUP "KERN_lc_a_2ND"\n' 224 'ENUM RANGE "a" TO "atilde" GLYPH "b" RANGE "c" TO "cdotaccent" ' 225 'END_ENUM\n' 226 'END_GROUP' 227 ).statements[-1] 228 self.assertEqual((def_group.name, def_group.enum.glyphSet()), 229 ("KERN_lc_a_2ND", 230 ("a", "agrave", "aacute", "acircumflex", "atilde", 231 "b", "c", "ccaron", "ccedilla", "cdotaccent"))) 232 233 def test_group_duplicate(self): 234 self.assertRaisesRegex( 235 VoltLibError, 236 'Glyph group "dupe" already defined, ' 237 'group names are case insensitive', 238 self.parse, 'DEF_GROUP "dupe"\n' 239 'ENUM GLYPH "a" GLYPH "b" END_ENUM\n' 240 'END_GROUP\n' 241 'DEF_GROUP "dupe"\n' 242 'ENUM GLYPH "x" END_ENUM\n' 243 'END_GROUP\n' 244 ) 245 246 def test_group_duplicate_case_insensitive(self): 247 self.assertRaisesRegex( 248 VoltLibError, 249 'Glyph group "Dupe" already defined, ' 250 'group names are case insensitive', 251 self.parse, 'DEF_GROUP "dupe"\n' 252 'ENUM GLYPH "a" GLYPH "b" END_ENUM\n' 253 'END_GROUP\n' 254 'DEF_GROUP "Dupe"\n' 255 'ENUM GLYPH "x" END_ENUM\n' 256 'END_GROUP\n' 257 ) 258 259 def test_script_without_langsys(self): 260 [script] = self.parse( 261 'DEF_SCRIPT NAME "Latin" TAG "latn"\n' 262 'END_SCRIPT' 263 ).statements 264 self.assertEqual((script.name, script.tag, script.langs), 265 ("Latin", "latn", [])) 266 267 def test_langsys_normal(self): 268 [def_script] = self.parse( 269 'DEF_SCRIPT NAME "Latin" TAG "latn"\n' 270 'DEF_LANGSYS NAME "Romanian" TAG "ROM "\n' 271 'END_LANGSYS\n' 272 'DEF_LANGSYS NAME "Moldavian" TAG "MOL "\n' 273 'END_LANGSYS\n' 274 'END_SCRIPT' 275 ).statements 276 self.assertEqual((def_script.name, def_script.tag), 277 ("Latin", 278 "latn")) 279 def_lang = def_script.langs[0] 280 self.assertEqual((def_lang.name, def_lang.tag), 281 ("Romanian", 282 "ROM ")) 283 def_lang = def_script.langs[1] 284 self.assertEqual((def_lang.name, def_lang.tag), 285 ("Moldavian", 286 "MOL ")) 287 288 def test_langsys_no_script_name(self): 289 [langsys] = self.parse( 290 'DEF_SCRIPT TAG "latn"\n' 291 'DEF_LANGSYS NAME "Default" TAG "dflt"\n' 292 'END_LANGSYS\n' 293 'END_SCRIPT' 294 ).statements 295 self.assertEqual((langsys.name, langsys.tag), 296 (None, 297 "latn")) 298 lang = langsys.langs[0] 299 self.assertEqual((lang.name, lang.tag), 300 ("Default", 301 "dflt")) 302 303 def test_langsys_no_script_tag_fails(self): 304 with self.assertRaisesRegex( 305 VoltLibError, 306 r'.*Expected "TAG"'): 307 [langsys] = self.parse( 308 'DEF_SCRIPT NAME "Latin"\n' 309 'DEF_LANGSYS NAME "Default" TAG "dflt"\n' 310 'END_LANGSYS\n' 311 'END_SCRIPT' 312 ).statements 313 314 def test_langsys_duplicate_script(self): 315 with self.assertRaisesRegex( 316 VoltLibError, 317 'Script "DFLT" already defined, ' 318 'script tags are case insensitive'): 319 [langsys1, langsys2] = self.parse( 320 'DEF_SCRIPT NAME "Default" TAG "DFLT"\n' 321 'DEF_LANGSYS NAME "Default" TAG "dflt"\n' 322 'END_LANGSYS\n' 323 'END_SCRIPT\n' 324 'DEF_SCRIPT TAG "DFLT"\n' 325 'DEF_LANGSYS NAME "Default" TAG "dflt"\n' 326 'END_LANGSYS\n' 327 'END_SCRIPT' 328 ).statements 329 330 def test_langsys_duplicate_lang(self): 331 with self.assertRaisesRegex( 332 VoltLibError, 333 'Language "dflt" already defined in script "DFLT", ' 334 'language tags are case insensitive'): 335 [langsys] = self.parse( 336 'DEF_SCRIPT NAME "Default" TAG "DFLT"\n' 337 'DEF_LANGSYS NAME "Default" TAG "dflt"\n' 338 'END_LANGSYS\n' 339 'DEF_LANGSYS NAME "Default" TAG "dflt"\n' 340 'END_LANGSYS\n' 341 'END_SCRIPT\n' 342 ).statements 343 344 def test_langsys_lang_in_separate_scripts(self): 345 [langsys1, langsys2] = self.parse( 346 'DEF_SCRIPT NAME "Default" TAG "DFLT"\n' 347 'DEF_LANGSYS NAME "Default" TAG "dflt"\n' 348 'END_LANGSYS\n' 349 'DEF_LANGSYS NAME "Default" TAG "ROM "\n' 350 'END_LANGSYS\n' 351 'END_SCRIPT\n' 352 'DEF_SCRIPT NAME "Latin" TAG "latn"\n' 353 'DEF_LANGSYS NAME "Default" TAG "dflt"\n' 354 'END_LANGSYS\n' 355 'DEF_LANGSYS NAME "Default" TAG "ROM "\n' 356 'END_LANGSYS\n' 357 'END_SCRIPT' 358 ).statements 359 self.assertEqual((langsys1.langs[0].tag, langsys1.langs[1].tag), 360 ("dflt", "ROM ")) 361 self.assertEqual((langsys2.langs[0].tag, langsys2.langs[1].tag), 362 ("dflt", "ROM ")) 363 364 def test_langsys_no_lang_name(self): 365 [langsys] = self.parse( 366 'DEF_SCRIPT NAME "Latin" TAG "latn"\n' 367 'DEF_LANGSYS TAG "dflt"\n' 368 'END_LANGSYS\n' 369 'END_SCRIPT' 370 ).statements 371 self.assertEqual((langsys.name, langsys.tag), 372 ("Latin", 373 "latn")) 374 lang = langsys.langs[0] 375 self.assertEqual((lang.name, lang.tag), 376 (None, 377 "dflt")) 378 379 def test_langsys_no_langsys_tag_fails(self): 380 with self.assertRaisesRegex( 381 VoltLibError, 382 r'.*Expected "TAG"'): 383 [langsys] = self.parse( 384 'DEF_SCRIPT NAME "Latin" TAG "latn"\n' 385 'DEF_LANGSYS NAME "Default"\n' 386 'END_LANGSYS\n' 387 'END_SCRIPT' 388 ).statements 389 390 def test_feature(self): 391 [def_script] = self.parse( 392 'DEF_SCRIPT NAME "Latin" TAG "latn"\n' 393 'DEF_LANGSYS NAME "Romanian" TAG "ROM "\n' 394 'DEF_FEATURE NAME "Fractions" TAG "frac"\n' 395 'LOOKUP "fraclookup"\n' 396 'END_FEATURE\n' 397 'END_LANGSYS\n' 398 'END_SCRIPT' 399 ).statements 400 def_feature = def_script.langs[0].features[0] 401 self.assertEqual((def_feature.name, def_feature.tag, 402 def_feature.lookups), 403 ("Fractions", 404 "frac", 405 ["fraclookup"])) 406 [def_script] = self.parse( 407 'DEF_SCRIPT NAME "Latin" TAG "latn"\n' 408 'DEF_LANGSYS NAME "Romanian" TAG "ROM "\n' 409 'DEF_FEATURE NAME "Kerning" TAG "kern"\n' 410 'LOOKUP "kern1" LOOKUP "kern2"\n' 411 'END_FEATURE\n' 412 'END_LANGSYS\n' 413 'END_SCRIPT' 414 ).statements 415 def_feature = def_script.langs[0].features[0] 416 self.assertEqual((def_feature.name, def_feature.tag, 417 def_feature.lookups), 418 ("Kerning", 419 "kern", 420 ["kern1", "kern2"])) 421 422 def test_lookup_duplicate(self): 423 with self.assertRaisesRegex( 424 VoltLibError, 425 'Lookup "dupe" already defined, ' 426 'lookup names are case insensitive', 427 ): 428 [lookup1, lookup2] = self.parse( 429 'DEF_LOOKUP "dupe"\n' 430 'AS_SUBSTITUTION\n' 431 'SUB GLYPH "a"\n' 432 'WITH GLYPH "a.alt"\n' 433 'END_SUB\n' 434 'END_SUBSTITUTION\n' 435 'DEF_LOOKUP "dupe"\n' 436 'AS_SUBSTITUTION\n' 437 'SUB GLYPH "b"\n' 438 'WITH GLYPH "b.alt"\n' 439 'END_SUB\n' 440 'END_SUBSTITUTION\n' 441 ).statements 442 443 def test_lookup_duplicate_insensitive_case(self): 444 with self.assertRaisesRegex( 445 VoltLibError, 446 'Lookup "Dupe" already defined, ' 447 'lookup names are case insensitive', 448 ): 449 [lookup1, lookup2] = self.parse( 450 'DEF_LOOKUP "dupe"\n' 451 'AS_SUBSTITUTION\n' 452 'SUB GLYPH "a"\n' 453 'WITH GLYPH "a.alt"\n' 454 'END_SUB\n' 455 'END_SUBSTITUTION\n' 456 'DEF_LOOKUP "Dupe"\n' 457 'AS_SUBSTITUTION\n' 458 'SUB GLYPH "b"\n' 459 'WITH GLYPH "b.alt"\n' 460 'END_SUB\n' 461 'END_SUBSTITUTION\n' 462 ).statements 463 464 def test_lookup_name_starts_with_letter(self): 465 with self.assertRaisesRegex( 466 VoltLibError, 467 r'Lookup name "\\lookupname" must start with a letter' 468 ): 469 [lookup] = self.parse( 470 'DEF_LOOKUP "\\lookupname"\n' 471 'AS_SUBSTITUTION\n' 472 'SUB GLYPH "a"\n' 473 'WITH GLYPH "a.alt"\n' 474 'END_SUB\n' 475 'END_SUBSTITUTION\n' 476 ).statements 477 478 def test_substitution_empty(self): 479 with self.assertRaisesRegex( 480 VoltLibError, 481 r'Expected SUB'): 482 [lookup] = self.parse( 483 'DEF_LOOKUP "empty_substitution" PROCESS_BASE PROCESS_MARKS ' 484 'ALL DIRECTION LTR\n' 485 'IN_CONTEXT\n' 486 'END_CONTEXT\n' 487 'AS_SUBSTITUTION\n' 488 'END_SUBSTITUTION' 489 ).statements 490 491 def test_substitution_invalid_many_to_many(self): 492 with self.assertRaisesRegex( 493 VoltLibError, 494 r'Invalid substitution type'): 495 [lookup] = self.parse( 496 'DEF_LOOKUP "invalid_substitution" PROCESS_BASE PROCESS_MARKS ' 497 'ALL DIRECTION LTR\n' 498 'IN_CONTEXT\n' 499 'END_CONTEXT\n' 500 'AS_SUBSTITUTION\n' 501 'SUB GLYPH "f" GLYPH "i"\n' 502 'WITH GLYPH "f.alt" GLYPH "i.alt"\n' 503 'END_SUB\n' 504 'END_SUBSTITUTION' 505 ).statements 506 507 def test_substitution_invalid_reverse_chaining_single(self): 508 with self.assertRaisesRegex( 509 VoltLibError, 510 r'Invalid substitution type'): 511 [lookup] = self.parse( 512 'DEF_LOOKUP "invalid_substitution" PROCESS_BASE PROCESS_MARKS ' 513 'ALL DIRECTION LTR REVERSAL\n' 514 'IN_CONTEXT\n' 515 'END_CONTEXT\n' 516 'AS_SUBSTITUTION\n' 517 'SUB GLYPH "f" GLYPH "i"\n' 518 'WITH GLYPH "f_i"\n' 519 'END_SUB\n' 520 'END_SUBSTITUTION' 521 ).statements 522 523 def test_substitution_invalid_mixed(self): 524 with self.assertRaisesRegex( 525 VoltLibError, 526 r'Invalid substitution type'): 527 [lookup] = self.parse( 528 'DEF_LOOKUP "invalid_substitution" PROCESS_BASE PROCESS_MARKS ' 529 'ALL DIRECTION LTR\n' 530 'IN_CONTEXT\n' 531 'END_CONTEXT\n' 532 'AS_SUBSTITUTION\n' 533 'SUB GLYPH "fi"\n' 534 'WITH GLYPH "f" GLYPH "i"\n' 535 'END_SUB\n' 536 'SUB GLYPH "f" GLYPH "l"\n' 537 'WITH GLYPH "f_l"\n' 538 'END_SUB\n' 539 'END_SUBSTITUTION' 540 ).statements 541 542 def test_substitution_single(self): 543 [lookup] = self.parse( 544 'DEF_LOOKUP "smcp" PROCESS_BASE PROCESS_MARKS ALL ' 545 'DIRECTION LTR\n' 546 'IN_CONTEXT\n' 547 'END_CONTEXT\n' 548 'AS_SUBSTITUTION\n' 549 'SUB GLYPH "a"\n' 550 'WITH GLYPH "a.sc"\n' 551 'END_SUB\n' 552 'SUB GLYPH "b"\n' 553 'WITH GLYPH "b.sc"\n' 554 'END_SUB\n' 555 'END_SUBSTITUTION' 556 ).statements 557 self.assertEqual(lookup.name, "smcp") 558 self.assertSubEqual(lookup.sub, [["a"], ["b"]], [["a.sc"], ["b.sc"]]) 559 560 def test_substitution_single_in_context(self): 561 [group, lookup] = self.parse( 562 'DEF_GROUP "Denominators" ENUM GLYPH "one.dnom" GLYPH "two.dnom" ' 563 'END_ENUM END_GROUP\n' 564 'DEF_LOOKUP "fracdnom" PROCESS_BASE PROCESS_MARKS ALL ' 565 'DIRECTION LTR\n' 566 'IN_CONTEXT LEFT ENUM GROUP "Denominators" GLYPH "fraction" ' 567 'END_ENUM\n' 568 'END_CONTEXT\n' 569 'AS_SUBSTITUTION\n' 570 'SUB GLYPH "one"\n' 571 'WITH GLYPH "one.dnom"\n' 572 'END_SUB\n' 573 'SUB GLYPH "two"\n' 574 'WITH GLYPH "two.dnom"\n' 575 'END_SUB\n' 576 'END_SUBSTITUTION' 577 ).statements 578 context = lookup.context[0] 579 580 self.assertEqual(lookup.name, "fracdnom") 581 self.assertEqual(context.ex_or_in, "IN_CONTEXT") 582 self.assertEqual(len(context.left), 1) 583 self.assertEqual(len(context.left[0]), 1) 584 self.assertEqual(len(context.left[0][0].enum), 2) 585 self.assertEqual(context.left[0][0].enum[0].group, "Denominators") 586 self.assertEqual(context.left[0][0].enum[1].glyph, "fraction") 587 self.assertEqual(context.right, []) 588 self.assertSubEqual(lookup.sub, [["one"], ["two"]], 589 [["one.dnom"], ["two.dnom"]]) 590 591 def test_substitution_single_in_contexts(self): 592 [group, lookup] = self.parse( 593 'DEF_GROUP "Hebrew" ENUM GLYPH "uni05D0" GLYPH "uni05D1" ' 594 'END_ENUM END_GROUP\n' 595 'DEF_LOOKUP "HebrewCurrency" PROCESS_BASE PROCESS_MARKS ALL ' 596 'DIRECTION LTR\n' 597 'IN_CONTEXT\n' 598 'RIGHT GROUP "Hebrew"\n' 599 'RIGHT GLYPH "one.Hebr"\n' 600 'END_CONTEXT\n' 601 'IN_CONTEXT\n' 602 'LEFT GROUP "Hebrew"\n' 603 'LEFT GLYPH "one.Hebr"\n' 604 'END_CONTEXT\n' 605 'AS_SUBSTITUTION\n' 606 'SUB GLYPH "dollar"\n' 607 'WITH GLYPH "dollar.Hebr"\n' 608 'END_SUB\n' 609 'END_SUBSTITUTION' 610 ).statements 611 context1 = lookup.context[0] 612 context2 = lookup.context[1] 613 614 self.assertEqual(lookup.name, "HebrewCurrency") 615 616 self.assertEqual(context1.ex_or_in, "IN_CONTEXT") 617 self.assertEqual(context1.left, []) 618 self.assertEqual(len(context1.right), 2) 619 self.assertEqual(len(context1.right[0]), 1) 620 self.assertEqual(len(context1.right[1]), 1) 621 self.assertEqual(context1.right[0][0].group, "Hebrew") 622 self.assertEqual(context1.right[1][0].glyph, "one.Hebr") 623 624 self.assertEqual(context2.ex_or_in, "IN_CONTEXT") 625 self.assertEqual(len(context2.left), 2) 626 self.assertEqual(len(context2.left[0]), 1) 627 self.assertEqual(len(context2.left[1]), 1) 628 self.assertEqual(context2.left[0][0].group, "Hebrew") 629 self.assertEqual(context2.left[1][0].glyph, "one.Hebr") 630 self.assertEqual(context2.right, []) 631 632 def test_substitution_skip_base(self): 633 [group, lookup] = self.parse( 634 'DEF_GROUP "SomeMarks" ENUM GLYPH "marka" GLYPH "markb" ' 635 'END_ENUM END_GROUP\n' 636 'DEF_LOOKUP "SomeSub" SKIP_BASE PROCESS_MARKS ALL ' 637 'DIRECTION LTR\n' 638 'IN_CONTEXT\n' 639 'END_CONTEXT\n' 640 'AS_SUBSTITUTION\n' 641 'SUB GLYPH "A"\n' 642 'WITH GLYPH "A.c2sc"\n' 643 'END_SUB\n' 644 'END_SUBSTITUTION' 645 ).statements 646 self.assertEqual( 647 (lookup.name, lookup.process_base), 648 ("SomeSub", False)) 649 650 def test_substitution_process_base(self): 651 [group, lookup] = self.parse( 652 'DEF_GROUP "SomeMarks" ENUM GLYPH "marka" GLYPH "markb" ' 653 'END_ENUM END_GROUP\n' 654 'DEF_LOOKUP "SomeSub" PROCESS_BASE PROCESS_MARKS ALL ' 655 'DIRECTION LTR\n' 656 'IN_CONTEXT\n' 657 'END_CONTEXT\n' 658 'AS_SUBSTITUTION\n' 659 'SUB GLYPH "A"\n' 660 'WITH GLYPH "A.c2sc"\n' 661 'END_SUB\n' 662 'END_SUBSTITUTION' 663 ).statements 664 self.assertEqual( 665 (lookup.name, lookup.process_base), 666 ("SomeSub", True)) 667 668 def test_substitution_skip_marks(self): 669 [group, lookup] = self.parse( 670 'DEF_GROUP "SomeMarks" ENUM GLYPH "marka" GLYPH "markb" ' 671 'END_ENUM END_GROUP\n' 672 'DEF_LOOKUP "SomeSub" PROCESS_BASE SKIP_MARKS ' 673 'DIRECTION LTR\n' 674 'IN_CONTEXT\n' 675 'END_CONTEXT\n' 676 'AS_SUBSTITUTION\n' 677 'SUB GLYPH "A"\n' 678 'WITH GLYPH "A.c2sc"\n' 679 'END_SUB\n' 680 'END_SUBSTITUTION' 681 ).statements 682 self.assertEqual( 683 (lookup.name, lookup.process_marks), 684 ("SomeSub", False)) 685 686 def test_substitution_mark_attachment(self): 687 [group, lookup] = self.parse( 688 'DEF_GROUP "SomeMarks" ENUM GLYPH "acutecmb" GLYPH "gravecmb" ' 689 'END_ENUM END_GROUP\n' 690 'DEF_LOOKUP "SomeSub" PROCESS_BASE ' 691 'PROCESS_MARKS "SomeMarks" \n' 692 'DIRECTION RTL\n' 693 'AS_SUBSTITUTION\n' 694 'SUB GLYPH "A"\n' 695 'WITH GLYPH "A.c2sc"\n' 696 'END_SUB\n' 697 'END_SUBSTITUTION' 698 ).statements 699 self.assertEqual( 700 (lookup.name, lookup.process_marks), 701 ("SomeSub", "SomeMarks")) 702 703 def test_substitution_mark_glyph_set(self): 704 [group, lookup] = self.parse( 705 'DEF_GROUP "SomeMarks" ENUM GLYPH "acutecmb" GLYPH "gravecmb" ' 706 'END_ENUM END_GROUP\n' 707 'DEF_LOOKUP "SomeSub" PROCESS_BASE ' 708 'PROCESS_MARKS MARK_GLYPH_SET "SomeMarks" \n' 709 'DIRECTION RTL\n' 710 'AS_SUBSTITUTION\n' 711 'SUB GLYPH "A"\n' 712 'WITH GLYPH "A.c2sc"\n' 713 'END_SUB\n' 714 'END_SUBSTITUTION' 715 ).statements 716 self.assertEqual( 717 (lookup.name, lookup.mark_glyph_set), 718 ("SomeSub", "SomeMarks")) 719 720 def test_substitution_process_all_marks(self): 721 [group, lookup] = self.parse( 722 'DEF_GROUP "SomeMarks" ENUM GLYPH "acutecmb" GLYPH "gravecmb" ' 723 'END_ENUM END_GROUP\n' 724 'DEF_LOOKUP "SomeSub" PROCESS_BASE ' 725 'PROCESS_MARKS ALL \n' 726 'DIRECTION RTL\n' 727 'AS_SUBSTITUTION\n' 728 'SUB GLYPH "A"\n' 729 'WITH GLYPH "A.c2sc"\n' 730 'END_SUB\n' 731 'END_SUBSTITUTION' 732 ).statements 733 self.assertEqual( 734 (lookup.name, lookup.process_marks), 735 ("SomeSub", True)) 736 737 def test_substitution_no_reversal(self): 738 # TODO: check right context with no reversal 739 [lookup] = self.parse( 740 'DEF_LOOKUP "Lookup" PROCESS_BASE PROCESS_MARKS ALL ' 741 'DIRECTION LTR\n' 742 'IN_CONTEXT\n' 743 'RIGHT ENUM GLYPH "a" GLYPH "b" END_ENUM\n' 744 'END_CONTEXT\n' 745 'AS_SUBSTITUTION\n' 746 'SUB GLYPH "a"\n' 747 'WITH GLYPH "a.alt"\n' 748 'END_SUB\n' 749 'END_SUBSTITUTION' 750 ).statements 751 self.assertEqual( 752 (lookup.name, lookup.reversal), 753 ("Lookup", None) 754 ) 755 756 def test_substitution_reversal(self): 757 lookup = self.parse( 758 'DEF_GROUP "DFLT_Num_standardFigures"\n' 759 'ENUM GLYPH "zero" GLYPH "one" GLYPH "two" END_ENUM\n' 760 'END_GROUP\n' 761 'DEF_GROUP "DFLT_Num_numerators"\n' 762 'ENUM GLYPH "zero.numr" GLYPH "one.numr" GLYPH "two.numr" END_ENUM\n' 763 'END_GROUP\n' 764 'DEF_LOOKUP "RevLookup" PROCESS_BASE PROCESS_MARKS ALL ' 765 'DIRECTION LTR REVERSAL\n' 766 'IN_CONTEXT\n' 767 'RIGHT ENUM GLYPH "a" GLYPH "b" END_ENUM\n' 768 'END_CONTEXT\n' 769 'AS_SUBSTITUTION\n' 770 'SUB GROUP "DFLT_Num_standardFigures"\n' 771 'WITH GROUP "DFLT_Num_numerators"\n' 772 'END_SUB\n' 773 'END_SUBSTITUTION' 774 ).statements[-1] 775 self.assertEqual( 776 (lookup.name, lookup.reversal), 777 ("RevLookup", True) 778 ) 779 780 def test_substitution_single_to_multiple(self): 781 [lookup] = self.parse( 782 'DEF_LOOKUP "ccmp" PROCESS_BASE PROCESS_MARKS ALL ' 783 'DIRECTION LTR\n' 784 'IN_CONTEXT\n' 785 'END_CONTEXT\n' 786 'AS_SUBSTITUTION\n' 787 'SUB GLYPH "aacute"\n' 788 'WITH GLYPH "a" GLYPH "acutecomb"\n' 789 'END_SUB\n' 790 'SUB GLYPH "agrave"\n' 791 'WITH GLYPH "a" GLYPH "gravecomb"\n' 792 'END_SUB\n' 793 'END_SUBSTITUTION' 794 ).statements 795 self.assertEqual(lookup.name, "ccmp") 796 self.assertSubEqual(lookup.sub, [["aacute"], ["agrave"]], 797 [["a", "acutecomb"], ["a", "gravecomb"]]) 798 799 def test_substitution_multiple_to_single(self): 800 [lookup] = self.parse( 801 'DEF_LOOKUP "liga" PROCESS_BASE PROCESS_MARKS ALL ' 802 'DIRECTION LTR\n' 803 'IN_CONTEXT\n' 804 'END_CONTEXT\n' 805 'AS_SUBSTITUTION\n' 806 'SUB GLYPH "f" GLYPH "i"\n' 807 'WITH GLYPH "f_i"\n' 808 'END_SUB\n' 809 'SUB GLYPH "f" GLYPH "t"\n' 810 'WITH GLYPH "f_t"\n' 811 'END_SUB\n' 812 'END_SUBSTITUTION' 813 ).statements 814 self.assertEqual(lookup.name, "liga") 815 self.assertSubEqual(lookup.sub, [["f", "i"], ["f", "t"]], 816 [["f_i"], ["f_t"]]) 817 818 def test_substitution_reverse_chaining_single(self): 819 [lookup] = self.parse( 820 'DEF_LOOKUP "numr" PROCESS_BASE PROCESS_MARKS ALL ' 821 'DIRECTION LTR REVERSAL\n' 822 'IN_CONTEXT\n' 823 'RIGHT ENUM ' 824 'GLYPH "fraction" ' 825 'RANGE "zero.numr" TO "nine.numr" ' 826 'END_ENUM\n' 827 'END_CONTEXT\n' 828 'AS_SUBSTITUTION\n' 829 'SUB RANGE "zero" TO "nine"\n' 830 'WITH RANGE "zero.numr" TO "nine.numr"\n' 831 'END_SUB\n' 832 'END_SUBSTITUTION' 833 ).statements 834 835 mapping = lookup.sub.mapping 836 glyphs = [[(r.start, r.end) for r in v] for v in mapping.keys()] 837 replacement = [[(r.start, r.end) for r in v] for v in mapping.values()] 838 839 self.assertEqual(lookup.name, "numr") 840 self.assertEqual(glyphs, [[('zero', 'nine')]]) 841 self.assertEqual(replacement, [[('zero.numr', 'nine.numr')]]) 842 843 self.assertEqual(len(lookup.context[0].right), 1) 844 self.assertEqual(len(lookup.context[0].right[0]), 1) 845 enum = lookup.context[0].right[0][0] 846 self.assertEqual(len(enum.enum), 2) 847 self.assertEqual(enum.enum[0].glyph, "fraction") 848 self.assertEqual((enum.enum[1].start, enum.enum[1].end), 849 ('zero.numr', 'nine.numr')) 850 851 # GPOS 852 # ATTACH_CURSIVE 853 # ATTACH 854 # ADJUST_PAIR 855 # ADJUST_SINGLE 856 def test_position_empty(self): 857 with self.assertRaisesRegex( 858 VoltLibError, 859 'Expected ATTACH, ATTACH_CURSIVE, ADJUST_PAIR, ADJUST_SINGLE'): 860 [lookup] = self.parse( 861 'DEF_LOOKUP "empty_position" PROCESS_BASE PROCESS_MARKS ALL ' 862 'DIRECTION LTR\n' 863 'EXCEPT_CONTEXT\n' 864 'LEFT GLYPH "glyph"\n' 865 'END_CONTEXT\n' 866 'AS_POSITION\n' 867 'END_POSITION' 868 ).statements 869 870 def test_position_attach(self): 871 [lookup, anchor1, anchor2, anchor3, anchor4] = self.parse( 872 'DEF_LOOKUP "anchor_top" PROCESS_BASE PROCESS_MARKS ALL ' 873 'DIRECTION RTL\n' 874 'IN_CONTEXT\n' 875 'END_CONTEXT\n' 876 'AS_POSITION\n' 877 'ATTACH GLYPH "a" GLYPH "e"\n' 878 'TO GLYPH "acutecomb" AT ANCHOR "top" ' 879 'GLYPH "gravecomb" AT ANCHOR "top"\n' 880 'END_ATTACH\n' 881 'END_POSITION\n' 882 'DEF_ANCHOR "MARK_top" ON 120 GLYPH acutecomb COMPONENT 1 ' 883 'AT POS DX 0 DY 450 END_POS END_ANCHOR\n' 884 'DEF_ANCHOR "MARK_top" ON 121 GLYPH gravecomb COMPONENT 1 ' 885 'AT POS DX 0 DY 450 END_POS END_ANCHOR\n' 886 'DEF_ANCHOR "top" ON 31 GLYPH a COMPONENT 1 ' 887 'AT POS DX 210 DY 450 END_POS END_ANCHOR\n' 888 'DEF_ANCHOR "top" ON 35 GLYPH e COMPONENT 1 ' 889 'AT POS DX 215 DY 450 END_POS END_ANCHOR\n' 890 ).statements 891 pos = lookup.pos 892 coverage = [g.glyph for g in pos.coverage] 893 coverage_to = [[[g.glyph for g in e], a] for (e, a) in pos.coverage_to] 894 self.assertEqual( 895 (lookup.name, coverage, coverage_to), 896 ("anchor_top", ["a", "e"], 897 [[["acutecomb"], "top"], [["gravecomb"], "top"]]) 898 ) 899 self.assertEqual( 900 (anchor1.name, anchor1.gid, anchor1.glyph_name, anchor1.component, 901 anchor1.locked, anchor1.pos), 902 ("MARK_top", 120, "acutecomb", 1, False, (None, 0, 450, {}, {}, 903 {})) 904 ) 905 self.assertEqual( 906 (anchor2.name, anchor2.gid, anchor2.glyph_name, anchor2.component, 907 anchor2.locked, anchor2.pos), 908 ("MARK_top", 121, "gravecomb", 1, False, (None, 0, 450, {}, {}, 909 {})) 910 ) 911 self.assertEqual( 912 (anchor3.name, anchor3.gid, anchor3.glyph_name, anchor3.component, 913 anchor3.locked, anchor3.pos), 914 ("top", 31, "a", 1, False, (None, 210, 450, {}, {}, {})) 915 ) 916 self.assertEqual( 917 (anchor4.name, anchor4.gid, anchor4.glyph_name, anchor4.component, 918 anchor4.locked, anchor4.pos), 919 ("top", 35, "e", 1, False, (None, 215, 450, {}, {}, {})) 920 ) 921 922 def test_position_attach_cursive(self): 923 [lookup] = self.parse( 924 'DEF_LOOKUP "SomeLookup" PROCESS_BASE PROCESS_MARKS ALL ' 925 'DIRECTION RTL\n' 926 'IN_CONTEXT\n' 927 'END_CONTEXT\n' 928 'AS_POSITION\n' 929 'ATTACH_CURSIVE EXIT GLYPH "a" GLYPH "b" ENTER GLYPH "c"\n' 930 'END_ATTACH\n' 931 'END_POSITION\n' 932 ).statements 933 exit = [[g.glyph for g in v] for v in lookup.pos.coverages_exit] 934 enter = [[g.glyph for g in v] for v in lookup.pos.coverages_enter] 935 self.assertEqual( 936 (lookup.name, exit, enter), 937 ("SomeLookup", [["a", "b"]], [["c"]]) 938 ) 939 940 def test_position_adjust_pair(self): 941 [lookup] = self.parse( 942 'DEF_LOOKUP "kern1" PROCESS_BASE PROCESS_MARKS ALL ' 943 'DIRECTION RTL\n' 944 'IN_CONTEXT\n' 945 'END_CONTEXT\n' 946 'AS_POSITION\n' 947 'ADJUST_PAIR\n' 948 ' FIRST GLYPH "A"\n' 949 ' SECOND GLYPH "V"\n' 950 ' 1 2 BY POS ADV -30 END_POS POS END_POS\n' 951 ' 2 1 BY POS ADV -30 END_POS POS END_POS\n' 952 'END_ADJUST\n' 953 'END_POSITION\n' 954 ).statements 955 coverages_1 = [[g.glyph for g in v] for v in lookup.pos.coverages_1] 956 coverages_2 = [[g.glyph for g in v] for v in lookup.pos.coverages_2] 957 self.assertEqual( 958 (lookup.name, coverages_1, coverages_2, 959 lookup.pos.adjust_pair), 960 ("kern1", [["A"]], [["V"]], 961 {(1, 2): ((-30, None, None, {}, {}, {}), 962 (None, None, None, {}, {}, {})), 963 (2, 1): ((-30, None, None, {}, {}, {}), 964 (None, None, None, {}, {}, {}))}) 965 ) 966 967 def test_position_adjust_single(self): 968 [lookup] = self.parse( 969 'DEF_LOOKUP "TestLookup" PROCESS_BASE PROCESS_MARKS ALL ' 970 'DIRECTION LTR\n' 971 'IN_CONTEXT\n' 972 # 'LEFT GLYPH "leftGlyph"\n' 973 # 'RIGHT GLYPH "rightGlyph"\n' 974 'END_CONTEXT\n' 975 'AS_POSITION\n' 976 'ADJUST_SINGLE' 977 ' GLYPH "glyph1" BY POS ADV 0 DX 123 END_POS\n' 978 ' GLYPH "glyph2" BY POS ADV 0 DX 456 END_POS\n' 979 'END_ADJUST\n' 980 'END_POSITION\n' 981 ).statements 982 pos = lookup.pos 983 adjust = [[[g.glyph for g in a], b] for (a, b) in pos.adjust_single] 984 self.assertEqual( 985 (lookup.name, adjust), 986 ("TestLookup", 987 [[["glyph1"], (0, 123, None, {}, {}, {})], 988 [["glyph2"], (0, 456, None, {}, {}, {})]]) 989 ) 990 991 def test_def_anchor(self): 992 [anchor1, anchor2, anchor3] = self.parse( 993 'DEF_ANCHOR "top" ON 120 GLYPH a ' 994 'COMPONENT 1 AT POS DX 250 DY 450 END_POS END_ANCHOR\n' 995 'DEF_ANCHOR "MARK_top" ON 120 GLYPH acutecomb ' 996 'COMPONENT 1 AT POS DX 0 DY 450 END_POS END_ANCHOR\n' 997 'DEF_ANCHOR "bottom" ON 120 GLYPH a ' 998 'COMPONENT 1 AT POS DX 250 DY 0 END_POS END_ANCHOR\n' 999 ).statements 1000 self.assertEqual( 1001 (anchor1.name, anchor1.gid, anchor1.glyph_name, anchor1.component, 1002 anchor1.locked, anchor1.pos), 1003 ("top", 120, "a", 1, 1004 False, (None, 250, 450, {}, {}, {})) 1005 ) 1006 self.assertEqual( 1007 (anchor2.name, anchor2.gid, anchor2.glyph_name, anchor2.component, 1008 anchor2.locked, anchor2.pos), 1009 ("MARK_top", 120, "acutecomb", 1, 1010 False, (None, 0, 450, {}, {}, {})) 1011 ) 1012 self.assertEqual( 1013 (anchor3.name, anchor3.gid, anchor3.glyph_name, anchor3.component, 1014 anchor3.locked, anchor3.pos), 1015 ("bottom", 120, "a", 1, 1016 False, (None, 250, 0, {}, {}, {})) 1017 ) 1018 1019 def test_def_anchor_multi_component(self): 1020 [anchor1, anchor2] = self.parse( 1021 'DEF_ANCHOR "top" ON 120 GLYPH a ' 1022 'COMPONENT 1 AT POS DX 250 DY 450 END_POS END_ANCHOR\n' 1023 'DEF_ANCHOR "top" ON 120 GLYPH a ' 1024 'COMPONENT 2 AT POS DX 250 DY 450 END_POS END_ANCHOR\n' 1025 ).statements 1026 self.assertEqual( 1027 (anchor1.name, anchor1.gid, anchor1.glyph_name, anchor1.component), 1028 ("top", 120, "a", 1) 1029 ) 1030 self.assertEqual( 1031 (anchor2.name, anchor2.gid, anchor2.glyph_name, anchor2.component), 1032 ("top", 120, "a", 2) 1033 ) 1034 1035 def test_def_anchor_duplicate(self): 1036 self.assertRaisesRegex( 1037 VoltLibError, 1038 'Anchor "dupe" already defined, ' 1039 'anchor names are case insensitive', 1040 self.parse, 1041 'DEF_ANCHOR "dupe" ON 120 GLYPH a ' 1042 'COMPONENT 1 AT POS DX 250 DY 450 END_POS END_ANCHOR\n' 1043 'DEF_ANCHOR "dupe" ON 120 GLYPH a ' 1044 'COMPONENT 1 AT POS DX 250 DY 450 END_POS END_ANCHOR\n' 1045 ) 1046 1047 def test_def_anchor_locked(self): 1048 [anchor] = self.parse( 1049 'DEF_ANCHOR "top" ON 120 GLYPH a ' 1050 'COMPONENT 1 LOCKED AT POS DX 250 DY 450 END_POS END_ANCHOR\n' 1051 ).statements 1052 self.assertEqual( 1053 (anchor.name, anchor.gid, anchor.glyph_name, anchor.component, 1054 anchor.locked, anchor.pos), 1055 ("top", 120, "a", 1, 1056 True, (None, 250, 450, {}, {}, {})) 1057 ) 1058 1059 def test_anchor_adjust_device(self): 1060 [anchor] = self.parse( 1061 'DEF_ANCHOR "MARK_top" ON 123 GLYPH diacglyph ' 1062 'COMPONENT 1 AT POS DX 0 DY 456 ADJUST_BY 12 AT 34 ' 1063 'ADJUST_BY 56 AT 78 END_POS END_ANCHOR' 1064 ).statements 1065 self.assertEqual( 1066 (anchor.name, anchor.pos), 1067 ("MARK_top", (None, 0, 456, {}, {}, {34: 12, 78: 56})) 1068 ) 1069 1070 def test_ppem(self): 1071 [grid_ppem, pres_ppem, ppos_ppem] = self.parse( 1072 'GRID_PPEM 20\n' 1073 'PRESENTATION_PPEM 72\n' 1074 'PPOSITIONING_PPEM 144\n' 1075 ).statements 1076 self.assertEqual( 1077 ((grid_ppem.name, grid_ppem.value), 1078 (pres_ppem.name, pres_ppem.value), 1079 (ppos_ppem.name, ppos_ppem.value)), 1080 (("GRID_PPEM", 20), ("PRESENTATION_PPEM", 72), 1081 ("PPOSITIONING_PPEM", 144)) 1082 ) 1083 1084 def test_compiler_flags(self): 1085 [setting1, setting2] = self.parse( 1086 'COMPILER_USEEXTENSIONLOOKUPS\n' 1087 'COMPILER_USEPAIRPOSFORMAT2\n' 1088 ).statements 1089 self.assertEqual( 1090 ((setting1.name, setting1.value), 1091 (setting2.name, setting2.value)), 1092 (("COMPILER_USEEXTENSIONLOOKUPS", True), 1093 ("COMPILER_USEPAIRPOSFORMAT2", True)) 1094 ) 1095 1096 def test_cmap(self): 1097 [cmap_format1, cmap_format2, cmap_format3] = self.parse( 1098 'CMAP_FORMAT 0 3 4\n' 1099 'CMAP_FORMAT 1 0 6\n' 1100 'CMAP_FORMAT 3 1 4\n' 1101 ).statements 1102 self.assertEqual( 1103 ((cmap_format1.name, cmap_format1.value), 1104 (cmap_format2.name, cmap_format2.value), 1105 (cmap_format3.name, cmap_format3.value)), 1106 (("CMAP_FORMAT", (0, 3, 4)), 1107 ("CMAP_FORMAT", (1, 0, 6)), 1108 ("CMAP_FORMAT", (3, 1, 4))) 1109 ) 1110 1111 def test_stop_at_end(self): 1112 [def_glyph] = self.parse( 1113 'DEF_GLYPH ".notdef" ID 0 TYPE BASE END_GLYPH END\0\0\0\0' 1114 ).statements 1115 self.assertEqual((def_glyph.name, def_glyph.id, def_glyph.unicode, 1116 def_glyph.type, def_glyph.components), 1117 (".notdef", 0, None, "BASE", None)) 1118 1119 def parse(self, text): 1120 return Parser(UnicodeIO(text)).parse() 1121 1122if __name__ == "__main__": 1123 import sys 1124 sys.exit(unittest.main()) 1125