| from fontTools.pens.recordingPen import RecordingPen |
| from fontTools.pens.reverseContourPen import ReverseContourPen |
| import pytest |
| |
| |
| TEST_DATA = [ |
| ( |
| [ |
| ("moveTo", ((0, 0),)), |
| ("lineTo", ((1, 1),)), |
| ("lineTo", ((2, 2),)), |
| ("lineTo", ((3, 3),)), # last not on move, line is implied |
| ("closePath", ()), |
| ], |
| False, # outputImpliedClosingLine |
| [ |
| ("moveTo", ((0, 0),)), |
| ("lineTo", ((3, 3),)), |
| ("lineTo", ((2, 2),)), |
| ("lineTo", ((1, 1),)), |
| ("closePath", ()), |
| ], |
| ), |
| ( |
| [ |
| ("moveTo", ((0, 0),)), |
| ("lineTo", ((1, 1),)), |
| ("lineTo", ((2, 2),)), |
| ("lineTo", ((3, 3),)), # last line does not overlap move... |
| ("closePath", ()), |
| ], |
| True, # outputImpliedClosingLine |
| [ |
| ("moveTo", ((0, 0),)), |
| ("lineTo", ((3, 3),)), |
| ("lineTo", ((2, 2),)), |
| ("lineTo", ((1, 1),)), |
| ("lineTo", ((0, 0),)), # ... but closing line is NOT implied |
| ("closePath", ()), |
| ], |
| ), |
| ( |
| [ |
| ("moveTo", ((0, 0),)), |
| ("lineTo", ((1, 1),)), |
| ("lineTo", ((2, 2),)), |
| ("lineTo", ((0, 0),)), # last line overlaps move, explicit line |
| ("closePath", ()), |
| ], |
| False, |
| [ |
| ("moveTo", ((0, 0),)), |
| ("lineTo", ((2, 2),)), |
| ("lineTo", ((1, 1),)), |
| ("closePath", ()), # closing line implied |
| ], |
| ), |
| ( |
| [ |
| ("moveTo", ((0, 0),)), |
| ("lineTo", ((1, 1),)), |
| ("lineTo", ((2, 2),)), |
| ("lineTo", ((0, 0),)), # last line overlaps move... |
| ("closePath", ()), |
| ], |
| True, |
| [ |
| ("moveTo", ((0, 0),)), |
| ("lineTo", ((2, 2),)), |
| ("lineTo", ((1, 1),)), |
| ("lineTo", ((0, 0),)), # ... but line is NOT implied |
| ("closePath", ()), |
| ], |
| ), |
| ( |
| [ |
| ("moveTo", ((0, 0),)), |
| ("lineTo", ((0, 0),)), # duplicate lineTo following moveTo |
| ("lineTo", ((1, 1),)), |
| ("lineTo", ((2, 2),)), |
| ("closePath", ()), |
| ], |
| False, |
| [ |
| ("moveTo", ((0, 0),)), |
| ("lineTo", ((2, 2),)), |
| ("lineTo", ((1, 1),)), |
| ("lineTo", ((0, 0),)), # extra explicit lineTo is always emitted to |
| ("lineTo", ((0, 0),)), # disambiguate from an implicit closing line |
| ("closePath", ()), |
| ], |
| ), |
| ( |
| [ |
| ("moveTo", ((0, 0),)), |
| ("lineTo", ((0, 0),)), # duplicate lineTo following moveTo |
| ("lineTo", ((1, 1),)), |
| ("lineTo", ((2, 2),)), |
| ("closePath", ()), |
| ], |
| True, |
| [ |
| ("moveTo", ((0, 0),)), |
| ("lineTo", ((2, 2),)), |
| ("lineTo", ((1, 1),)), |
| ("lineTo", ((0, 0),)), # duplicate lineTo is retained also in this case, |
| ("lineTo", ((0, 0),)), # same result as with outputImpliedClosingLine=False |
| ("closePath", ()), |
| ], |
| ), |
| ( |
| [ |
| ("moveTo", ((0, 0),)), |
| ("lineTo", ((1, 1),)), |
| ("closePath", ()), |
| ], |
| False, |
| [ |
| ("moveTo", ((0, 0),)), |
| ("lineTo", ((1, 1),)), |
| ("closePath", ()), |
| ], |
| ), |
| ( |
| [ |
| ("moveTo", ((0, 0),)), |
| ("lineTo", ((1, 1),)), |
| ("closePath", ()), |
| ], |
| True, |
| [ |
| ("moveTo", ((0, 0),)), |
| ("lineTo", ((1, 1),)), |
| ("lineTo", ((0, 0),)), |
| ("closePath", ()), |
| ], |
| ), |
| ( |
| [ |
| ("moveTo", ((0, 0),)), |
| ("curveTo", ((1, 1), (2, 2), (3, 3))), |
| ("curveTo", ((4, 4), (5, 5), (0, 0))), # closed curveTo overlaps moveTo |
| ("closePath", ()), |
| ], |
| False, |
| [ |
| ("moveTo", ((0, 0),)), # no extra lineTo added here |
| ("curveTo", ((5, 5), (4, 4), (3, 3))), |
| ("curveTo", ((2, 2), (1, 1), (0, 0))), |
| ("closePath", ()), |
| ], |
| ), |
| ( |
| [ |
| ("moveTo", ((0, 0),)), |
| ("curveTo", ((1, 1), (2, 2), (3, 3))), |
| ("curveTo", ((4, 4), (5, 5), (0, 0))), # closed curveTo overlaps moveTo |
| ("closePath", ()), |
| ], |
| True, |
| [ |
| ("moveTo", ((0, 0),)), # no extra lineTo added here, same as preceding |
| ("curveTo", ((5, 5), (4, 4), (3, 3))), |
| ("curveTo", ((2, 2), (1, 1), (0, 0))), |
| ("closePath", ()), |
| ], |
| ), |
| ( |
| [ |
| ("moveTo", ((0, 0),)), |
| ("curveTo", ((1, 1), (2, 2), (3, 3))), |
| ("curveTo", ((4, 4), (5, 5), (6, 6))), # closed curve not overlapping move |
| ("closePath", ()), |
| ], |
| False, |
| [ |
| ("moveTo", ((0, 0),)), |
| ("lineTo", ((6, 6),)), # the previously implied line |
| ("curveTo", ((5, 5), (4, 4), (3, 3))), |
| ("curveTo", ((2, 2), (1, 1), (0, 0))), |
| ("closePath", ()), |
| ], |
| ), |
| ( |
| [ |
| ("moveTo", ((0, 0),)), |
| ("curveTo", ((1, 1), (2, 2), (3, 3))), |
| ("curveTo", ((4, 4), (5, 5), (6, 6))), # closed curve not overlapping move |
| ("closePath", ()), |
| ], |
| True, |
| [ |
| ("moveTo", ((0, 0),)), |
| ("lineTo", ((6, 6),)), # the previously implied line (same as above) |
| ("curveTo", ((5, 5), (4, 4), (3, 3))), |
| ("curveTo", ((2, 2), (1, 1), (0, 0))), |
| ("closePath", ()), |
| ], |
| ), |
| ( |
| [ |
| ("moveTo", ((0, 0),)), |
| ("lineTo", ((1, 1),)), # this line becomes implied |
| ("curveTo", ((2, 2), (3, 3), (4, 4))), |
| ("curveTo", ((5, 5), (6, 6), (7, 7))), |
| ("closePath", ()), |
| ], |
| False, |
| [ |
| ("moveTo", ((0, 0),)), |
| ("lineTo", ((7, 7),)), |
| ("curveTo", ((6, 6), (5, 5), (4, 4))), |
| ("curveTo", ((3, 3), (2, 2), (1, 1))), |
| ("closePath", ()), |
| ], |
| ), |
| ( |
| [ |
| ("moveTo", ((0, 0),)), |
| ("lineTo", ((1, 1),)), # this line... |
| ("curveTo", ((2, 2), (3, 3), (4, 4))), |
| ("curveTo", ((5, 5), (6, 6), (7, 7))), |
| ("closePath", ()), |
| ], |
| True, |
| [ |
| ("moveTo", ((0, 0),)), |
| ("lineTo", ((7, 7),)), |
| ("curveTo", ((6, 6), (5, 5), (4, 4))), |
| ("curveTo", ((3, 3), (2, 2), (1, 1))), |
| ("lineTo", ((0, 0),)), # ... does NOT become implied |
| ("closePath", ()), |
| ], |
| ), |
| ( |
| [ |
| ("moveTo", ((0, 0),)), |
| ("qCurveTo", ((1, 1), (2, 2))), |
| ("qCurveTo", ((3, 3), (0, 0))), # closed qCurve overlaps move |
| ("closePath", ()), |
| ], |
| False, |
| [ |
| ("moveTo", ((0, 0),)), # no extra lineTo added here |
| ("qCurveTo", ((3, 3), (2, 2))), |
| ("qCurveTo", ((1, 1), (0, 0))), |
| ("closePath", ()), |
| ], |
| ), |
| ( |
| [ |
| ("moveTo", ((0, 0),)), |
| ("qCurveTo", ((1, 1), (2, 2))), |
| ("qCurveTo", ((3, 3), (0, 0))), # closed qCurve overlaps move |
| ("closePath", ()), |
| ], |
| True, # <-- |
| [ |
| ("moveTo", ((0, 0),)), # no extra lineTo added here, same as above |
| ("qCurveTo", ((3, 3), (2, 2))), |
| ("qCurveTo", ((1, 1), (0, 0))), |
| ("closePath", ()), |
| ], |
| ), |
| ( |
| [ |
| ("moveTo", ((0, 0),)), |
| ("qCurveTo", ((1, 1), (2, 2))), |
| ("qCurveTo", ((3, 3), (4, 4))), # closed qCurve not overlapping move |
| ("closePath", ()), |
| ], |
| False, |
| [ |
| ("moveTo", ((0, 0),)), |
| ("lineTo", ((4, 4),)), # the previously implied line |
| ("qCurveTo", ((3, 3), (2, 2))), |
| ("qCurveTo", ((1, 1), (0, 0))), |
| ("closePath", ()), |
| ], |
| ), |
| ( |
| [ |
| ("moveTo", ((0, 0),)), |
| ("qCurveTo", ((1, 1), (2, 2))), |
| ("qCurveTo", ((3, 3), (4, 4))), # closed qCurve not overlapping move |
| ("closePath", ()), |
| ], |
| True, |
| [ |
| ("moveTo", ((0, 0),)), |
| ("lineTo", ((4, 4),)), # the previously implied line (same as above) |
| ("qCurveTo", ((3, 3), (2, 2))), |
| ("qCurveTo", ((1, 1), (0, 0))), |
| ("closePath", ()), |
| ], |
| ), |
| ( |
| [ |
| ("moveTo", ((0, 0),)), |
| ("lineTo", ((1, 1),)), |
| ("qCurveTo", ((2, 2), (3, 3))), |
| ("closePath", ()), |
| ], |
| False, |
| [ |
| ("moveTo", ((0, 0),)), |
| ("lineTo", ((3, 3),)), |
| ("qCurveTo", ((2, 2), (1, 1))), |
| ("closePath", ()), |
| ], |
| ), |
| ( |
| [("addComponent", ("a", (1, 0, 0, 1, 0, 0)))], |
| False, |
| [("addComponent", ("a", (1, 0, 0, 1, 0, 0)))], |
| ), |
| ([], False, []), |
| ( |
| [ |
| ("moveTo", ((0, 0),)), |
| ("endPath", ()), |
| ], |
| False, |
| [ |
| ("moveTo", ((0, 0),)), |
| ("endPath", ()), |
| ], |
| ), |
| ( |
| [ |
| ("moveTo", ((0, 0),)), |
| ("closePath", ()), |
| ], |
| False, |
| [ |
| ("moveTo", ((0, 0),)), |
| ("endPath", ()), # single-point paths is always open |
| ], |
| ), |
| ( |
| [("moveTo", ((0, 0),)), ("lineTo", ((1, 1),)), ("endPath", ())], |
| False, |
| [("moveTo", ((1, 1),)), ("lineTo", ((0, 0),)), ("endPath", ())], |
| ), |
| ( |
| [("moveTo", ((0, 0),)), ("curveTo", ((1, 1), (2, 2), (3, 3))), ("endPath", ())], |
| False, |
| [("moveTo", ((3, 3),)), ("curveTo", ((2, 2), (1, 1), (0, 0))), ("endPath", ())], |
| ), |
| ( |
| [ |
| ("moveTo", ((0, 0),)), |
| ("curveTo", ((1, 1), (2, 2), (3, 3))), |
| ("lineTo", ((4, 4),)), |
| ("endPath", ()), |
| ], |
| False, |
| [ |
| ("moveTo", ((4, 4),)), |
| ("lineTo", ((3, 3),)), |
| ("curveTo", ((2, 2), (1, 1), (0, 0))), |
| ("endPath", ()), |
| ], |
| ), |
| ( |
| [ |
| ("moveTo", ((0, 0),)), |
| ("lineTo", ((1, 1),)), |
| ("curveTo", ((2, 2), (3, 3), (4, 4))), |
| ("endPath", ()), |
| ], |
| False, |
| [ |
| ("moveTo", ((4, 4),)), |
| ("curveTo", ((3, 3), (2, 2), (1, 1))), |
| ("lineTo", ((0, 0),)), |
| ("endPath", ()), |
| ], |
| ), |
| ( |
| [("qCurveTo", ((0, 0), (1, 1), (2, 2), None)), ("closePath", ())], |
| False, |
| [("qCurveTo", ((0, 0), (2, 2), (1, 1), None)), ("closePath", ())], |
| ), |
| ( |
| [("qCurveTo", ((0, 0), (1, 1), (2, 2), None)), ("endPath", ())], |
| False, |
| [ |
| ("qCurveTo", ((0, 0), (2, 2), (1, 1), None)), |
| ("closePath", ()), # this is always "closed" |
| ], |
| ), |
| # Test case from: |
| # https://github.com/googlei18n/cu2qu/issues/51#issue-179370514 |
| ( |
| [ |
| ("moveTo", ((848, 348),)), |
| ("lineTo", ((848, 348),)), # duplicate lineTo point after moveTo |
| ("qCurveTo", ((848, 526), (649, 704), (449, 704))), |
| ("qCurveTo", ((449, 704), (248, 704), (50, 526), (50, 348))), |
| ("lineTo", ((50, 348),)), |
| ("qCurveTo", ((50, 348), (50, 171), (248, -3), (449, -3))), |
| ("qCurveTo", ((449, -3), (649, -3), (848, 171), (848, 348))), |
| ("closePath", ()), |
| ], |
| False, |
| [ |
| ("moveTo", ((848, 348),)), |
| ("qCurveTo", ((848, 171), (649, -3), (449, -3), (449, -3))), |
| ("qCurveTo", ((248, -3), (50, 171), (50, 348), (50, 348))), |
| ("lineTo", ((50, 348),)), |
| ("qCurveTo", ((50, 526), (248, 704), (449, 704), (449, 704))), |
| ("qCurveTo", ((649, 704), (848, 526), (848, 348))), |
| ("lineTo", ((848, 348),)), # the duplicate point is kept |
| ("closePath", ()), |
| ], |
| ), |
| # Test case from https://github.com/googlefonts/fontmake/issues/572 |
| # An additional closing lineTo is required to disambiguate a duplicate |
| # point at the end of a contour from the implied closing line. |
| ( |
| [ |
| ("moveTo", ((0, 651),)), |
| ("lineTo", ((0, 101),)), |
| ("lineTo", ((0, 101),)), |
| ("lineTo", ((0, 651),)), |
| ("lineTo", ((0, 651),)), |
| ("closePath", ()), |
| ], |
| False, |
| [ |
| ("moveTo", ((0, 651),)), |
| ("lineTo", ((0, 651),)), |
| ("lineTo", ((0, 101),)), |
| ("lineTo", ((0, 101),)), |
| ("closePath", ()), |
| ], |
| ), |
| ( |
| [ |
| ("moveTo", ((0, 651),)), |
| ("lineTo", ((0, 101),)), |
| ("lineTo", ((0, 101),)), |
| ("lineTo", ((0, 651),)), |
| ("lineTo", ((0, 651),)), |
| ("closePath", ()), |
| ], |
| True, |
| [ |
| ("moveTo", ((0, 651),)), |
| ("lineTo", ((0, 651),)), |
| ("lineTo", ((0, 101),)), |
| ("lineTo", ((0, 101),)), |
| ("lineTo", ((0, 651),)), # closing line not implied |
| ("closePath", ()), |
| ], |
| ), |
| ] |
| |
| |
| @pytest.mark.parametrize("contour, outputImpliedClosingLine, expected", TEST_DATA) |
| def test_reverse_pen(contour, outputImpliedClosingLine, expected): |
| recpen = RecordingPen() |
| revpen = ReverseContourPen(recpen, outputImpliedClosingLine) |
| for operator, operands in contour: |
| getattr(revpen, operator)(*operands) |
| assert recpen.value == expected |
| |
| |
| def test_reverse_pen_outputImpliedClosingLine(): |
| recpen = RecordingPen() |
| revpen = ReverseContourPen(recpen) |
| revpen.moveTo((0, 0)) |
| revpen.lineTo((10, 0)) |
| revpen.lineTo((0, 10)) |
| revpen.lineTo((0, 0)) |
| revpen.closePath() |
| assert recpen.value == [ |
| ("moveTo", ((0, 0),)), |
| ("lineTo", ((0, 10),)), |
| ("lineTo", ((10, 0),)), |
| # ("lineTo", ((0, 0),)), # implied |
| ("closePath", ()), |
| ] |
| |
| recpen = RecordingPen() |
| revpen = ReverseContourPen(recpen, outputImpliedClosingLine=True) |
| revpen.moveTo((0, 0)) |
| revpen.lineTo((10, 0)) |
| revpen.lineTo((0, 10)) |
| revpen.lineTo((0, 0)) |
| revpen.closePath() |
| assert recpen.value == [ |
| ("moveTo", ((0, 0),)), |
| ("lineTo", ((0, 10),)), |
| ("lineTo", ((10, 0),)), |
| ("lineTo", ((0, 0),)), # not implied |
| ("closePath", ()), |
| ] |
| |
| |
| @pytest.mark.parametrize("contour, outputImpliedClosingLine, expected", TEST_DATA) |
| def test_reverse_point_pen(contour, outputImpliedClosingLine, expected): |
| from fontTools.pens.pointPen import ( |
| ReverseContourPointPen, |
| PointToSegmentPen, |
| SegmentToPointPen, |
| ) |
| |
| recpen = RecordingPen() |
| pt2seg = PointToSegmentPen(recpen, outputImpliedClosingLine) |
| revpen = ReverseContourPointPen(pt2seg) |
| seg2pt = SegmentToPointPen(revpen) |
| for operator, operands in contour: |
| getattr(seg2pt, operator)(*operands) |
| |
| assert recpen.value == expected |