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