1 from __future__ import print_function, division, absolute_import 2 from __future__ import unicode_literals 3 from fontTools.misc.testTools import getXML 4 from fontTools.otlLib import builder 5 from fontTools.ttLib.tables import otTables 6 from itertools import chain 7 import unittest 8 9 10 class BuilderTest(unittest.TestCase): 11 GLYPHS = (".notdef space zero one two three four five six " 12 "A B C a b c grave acute cedilla f_f_i f_i c_t").split() 13 GLYPHMAP = {name: num for num, name in enumerate(GLYPHS)} 14 15 ANCHOR1 = builder.buildAnchor(11, -11) 16 ANCHOR2 = builder.buildAnchor(22, -22) 17 ANCHOR3 = builder.buildAnchor(33, -33) 18 19 def __init__(self, methodName): 20 unittest.TestCase.__init__(self, methodName) 21 # Python 3 renamed assertRaisesRegexp to assertRaisesRegex, 22 # and fires deprecation warnings if a program uses the old name. 23 if not hasattr(self, "assertRaisesRegex"): 24 self.assertRaisesRegex = self.assertRaisesRegexp 25 26 @classmethod 27 def setUpClass(cls): 28 cls.maxDiff = None 29 30 def test_buildAnchor_format1(self): 31 anchor = builder.buildAnchor(23, 42) 32 self.assertEqual(getXML(anchor.toXML), 33 ['<Anchor Format="1">', 34 ' <XCoordinate value="23"/>', 35 ' <YCoordinate value="42"/>', 36 '</Anchor>']) 37 38 def test_buildAnchor_format2(self): 39 anchor = builder.buildAnchor(23, 42, point=17) 40 self.assertEqual(getXML(anchor.toXML), 41 ['<Anchor Format="2">', 42 ' <XCoordinate value="23"/>', 43 ' <YCoordinate value="42"/>', 44 ' <AnchorPoint value="17"/>', 45 '</Anchor>']) 46 47 def test_buildAnchor_format3(self): 48 anchor = builder.buildAnchor( 49 23, 42, 50 deviceX=builder.buildDevice({1: 1, 0: 0}), 51 deviceY=builder.buildDevice({7: 7})) 52 self.assertEqual(getXML(anchor.toXML), 53 ['<Anchor Format="3">', 54 ' <XCoordinate value="23"/>', 55 ' <YCoordinate value="42"/>', 56 ' <XDeviceTable>', 57 ' <StartSize value="0"/>', 58 ' <EndSize value="1"/>', 59 ' <DeltaFormat value="1"/>', 60 ' <DeltaValue value="[0, 1]"/>', 61 ' </XDeviceTable>', 62 ' <YDeviceTable>', 63 ' <StartSize value="7"/>', 64 ' <EndSize value="7"/>', 65 ' <DeltaFormat value="2"/>', 66 ' <DeltaValue value="[7]"/>', 67 ' </YDeviceTable>', 68 '</Anchor>']) 69 70 def test_buildAttachList(self): 71 attachList = builder.buildAttachList({ 72 "zero": [23, 7], "one": [1], 73 }, self.GLYPHMAP) 74 self.assertEqual(getXML(attachList.toXML), 75 ['<AttachList>', 76 ' <Coverage>', 77 ' <Glyph value="zero"/>', 78 ' <Glyph value="one"/>', 79 ' </Coverage>', 80 ' <!-- GlyphCount=2 -->', 81 ' <AttachPoint index="0">', 82 ' <!-- PointCount=2 -->', 83 ' <PointIndex index="0" value="7"/>', 84 ' <PointIndex index="1" value="23"/>', 85 ' </AttachPoint>', 86 ' <AttachPoint index="1">', 87 ' <!-- PointCount=1 -->', 88 ' <PointIndex index="0" value="1"/>', 89 ' </AttachPoint>', 90 '</AttachList>']) 91 92 def test_buildAttachList_empty(self): 93 self.assertIsNone(builder.buildAttachList({}, self.GLYPHMAP)) 94 95 def test_buildAttachPoint(self): 96 attachPoint = builder.buildAttachPoint([7, 3]) 97 self.assertEqual(getXML(attachPoint.toXML), 98 ['<AttachPoint>', 99 ' <!-- PointCount=2 -->', 100 ' <PointIndex index="0" value="3"/>', 101 ' <PointIndex index="1" value="7"/>', 102 '</AttachPoint>']) 103 104 def test_buildAttachPoint_empty(self): 105 self.assertIsNone(builder.buildAttachPoint([])) 106 107 def test_buildAttachPoint_duplicate(self): 108 attachPoint = builder.buildAttachPoint([7, 3, 7]) 109 self.assertEqual(getXML(attachPoint.toXML), 110 ['<AttachPoint>', 111 ' <!-- PointCount=2 -->', 112 ' <PointIndex index="0" value="3"/>', 113 ' <PointIndex index="1" value="7"/>', 114 '</AttachPoint>']) 115 116 117 def test_buildBaseArray(self): 118 anchor = builder.buildAnchor 119 baseArray = builder.buildBaseArray({ 120 "a": {2: anchor(300, 80)}, 121 "c": {1: anchor(300, 80), 2: anchor(300, -20)} 122 }, numMarkClasses=4, glyphMap=self.GLYPHMAP) 123 self.assertEqual(getXML(baseArray.toXML), 124 ['<BaseArray>', 125 ' <!-- BaseCount=2 -->', 126 ' <BaseRecord index="0">', 127 ' <BaseAnchor index="0" empty="1"/>', 128 ' <BaseAnchor index="1" empty="1"/>', 129 ' <BaseAnchor index="2" Format="1">', 130 ' <XCoordinate value="300"/>', 131 ' <YCoordinate value="80"/>', 132 ' </BaseAnchor>', 133 ' <BaseAnchor index="3" empty="1"/>', 134 ' </BaseRecord>', 135 ' <BaseRecord index="1">', 136 ' <BaseAnchor index="0" empty="1"/>', 137 ' <BaseAnchor index="1" Format="1">', 138 ' <XCoordinate value="300"/>', 139 ' <YCoordinate value="80"/>', 140 ' </BaseAnchor>', 141 ' <BaseAnchor index="2" Format="1">', 142 ' <XCoordinate value="300"/>', 143 ' <YCoordinate value="-20"/>', 144 ' </BaseAnchor>', 145 ' <BaseAnchor index="3" empty="1"/>', 146 ' </BaseRecord>', 147 '</BaseArray>']) 148 149 def test_buildBaseRecord(self): 150 a = builder.buildAnchor 151 rec = builder.buildBaseRecord([a(500, -20), None, a(300, -15)]) 152 self.assertEqual(getXML(rec.toXML), 153 ['<BaseRecord>', 154 ' <BaseAnchor index="0" Format="1">', 155 ' <XCoordinate value="500"/>', 156 ' <YCoordinate value="-20"/>', 157 ' </BaseAnchor>', 158 ' <BaseAnchor index="1" empty="1"/>', 159 ' <BaseAnchor index="2" Format="1">', 160 ' <XCoordinate value="300"/>', 161 ' <YCoordinate value="-15"/>', 162 ' </BaseAnchor>', 163 '</BaseRecord>']) 164 165 def test_buildCaretValueForCoord(self): 166 caret = builder.buildCaretValueForCoord(500) 167 self.assertEqual(getXML(caret.toXML), 168 ['<CaretValue Format="1">', 169 ' <Coordinate value="500"/>', 170 '</CaretValue>']) 171 172 def test_buildCaretValueForPoint(self): 173 caret = builder.buildCaretValueForPoint(23) 174 self.assertEqual(getXML(caret.toXML), 175 ['<CaretValue Format="2">', 176 ' <CaretValuePoint value="23"/>', 177 '</CaretValue>']) 178 179 def test_buildComponentRecord(self): 180 a = builder.buildAnchor 181 rec = builder.buildComponentRecord([a(500, -20), None, a(300, -15)]) 182 self.assertEqual(getXML(rec.toXML), 183 ['<ComponentRecord>', 184 ' <LigatureAnchor index="0" Format="1">', 185 ' <XCoordinate value="500"/>', 186 ' <YCoordinate value="-20"/>', 187 ' </LigatureAnchor>', 188 ' <LigatureAnchor index="1" empty="1"/>', 189 ' <LigatureAnchor index="2" Format="1">', 190 ' <XCoordinate value="300"/>', 191 ' <YCoordinate value="-15"/>', 192 ' </LigatureAnchor>', 193 '</ComponentRecord>']) 194 195 def test_buildComponentRecord_empty(self): 196 self.assertIsNone(builder.buildComponentRecord([])) 197 198 def test_buildComponentRecord_None(self): 199 self.assertIsNone(builder.buildComponentRecord(None)) 200 201 def test_buildCoverage(self): 202 cov = builder.buildCoverage({"two", "four"}, {"two": 2, "four": 4}) 203 self.assertEqual(getXML(cov.toXML), 204 ['<Coverage>', 205 ' <Glyph value="two"/>', 206 ' <Glyph value="four"/>', 207 '</Coverage>']) 208 209 def test_buildCursivePos(self): 210 pos = builder.buildCursivePosSubtable({ 211 "two": (self.ANCHOR1, self.ANCHOR2), 212 "four": (self.ANCHOR3, self.ANCHOR1) 213 }, self.GLYPHMAP) 214 self.assertEqual(getXML(pos.toXML), 215 ['<CursivePos Format="1">', 216 ' <Coverage>', 217 ' <Glyph value="two"/>', 218 ' <Glyph value="four"/>', 219 ' </Coverage>', 220 ' <!-- EntryExitCount=2 -->', 221 ' <EntryExitRecord index="0">', 222 ' <EntryAnchor Format="1">', 223 ' <XCoordinate value="11"/>', 224 ' <YCoordinate value="-11"/>', 225 ' </EntryAnchor>', 226 ' <ExitAnchor Format="1">', 227 ' <XCoordinate value="22"/>', 228 ' <YCoordinate value="-22"/>', 229 ' </ExitAnchor>', 230 ' </EntryExitRecord>', 231 ' <EntryExitRecord index="1">', 232 ' <EntryAnchor Format="1">', 233 ' <XCoordinate value="33"/>', 234 ' <YCoordinate value="-33"/>', 235 ' </EntryAnchor>', 236 ' <ExitAnchor Format="1">', 237 ' <XCoordinate value="11"/>', 238 ' <YCoordinate value="-11"/>', 239 ' </ExitAnchor>', 240 ' </EntryExitRecord>', 241 '</CursivePos>']) 242 243 def test_buildDevice_format1(self): 244 device = builder.buildDevice({1:1, 0:0}) 245 self.assertEqual(getXML(device.toXML), 246 ['<Device>', 247 ' <StartSize value="0"/>', 248 ' <EndSize value="1"/>', 249 ' <DeltaFormat value="1"/>', 250 ' <DeltaValue value="[0, 1]"/>', 251 '</Device>']) 252 253 def test_buildDevice_format2(self): 254 device = builder.buildDevice({2:2, 0:1, 1:0}) 255 self.assertEqual(getXML(device.toXML), 256 ['<Device>', 257 ' <StartSize value="0"/>', 258 ' <EndSize value="2"/>', 259 ' <DeltaFormat value="2"/>', 260 ' <DeltaValue value="[1, 0, 2]"/>', 261 '</Device>']) 262 263 def test_buildDevice_format3(self): 264 device = builder.buildDevice({5:3, 1:77}) 265 self.assertEqual(getXML(device.toXML), 266 ['<Device>', 267 ' <StartSize value="1"/>', 268 ' <EndSize value="5"/>', 269 ' <DeltaFormat value="3"/>', 270 ' <DeltaValue value="[77, 0, 0, 0, 3]"/>', 271 '</Device>']) 272 273 def test_buildLigatureArray(self): 274 anchor = builder.buildAnchor 275 ligatureArray = builder.buildLigatureArray({ 276 "f_i": [{2: anchor(300, -20)}, {}], 277 "c_t": [{}, {1: anchor(500, 350), 2: anchor(1300, -20)}] 278 }, numMarkClasses=4, glyphMap=self.GLYPHMAP) 279 self.assertEqual(getXML(ligatureArray.toXML), 280 ['<LigatureArray>', 281 ' <!-- LigatureCount=2 -->', 282 ' <LigatureAttach index="0">', # f_i 283 ' <!-- ComponentCount=2 -->', 284 ' <ComponentRecord index="0">', 285 ' <LigatureAnchor index="0" empty="1"/>', 286 ' <LigatureAnchor index="1" empty="1"/>', 287 ' <LigatureAnchor index="2" Format="1">', 288 ' <XCoordinate value="300"/>', 289 ' <YCoordinate value="-20"/>', 290 ' </LigatureAnchor>', 291 ' <LigatureAnchor index="3" empty="1"/>', 292 ' </ComponentRecord>', 293 ' <ComponentRecord index="1">', 294 ' <LigatureAnchor index="0" empty="1"/>', 295 ' <LigatureAnchor index="1" empty="1"/>', 296 ' <LigatureAnchor index="2" empty="1"/>', 297 ' <LigatureAnchor index="3" empty="1"/>', 298 ' </ComponentRecord>', 299 ' </LigatureAttach>', 300 ' <LigatureAttach index="1">', 301 ' <!-- ComponentCount=2 -->', 302 ' <ComponentRecord index="0">', 303 ' <LigatureAnchor index="0" empty="1"/>', 304 ' <LigatureAnchor index="1" empty="1"/>', 305 ' <LigatureAnchor index="2" empty="1"/>', 306 ' <LigatureAnchor index="3" empty="1"/>', 307 ' </ComponentRecord>', 308 ' <ComponentRecord index="1">', 309 ' <LigatureAnchor index="0" empty="1"/>', 310 ' <LigatureAnchor index="1" Format="1">', 311 ' <XCoordinate value="500"/>', 312 ' <YCoordinate value="350"/>', 313 ' </LigatureAnchor>', 314 ' <LigatureAnchor index="2" Format="1">', 315 ' <XCoordinate value="1300"/>', 316 ' <YCoordinate value="-20"/>', 317 ' </LigatureAnchor>', 318 ' <LigatureAnchor index="3" empty="1"/>', 319 ' </ComponentRecord>', 320 ' </LigatureAttach>', 321 '</LigatureArray>']) 322 323 def test_buildLigatureAttach(self): 324 anchor = builder.buildAnchor 325 attach = builder.buildLigatureAttach([ 326 [anchor(500, -10), None], 327 [None, anchor(300, -20), None]]) 328 self.assertEqual(getXML(attach.toXML), 329 ['<LigatureAttach>', 330 ' <!-- ComponentCount=2 -->', 331 ' <ComponentRecord index="0">', 332 ' <LigatureAnchor index="0" Format="1">', 333 ' <XCoordinate value="500"/>', 334 ' <YCoordinate value="-10"/>', 335 ' </LigatureAnchor>', 336 ' <LigatureAnchor index="1" empty="1"/>', 337 ' </ComponentRecord>', 338 ' <ComponentRecord index="1">', 339 ' <LigatureAnchor index="0" empty="1"/>', 340 ' <LigatureAnchor index="1" Format="1">', 341 ' <XCoordinate value="300"/>', 342 ' <YCoordinate value="-20"/>', 343 ' </LigatureAnchor>', 344 ' <LigatureAnchor index="2" empty="1"/>', 345 ' </ComponentRecord>', 346 '</LigatureAttach>']) 347 348 def test_buildLigatureAttach_emptyComponents(self): 349 attach = builder.buildLigatureAttach([[], None]) 350 self.assertEqual(getXML(attach.toXML), 351 ['<LigatureAttach>', 352 ' <!-- ComponentCount=2 -->', 353 ' <ComponentRecord index="0" empty="1"/>', 354 ' <ComponentRecord index="1" empty="1"/>', 355 '</LigatureAttach>']) 356 357 def test_buildLigatureAttach_noComponents(self): 358 attach = builder.buildLigatureAttach([]) 359 self.assertEqual(getXML(attach.toXML), 360 ['<LigatureAttach>', 361 ' <!-- ComponentCount=0 -->', 362 '</LigatureAttach>']) 363 364 def test_buildLigCaretList(self): 365 carets = builder.buildLigCaretList( 366 {"f_f_i": [300, 600]}, {"c_t": [42]}, self.GLYPHMAP) 367 self.assertEqual(getXML(carets.toXML), 368 ['<LigCaretList>', 369 ' <Coverage>', 370 ' <Glyph value="f_f_i"/>', 371 ' <Glyph value="c_t"/>', 372 ' </Coverage>', 373 ' <!-- LigGlyphCount=2 -->', 374 ' <LigGlyph index="0">', 375 ' <!-- CaretCount=2 -->', 376 ' <CaretValue index="0" Format="1">', 377 ' <Coordinate value="300"/>', 378 ' </CaretValue>', 379 ' <CaretValue index="1" Format="1">', 380 ' <Coordinate value="600"/>', 381 ' </CaretValue>', 382 ' </LigGlyph>', 383 ' <LigGlyph index="1">', 384 ' <!-- CaretCount=1 -->', 385 ' <CaretValue index="0" Format="2">', 386 ' <CaretValuePoint value="42"/>', 387 ' </CaretValue>', 388 ' </LigGlyph>', 389 '</LigCaretList>']) 390 391 def test_buildLigCaretList_bothCoordsAndPointsForSameGlyph(self): 392 carets = builder.buildLigCaretList( 393 {"f_f_i": [300]}, {"f_f_i": [7]}, self.GLYPHMAP) 394 self.assertEqual(getXML(carets.toXML), 395 ['<LigCaretList>', 396 ' <Coverage>', 397 ' <Glyph value="f_f_i"/>', 398 ' </Coverage>', 399 ' <!-- LigGlyphCount=1 -->', 400 ' <LigGlyph index="0">', 401 ' <!-- CaretCount=2 -->', 402 ' <CaretValue index="0" Format="1">', 403 ' <Coordinate value="300"/>', 404 ' </CaretValue>', 405 ' <CaretValue index="1" Format="2">', 406 ' <CaretValuePoint value="7"/>', 407 ' </CaretValue>', 408 ' </LigGlyph>', 409 '</LigCaretList>']) 410 411 def test_buildLigCaretList_empty(self): 412 self.assertIsNone(builder.buildLigCaretList({}, {}, self.GLYPHMAP)) 413 414 def test_buildLigCaretList_None(self): 415 self.assertIsNone(builder.buildLigCaretList(None, None, self.GLYPHMAP)) 416 417 def test_buildLigGlyph_coords(self): 418 lig = builder.buildLigGlyph([500, 800], None) 419 self.assertEqual(getXML(lig.toXML), 420 ['<LigGlyph>', 421 ' <!-- CaretCount=2 -->', 422 ' <CaretValue index="0" Format="1">', 423 ' <Coordinate value="500"/>', 424 ' </CaretValue>', 425 ' <CaretValue index="1" Format="1">', 426 ' <Coordinate value="800"/>', 427 ' </CaretValue>', 428 '</LigGlyph>']) 429 430 def test_buildLigGlyph_empty(self): 431 self.assertIsNone(builder.buildLigGlyph([], [])) 432 433 def test_buildLigGlyph_None(self): 434 self.assertIsNone(builder.buildLigGlyph(None, None)) 435 436 def test_buildLigGlyph_points(self): 437 lig = builder.buildLigGlyph(None, [2]) 438 self.assertEqual(getXML(lig.toXML), 439 ['<LigGlyph>', 440 ' <!-- CaretCount=1 -->', 441 ' <CaretValue index="0" Format="2">', 442 ' <CaretValuePoint value="2"/>', 443 ' </CaretValue>', 444 '</LigGlyph>']) 445 446 def test_buildLookup(self): 447 s1 = builder.buildSingleSubstSubtable({"one": "two"}) 448 s2 = builder.buildSingleSubstSubtable({"three": "four"}) 449 lookup = builder.buildLookup([s1, s2], flags=7) 450 self.assertEqual(getXML(lookup.toXML), 451 ['<Lookup>', 452 ' <LookupType value="1"/>', 453 ' <LookupFlag value="7"/>', 454 ' <!-- SubTableCount=2 -->', 455 ' <SingleSubst index="0">', 456 ' <Substitution in="one" out="two"/>', 457 ' </SingleSubst>', 458 ' <SingleSubst index="1">', 459 ' <Substitution in="three" out="four"/>', 460 ' </SingleSubst>', 461 '</Lookup>']) 462 463 def test_buildLookup_badFlags(self): 464 s = builder.buildSingleSubstSubtable({"one": "two"}) 465 self.assertRaisesRegex( 466 AssertionError, "if markFilterSet is None, " 467 "flags must not set LOOKUP_FLAG_USE_MARK_FILTERING_SET; " 468 "flags=0x0010", 469 builder.buildLookup, [s], 470 builder.LOOKUP_FLAG_USE_MARK_FILTERING_SET, None) 471 self.assertRaisesRegex( 472 AssertionError, "if markFilterSet is not None, " 473 "flags must set LOOKUP_FLAG_USE_MARK_FILTERING_SET; " 474 "flags=0x0004", 475 builder.buildLookup, [s], 476 builder.LOOKUP_FLAG_IGNORE_LIGATURES, 777) 477 478 def test_buildLookup_conflictingSubtableTypes(self): 479 s1 = builder.buildSingleSubstSubtable({"one": "two"}) 480 s2 = builder.buildAlternateSubstSubtable({"one": ["two", "three"]}) 481 self.assertRaisesRegex( 482 AssertionError, "all subtables must have the same LookupType", 483 builder.buildLookup, [s1, s2]) 484 485 def test_buildLookup_noSubtables(self): 486 self.assertIsNone(builder.buildLookup([])) 487 self.assertIsNone(builder.buildLookup(None)) 488 self.assertIsNone(builder.buildLookup([None])) 489 self.assertIsNone(builder.buildLookup([None, None])) 490 491 def test_buildLookup_markFilterSet(self): 492 s = builder.buildSingleSubstSubtable({"one": "two"}) 493 flags = (builder.LOOKUP_FLAG_RIGHT_TO_LEFT | 494 builder.LOOKUP_FLAG_USE_MARK_FILTERING_SET) 495 lookup = builder.buildLookup([s], flags, markFilterSet=999) 496 self.assertEqual(getXML(lookup.toXML), 497 ['<Lookup>', 498 ' <LookupType value="1"/>', 499 ' <LookupFlag value="17"/>', 500 ' <!-- SubTableCount=1 -->', 501 ' <SingleSubst index="0">', 502 ' <Substitution in="one" out="two"/>', 503 ' </SingleSubst>', 504 ' <MarkFilteringSet value="999"/>', 505 '</Lookup>']) 506 507 def test_buildMarkArray(self): 508 markArray = builder.buildMarkArray({ 509 "acute": (7, builder.buildAnchor(300, 800)), 510 "grave": (2, builder.buildAnchor(10, 80)) 511 }, self.GLYPHMAP) 512 self.assertLess(self.GLYPHMAP["grave"], self.GLYPHMAP["acute"]) 513 self.assertEqual(getXML(markArray.toXML), 514 ['<MarkArray>', 515 ' <!-- MarkCount=2 -->', 516 ' <MarkRecord index="0">', 517 ' <Class value="2"/>', 518 ' <MarkAnchor Format="1">', 519 ' <XCoordinate value="10"/>', 520 ' <YCoordinate value="80"/>', 521 ' </MarkAnchor>', 522 ' </MarkRecord>', 523 ' <MarkRecord index="1">', 524 ' <Class value="7"/>', 525 ' <MarkAnchor Format="1">', 526 ' <XCoordinate value="300"/>', 527 ' <YCoordinate value="800"/>', 528 ' </MarkAnchor>', 529 ' </MarkRecord>', 530 '</MarkArray>']) 531 532 def test_buildMarkBasePosSubtable(self): 533 anchor = builder.buildAnchor 534 marks = { 535 "acute": (0, anchor(300, 700)), 536 "cedilla": (1, anchor(300, -100)), 537 "grave": (0, anchor(300, 700)) 538 } 539 bases = { 540 # Make sure we can handle missing entries. 541 "A": {}, # no entry for any markClass 542 "B": {0: anchor(500, 900)}, # only markClass 0 specified 543 "C": {1: anchor(500, -10)}, # only markClass 1 specified 544 545 "a": {0: anchor(500, 400), 1: anchor(500, -20)}, 546 "b": {0: anchor(500, 800), 1: anchor(500, -20)} 547 } 548 table = builder.buildMarkBasePosSubtable(marks, bases, self.GLYPHMAP) 549 self.assertEqual(getXML(table.toXML), 550 ['<MarkBasePos Format="1">', 551 ' <MarkCoverage>', 552 ' <Glyph value="grave"/>', 553 ' <Glyph value="acute"/>', 554 ' <Glyph value="cedilla"/>', 555 ' </MarkCoverage>', 556 ' <BaseCoverage>', 557 ' <Glyph value="A"/>', 558 ' <Glyph value="B"/>', 559 ' <Glyph value="C"/>', 560 ' <Glyph value="a"/>', 561 ' <Glyph value="b"/>', 562 ' </BaseCoverage>', 563 ' <!-- ClassCount=2 -->', 564 ' <MarkArray>', 565 ' <!-- MarkCount=3 -->', 566 ' <MarkRecord index="0">', # grave 567 ' <Class value="0"/>', 568 ' <MarkAnchor Format="1">', 569 ' <XCoordinate value="300"/>', 570 ' <YCoordinate value="700"/>', 571 ' </MarkAnchor>', 572 ' </MarkRecord>', 573 ' <MarkRecord index="1">', # acute 574 ' <Class value="0"/>', 575 ' <MarkAnchor Format="1">', 576 ' <XCoordinate value="300"/>', 577 ' <YCoordinate value="700"/>', 578 ' </MarkAnchor>', 579 ' </MarkRecord>', 580 ' <MarkRecord index="2">', # cedilla 581 ' <Class value="1"/>', 582 ' <MarkAnchor Format="1">', 583 ' <XCoordinate value="300"/>', 584 ' <YCoordinate value="-100"/>', 585 ' </MarkAnchor>', 586 ' </MarkRecord>', 587 ' </MarkArray>', 588 ' <BaseArray>', 589 ' <!-- BaseCount=5 -->', 590 ' <BaseRecord index="0">', # A 591 ' <BaseAnchor index="0" empty="1"/>', 592 ' <BaseAnchor index="1" empty="1"/>', 593 ' </BaseRecord>', 594 ' <BaseRecord index="1">', # B 595 ' <BaseAnchor index="0" Format="1">', 596 ' <XCoordinate value="500"/>', 597 ' <YCoordinate value="900"/>', 598 ' </BaseAnchor>', 599 ' <BaseAnchor index="1" empty="1"/>', 600 ' </BaseRecord>', 601 ' <BaseRecord index="2">', # C 602 ' <BaseAnchor index="0" empty="1"/>', 603 ' <BaseAnchor index="1" Format="1">', 604 ' <XCoordinate value="500"/>', 605 ' <YCoordinate value="-10"/>', 606 ' </BaseAnchor>', 607 ' </BaseRecord>', 608 ' <BaseRecord index="3">', # a 609 ' <BaseAnchor index="0" Format="1">', 610 ' <XCoordinate value="500"/>', 611 ' <YCoordinate value="400"/>', 612 ' </BaseAnchor>', 613 ' <BaseAnchor index="1" Format="1">', 614 ' <XCoordinate value="500"/>', 615 ' <YCoordinate value="-20"/>', 616 ' </BaseAnchor>', 617 ' </BaseRecord>', 618 ' <BaseRecord index="4">', # b 619 ' <BaseAnchor index="0" Format="1">', 620 ' <XCoordinate value="500"/>', 621 ' <YCoordinate value="800"/>', 622 ' </BaseAnchor>', 623 ' <BaseAnchor index="1" Format="1">', 624 ' <XCoordinate value="500"/>', 625 ' <YCoordinate value="-20"/>', 626 ' </BaseAnchor>', 627 ' </BaseRecord>', 628 ' </BaseArray>', 629 '</MarkBasePos>']) 630 631 def test_buildMarkGlyphSetsDef(self): 632 marksets = builder.buildMarkGlyphSetsDef( 633 [{"acute", "grave"}, {"cedilla", "grave"}], self.GLYPHMAP) 634 self.assertEqual(getXML(marksets.toXML), 635 ['<MarkGlyphSetsDef>', 636 ' <MarkSetTableFormat value="1"/>', 637 ' <!-- MarkSetCount=2 -->', 638 ' <Coverage index="0">', 639 ' <Glyph value="grave"/>', 640 ' <Glyph value="acute"/>', 641 ' </Coverage>', 642 ' <Coverage index="1">', 643 ' <Glyph value="grave"/>', 644 ' <Glyph value="cedilla"/>', 645 ' </Coverage>', 646 '</MarkGlyphSetsDef>']) 647 648 def test_buildMarkGlyphSetsDef_empty(self): 649 self.assertIsNone(builder.buildMarkGlyphSetsDef([], self.GLYPHMAP)) 650 651 def test_buildMarkGlyphSetsDef_None(self): 652 self.assertIsNone(builder.buildMarkGlyphSetsDef(None, self.GLYPHMAP)) 653 654 def test_buildMarkLigPosSubtable(self): 655 anchor = builder.buildAnchor 656 marks = { 657 "acute": (0, anchor(300, 700)), 658 "cedilla": (1, anchor(300, -100)), 659 "grave": (0, anchor(300, 700)) 660 } 661 bases = { 662 "f_i": [{}, {0: anchor(200, 400)}], # nothing on f; only 1 on i 663 "c_t": [ 664 {0: anchor(500, 600), 1: anchor(500, -20)}, # c 665 {0: anchor(1300, 800), 1: anchor(1300, -20)} # t 666 ] 667 } 668 table = builder.buildMarkLigPosSubtable(marks, bases, self.GLYPHMAP) 669 self.assertEqual(getXML(table.toXML), 670 ['<MarkLigPos Format="1">', 671 ' <MarkCoverage>', 672 ' <Glyph value="grave"/>', 673 ' <Glyph value="acute"/>', 674 ' <Glyph value="cedilla"/>', 675 ' </MarkCoverage>', 676 ' <LigatureCoverage>', 677 ' <Glyph value="f_i"/>', 678 ' <Glyph value="c_t"/>', 679 ' </LigatureCoverage>', 680 ' <!-- ClassCount=2 -->', 681 ' <MarkArray>', 682 ' <!-- MarkCount=3 -->', 683 ' <MarkRecord index="0">', 684 ' <Class value="0"/>', 685 ' <MarkAnchor Format="1">', 686 ' <XCoordinate value="300"/>', 687 ' <YCoordinate value="700"/>', 688 ' </MarkAnchor>', 689 ' </MarkRecord>', 690 ' <MarkRecord index="1">', 691 ' <Class value="0"/>', 692 ' <MarkAnchor Format="1">', 693 ' <XCoordinate value="300"/>', 694 ' <YCoordinate value="700"/>', 695 ' </MarkAnchor>', 696 ' </MarkRecord>', 697 ' <MarkRecord index="2">', 698 ' <Class value="1"/>', 699 ' <MarkAnchor Format="1">', 700 ' <XCoordinate value="300"/>', 701 ' <YCoordinate value="-100"/>', 702 ' </MarkAnchor>', 703 ' </MarkRecord>', 704 ' </MarkArray>', 705 ' <LigatureArray>', 706 ' <!-- LigatureCount=2 -->', 707 ' <LigatureAttach index="0">', 708 ' <!-- ComponentCount=2 -->', 709 ' <ComponentRecord index="0">', 710 ' <LigatureAnchor index="0" empty="1"/>', 711 ' <LigatureAnchor index="1" empty="1"/>', 712 ' </ComponentRecord>', 713 ' <ComponentRecord index="1">', 714 ' <LigatureAnchor index="0" Format="1">', 715 ' <XCoordinate value="200"/>', 716 ' <YCoordinate value="400"/>', 717 ' </LigatureAnchor>', 718 ' <LigatureAnchor index="1" empty="1"/>', 719 ' </ComponentRecord>', 720 ' </LigatureAttach>', 721 ' <LigatureAttach index="1">', 722 ' <!-- ComponentCount=2 -->', 723 ' <ComponentRecord index="0">', 724 ' <LigatureAnchor index="0" Format="1">', 725 ' <XCoordinate value="500"/>', 726 ' <YCoordinate value="600"/>', 727 ' </LigatureAnchor>', 728 ' <LigatureAnchor index="1" Format="1">', 729 ' <XCoordinate value="500"/>', 730 ' <YCoordinate value="-20"/>', 731 ' </LigatureAnchor>', 732 ' </ComponentRecord>', 733 ' <ComponentRecord index="1">', 734 ' <LigatureAnchor index="0" Format="1">', 735 ' <XCoordinate value="1300"/>', 736 ' <YCoordinate value="800"/>', 737 ' </LigatureAnchor>', 738 ' <LigatureAnchor index="1" Format="1">', 739 ' <XCoordinate value="1300"/>', 740 ' <YCoordinate value="-20"/>', 741 ' </LigatureAnchor>', 742 ' </ComponentRecord>', 743 ' </LigatureAttach>', 744 ' </LigatureArray>', 745 '</MarkLigPos>']) 746 747 def test_buildMarkRecord(self): 748 rec = builder.buildMarkRecord(17, builder.buildAnchor(500, -20)) 749 self.assertEqual(getXML(rec.toXML), 750 ['<MarkRecord>', 751 ' <Class value="17"/>', 752 ' <MarkAnchor Format="1">', 753 ' <XCoordinate value="500"/>', 754 ' <YCoordinate value="-20"/>', 755 ' </MarkAnchor>', 756 '</MarkRecord>']) 757 758 def test_buildMark2Record(self): 759 a = builder.buildAnchor 760 rec = builder.buildMark2Record([a(500, -20), None, a(300, -15)]) 761 self.assertEqual(getXML(rec.toXML), 762 ['<Mark2Record>', 763 ' <Mark2Anchor index="0" Format="1">', 764 ' <XCoordinate value="500"/>', 765 ' <YCoordinate value="-20"/>', 766 ' </Mark2Anchor>', 767 ' <Mark2Anchor index="1" empty="1"/>', 768 ' <Mark2Anchor index="2" Format="1">', 769 ' <XCoordinate value="300"/>', 770 ' <YCoordinate value="-15"/>', 771 ' </Mark2Anchor>', 772 '</Mark2Record>']) 773 774 def test_buildPairPosClassesSubtable(self): 775 d20 = builder.buildValue({"XPlacement": -20}) 776 d50 = builder.buildValue({"XPlacement": -50}) 777 d0 = builder.buildValue({}) 778 d8020 = builder.buildValue({"XPlacement": -80, "YPlacement": -20}) 779 subtable = builder.buildPairPosClassesSubtable({ 780 (tuple("A",), tuple(["zero"])): (d0, d50), 781 (tuple("A",), tuple(["one", "two"])): (None, d20), 782 (tuple(["B", "C"]), tuple(["zero"])): (d8020, d50), 783 }, self.GLYPHMAP) 784 self.assertEqual(getXML(subtable.toXML), 785 ['<PairPos Format="2">', 786 ' <Coverage>', 787 ' <Glyph value="A"/>', 788 ' <Glyph value="B"/>', 789 ' <Glyph value="C"/>', 790 ' </Coverage>', 791 ' <ValueFormat1 value="3"/>', 792 ' <ValueFormat2 value="1"/>', 793 ' <ClassDef1>', 794 ' <ClassDef glyph="A" class="1"/>', 795 ' </ClassDef1>', 796 ' <ClassDef2>', 797 ' <ClassDef glyph="one" class="1"/>', 798 ' <ClassDef glyph="two" class="1"/>', 799 ' <ClassDef glyph="zero" class="2"/>', 800 ' </ClassDef2>', 801 ' <!-- Class1Count=2 -->', 802 ' <!-- Class2Count=3 -->', 803 ' <Class1Record index="0">', 804 ' <Class2Record index="0">', 805 ' </Class2Record>', 806 ' <Class2Record index="1">', 807 ' </Class2Record>', 808 ' <Class2Record index="2">', 809 ' <Value1 XPlacement="-80" YPlacement="-20"/>', 810 ' <Value2 XPlacement="-50"/>', 811 ' </Class2Record>', 812 ' </Class1Record>', 813 ' <Class1Record index="1">', 814 ' <Class2Record index="0">', 815 ' </Class2Record>', 816 ' <Class2Record index="1">', 817 ' <Value2 XPlacement="-20"/>', 818 ' </Class2Record>', 819 ' <Class2Record index="2">', 820 ' <Value1/>', 821 ' <Value2 XPlacement="-50"/>', 822 ' </Class2Record>', 823 ' </Class1Record>', 824 '</PairPos>']) 825 826 def test_buildPairPosGlyphs(self): 827 d50 = builder.buildValue({"XPlacement": -50}) 828 d8020 = builder.buildValue({"XPlacement": -80, "YPlacement": -20}) 829 subtables = builder.buildPairPosGlyphs({ 830 ("A", "zero"): (None, d50), 831 ("A", "one"): (d8020, d50), 832 }, self.GLYPHMAP) 833 self.assertEqual(sum([getXML(t.toXML) for t in subtables], []), 834 ['<PairPos Format="1">', 835 ' <Coverage>', 836 ' <Glyph value="A"/>', 837 ' </Coverage>', 838 ' <ValueFormat1 value="0"/>', 839 ' <ValueFormat2 value="1"/>', 840 ' <!-- PairSetCount=1 -->', 841 ' <PairSet index="0">', 842 ' <!-- PairValueCount=1 -->', 843 ' <PairValueRecord index="0">', 844 ' <SecondGlyph value="zero"/>', 845 ' <Value2 XPlacement="-50"/>', 846 ' </PairValueRecord>', 847 ' </PairSet>', 848 '</PairPos>', 849 '<PairPos Format="1">', 850 ' <Coverage>', 851 ' <Glyph value="A"/>', 852 ' </Coverage>', 853 ' <ValueFormat1 value="3"/>', 854 ' <ValueFormat2 value="1"/>', 855 ' <!-- PairSetCount=1 -->', 856 ' <PairSet index="0">', 857 ' <!-- PairValueCount=1 -->', 858 ' <PairValueRecord index="0">', 859 ' <SecondGlyph value="one"/>', 860 ' <Value1 XPlacement="-80" YPlacement="-20"/>', 861 ' <Value2 XPlacement="-50"/>', 862 ' </PairValueRecord>', 863 ' </PairSet>', 864 '</PairPos>']) 865 866 def test_buildPairPosGlyphsSubtable(self): 867 d20 = builder.buildValue({"XPlacement": -20}) 868 d50 = builder.buildValue({"XPlacement": -50}) 869 d0 = builder.buildValue({}) 870 d8020 = builder.buildValue({"XPlacement": -80, "YPlacement": -20}) 871 subtable = builder.buildPairPosGlyphsSubtable({ 872 ("A", "zero"): (d0, d50), 873 ("A", "one"): (None, d20), 874 ("B", "five"): (d8020, d50), 875 }, self.GLYPHMAP) 876 self.assertEqual(getXML(subtable.toXML), 877 ['<PairPos Format="1">', 878 ' <Coverage>', 879 ' <Glyph value="A"/>', 880 ' <Glyph value="B"/>', 881 ' </Coverage>', 882 ' <ValueFormat1 value="3"/>', 883 ' <ValueFormat2 value="1"/>', 884 ' <!-- PairSetCount=2 -->', 885 ' <PairSet index="0">', 886 ' <!-- PairValueCount=2 -->', 887 ' <PairValueRecord index="0">', 888 ' <SecondGlyph value="zero"/>', 889 ' <Value2 XPlacement="-50"/>', 890 ' </PairValueRecord>', 891 ' <PairValueRecord index="1">', 892 ' <SecondGlyph value="one"/>', 893 ' <Value2 XPlacement="-20"/>', 894 ' </PairValueRecord>', 895 ' </PairSet>', 896 ' <PairSet index="1">', 897 ' <!-- PairValueCount=1 -->', 898 ' <PairValueRecord index="0">', 899 ' <SecondGlyph value="five"/>', 900 ' <Value1 XPlacement="-80" YPlacement="-20"/>', 901 ' <Value2 XPlacement="-50"/>', 902 ' </PairValueRecord>', 903 ' </PairSet>', 904 '</PairPos>']) 905 906 def test_buildSinglePos(self): 907 subtables = builder.buildSinglePos({ 908 "one": builder.buildValue({"XPlacement": 500}), 909 "two": builder.buildValue({"XPlacement": 500}), 910 "three": builder.buildValue({"XPlacement": 200}), 911 "four": builder.buildValue({"XPlacement": 400}), 912 "five": builder.buildValue({"XPlacement": 500}), 913 "six": builder.buildValue({"YPlacement": -6}), 914 }, self.GLYPHMAP) 915 self.assertEqual(sum([getXML(t.toXML) for t in subtables], []), 916 ['<SinglePos Format="1">', 917 ' <Coverage>', 918 ' <Glyph value="one"/>', 919 ' <Glyph value="two"/>', 920 ' <Glyph value="five"/>', 921 ' </Coverage>', 922 ' <ValueFormat value="1"/>', 923 ' <Value XPlacement="500"/>', 924 '</SinglePos>', 925 '<SinglePos Format="2">', 926 ' <Coverage>', 927 ' <Glyph value="three"/>', 928 ' <Glyph value="four"/>', 929 ' </Coverage>', 930 ' <ValueFormat value="1"/>', 931 ' <!-- ValueCount=2 -->', 932 ' <Value index="0" XPlacement="200"/>', 933 ' <Value index="1" XPlacement="400"/>', 934 '</SinglePos>', 935 '<SinglePos Format="1">', 936 ' <Coverage>', 937 ' <Glyph value="six"/>', 938 ' </Coverage>', 939 ' <ValueFormat value="2"/>', 940 ' <Value YPlacement="-6"/>', 941 '</SinglePos>']) 942 943 def test_buildSinglePos_ValueFormat0(self): 944 subtables = builder.buildSinglePos({ 945 "zero": builder.buildValue({}) 946 }, self.GLYPHMAP) 947 self.assertEqual(sum([getXML(t.toXML) for t in subtables], []), 948 ['<SinglePos Format="1">', 949 ' <Coverage>', 950 ' <Glyph value="zero"/>', 951 ' </Coverage>', 952 ' <ValueFormat value="0"/>', 953 '</SinglePos>']) 954 955 def test_buildSinglePosSubtable_format1(self): 956 subtable = builder.buildSinglePosSubtable({ 957 "one": builder.buildValue({"XPlacement": 777}), 958 "two": builder.buildValue({"XPlacement": 777}), 959 }, self.GLYPHMAP) 960 self.assertEqual(getXML(subtable.toXML), 961 ['<SinglePos Format="1">', 962 ' <Coverage>', 963 ' <Glyph value="one"/>', 964 ' <Glyph value="two"/>', 965 ' </Coverage>', 966 ' <ValueFormat value="1"/>', 967 ' <Value XPlacement="777"/>', 968 '</SinglePos>']) 969 970 def test_buildSinglePosSubtable_format2(self): 971 subtable = builder.buildSinglePosSubtable({ 972 "one": builder.buildValue({"XPlacement": 777}), 973 "two": builder.buildValue({"YPlacement": -888}), 974 }, self.GLYPHMAP) 975 self.assertEqual(getXML(subtable.toXML), 976 ['<SinglePos Format="2">', 977 ' <Coverage>', 978 ' <Glyph value="one"/>', 979 ' <Glyph value="two"/>', 980 ' </Coverage>', 981 ' <ValueFormat value="3"/>', 982 ' <!-- ValueCount=2 -->', 983 ' <Value index="0" XPlacement="777"/>', 984 ' <Value index="1" YPlacement="-888"/>', 985 '</SinglePos>']) 986 987 def test_buildValue(self): 988 value = builder.buildValue({"XPlacement": 7, "YPlacement": 23}) 989 func = lambda writer, font: value.toXML(writer, font, valueName="Val") 990 self.assertEqual(getXML(func), 991 ['<Val XPlacement="7" YPlacement="23"/>']) 992 993 def test_getLigatureKey(self): 994 components = lambda s: [tuple(word) for word in s.split()] 995 c = components("fi fl ff ffi fff") 996 c.sort(key=builder._getLigatureKey) 997 self.assertEqual(c, components("fff ffi ff fi fl")) 998 999 def test_getSinglePosValueKey(self): 1000 device = builder.buildDevice({10:1, 11:3}) 1001 a1 = builder.buildValue({"XPlacement": 500, "XPlaDevice": device}) 1002 a2 = builder.buildValue({"XPlacement": 500, "XPlaDevice": device}) 1003 b = builder.buildValue({"XPlacement": 500}) 1004 keyA1 = builder._getSinglePosValueKey(a1) 1005 keyA2 = builder._getSinglePosValueKey(a1) 1006 keyB = builder._getSinglePosValueKey(b) 1007 self.assertEqual(keyA1, keyA2) 1008 self.assertEqual(hash(keyA1), hash(keyA2)) 1009 self.assertNotEqual(keyA1, keyB) 1010 self.assertNotEqual(hash(keyA1), hash(keyB)) 1011 1012 1013 class ClassDefBuilderTest(unittest.TestCase): 1014 def test_build_usingClass0(self): 1015 b = builder.ClassDefBuilder(useClass0=True) 1016 b.add({"aa", "bb"}) 1017 b.add({"a", "b"}) 1018 b.add({"c"}) 1019 b.add({"e", "f", "g", "h"}) 1020 cdef = b.build() 1021 self.assertIsInstance(cdef, otTables.ClassDef) 1022 self.assertEqual(cdef.classDefs, { 1023 "a": 2, 1024 "b": 2, 1025 "c": 3, 1026 "aa": 1, 1027 "bb": 1 1028 }) 1029 1030 def test_build_notUsingClass0(self): 1031 b = builder.ClassDefBuilder(useClass0=False) 1032 b.add({"a", "b"}) 1033 b.add({"c"}) 1034 b.add({"e", "f", "g", "h"}) 1035 cdef = b.build() 1036 self.assertIsInstance(cdef, otTables.ClassDef) 1037 self.assertEqual(cdef.classDefs, { 1038 "a": 2, 1039 "b": 2, 1040 "c": 3, 1041 "e": 1, 1042 "f": 1, 1043 "g": 1, 1044 "h": 1 1045 }) 1046 1047 def test_canAdd(self): 1048 b = builder.ClassDefBuilder(useClass0=True) 1049 b.add({"a", "b", "c", "d"}) 1050 b.add({"e", "f"}) 1051 self.assertTrue(b.canAdd({"a", "b", "c", "d"})) 1052 self.assertTrue(b.canAdd({"e", "f"})) 1053 self.assertTrue(b.canAdd({"g", "h", "i"})) 1054 self.assertFalse(b.canAdd({"b", "c", "d"})) 1055 self.assertFalse(b.canAdd({"a", "b", "c", "d", "e", "f"})) 1056 self.assertFalse(b.canAdd({"d", "e", "f"})) 1057 self.assertFalse(b.canAdd({"f"})) 1058 1059 1060 if __name__ == "__main__": 1061 import sys 1062 sys.exit(unittest.main()) 1063