1import os 2import pytest 3import struct 4 5from fontTools import ttLib 6from fontTools.pens.basePen import PenError 7from fontTools.pens.recordingPen import RecordingPen, RecordingPointPen 8from fontTools.pens.ttGlyphPen import TTGlyphPen, TTGlyphPointPen, MAX_F2DOT14 9 10 11class TTGlyphPenTestBase: 12 def runEndToEnd(self, filename): 13 font = ttLib.TTFont() 14 ttx_path = os.path.join( 15 os.path.abspath(os.path.dirname(os.path.realpath(__file__))), 16 "..", 17 "ttLib", 18 "data", 19 filename, 20 ) 21 font.importXML(ttx_path) 22 23 glyphSet = font.getGlyphSet() 24 glyfTable = font["glyf"] 25 pen = self.penClass(glyphSet) 26 27 for name in font.getGlyphOrder(): 28 getattr(glyphSet[name], self.drawMethod)(pen) 29 oldGlyph = glyfTable[name] 30 newGlyph = pen.glyph() 31 32 if hasattr(oldGlyph, "program"): 33 newGlyph.program = oldGlyph.program 34 35 assert oldGlyph.compile(glyfTable) == newGlyph.compile(glyfTable) 36 37 def test_e2e_linesAndSimpleComponents(self): 38 self.runEndToEnd("TestTTF-Regular.ttx") 39 40 def test_e2e_curvesAndComponentTransforms(self): 41 self.runEndToEnd("TestTTFComplex-Regular.ttx") 42 43 44class TTGlyphPenTest(TTGlyphPenTestBase): 45 penClass = TTGlyphPen 46 drawMethod = "draw" 47 48 def test_moveTo_errorWithinContour(self): 49 pen = TTGlyphPen(None) 50 pen.moveTo((0, 0)) 51 with pytest.raises(PenError): 52 pen.moveTo((1, 0)) 53 54 def test_closePath_ignoresAnchors(self): 55 pen = TTGlyphPen(None) 56 pen.moveTo((0, 0)) 57 pen.closePath() 58 assert not pen.points 59 assert not pen.types 60 assert not pen.endPts 61 62 def test_endPath_sameAsClosePath(self): 63 pen = TTGlyphPen(None) 64 65 pen.moveTo((0, 0)) 66 pen.lineTo((0, 1)) 67 pen.lineTo((1, 0)) 68 pen.closePath() 69 closePathGlyph = pen.glyph() 70 71 pen.moveTo((0, 0)) 72 pen.lineTo((0, 1)) 73 pen.lineTo((1, 0)) 74 pen.endPath() 75 endPathGlyph = pen.glyph() 76 77 assert closePathGlyph == endPathGlyph 78 79 def test_glyph_errorOnUnendedContour(self): 80 pen = TTGlyphPen(None) 81 pen.moveTo((0, 0)) 82 with pytest.raises(PenError): 83 pen.glyph() 84 85 def test_glyph_decomposes(self): 86 componentName = "a" 87 glyphSet = {} 88 pen = TTGlyphPen(glyphSet) 89 90 pen.moveTo((0, 0)) 91 pen.lineTo((0, 1)) 92 pen.lineTo((1, 0)) 93 pen.closePath() 94 glyphSet[componentName] = _TestGlyph(pen.glyph()) 95 96 pen.moveTo((0, 0)) 97 pen.lineTo((0, 1)) 98 pen.lineTo((1, 0)) 99 pen.closePath() 100 pen.addComponent(componentName, (1, 0, 0, 1, 2, 0)) 101 pen.addComponent("missing", (1, 0, 0, 1, 0, 0)) # skipped 102 compositeGlyph = pen.glyph() 103 104 pen.moveTo((0, 0)) 105 pen.lineTo((0, 1)) 106 pen.lineTo((1, 0)) 107 pen.closePath() 108 pen.moveTo((2, 0)) 109 pen.lineTo((2, 1)) 110 pen.lineTo((3, 0)) 111 pen.closePath() 112 plainGlyph = pen.glyph() 113 114 assert plainGlyph == compositeGlyph 115 116 def test_remove_extra_move_points(self): 117 pen = TTGlyphPen(None) 118 pen.moveTo((0, 0)) 119 pen.lineTo((100, 0)) 120 pen.qCurveTo((100, 50), (50, 100), (0, 0)) 121 pen.closePath() 122 assert len(pen.points) == 4 123 assert pen.points[0] == (0, 0) 124 125 def test_keep_move_point(self): 126 pen = TTGlyphPen(None) 127 pen.moveTo((0, 0)) 128 pen.lineTo((100, 0)) 129 pen.qCurveTo((100, 50), (50, 100), (30, 30)) 130 # when last and move pts are different, closePath() implies a lineTo 131 pen.closePath() 132 assert len(pen.points) == 5 133 assert pen.points[0] == (0, 0) 134 135 def test_keep_duplicate_end_point(self): 136 pen = TTGlyphPen(None) 137 pen.moveTo((0, 0)) 138 pen.lineTo((100, 0)) 139 pen.qCurveTo((100, 50), (50, 100), (0, 0)) 140 pen.lineTo((0, 0)) # the duplicate point is not removed 141 pen.closePath() 142 assert len(pen.points) == 5 143 assert pen.points[0] == (0, 0) 144 145 def test_within_range_component_transform(self): 146 componentName = "a" 147 glyphSet = {} 148 pen = TTGlyphPen(glyphSet) 149 150 pen.moveTo((0, 0)) 151 pen.lineTo((0, 1)) 152 pen.lineTo((1, 0)) 153 pen.closePath() 154 glyphSet[componentName] = _TestGlyph(pen.glyph()) 155 156 pen.addComponent(componentName, (1.5, 0, 0, 1, 0, 0)) 157 pen.addComponent(componentName, (1, 0, 0, -1.5, 0, 0)) 158 compositeGlyph = pen.glyph() 159 160 pen.addComponent(componentName, (1.5, 0, 0, 1, 0, 0)) 161 pen.addComponent(componentName, (1, 0, 0, -1.5, 0, 0)) 162 expectedGlyph = pen.glyph() 163 164 assert expectedGlyph == compositeGlyph 165 166 def test_clamp_to_almost_2_component_transform(self): 167 componentName = "a" 168 glyphSet = {} 169 pen = TTGlyphPen(glyphSet) 170 171 pen.moveTo((0, 0)) 172 pen.lineTo((0, 1)) 173 pen.lineTo((1, 0)) 174 pen.closePath() 175 glyphSet[componentName] = _TestGlyph(pen.glyph()) 176 177 pen.addComponent(componentName, (1.99999, 0, 0, 1, 0, 0)) 178 pen.addComponent(componentName, (1, 2, 0, 1, 0, 0)) 179 pen.addComponent(componentName, (1, 0, 2, 1, 0, 0)) 180 pen.addComponent(componentName, (1, 0, 0, 2, 0, 0)) 181 pen.addComponent(componentName, (-2, 0, 0, -2, 0, 0)) 182 compositeGlyph = pen.glyph() 183 184 almost2 = MAX_F2DOT14 # 0b1.11111111111111 185 pen.addComponent(componentName, (almost2, 0, 0, 1, 0, 0)) 186 pen.addComponent(componentName, (1, almost2, 0, 1, 0, 0)) 187 pen.addComponent(componentName, (1, 0, almost2, 1, 0, 0)) 188 pen.addComponent(componentName, (1, 0, 0, almost2, 0, 0)) 189 pen.addComponent(componentName, (-2, 0, 0, -2, 0, 0)) 190 expectedGlyph = pen.glyph() 191 192 assert expectedGlyph == compositeGlyph 193 194 def test_out_of_range_transform_decomposed(self): 195 componentName = "a" 196 glyphSet = {} 197 pen = TTGlyphPen(glyphSet) 198 199 pen.moveTo((0, 0)) 200 pen.lineTo((0, 1)) 201 pen.lineTo((1, 0)) 202 pen.closePath() 203 glyphSet[componentName] = _TestGlyph(pen.glyph()) 204 205 pen.addComponent(componentName, (3, 0, 0, 2, 0, 0)) 206 pen.addComponent(componentName, (1, 0, 0, 1, -1, 2)) 207 pen.addComponent(componentName, (2, 0, 0, -3, 0, 0)) 208 compositeGlyph = pen.glyph() 209 210 pen.moveTo((0, 0)) 211 pen.lineTo((0, 2)) 212 pen.lineTo((3, 0)) 213 pen.closePath() 214 pen.moveTo((-1, 2)) 215 pen.lineTo((-1, 3)) 216 pen.lineTo((0, 2)) 217 pen.closePath() 218 pen.moveTo((0, 0)) 219 pen.lineTo((0, -3)) 220 pen.lineTo((2, 0)) 221 pen.closePath() 222 expectedGlyph = pen.glyph() 223 224 assert expectedGlyph == compositeGlyph 225 226 def test_no_handle_overflowing_transform(self): 227 componentName = "a" 228 glyphSet = {} 229 pen = TTGlyphPen(glyphSet, handleOverflowingTransforms=False) 230 231 pen.moveTo((0, 0)) 232 pen.lineTo((0, 1)) 233 pen.lineTo((1, 0)) 234 pen.closePath() 235 baseGlyph = pen.glyph() 236 glyphSet[componentName] = _TestGlyph(baseGlyph) 237 238 pen.addComponent(componentName, (3, 0, 0, 1, 0, 0)) 239 compositeGlyph = pen.glyph() 240 241 assert compositeGlyph.components[0].transform == ((3, 0), (0, 1)) 242 243 with pytest.raises(struct.error): 244 compositeGlyph.compile({"a": baseGlyph}) 245 246 def assertGlyphBoundsEqual(self, glyph, bounds): 247 assert (glyph.xMin, glyph.yMin, glyph.xMax, glyph.yMax) == bounds 248 249 def test_round_float_coordinates_and_component_offsets(self): 250 glyphSet = {} 251 pen = TTGlyphPen(glyphSet) 252 253 pen.moveTo((0, 0)) 254 pen.lineTo((0, 1)) 255 pen.lineTo((367.6, 0)) 256 pen.closePath() 257 simpleGlyph = pen.glyph() 258 259 simpleGlyph.recalcBounds(glyphSet) 260 self.assertGlyphBoundsEqual(simpleGlyph, (0, 0, 368, 1)) 261 262 componentName = "a" 263 glyphSet[componentName] = simpleGlyph 264 265 pen.addComponent(componentName, (1, 0, 0, 1, -86.4, 0)) 266 compositeGlyph = pen.glyph() 267 268 compositeGlyph.recalcBounds(glyphSet) 269 self.assertGlyphBoundsEqual(compositeGlyph, (-86, 0, 282, 1)) 270 271 def test_scaled_component_bounds(self): 272 glyphSet = {} 273 274 pen = TTGlyphPen(glyphSet) 275 pen.moveTo((-231, 939)) 276 pen.lineTo((-55, 939)) 277 pen.lineTo((-55, 745)) 278 pen.lineTo((-231, 745)) 279 pen.closePath() 280 glyphSet["gravecomb"] = pen.glyph() 281 282 pen = TTGlyphPen(glyphSet) 283 pen.moveTo((-278, 939)) 284 pen.lineTo((8, 939)) 285 pen.lineTo((8, 745)) 286 pen.lineTo((-278, 745)) 287 pen.closePath() 288 glyphSet["circumflexcomb"] = pen.glyph() 289 290 pen = TTGlyphPen(glyphSet) 291 pen.addComponent("circumflexcomb", (1, 0, 0, 1, 0, 0)) 292 pen.addComponent("gravecomb", (0.9, 0, 0, 0.9, 198, 180)) 293 glyphSet["uni0302_uni0300"] = uni0302_uni0300 = pen.glyph() 294 295 uni0302_uni0300.recalcBounds(glyphSet) 296 self.assertGlyphBoundsEqual(uni0302_uni0300, (-278, 745, 148, 1025)) 297 298 def test_outputImpliedClosingLine(self): 299 glyphSet = {} 300 301 pen = TTGlyphPen(glyphSet) 302 pen.moveTo((0, 0)) 303 pen.lineTo((10, 0)) 304 pen.lineTo((0, 10)) 305 pen.lineTo((0, 0)) 306 pen.closePath() 307 glyph = pen.glyph() 308 assert len(glyph.coordinates) == 3 309 310 pen = TTGlyphPen(glyphSet, outputImpliedClosingLine=True) 311 pen.moveTo((0, 0)) 312 pen.lineTo((10, 0)) 313 pen.lineTo((0, 10)) 314 pen.lineTo((0, 0)) 315 pen.closePath() 316 glyph = pen.glyph() 317 assert len(glyph.coordinates) == 4 318 319 320class TTGlyphPointPenTest(TTGlyphPenTestBase): 321 penClass = TTGlyphPointPen 322 drawMethod = "drawPoints" 323 324 def test_glyph_simple(self): 325 pen = TTGlyphPointPen(None) 326 pen.beginPath() 327 pen.addPoint((50, 0), "line") 328 pen.addPoint((450, 0), "line") 329 pen.addPoint((450, 700), "line") 330 pen.addPoint((50, 700), "line") 331 pen.endPath() 332 glyph = pen.glyph() 333 assert glyph.numberOfContours == 1 334 assert glyph.endPtsOfContours == [3] 335 336 def test_addPoint_noErrorOnCurve(self): 337 pen = TTGlyphPointPen(None) 338 pen.beginPath() 339 pen.addPoint((0, 0), "curve") 340 pen.endPath() 341 342 def test_beginPath_beginPathOnOpenPath(self): 343 pen = TTGlyphPointPen(None) 344 pen.beginPath() 345 pen.addPoint((0, 0)) 346 with pytest.raises(PenError): 347 pen.beginPath() 348 349 def test_glyph_errorOnUnendedContour(self): 350 pen = TTGlyphPointPen(None) 351 pen.beginPath() 352 pen.addPoint((0, 0)) 353 with pytest.raises(PenError): 354 pen.glyph() 355 356 def test_glyph_decomposes(self): 357 componentName = "a" 358 glyphSet = {} 359 pen = TTGlyphPointPen(glyphSet) 360 361 pen.beginPath() 362 pen.addPoint((0, 0), "line") 363 pen.addPoint((0, 1), "line") 364 pen.addPoint((1, 0), "line") 365 pen.endPath() 366 glyphSet[componentName] = _TestGlyph(pen.glyph()) 367 368 pen.beginPath() 369 pen.addPoint((0, 0), "line") 370 pen.addPoint((0, 1), "line") 371 pen.addPoint((1, 0), "line") 372 pen.endPath() 373 pen.addComponent(componentName, (1, 0, 0, 1, 2, 0)) 374 pen.addComponent("missing", (1, 0, 0, 1, 0, 0)) # skipped 375 compositeGlyph = pen.glyph() 376 377 pen.beginPath() 378 pen.addPoint((0, 0), "line") 379 pen.addPoint((0, 1), "line") 380 pen.addPoint((1, 0), "line") 381 pen.endPath() 382 pen.beginPath() 383 pen.addPoint((2, 0), "line") 384 pen.addPoint((2, 1), "line") 385 pen.addPoint((3, 0), "line") 386 pen.endPath() 387 plainGlyph = pen.glyph() 388 389 assert plainGlyph == compositeGlyph 390 391 def test_keep_duplicate_end_point(self): 392 pen = TTGlyphPointPen(None) 393 pen.beginPath() 394 pen.addPoint((0, 0), "line") 395 pen.addPoint((100, 0), "line") 396 pen.addPoint((100, 50)) 397 pen.addPoint((50, 100)) 398 pen.addPoint((0, 0), "qcurve") 399 pen.addPoint((0, 0), "line") # the duplicate point is not removed 400 pen.endPath() 401 assert len(pen.points) == 6 402 assert pen.points[0] == (0, 0) 403 404 def test_within_range_component_transform(self): 405 componentName = "a" 406 glyphSet = {} 407 pen = TTGlyphPointPen(glyphSet) 408 409 pen.beginPath() 410 pen.addPoint((0, 0), "line") 411 pen.addPoint((0, 1), "line") 412 pen.addPoint((1, 0), "line") 413 pen.endPath() 414 glyphSet[componentName] = _TestGlyph(pen.glyph()) 415 416 pen.addComponent(componentName, (1.5, 0, 0, 1, 0, 0)) 417 pen.addComponent(componentName, (1, 0, 0, -1.5, 0, 0)) 418 compositeGlyph = pen.glyph() 419 420 pen.addComponent(componentName, (1.5, 0, 0, 1, 0, 0)) 421 pen.addComponent(componentName, (1, 0, 0, -1.5, 0, 0)) 422 expectedGlyph = pen.glyph() 423 424 assert expectedGlyph == compositeGlyph 425 426 def test_clamp_to_almost_2_component_transform(self): 427 componentName = "a" 428 glyphSet = {} 429 pen = TTGlyphPointPen(glyphSet) 430 431 pen.beginPath() 432 pen.addPoint((0, 0), "line") 433 pen.addPoint((0, 1), "line") 434 pen.addPoint((1, 0), "line") 435 pen.endPath() 436 glyphSet[componentName] = _TestGlyph(pen.glyph()) 437 438 pen.addComponent(componentName, (1.99999, 0, 0, 1, 0, 0)) 439 pen.addComponent(componentName, (1, 2, 0, 1, 0, 0)) 440 pen.addComponent(componentName, (1, 0, 2, 1, 0, 0)) 441 pen.addComponent(componentName, (1, 0, 0, 2, 0, 0)) 442 pen.addComponent(componentName, (-2, 0, 0, -2, 0, 0)) 443 compositeGlyph = pen.glyph() 444 445 almost2 = MAX_F2DOT14 # 0b1.11111111111111 446 pen.addComponent(componentName, (almost2, 0, 0, 1, 0, 0)) 447 pen.addComponent(componentName, (1, almost2, 0, 1, 0, 0)) 448 pen.addComponent(componentName, (1, 0, almost2, 1, 0, 0)) 449 pen.addComponent(componentName, (1, 0, 0, almost2, 0, 0)) 450 pen.addComponent(componentName, (-2, 0, 0, -2, 0, 0)) 451 expectedGlyph = pen.glyph() 452 453 assert expectedGlyph == compositeGlyph 454 455 def test_out_of_range_transform_decomposed(self): 456 componentName = "a" 457 glyphSet = {} 458 pen = TTGlyphPointPen(glyphSet) 459 460 pen.beginPath() 461 pen.addPoint((0, 0), "line") 462 pen.addPoint((0, 1), "line") 463 pen.addPoint((1, 0), "line") 464 pen.endPath() 465 glyphSet[componentName] = _TestGlyph(pen.glyph()) 466 467 pen.addComponent(componentName, (3, 0, 0, 2, 0, 0)) 468 pen.addComponent(componentName, (1, 0, 0, 1, -1, 2)) 469 pen.addComponent(componentName, (2, 0, 0, -3, 0, 0)) 470 compositeGlyph = pen.glyph() 471 472 pen.beginPath() 473 pen.addPoint((0, 0), "line") 474 pen.addPoint((0, 2), "line") 475 pen.addPoint((3, 0), "line") 476 pen.endPath() 477 pen.beginPath() 478 pen.addPoint((-1, 2), "line") 479 pen.addPoint((-1, 3), "line") 480 pen.addPoint((0, 2), "line") 481 pen.endPath() 482 pen.beginPath() 483 pen.addPoint((0, 0), "line") 484 pen.addPoint((0, -3), "line") 485 pen.addPoint((2, 0), "line") 486 pen.endPath() 487 expectedGlyph = pen.glyph() 488 489 assert expectedGlyph == compositeGlyph 490 491 def test_no_handle_overflowing_transform(self): 492 componentName = "a" 493 glyphSet = {} 494 pen = TTGlyphPointPen(glyphSet, handleOverflowingTransforms=False) 495 496 pen.beginPath() 497 pen.addPoint((0, 0), "line") 498 pen.addPoint((0, 1), "line") 499 pen.addPoint((1, 0), "line") 500 pen.endPath() 501 baseGlyph = pen.glyph() 502 glyphSet[componentName] = _TestGlyph(baseGlyph) 503 504 pen.addComponent(componentName, (3, 0, 0, 1, 0, 0)) 505 compositeGlyph = pen.glyph() 506 507 assert compositeGlyph.components[0].transform == ((3, 0), (0, 1)) 508 509 with pytest.raises(struct.error): 510 compositeGlyph.compile({"a": baseGlyph}) 511 512 def assertGlyphBoundsEqual(self, glyph, bounds): 513 assert (glyph.xMin, glyph.yMin, glyph.xMax, glyph.yMax) == bounds 514 515 def test_round_float_coordinates_and_component_offsets(self): 516 glyphSet = {} 517 pen = TTGlyphPointPen(glyphSet) 518 519 pen.beginPath() 520 pen.addPoint((0, 0), "line") 521 pen.addPoint((0, 1), "line") 522 pen.addPoint((367.6, 0), "line") 523 pen.endPath() 524 simpleGlyph = pen.glyph() 525 526 simpleGlyph.recalcBounds(glyphSet) 527 self.assertGlyphBoundsEqual(simpleGlyph, (0, 0, 368, 1)) 528 529 componentName = "a" 530 glyphSet[componentName] = simpleGlyph 531 532 pen.addComponent(componentName, (1, 0, 0, 1, -86.4, 0)) 533 compositeGlyph = pen.glyph() 534 535 compositeGlyph.recalcBounds(glyphSet) 536 self.assertGlyphBoundsEqual(compositeGlyph, (-86, 0, 282, 1)) 537 538 def test_scaled_component_bounds(self): 539 glyphSet = {} 540 541 pen = TTGlyphPointPen(glyphSet) 542 pen.beginPath() 543 pen.addPoint((-231, 939), "line") 544 pen.addPoint((-55, 939), "line") 545 pen.addPoint((-55, 745), "line") 546 pen.addPoint((-231, 745), "line") 547 pen.endPath() 548 glyphSet["gravecomb"] = pen.glyph() 549 550 pen = TTGlyphPointPen(glyphSet) 551 pen.beginPath() 552 pen.addPoint((-278, 939), "line") 553 pen.addPoint((8, 939), "line") 554 pen.addPoint((8, 745), "line") 555 pen.addPoint((-278, 745), "line") 556 pen.endPath() 557 glyphSet["circumflexcomb"] = pen.glyph() 558 559 pen = TTGlyphPointPen(glyphSet) 560 pen.addComponent("circumflexcomb", (1, 0, 0, 1, 0, 0)) 561 pen.addComponent("gravecomb", (0.9, 0, 0, 0.9, 198, 180)) 562 glyphSet["uni0302_uni0300"] = uni0302_uni0300 = pen.glyph() 563 564 uni0302_uni0300.recalcBounds(glyphSet) 565 self.assertGlyphBoundsEqual(uni0302_uni0300, (-278, 745, 148, 1025)) 566 567 def test_open_path_starting_with_move(self): 568 # when a contour starts with a 'move' point, it signifies the beginnig 569 # of an open contour. 570 # https://unifiedfontobject.org/versions/ufo3/glyphs/glif/#point-types 571 pen1 = TTGlyphPointPen(None) 572 pen1.beginPath() 573 pen1.addPoint((0, 0), "move") # contour is open 574 pen1.addPoint((10, 10), "line") 575 pen1.addPoint((20, 20)) 576 pen1.addPoint((20, 0), "qcurve") 577 pen1.endPath() 578 579 pen2 = TTGlyphPointPen(None) 580 pen2.beginPath() 581 pen2.addPoint((0, 0), "line") # contour is closed 582 pen2.addPoint((10, 10), "line") 583 pen2.addPoint((20, 20)) 584 pen2.addPoint((20, 0), "qcurve") 585 pen2.endPath() 586 587 # Since TrueType contours are always implicitly closed, the pen will 588 # interpret both these paths as equivalent 589 assert pen1.points == pen2.points == [(0, 0), (10, 10), (20, 20), (20, 0)] 590 assert pen1.types == pen2.types == [1, 1, 0, 1] 591 592 def test_skip_empty_contours(self): 593 pen = TTGlyphPointPen(None) 594 pen.beginPath() 595 pen.endPath() 596 pen.beginPath() 597 pen.endPath() 598 glyph = pen.glyph() 599 assert glyph.numberOfContours == 0 600 601 602class CubicGlyfTest: 603 def test_cubic_simple(self): 604 spen = TTGlyphPen(None) 605 spen.moveTo((0, 0)) 606 spen.curveTo((0, 1), (1, 1), (1, 0)) 607 spen.closePath() 608 609 ppen = TTGlyphPointPen(None) 610 ppen.beginPath() 611 ppen.addPoint((0, 0), "line") 612 ppen.addPoint((0, 1)) 613 ppen.addPoint((1, 1)) 614 ppen.addPoint((1, 0), "curve") 615 ppen.endPath() 616 617 for pen in (spen, ppen): 618 glyph = pen.glyph() 619 620 for i in range(2): 621 if i == 1: 622 glyph.compile(None) 623 624 assert list(glyph.coordinates) == [(0, 0), (0, 1), (1, 1), (1, 0)] 625 assert list(glyph.flags) == [0x01, 0x80, 0x80, 0x01] 626 627 rpen = RecordingPen() 628 glyph.draw(rpen, None) 629 assert rpen.value == [ 630 ("moveTo", ((0, 0),)), 631 ( 632 "curveTo", 633 ( 634 (0, 1), 635 (1, 1), 636 (1, 0), 637 ), 638 ), 639 ("closePath", ()), 640 ] 641 642 @pytest.mark.parametrize( 643 "dropImpliedOnCurves, segment_pen_commands, point_pen_commands, expected_coordinates, expected_flags, expected_endPts", 644 [ 645 ( # Two curves that do NOT merge; request merging 646 True, 647 [ 648 ("moveTo", ((0, 0),)), 649 ("curveTo", ((0, 1), (1, 2), (2, 2))), 650 ("curveTo", ((3, 3), (4, 1), (4, 0))), 651 ("closePath", ()), 652 ], 653 [ 654 ("beginPath", (), {}), 655 ("addPoint", ((0, 0), "line", None, None), {}), 656 ("addPoint", ((0, 1), None, None, None), {}), 657 ("addPoint", ((1, 2), None, None, None), {}), 658 ("addPoint", ((2, 2), "curve", None, None), {}), 659 ("addPoint", ((3, 3), None, None, None), {}), 660 ("addPoint", ((4, 1), None, None, None), {}), 661 ("addPoint", ((4, 0), "curve", None, None), {}), 662 ("endPath", (), {}), 663 ], 664 [(0, 0), (0, 1), (1, 2), (2, 2), (3, 3), (4, 1), (4, 0)], 665 [0x01, 0x80, 0x80, 0x01, 0x80, 0x80, 0x01], 666 [6], 667 ), 668 ( # Two curves that merge; request merging 669 True, 670 [ 671 ("moveTo", ((0, 0),)), 672 ("curveTo", ((0, 1), (1, 2), (2, 2))), 673 ("curveTo", ((3, 2), (4, 1), (4, 0))), 674 ("closePath", ()), 675 ], 676 [ 677 ("beginPath", (), {}), 678 ("addPoint", ((0, 0), "line", None, None), {}), 679 ("addPoint", ((0, 1), None, None, None), {}), 680 ("addPoint", ((1, 2), None, None, None), {}), 681 ("addPoint", ((2, 2), "curve", None, None), {}), 682 ("addPoint", ((3, 2), None, None, None), {}), 683 ("addPoint", ((4, 1), None, None, None), {}), 684 ("addPoint", ((4, 0), "curve", None, None), {}), 685 ("endPath", (), {}), 686 ], 687 [(0, 0), (0, 1), (1, 2), (3, 2), (4, 1), (4, 0)], 688 [0x01, 0x80, 0x80, 0x80, 0x80, 0x01], 689 [5], 690 ), 691 ( # Two curves that merge; request NOT merging 692 False, 693 [ 694 ("moveTo", ((0, 0),)), 695 ("curveTo", ((0, 1), (1, 2), (2, 2))), 696 ("curveTo", ((3, 2), (4, 1), (4, 0))), 697 ("closePath", ()), 698 ], 699 [ 700 ("beginPath", (), {}), 701 ("addPoint", ((0, 0), "line", None, None), {}), 702 ("addPoint", ((0, 1), None, None, None), {}), 703 ("addPoint", ((1, 2), None, None, None), {}), 704 ("addPoint", ((2, 2), "curve", None, None), {}), 705 ("addPoint", ((3, 2), None, None, None), {}), 706 ("addPoint", ((4, 1), None, None, None), {}), 707 ("addPoint", ((4, 0), "curve", None, None), {}), 708 ("endPath", (), {}), 709 ], 710 [(0, 0), (0, 1), (1, 2), (2, 2), (3, 2), (4, 1), (4, 0)], 711 [0x01, 0x80, 0x80, 0x01, 0x80, 0x80, 0x01], 712 [6], 713 ), 714 ( # Two (duplicate) contours 715 True, 716 [ 717 ("moveTo", ((0, 0),)), 718 ("curveTo", ((0, 1), (1, 2), (2, 2))), 719 ("curveTo", ((3, 2), (4, 1), (4, 0))), 720 ("closePath", ()), 721 ("moveTo", ((0, 0),)), 722 ("curveTo", ((0, 1), (1, 2), (2, 2))), 723 ("curveTo", ((3, 2), (4, 1), (4, 0))), 724 ("closePath", ()), 725 ], 726 [ 727 ("beginPath", (), {}), 728 ("addPoint", ((0, 0), "line", None, None), {}), 729 ("addPoint", ((0, 1), None, None, None), {}), 730 ("addPoint", ((1, 2), None, None, None), {}), 731 ("addPoint", ((2, 2), "curve", None, None), {}), 732 ("addPoint", ((3, 2), None, None, None), {}), 733 ("addPoint", ((4, 1), None, None, None), {}), 734 ("addPoint", ((4, 0), "curve", None, None), {}), 735 ("endPath", (), {}), 736 ("beginPath", (), {}), 737 ("addPoint", ((0, 0), "line", None, None), {}), 738 ("addPoint", ((0, 1), None, None, None), {}), 739 ("addPoint", ((1, 2), None, None, None), {}), 740 ("addPoint", ((2, 2), "curve", None, None), {}), 741 ("addPoint", ((3, 2), None, None, None), {}), 742 ("addPoint", ((4, 1), None, None, None), {}), 743 ("addPoint", ((4, 0), "curve", None, None), {}), 744 ("endPath", (), {}), 745 ], 746 [ 747 (0, 0), 748 (0, 1), 749 (1, 2), 750 (3, 2), 751 (4, 1), 752 (4, 0), 753 (0, 0), 754 (0, 1), 755 (1, 2), 756 (3, 2), 757 (4, 1), 758 (4, 0), 759 ], 760 [ 761 0x01, 762 0x80, 763 0x80, 764 0x80, 765 0x80, 766 0x01, 767 0x01, 768 0x80, 769 0x80, 770 0x80, 771 0x80, 772 0x01, 773 ], 774 [5, 11], 775 ), 776 ], 777 ) 778 def test_cubic_topology( 779 self, 780 dropImpliedOnCurves, 781 segment_pen_commands, 782 point_pen_commands, 783 expected_coordinates, 784 expected_flags, 785 expected_endPts, 786 ): 787 spen = TTGlyphPen(None) 788 rpen = RecordingPen() 789 rpen.value = segment_pen_commands 790 rpen.replay(spen) 791 792 ppen = TTGlyphPointPen(None) 793 rpen = RecordingPointPen() 794 rpen.value = point_pen_commands 795 rpen.replay(ppen) 796 797 for pen in (spen, ppen): 798 glyph = pen.glyph(dropImpliedOnCurves=dropImpliedOnCurves) 799 800 assert list(glyph.coordinates) == expected_coordinates 801 assert list(glyph.flags) == expected_flags 802 assert list(glyph.endPtsOfContours) == expected_endPts 803 804 rpen = RecordingPen() 805 glyph.draw(rpen, None) 806 assert rpen.value == segment_pen_commands 807 808 809class _TestGlyph(object): 810 def __init__(self, glyph): 811 self.coordinates = glyph.coordinates 812 813 def draw(self, pen): 814 pen.moveTo(self.coordinates[0]) 815 for point in self.coordinates[1:]: 816 pen.lineTo(point) 817 pen.closePath() 818 819 def drawPoints(self, pen): 820 pen.beginPath() 821 for point in self.coordinates: 822 pen.addPoint(point, "line") 823 pen.endPath() 824