| import os |
| import pytest |
| import struct |
| |
| from fontTools import ttLib |
| from fontTools.pens.basePen import PenError |
| from fontTools.pens.recordingPen import RecordingPen, RecordingPointPen |
| from fontTools.pens.ttGlyphPen import TTGlyphPen, TTGlyphPointPen, MAX_F2DOT14 |
| |
| |
| class TTGlyphPenTestBase: |
| def runEndToEnd(self, filename): |
| font = ttLib.TTFont() |
| ttx_path = os.path.join( |
| os.path.abspath(os.path.dirname(os.path.realpath(__file__))), |
| "..", |
| "ttLib", |
| "data", |
| filename, |
| ) |
| font.importXML(ttx_path) |
| |
| glyphSet = font.getGlyphSet() |
| glyfTable = font["glyf"] |
| pen = self.penClass(glyphSet) |
| |
| for name in font.getGlyphOrder(): |
| getattr(glyphSet[name], self.drawMethod)(pen) |
| oldGlyph = glyfTable[name] |
| newGlyph = pen.glyph() |
| |
| if hasattr(oldGlyph, "program"): |
| newGlyph.program = oldGlyph.program |
| |
| assert oldGlyph.compile(glyfTable) == newGlyph.compile(glyfTable) |
| |
| def test_e2e_linesAndSimpleComponents(self): |
| self.runEndToEnd("TestTTF-Regular.ttx") |
| |
| def test_e2e_curvesAndComponentTransforms(self): |
| self.runEndToEnd("TestTTFComplex-Regular.ttx") |
| |
| |
| class TTGlyphPenTest(TTGlyphPenTestBase): |
| penClass = TTGlyphPen |
| drawMethod = "draw" |
| |
| def test_moveTo_errorWithinContour(self): |
| pen = TTGlyphPen(None) |
| pen.moveTo((0, 0)) |
| with pytest.raises(PenError): |
| pen.moveTo((1, 0)) |
| |
| def test_closePath_ignoresAnchors(self): |
| pen = TTGlyphPen(None) |
| pen.moveTo((0, 0)) |
| pen.closePath() |
| assert not pen.points |
| assert not pen.types |
| assert not pen.endPts |
| |
| def test_endPath_sameAsClosePath(self): |
| pen = TTGlyphPen(None) |
| |
| pen.moveTo((0, 0)) |
| pen.lineTo((0, 1)) |
| pen.lineTo((1, 0)) |
| pen.closePath() |
| closePathGlyph = pen.glyph() |
| |
| pen.moveTo((0, 0)) |
| pen.lineTo((0, 1)) |
| pen.lineTo((1, 0)) |
| pen.endPath() |
| endPathGlyph = pen.glyph() |
| |
| assert closePathGlyph == endPathGlyph |
| |
| def test_glyph_errorOnUnendedContour(self): |
| pen = TTGlyphPen(None) |
| pen.moveTo((0, 0)) |
| with pytest.raises(PenError): |
| pen.glyph() |
| |
| def test_glyph_decomposes(self): |
| componentName = "a" |
| glyphSet = {} |
| pen = TTGlyphPen(glyphSet) |
| |
| pen.moveTo((0, 0)) |
| pen.lineTo((0, 1)) |
| pen.lineTo((1, 0)) |
| pen.closePath() |
| glyphSet[componentName] = _TestGlyph(pen.glyph()) |
| |
| pen.moveTo((0, 0)) |
| pen.lineTo((0, 1)) |
| pen.lineTo((1, 0)) |
| pen.closePath() |
| pen.addComponent(componentName, (1, 0, 0, 1, 2, 0)) |
| pen.addComponent("missing", (1, 0, 0, 1, 0, 0)) # skipped |
| compositeGlyph = pen.glyph() |
| |
| pen.moveTo((0, 0)) |
| pen.lineTo((0, 1)) |
| pen.lineTo((1, 0)) |
| pen.closePath() |
| pen.moveTo((2, 0)) |
| pen.lineTo((2, 1)) |
| pen.lineTo((3, 0)) |
| pen.closePath() |
| plainGlyph = pen.glyph() |
| |
| assert plainGlyph == compositeGlyph |
| |
| def test_remove_extra_move_points(self): |
| pen = TTGlyphPen(None) |
| pen.moveTo((0, 0)) |
| pen.lineTo((100, 0)) |
| pen.qCurveTo((100, 50), (50, 100), (0, 0)) |
| pen.closePath() |
| assert len(pen.points) == 4 |
| assert pen.points[0] == (0, 0) |
| |
| def test_keep_move_point(self): |
| pen = TTGlyphPen(None) |
| pen.moveTo((0, 0)) |
| pen.lineTo((100, 0)) |
| pen.qCurveTo((100, 50), (50, 100), (30, 30)) |
| # when last and move pts are different, closePath() implies a lineTo |
| pen.closePath() |
| assert len(pen.points) == 5 |
| assert pen.points[0] == (0, 0) |
| |
| def test_keep_duplicate_end_point(self): |
| pen = TTGlyphPen(None) |
| pen.moveTo((0, 0)) |
| pen.lineTo((100, 0)) |
| pen.qCurveTo((100, 50), (50, 100), (0, 0)) |
| pen.lineTo((0, 0)) # the duplicate point is not removed |
| pen.closePath() |
| assert len(pen.points) == 5 |
| assert pen.points[0] == (0, 0) |
| |
| def test_within_range_component_transform(self): |
| componentName = "a" |
| glyphSet = {} |
| pen = TTGlyphPen(glyphSet) |
| |
| pen.moveTo((0, 0)) |
| pen.lineTo((0, 1)) |
| pen.lineTo((1, 0)) |
| pen.closePath() |
| glyphSet[componentName] = _TestGlyph(pen.glyph()) |
| |
| pen.addComponent(componentName, (1.5, 0, 0, 1, 0, 0)) |
| pen.addComponent(componentName, (1, 0, 0, -1.5, 0, 0)) |
| compositeGlyph = pen.glyph() |
| |
| pen.addComponent(componentName, (1.5, 0, 0, 1, 0, 0)) |
| pen.addComponent(componentName, (1, 0, 0, -1.5, 0, 0)) |
| expectedGlyph = pen.glyph() |
| |
| assert expectedGlyph == compositeGlyph |
| |
| def test_clamp_to_almost_2_component_transform(self): |
| componentName = "a" |
| glyphSet = {} |
| pen = TTGlyphPen(glyphSet) |
| |
| pen.moveTo((0, 0)) |
| pen.lineTo((0, 1)) |
| pen.lineTo((1, 0)) |
| pen.closePath() |
| glyphSet[componentName] = _TestGlyph(pen.glyph()) |
| |
| pen.addComponent(componentName, (1.99999, 0, 0, 1, 0, 0)) |
| pen.addComponent(componentName, (1, 2, 0, 1, 0, 0)) |
| pen.addComponent(componentName, (1, 0, 2, 1, 0, 0)) |
| pen.addComponent(componentName, (1, 0, 0, 2, 0, 0)) |
| pen.addComponent(componentName, (-2, 0, 0, -2, 0, 0)) |
| compositeGlyph = pen.glyph() |
| |
| almost2 = MAX_F2DOT14 # 0b1.11111111111111 |
| pen.addComponent(componentName, (almost2, 0, 0, 1, 0, 0)) |
| pen.addComponent(componentName, (1, almost2, 0, 1, 0, 0)) |
| pen.addComponent(componentName, (1, 0, almost2, 1, 0, 0)) |
| pen.addComponent(componentName, (1, 0, 0, almost2, 0, 0)) |
| pen.addComponent(componentName, (-2, 0, 0, -2, 0, 0)) |
| expectedGlyph = pen.glyph() |
| |
| assert expectedGlyph == compositeGlyph |
| |
| def test_out_of_range_transform_decomposed(self): |
| componentName = "a" |
| glyphSet = {} |
| pen = TTGlyphPen(glyphSet) |
| |
| pen.moveTo((0, 0)) |
| pen.lineTo((0, 1)) |
| pen.lineTo((1, 0)) |
| pen.closePath() |
| glyphSet[componentName] = _TestGlyph(pen.glyph()) |
| |
| pen.addComponent(componentName, (3, 0, 0, 2, 0, 0)) |
| pen.addComponent(componentName, (1, 0, 0, 1, -1, 2)) |
| pen.addComponent(componentName, (2, 0, 0, -3, 0, 0)) |
| compositeGlyph = pen.glyph() |
| |
| pen.moveTo((0, 0)) |
| pen.lineTo((0, 2)) |
| pen.lineTo((3, 0)) |
| pen.closePath() |
| pen.moveTo((-1, 2)) |
| pen.lineTo((-1, 3)) |
| pen.lineTo((0, 2)) |
| pen.closePath() |
| pen.moveTo((0, 0)) |
| pen.lineTo((0, -3)) |
| pen.lineTo((2, 0)) |
| pen.closePath() |
| expectedGlyph = pen.glyph() |
| |
| assert expectedGlyph == compositeGlyph |
| |
| def test_no_handle_overflowing_transform(self): |
| componentName = "a" |
| glyphSet = {} |
| pen = TTGlyphPen(glyphSet, handleOverflowingTransforms=False) |
| |
| pen.moveTo((0, 0)) |
| pen.lineTo((0, 1)) |
| pen.lineTo((1, 0)) |
| pen.closePath() |
| baseGlyph = pen.glyph() |
| glyphSet[componentName] = _TestGlyph(baseGlyph) |
| |
| pen.addComponent(componentName, (3, 0, 0, 1, 0, 0)) |
| compositeGlyph = pen.glyph() |
| |
| assert compositeGlyph.components[0].transform == ((3, 0), (0, 1)) |
| |
| with pytest.raises(struct.error): |
| compositeGlyph.compile({"a": baseGlyph}) |
| |
| def assertGlyphBoundsEqual(self, glyph, bounds): |
| assert (glyph.xMin, glyph.yMin, glyph.xMax, glyph.yMax) == bounds |
| |
| def test_round_float_coordinates_and_component_offsets(self): |
| glyphSet = {} |
| pen = TTGlyphPen(glyphSet) |
| |
| pen.moveTo((0, 0)) |
| pen.lineTo((0, 1)) |
| pen.lineTo((367.6, 0)) |
| pen.closePath() |
| simpleGlyph = pen.glyph() |
| |
| simpleGlyph.recalcBounds(glyphSet) |
| self.assertGlyphBoundsEqual(simpleGlyph, (0, 0, 368, 1)) |
| |
| componentName = "a" |
| glyphSet[componentName] = simpleGlyph |
| |
| pen.addComponent(componentName, (1, 0, 0, 1, -86.4, 0)) |
| compositeGlyph = pen.glyph() |
| |
| compositeGlyph.recalcBounds(glyphSet) |
| self.assertGlyphBoundsEqual(compositeGlyph, (-86, 0, 282, 1)) |
| |
| def test_scaled_component_bounds(self): |
| glyphSet = {} |
| |
| pen = TTGlyphPen(glyphSet) |
| pen.moveTo((-231, 939)) |
| pen.lineTo((-55, 939)) |
| pen.lineTo((-55, 745)) |
| pen.lineTo((-231, 745)) |
| pen.closePath() |
| glyphSet["gravecomb"] = pen.glyph() |
| |
| pen = TTGlyphPen(glyphSet) |
| pen.moveTo((-278, 939)) |
| pen.lineTo((8, 939)) |
| pen.lineTo((8, 745)) |
| pen.lineTo((-278, 745)) |
| pen.closePath() |
| glyphSet["circumflexcomb"] = pen.glyph() |
| |
| pen = TTGlyphPen(glyphSet) |
| pen.addComponent("circumflexcomb", (1, 0, 0, 1, 0, 0)) |
| pen.addComponent("gravecomb", (0.9, 0, 0, 0.9, 198, 180)) |
| glyphSet["uni0302_uni0300"] = uni0302_uni0300 = pen.glyph() |
| |
| uni0302_uni0300.recalcBounds(glyphSet) |
| self.assertGlyphBoundsEqual(uni0302_uni0300, (-278, 745, 148, 1025)) |
| |
| def test_outputImpliedClosingLine(self): |
| glyphSet = {} |
| |
| pen = TTGlyphPen(glyphSet) |
| pen.moveTo((0, 0)) |
| pen.lineTo((10, 0)) |
| pen.lineTo((0, 10)) |
| pen.lineTo((0, 0)) |
| pen.closePath() |
| glyph = pen.glyph() |
| assert len(glyph.coordinates) == 3 |
| |
| pen = TTGlyphPen(glyphSet, outputImpliedClosingLine=True) |
| pen.moveTo((0, 0)) |
| pen.lineTo((10, 0)) |
| pen.lineTo((0, 10)) |
| pen.lineTo((0, 0)) |
| pen.closePath() |
| glyph = pen.glyph() |
| assert len(glyph.coordinates) == 4 |
| |
| |
| class TTGlyphPointPenTest(TTGlyphPenTestBase): |
| penClass = TTGlyphPointPen |
| drawMethod = "drawPoints" |
| |
| def test_glyph_simple(self): |
| pen = TTGlyphPointPen(None) |
| pen.beginPath() |
| pen.addPoint((50, 0), "line") |
| pen.addPoint((450, 0), "line") |
| pen.addPoint((450, 700), "line") |
| pen.addPoint((50, 700), "line") |
| pen.endPath() |
| glyph = pen.glyph() |
| assert glyph.numberOfContours == 1 |
| assert glyph.endPtsOfContours == [3] |
| |
| def test_addPoint_noErrorOnCurve(self): |
| pen = TTGlyphPointPen(None) |
| pen.beginPath() |
| pen.addPoint((0, 0), "curve") |
| pen.endPath() |
| |
| def test_beginPath_beginPathOnOpenPath(self): |
| pen = TTGlyphPointPen(None) |
| pen.beginPath() |
| pen.addPoint((0, 0)) |
| with pytest.raises(PenError): |
| pen.beginPath() |
| |
| def test_glyph_errorOnUnendedContour(self): |
| pen = TTGlyphPointPen(None) |
| pen.beginPath() |
| pen.addPoint((0, 0)) |
| with pytest.raises(PenError): |
| pen.glyph() |
| |
| def test_glyph_decomposes(self): |
| componentName = "a" |
| glyphSet = {} |
| pen = TTGlyphPointPen(glyphSet) |
| |
| pen.beginPath() |
| pen.addPoint((0, 0), "line") |
| pen.addPoint((0, 1), "line") |
| pen.addPoint((1, 0), "line") |
| pen.endPath() |
| glyphSet[componentName] = _TestGlyph(pen.glyph()) |
| |
| pen.beginPath() |
| pen.addPoint((0, 0), "line") |
| pen.addPoint((0, 1), "line") |
| pen.addPoint((1, 0), "line") |
| pen.endPath() |
| pen.addComponent(componentName, (1, 0, 0, 1, 2, 0)) |
| pen.addComponent("missing", (1, 0, 0, 1, 0, 0)) # skipped |
| compositeGlyph = pen.glyph() |
| |
| pen.beginPath() |
| pen.addPoint((0, 0), "line") |
| pen.addPoint((0, 1), "line") |
| pen.addPoint((1, 0), "line") |
| pen.endPath() |
| pen.beginPath() |
| pen.addPoint((2, 0), "line") |
| pen.addPoint((2, 1), "line") |
| pen.addPoint((3, 0), "line") |
| pen.endPath() |
| plainGlyph = pen.glyph() |
| |
| assert plainGlyph == compositeGlyph |
| |
| def test_keep_duplicate_end_point(self): |
| pen = TTGlyphPointPen(None) |
| pen.beginPath() |
| pen.addPoint((0, 0), "line") |
| pen.addPoint((100, 0), "line") |
| pen.addPoint((100, 50)) |
| pen.addPoint((50, 100)) |
| pen.addPoint((0, 0), "qcurve") |
| pen.addPoint((0, 0), "line") # the duplicate point is not removed |
| pen.endPath() |
| assert len(pen.points) == 6 |
| assert pen.points[0] == (0, 0) |
| |
| def test_within_range_component_transform(self): |
| componentName = "a" |
| glyphSet = {} |
| pen = TTGlyphPointPen(glyphSet) |
| |
| pen.beginPath() |
| pen.addPoint((0, 0), "line") |
| pen.addPoint((0, 1), "line") |
| pen.addPoint((1, 0), "line") |
| pen.endPath() |
| glyphSet[componentName] = _TestGlyph(pen.glyph()) |
| |
| pen.addComponent(componentName, (1.5, 0, 0, 1, 0, 0)) |
| pen.addComponent(componentName, (1, 0, 0, -1.5, 0, 0)) |
| compositeGlyph = pen.glyph() |
| |
| pen.addComponent(componentName, (1.5, 0, 0, 1, 0, 0)) |
| pen.addComponent(componentName, (1, 0, 0, -1.5, 0, 0)) |
| expectedGlyph = pen.glyph() |
| |
| assert expectedGlyph == compositeGlyph |
| |
| def test_clamp_to_almost_2_component_transform(self): |
| componentName = "a" |
| glyphSet = {} |
| pen = TTGlyphPointPen(glyphSet) |
| |
| pen.beginPath() |
| pen.addPoint((0, 0), "line") |
| pen.addPoint((0, 1), "line") |
| pen.addPoint((1, 0), "line") |
| pen.endPath() |
| glyphSet[componentName] = _TestGlyph(pen.glyph()) |
| |
| pen.addComponent(componentName, (1.99999, 0, 0, 1, 0, 0)) |
| pen.addComponent(componentName, (1, 2, 0, 1, 0, 0)) |
| pen.addComponent(componentName, (1, 0, 2, 1, 0, 0)) |
| pen.addComponent(componentName, (1, 0, 0, 2, 0, 0)) |
| pen.addComponent(componentName, (-2, 0, 0, -2, 0, 0)) |
| compositeGlyph = pen.glyph() |
| |
| almost2 = MAX_F2DOT14 # 0b1.11111111111111 |
| pen.addComponent(componentName, (almost2, 0, 0, 1, 0, 0)) |
| pen.addComponent(componentName, (1, almost2, 0, 1, 0, 0)) |
| pen.addComponent(componentName, (1, 0, almost2, 1, 0, 0)) |
| pen.addComponent(componentName, (1, 0, 0, almost2, 0, 0)) |
| pen.addComponent(componentName, (-2, 0, 0, -2, 0, 0)) |
| expectedGlyph = pen.glyph() |
| |
| assert expectedGlyph == compositeGlyph |
| |
| def test_out_of_range_transform_decomposed(self): |
| componentName = "a" |
| glyphSet = {} |
| pen = TTGlyphPointPen(glyphSet) |
| |
| pen.beginPath() |
| pen.addPoint((0, 0), "line") |
| pen.addPoint((0, 1), "line") |
| pen.addPoint((1, 0), "line") |
| pen.endPath() |
| glyphSet[componentName] = _TestGlyph(pen.glyph()) |
| |
| pen.addComponent(componentName, (3, 0, 0, 2, 0, 0)) |
| pen.addComponent(componentName, (1, 0, 0, 1, -1, 2)) |
| pen.addComponent(componentName, (2, 0, 0, -3, 0, 0)) |
| compositeGlyph = pen.glyph() |
| |
| pen.beginPath() |
| pen.addPoint((0, 0), "line") |
| pen.addPoint((0, 2), "line") |
| pen.addPoint((3, 0), "line") |
| pen.endPath() |
| pen.beginPath() |
| pen.addPoint((-1, 2), "line") |
| pen.addPoint((-1, 3), "line") |
| pen.addPoint((0, 2), "line") |
| pen.endPath() |
| pen.beginPath() |
| pen.addPoint((0, 0), "line") |
| pen.addPoint((0, -3), "line") |
| pen.addPoint((2, 0), "line") |
| pen.endPath() |
| expectedGlyph = pen.glyph() |
| |
| assert expectedGlyph == compositeGlyph |
| |
| def test_no_handle_overflowing_transform(self): |
| componentName = "a" |
| glyphSet = {} |
| pen = TTGlyphPointPen(glyphSet, handleOverflowingTransforms=False) |
| |
| pen.beginPath() |
| pen.addPoint((0, 0), "line") |
| pen.addPoint((0, 1), "line") |
| pen.addPoint((1, 0), "line") |
| pen.endPath() |
| baseGlyph = pen.glyph() |
| glyphSet[componentName] = _TestGlyph(baseGlyph) |
| |
| pen.addComponent(componentName, (3, 0, 0, 1, 0, 0)) |
| compositeGlyph = pen.glyph() |
| |
| assert compositeGlyph.components[0].transform == ((3, 0), (0, 1)) |
| |
| with pytest.raises(struct.error): |
| compositeGlyph.compile({"a": baseGlyph}) |
| |
| def assertGlyphBoundsEqual(self, glyph, bounds): |
| assert (glyph.xMin, glyph.yMin, glyph.xMax, glyph.yMax) == bounds |
| |
| def test_round_float_coordinates_and_component_offsets(self): |
| glyphSet = {} |
| pen = TTGlyphPointPen(glyphSet) |
| |
| pen.beginPath() |
| pen.addPoint((0, 0), "line") |
| pen.addPoint((0, 1), "line") |
| pen.addPoint((367.6, 0), "line") |
| pen.endPath() |
| simpleGlyph = pen.glyph() |
| |
| simpleGlyph.recalcBounds(glyphSet) |
| self.assertGlyphBoundsEqual(simpleGlyph, (0, 0, 368, 1)) |
| |
| componentName = "a" |
| glyphSet[componentName] = simpleGlyph |
| |
| pen.addComponent(componentName, (1, 0, 0, 1, -86.4, 0)) |
| compositeGlyph = pen.glyph() |
| |
| compositeGlyph.recalcBounds(glyphSet) |
| self.assertGlyphBoundsEqual(compositeGlyph, (-86, 0, 282, 1)) |
| |
| def test_scaled_component_bounds(self): |
| glyphSet = {} |
| |
| pen = TTGlyphPointPen(glyphSet) |
| pen.beginPath() |
| pen.addPoint((-231, 939), "line") |
| pen.addPoint((-55, 939), "line") |
| pen.addPoint((-55, 745), "line") |
| pen.addPoint((-231, 745), "line") |
| pen.endPath() |
| glyphSet["gravecomb"] = pen.glyph() |
| |
| pen = TTGlyphPointPen(glyphSet) |
| pen.beginPath() |
| pen.addPoint((-278, 939), "line") |
| pen.addPoint((8, 939), "line") |
| pen.addPoint((8, 745), "line") |
| pen.addPoint((-278, 745), "line") |
| pen.endPath() |
| glyphSet["circumflexcomb"] = pen.glyph() |
| |
| pen = TTGlyphPointPen(glyphSet) |
| pen.addComponent("circumflexcomb", (1, 0, 0, 1, 0, 0)) |
| pen.addComponent("gravecomb", (0.9, 0, 0, 0.9, 198, 180)) |
| glyphSet["uni0302_uni0300"] = uni0302_uni0300 = pen.glyph() |
| |
| uni0302_uni0300.recalcBounds(glyphSet) |
| self.assertGlyphBoundsEqual(uni0302_uni0300, (-278, 745, 148, 1025)) |
| |
| def test_open_path_starting_with_move(self): |
| # when a contour starts with a 'move' point, it signifies the beginnig |
| # of an open contour. |
| # https://unifiedfontobject.org/versions/ufo3/glyphs/glif/#point-types |
| pen1 = TTGlyphPointPen(None) |
| pen1.beginPath() |
| pen1.addPoint((0, 0), "move") # contour is open |
| pen1.addPoint((10, 10), "line") |
| pen1.addPoint((20, 20)) |
| pen1.addPoint((20, 0), "qcurve") |
| pen1.endPath() |
| |
| pen2 = TTGlyphPointPen(None) |
| pen2.beginPath() |
| pen2.addPoint((0, 0), "line") # contour is closed |
| pen2.addPoint((10, 10), "line") |
| pen2.addPoint((20, 20)) |
| pen2.addPoint((20, 0), "qcurve") |
| pen2.endPath() |
| |
| # Since TrueType contours are always implicitly closed, the pen will |
| # interpret both these paths as equivalent |
| assert pen1.points == pen2.points == [(0, 0), (10, 10), (20, 20), (20, 0)] |
| assert pen1.types == pen2.types == [1, 1, 0, 1] |
| |
| def test_skip_empty_contours(self): |
| pen = TTGlyphPointPen(None) |
| pen.beginPath() |
| pen.endPath() |
| pen.beginPath() |
| pen.endPath() |
| glyph = pen.glyph() |
| assert glyph.numberOfContours == 0 |
| |
| |
| class CubicGlyfTest: |
| def test_cubic_simple(self): |
| spen = TTGlyphPen(None) |
| spen.moveTo((0, 0)) |
| spen.curveTo((0, 1), (1, 1), (1, 0)) |
| spen.closePath() |
| |
| ppen = TTGlyphPointPen(None) |
| ppen.beginPath() |
| ppen.addPoint((0, 0), "line") |
| ppen.addPoint((0, 1)) |
| ppen.addPoint((1, 1)) |
| ppen.addPoint((1, 0), "curve") |
| ppen.endPath() |
| |
| for pen in (spen, ppen): |
| glyph = pen.glyph() |
| |
| for i in range(2): |
| if i == 1: |
| glyph.compile(None) |
| |
| assert list(glyph.coordinates) == [(0, 0), (0, 1), (1, 1), (1, 0)] |
| assert list(glyph.flags) == [0x01, 0x80, 0x80, 0x01] |
| |
| rpen = RecordingPen() |
| glyph.draw(rpen, None) |
| assert rpen.value == [ |
| ("moveTo", ((0, 0),)), |
| ( |
| "curveTo", |
| ( |
| (0, 1), |
| (1, 1), |
| (1, 0), |
| ), |
| ), |
| ("closePath", ()), |
| ] |
| |
| @pytest.mark.parametrize( |
| "dropImpliedOnCurves, segment_pen_commands, point_pen_commands, expected_coordinates, expected_flags, expected_endPts", |
| [ |
| ( # Two curves that do NOT merge; request merging |
| True, |
| [ |
| ("moveTo", ((0, 0),)), |
| ("curveTo", ((0, 1), (1, 2), (2, 2))), |
| ("curveTo", ((3, 3), (4, 1), (4, 0))), |
| ("closePath", ()), |
| ], |
| [ |
| ("beginPath", (), {}), |
| ("addPoint", ((0, 0), "line", None, None), {}), |
| ("addPoint", ((0, 1), None, None, None), {}), |
| ("addPoint", ((1, 2), None, None, None), {}), |
| ("addPoint", ((2, 2), "curve", None, None), {}), |
| ("addPoint", ((3, 3), None, None, None), {}), |
| ("addPoint", ((4, 1), None, None, None), {}), |
| ("addPoint", ((4, 0), "curve", None, None), {}), |
| ("endPath", (), {}), |
| ], |
| [(0, 0), (0, 1), (1, 2), (2, 2), (3, 3), (4, 1), (4, 0)], |
| [0x01, 0x80, 0x80, 0x01, 0x80, 0x80, 0x01], |
| [6], |
| ), |
| ( # Two curves that merge; request merging |
| True, |
| [ |
| ("moveTo", ((0, 0),)), |
| ("curveTo", ((0, 1), (1, 2), (2, 2))), |
| ("curveTo", ((3, 2), (4, 1), (4, 0))), |
| ("closePath", ()), |
| ], |
| [ |
| ("beginPath", (), {}), |
| ("addPoint", ((0, 0), "line", None, None), {}), |
| ("addPoint", ((0, 1), None, None, None), {}), |
| ("addPoint", ((1, 2), None, None, None), {}), |
| ("addPoint", ((2, 2), "curve", None, None), {}), |
| ("addPoint", ((3, 2), None, None, None), {}), |
| ("addPoint", ((4, 1), None, None, None), {}), |
| ("addPoint", ((4, 0), "curve", None, None), {}), |
| ("endPath", (), {}), |
| ], |
| [(0, 0), (0, 1), (1, 2), (3, 2), (4, 1), (4, 0)], |
| [0x01, 0x80, 0x80, 0x80, 0x80, 0x01], |
| [5], |
| ), |
| ( # Two curves that merge; request NOT merging |
| False, |
| [ |
| ("moveTo", ((0, 0),)), |
| ("curveTo", ((0, 1), (1, 2), (2, 2))), |
| ("curveTo", ((3, 2), (4, 1), (4, 0))), |
| ("closePath", ()), |
| ], |
| [ |
| ("beginPath", (), {}), |
| ("addPoint", ((0, 0), "line", None, None), {}), |
| ("addPoint", ((0, 1), None, None, None), {}), |
| ("addPoint", ((1, 2), None, None, None), {}), |
| ("addPoint", ((2, 2), "curve", None, None), {}), |
| ("addPoint", ((3, 2), None, None, None), {}), |
| ("addPoint", ((4, 1), None, None, None), {}), |
| ("addPoint", ((4, 0), "curve", None, None), {}), |
| ("endPath", (), {}), |
| ], |
| [(0, 0), (0, 1), (1, 2), (2, 2), (3, 2), (4, 1), (4, 0)], |
| [0x01, 0x80, 0x80, 0x01, 0x80, 0x80, 0x01], |
| [6], |
| ), |
| ( # Two (duplicate) contours |
| True, |
| [ |
| ("moveTo", ((0, 0),)), |
| ("curveTo", ((0, 1), (1, 2), (2, 2))), |
| ("curveTo", ((3, 2), (4, 1), (4, 0))), |
| ("closePath", ()), |
| ("moveTo", ((0, 0),)), |
| ("curveTo", ((0, 1), (1, 2), (2, 2))), |
| ("curveTo", ((3, 2), (4, 1), (4, 0))), |
| ("closePath", ()), |
| ], |
| [ |
| ("beginPath", (), {}), |
| ("addPoint", ((0, 0), "line", None, None), {}), |
| ("addPoint", ((0, 1), None, None, None), {}), |
| ("addPoint", ((1, 2), None, None, None), {}), |
| ("addPoint", ((2, 2), "curve", None, None), {}), |
| ("addPoint", ((3, 2), None, None, None), {}), |
| ("addPoint", ((4, 1), None, None, None), {}), |
| ("addPoint", ((4, 0), "curve", None, None), {}), |
| ("endPath", (), {}), |
| ("beginPath", (), {}), |
| ("addPoint", ((0, 0), "line", None, None), {}), |
| ("addPoint", ((0, 1), None, None, None), {}), |
| ("addPoint", ((1, 2), None, None, None), {}), |
| ("addPoint", ((2, 2), "curve", None, None), {}), |
| ("addPoint", ((3, 2), None, None, None), {}), |
| ("addPoint", ((4, 1), None, None, None), {}), |
| ("addPoint", ((4, 0), "curve", None, None), {}), |
| ("endPath", (), {}), |
| ], |
| [ |
| (0, 0), |
| (0, 1), |
| (1, 2), |
| (3, 2), |
| (4, 1), |
| (4, 0), |
| (0, 0), |
| (0, 1), |
| (1, 2), |
| (3, 2), |
| (4, 1), |
| (4, 0), |
| ], |
| [ |
| 0x01, |
| 0x80, |
| 0x80, |
| 0x80, |
| 0x80, |
| 0x01, |
| 0x01, |
| 0x80, |
| 0x80, |
| 0x80, |
| 0x80, |
| 0x01, |
| ], |
| [5, 11], |
| ), |
| ], |
| ) |
| def test_cubic_topology( |
| self, |
| dropImpliedOnCurves, |
| segment_pen_commands, |
| point_pen_commands, |
| expected_coordinates, |
| expected_flags, |
| expected_endPts, |
| ): |
| spen = TTGlyphPen(None) |
| rpen = RecordingPen() |
| rpen.value = segment_pen_commands |
| rpen.replay(spen) |
| |
| ppen = TTGlyphPointPen(None) |
| rpen = RecordingPointPen() |
| rpen.value = point_pen_commands |
| rpen.replay(ppen) |
| |
| for pen in (spen, ppen): |
| glyph = pen.glyph(dropImpliedOnCurves=dropImpliedOnCurves) |
| |
| assert list(glyph.coordinates) == expected_coordinates |
| assert list(glyph.flags) == expected_flags |
| assert list(glyph.endPtsOfContours) == expected_endPts |
| |
| rpen = RecordingPen() |
| glyph.draw(rpen, None) |
| assert rpen.value == segment_pen_commands |
| |
| |
| class _TestGlyph(object): |
| def __init__(self, glyph): |
| self.coordinates = glyph.coordinates |
| |
| def draw(self, pen): |
| pen.moveTo(self.coordinates[0]) |
| for point in self.coordinates[1:]: |
| pen.lineTo(point) |
| pen.closePath() |
| |
| def drawPoints(self, pen): |
| pen.beginPath() |
| for point in self.coordinates: |
| pen.addPoint(point, "line") |
| pen.endPath() |