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', ()), ], [ ('moveTo', ((0, 0),)), ('lineTo', ((3, 3),)), ('lineTo', ((2, 2),)), ('lineTo', ((1, 1),)), ('closePath', ()), ] ), ( [ ('moveTo', ((0, 0),)), ('lineTo', ((1, 1),)), ('lineTo', ((2, 2),)), ('lineTo', ((0, 0),)), # last on move, no implied line ('closePath', ()), ], [ ('moveTo', ((0, 0),)), ('lineTo', ((2, 2),)), ('lineTo', ((1, 1),)), ('closePath', ()), ] ), ( [ ('moveTo', ((0, 0),)), ('lineTo', ((0, 0),)), ('lineTo', ((1, 1),)), ('lineTo', ((2, 2),)), ('closePath', ()), ], [ ('moveTo', ((0, 0),)), ('lineTo', ((2, 2),)), ('lineTo', ((1, 1),)), ('lineTo', ((0, 0),)), ('lineTo', ((0, 0),)), ('closePath', ()), ] ), ( [ ('moveTo', ((0, 0),)), ('lineTo', ((1, 1),)), ('closePath', ()), ], [ ('moveTo', ((0, 0),)), ('lineTo', ((1, 1),)), ('closePath', ()), ] ), ( [ ('moveTo', ((0, 0),)), ('curveTo', ((1, 1), (2, 2), (3, 3))), ('curveTo', ((4, 4), (5, 5), (0, 0))), ('closePath', ()), ], [ ('moveTo', ((0, 0),)), ('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))), ('closePath', ()), ], [ ('moveTo', ((0, 0),)), ('lineTo', ((6, 6),)), # implied line ('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', ()), ], [ ('moveTo', ((0, 0),)), ('lineTo', ((7, 7),)), ('curveTo', ((6, 6), (5, 5), (4, 4))), ('curveTo', ((3, 3), (2, 2), (1, 1))), ('closePath', ()), ] ), ( [ ('moveTo', ((0, 0),)), ('qCurveTo', ((1, 1), (2, 2))), ('qCurveTo', ((3, 3), (0, 0))), ('closePath', ()), ], [ ('moveTo', ((0, 0),)), ('qCurveTo', ((3, 3), (2, 2))), ('qCurveTo', ((1, 1), (0, 0))), ('closePath', ()), ] ), ( [ ('moveTo', ((0, 0),)), ('qCurveTo', ((1, 1), (2, 2))), ('qCurveTo', ((3, 3), (4, 4))), ('closePath', ()), ], [ ('moveTo', ((0, 0),)), ('lineTo', ((4, 4),)), ('qCurveTo', ((3, 3), (2, 2))), ('qCurveTo', ((1, 1), (0, 0))), ('closePath', ()), ] ), ( [ ('moveTo', ((0, 0),)), ('lineTo', ((1, 1),)), ('qCurveTo', ((2, 2), (3, 3))), ('closePath', ()), ], [ ('moveTo', ((0, 0),)), ('lineTo', ((3, 3),)), ('qCurveTo', ((2, 2), (1, 1))), ('closePath', ()), ] ), ( [ ('addComponent', ('a', (1, 0, 0, 1, 0, 0))) ], [ ('addComponent', ('a', (1, 0, 0, 1, 0, 0))) ] ), ( [], [] ), ( [ ('moveTo', ((0, 0),)), ('endPath', ()), ], [ ('moveTo', ((0, 0),)), ('endPath', ()), ], ), ( [ ('moveTo', ((0, 0),)), ('closePath', ()), ], [ ('moveTo', ((0, 0),)), ('endPath', ()), # single-point paths is always open ], ), ( [ ('moveTo', ((0, 0),)), ('lineTo', ((1, 1),)), ('endPath', ()) ], [ ('moveTo', ((1, 1),)), ('lineTo', ((0, 0),)), ('endPath', ()) ] ), ( [ ('moveTo', ((0, 0),)), ('curveTo', ((1, 1), (2, 2), (3, 3))), ('endPath', ()) ], [ ('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', ()) ], [ ('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', ()) ], [ ('moveTo', ((4, 4),)), ('curveTo', ((3, 3), (2, 2), (1, 1))), ('lineTo', ((0, 0),)), ('endPath', ()) ] ), ( [ ('qCurveTo', ((0, 0), (1, 1), (2, 2), None)), ('closePath', ()) ], [ ('qCurveTo', ((0, 0), (2, 2), (1, 1), None)), ('closePath', ()) ] ), ( [ ('qCurveTo', ((0, 0), (1, 1), (2, 2), None)), ('endPath', ()) ], [ ('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', ()) ], [ ('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', ()) ], [ ('moveTo', ((0, 651),)), ('lineTo', ((0, 651),)), ('lineTo', ((0, 101),)), ('lineTo', ((0, 101),)), ('closePath', ()) ] ) ] @pytest.mark.parametrize("contour, expected", TEST_DATA) def test_reverse_pen(contour, expected): recpen = RecordingPen() revpen = ReverseContourPen(recpen) for operator, operands in contour: getattr(revpen, operator)(*operands) assert recpen.value == expected @pytest.mark.parametrize("contour, expected", TEST_DATA) def test_reverse_point_pen(contour, expected): from fontTools.ufoLib.pointPen import ( ReverseContourPointPen, PointToSegmentPen, SegmentToPointPen) recpen = RecordingPen() pt2seg = PointToSegmentPen(recpen, outputImpliedClosingLine=True) revpen = ReverseContourPointPen(pt2seg) seg2pt = SegmentToPointPen(revpen) for operator, operands in contour: getattr(seg2pt, operator)(*operands) # for closed contours that have a lineTo following the moveTo, # and whose points don't overlap, our current implementation diverges # from the ReverseContourPointPen as wrapped by ufoLib's pen converters. # In the latter case, an extra lineTo is added because of # outputImpliedClosingLine=True. This is redundant but not incorrect, # as the number of points is the same in both. if (contour and contour[-1][0] == "closePath" and contour[1][0] == "lineTo" and contour[1][1] != contour[0][1]): expected = expected[:-1] + [("lineTo", contour[0][1])] + expected[-1:] assert recpen.value == expected