| import unittest | |
| from fontTools.pens.basePen import AbstractPen | |
| from fontTools.pens.pointPen import ( | |
| AbstractPointPen, | |
| PointToSegmentPen, | |
| SegmentToPointPen, | |
| GuessSmoothPointPen, | |
| ReverseContourPointPen, | |
| ) | |
| class _TestSegmentPen(AbstractPen): | |
| def __init__(self): | |
| self._commands = [] | |
| def __repr__(self): | |
| return " ".join(self._commands) | |
| def moveTo(self, pt): | |
| self._commands.append("%s %s moveto" % (pt[0], pt[1])) | |
| def lineTo(self, pt): | |
| self._commands.append("%s %s lineto" % (pt[0], pt[1])) | |
| def curveTo(self, *pts): | |
| pts = ["%s %s" % pt for pt in pts] | |
| self._commands.append("%s curveto" % " ".join(pts)) | |
| def qCurveTo(self, *pts): | |
| pts = ["%s %s" % pt if pt is not None else "None" for pt in pts] | |
| self._commands.append("%s qcurveto" % " ".join(pts)) | |
| def closePath(self): | |
| self._commands.append("closepath") | |
| def endPath(self): | |
| self._commands.append("endpath") | |
| def addComponent(self, glyphName, transformation): | |
| self._commands.append("'%s' %s addcomponent" % (glyphName, transformation)) | |
| def _reprKwargs(kwargs): | |
| items = [] | |
| for key in sorted(kwargs): | |
| value = kwargs[key] | |
| if isinstance(value, str): | |
| items.append("%s='%s'" % (key, value)) | |
| else: | |
| items.append("%s=%s" % (key, value)) | |
| return items | |
| class _TestPointPen(AbstractPointPen): | |
| def __init__(self): | |
| self._commands = [] | |
| def __repr__(self): | |
| return " ".join(self._commands) | |
| def beginPath(self, identifier=None, **kwargs): | |
| items = [] | |
| if identifier is not None: | |
| items.append("identifier='%s'" % identifier) | |
| items.extend(_reprKwargs(kwargs)) | |
| self._commands.append("beginPath(%s)" % ", ".join(items)) | |
| def addPoint( | |
| self, pt, segmentType=None, smooth=False, name=None, identifier=None, **kwargs | |
| ): | |
| items = ["%s" % (pt,)] | |
| if segmentType is not None: | |
| items.append("segmentType='%s'" % segmentType) | |
| if smooth: | |
| items.append("smooth=True") | |
| if name is not None: | |
| items.append("name='%s'" % name) | |
| if identifier is not None: | |
| items.append("identifier='%s'" % identifier) | |
| items.extend(_reprKwargs(kwargs)) | |
| self._commands.append("addPoint(%s)" % ", ".join(items)) | |
| def endPath(self): | |
| self._commands.append("endPath()") | |
| def addComponent(self, glyphName, transform, identifier=None, **kwargs): | |
| items = ["'%s'" % glyphName, "%s" % transform] | |
| if identifier is not None: | |
| items.append("identifier='%s'" % identifier) | |
| items.extend(_reprKwargs(kwargs)) | |
| self._commands.append("addComponent(%s)" % ", ".join(items)) | |
| class PointToSegmentPenTest(unittest.TestCase): | |
| def test_open(self): | |
| pen = _TestSegmentPen() | |
| ppen = PointToSegmentPen(pen) | |
| ppen.beginPath() | |
| ppen.addPoint((10, 10), "move") | |
| ppen.addPoint((10, 20), "line") | |
| ppen.endPath() | |
| self.assertEqual("10 10 moveto 10 20 lineto endpath", repr(pen)) | |
| def test_closed(self): | |
| pen = _TestSegmentPen() | |
| ppen = PointToSegmentPen(pen) | |
| ppen.beginPath() | |
| ppen.addPoint((10, 10), "line") | |
| ppen.addPoint((10, 20), "line") | |
| ppen.addPoint((20, 20), "line") | |
| ppen.endPath() | |
| self.assertEqual("10 10 moveto 10 20 lineto 20 20 lineto closepath", repr(pen)) | |
| def test_cubic(self): | |
| pen = _TestSegmentPen() | |
| ppen = PointToSegmentPen(pen) | |
| ppen.beginPath() | |
| ppen.addPoint((10, 10), "line") | |
| ppen.addPoint((10, 20)) | |
| ppen.addPoint((20, 20)) | |
| ppen.addPoint((20, 40), "curve") | |
| ppen.endPath() | |
| self.assertEqual("10 10 moveto 10 20 20 20 20 40 curveto closepath", repr(pen)) | |
| def test_quad(self): | |
| pen = _TestSegmentPen() | |
| ppen = PointToSegmentPen(pen) | |
| ppen.beginPath(identifier="foo") | |
| ppen.addPoint((10, 10), "line") | |
| ppen.addPoint((10, 40)) | |
| ppen.addPoint((40, 40)) | |
| ppen.addPoint((10, 40), "qcurve") | |
| ppen.endPath() | |
| self.assertEqual("10 10 moveto 10 40 40 40 10 40 qcurveto closepath", repr(pen)) | |
| def test_quad_onlyOffCurvePoints(self): | |
| pen = _TestSegmentPen() | |
| ppen = PointToSegmentPen(pen) | |
| ppen.beginPath() | |
| ppen.addPoint((10, 10)) | |
| ppen.addPoint((10, 40)) | |
| ppen.addPoint((40, 40)) | |
| ppen.endPath() | |
| self.assertEqual("10 10 10 40 40 40 None qcurveto closepath", repr(pen)) | |
| def test_roundTrip1(self): | |
| tpen = _TestPointPen() | |
| ppen = PointToSegmentPen(SegmentToPointPen(tpen)) | |
| ppen.beginPath() | |
| ppen.addPoint((10, 10), "line") | |
| ppen.addPoint((10, 20)) | |
| ppen.addPoint((20, 20)) | |
| ppen.addPoint((20, 40), "curve") | |
| ppen.endPath() | |
| self.assertEqual( | |
| "beginPath() addPoint((10, 10), segmentType='line') addPoint((10, 20)) " | |
| "addPoint((20, 20)) addPoint((20, 40), segmentType='curve') endPath()", | |
| repr(tpen), | |
| ) | |
| def test_closed_outputImpliedClosingLine(self): | |
| tpen = _TestSegmentPen() | |
| ppen = PointToSegmentPen(tpen, outputImpliedClosingLine=True) | |
| ppen.beginPath() | |
| ppen.addPoint((10, 10), "line") | |
| ppen.addPoint((10, 20), "line") | |
| ppen.addPoint((20, 20), "line") | |
| ppen.endPath() | |
| self.assertEqual( | |
| "10 10 moveto " | |
| "10 20 lineto " | |
| "20 20 lineto " | |
| "10 10 lineto " # explicit closing line | |
| "closepath", | |
| repr(tpen), | |
| ) | |
| def test_closed_line_overlapping_start_end_points(self): | |
| # Test case from https://github.com/googlefonts/fontmake/issues/572. | |
| tpen = _TestSegmentPen() | |
| ppen = PointToSegmentPen(tpen, outputImpliedClosingLine=False) | |
| # The last oncurve point on this closed contour is a "line" segment and has | |
| # same coordinates as the starting point. | |
| ppen.beginPath() | |
| ppen.addPoint((0, 651), segmentType="line") | |
| ppen.addPoint((0, 101), segmentType="line") | |
| ppen.addPoint((0, 101), segmentType="line") | |
| ppen.addPoint((0, 651), segmentType="line") | |
| ppen.endPath() | |
| # Check that we always output an explicit 'lineTo' segment at the end, | |
| # regardless of the value of 'outputImpliedClosingLine', to disambiguate | |
| # the duplicate point from the implied closing line. | |
| self.assertEqual( | |
| "0 651 moveto " | |
| "0 101 lineto " | |
| "0 101 lineto " | |
| "0 651 lineto " | |
| "0 651 lineto " | |
| "closepath", | |
| repr(tpen), | |
| ) | |
| def test_roundTrip2(self): | |
| tpen = _TestPointPen() | |
| ppen = PointToSegmentPen(SegmentToPointPen(tpen)) | |
| ppen.beginPath() | |
| ppen.addPoint((0, 651), segmentType="line") | |
| ppen.addPoint((0, 101), segmentType="line") | |
| ppen.addPoint((0, 101), segmentType="line") | |
| ppen.addPoint((0, 651), segmentType="line") | |
| ppen.endPath() | |
| self.assertEqual( | |
| "beginPath() " | |
| "addPoint((0, 651), segmentType='line') " | |
| "addPoint((0, 101), segmentType='line') " | |
| "addPoint((0, 101), segmentType='line') " | |
| "addPoint((0, 651), segmentType='line') " | |
| "endPath()", | |
| repr(tpen), | |
| ) | |
| class TestSegmentToPointPen(unittest.TestCase): | |
| def test_move(self): | |
| tpen = _TestPointPen() | |
| pen = SegmentToPointPen(tpen) | |
| pen.moveTo((10, 10)) | |
| pen.endPath() | |
| self.assertEqual( | |
| "beginPath() addPoint((10, 10), segmentType='move') endPath()", repr(tpen) | |
| ) | |
| def test_poly(self): | |
| tpen = _TestPointPen() | |
| pen = SegmentToPointPen(tpen) | |
| pen.moveTo((10, 10)) | |
| pen.lineTo((10, 20)) | |
| pen.lineTo((20, 20)) | |
| pen.closePath() | |
| self.assertEqual( | |
| "beginPath() addPoint((10, 10), segmentType='line') " | |
| "addPoint((10, 20), segmentType='line') " | |
| "addPoint((20, 20), segmentType='line') endPath()", | |
| repr(tpen), | |
| ) | |
| def test_cubic(self): | |
| tpen = _TestPointPen() | |
| pen = SegmentToPointPen(tpen) | |
| pen.moveTo((10, 10)) | |
| pen.curveTo((10, 20), (20, 20), (20, 10)) | |
| pen.closePath() | |
| self.assertEqual( | |
| "beginPath() addPoint((10, 10), segmentType='line') " | |
| "addPoint((10, 20)) addPoint((20, 20)) addPoint((20, 10), " | |
| "segmentType='curve') endPath()", | |
| repr(tpen), | |
| ) | |
| def test_quad(self): | |
| tpen = _TestPointPen() | |
| pen = SegmentToPointPen(tpen) | |
| pen.moveTo((10, 10)) | |
| pen.qCurveTo((10, 20), (20, 20), (20, 10)) | |
| pen.closePath() | |
| self.assertEqual( | |
| "beginPath() addPoint((10, 10), segmentType='line') " | |
| "addPoint((10, 20)) addPoint((20, 20)) " | |
| "addPoint((20, 10), segmentType='qcurve') endPath()", | |
| repr(tpen), | |
| ) | |
| def test_quad2(self): | |
| tpen = _TestPointPen() | |
| pen = SegmentToPointPen(tpen) | |
| pen.qCurveTo((10, 20), (20, 20), (20, 10), (10, 10), None) | |
| pen.closePath() | |
| self.assertEqual( | |
| "beginPath() addPoint((10, 20)) addPoint((20, 20)) " | |
| "addPoint((20, 10)) addPoint((10, 10)) endPath()", | |
| repr(tpen), | |
| ) | |
| def test_roundTrip1(self): | |
| spen = _TestSegmentPen() | |
| pen = SegmentToPointPen(PointToSegmentPen(spen)) | |
| pen.moveTo((10, 10)) | |
| pen.lineTo((10, 20)) | |
| pen.lineTo((20, 20)) | |
| pen.closePath() | |
| self.assertEqual("10 10 moveto 10 20 lineto 20 20 lineto closepath", repr(spen)) | |
| def test_roundTrip2(self): | |
| spen = _TestSegmentPen() | |
| pen = SegmentToPointPen(PointToSegmentPen(spen)) | |
| pen.qCurveTo((10, 20), (20, 20), (20, 10), (10, 10), None) | |
| pen.closePath() | |
| pen.addComponent("base", [1, 0, 0, 1, 0, 0]) | |
| self.assertEqual( | |
| "10 20 20 20 20 10 10 10 None qcurveto closepath " | |
| "'base' [1, 0, 0, 1, 0, 0] addcomponent", | |
| repr(spen), | |
| ) | |
| class TestGuessSmoothPointPen(unittest.TestCase): | |
| def test_guessSmooth_exact(self): | |
| tpen = _TestPointPen() | |
| pen = GuessSmoothPointPen(tpen) | |
| pen.beginPath(identifier="foo") | |
| pen.addPoint((0, 100), segmentType="curve") | |
| pen.addPoint((0, 200)) | |
| pen.addPoint((400, 200), identifier="bar") | |
| pen.addPoint((400, 100), segmentType="curve") | |
| pen.addPoint((400, 0)) | |
| pen.addPoint((0, 0)) | |
| pen.endPath() | |
| self.assertEqual( | |
| "beginPath(identifier='foo') " | |
| "addPoint((0, 100), segmentType='curve', smooth=True) " | |
| "addPoint((0, 200)) addPoint((400, 200), identifier='bar') " | |
| "addPoint((400, 100), segmentType='curve', smooth=True) " | |
| "addPoint((400, 0)) addPoint((0, 0)) endPath()", | |
| repr(tpen), | |
| ) | |
| def test_guessSmooth_almost(self): | |
| tpen = _TestPointPen() | |
| pen = GuessSmoothPointPen(tpen) | |
| pen.beginPath() | |
| pen.addPoint((0, 100), segmentType="curve") | |
| pen.addPoint((1, 200)) | |
| pen.addPoint((395, 200)) | |
| pen.addPoint((400, 100), segmentType="curve") | |
| pen.addPoint((400, 0)) | |
| pen.addPoint((0, 0)) | |
| pen.endPath() | |
| self.assertEqual( | |
| "beginPath() addPoint((0, 100), segmentType='curve', smooth=True) " | |
| "addPoint((1, 200)) addPoint((395, 200)) " | |
| "addPoint((400, 100), segmentType='curve', smooth=True) " | |
| "addPoint((400, 0)) addPoint((0, 0)) endPath()", | |
| repr(tpen), | |
| ) | |
| def test_guessSmooth_tangent(self): | |
| tpen = _TestPointPen() | |
| pen = GuessSmoothPointPen(tpen) | |
| pen.beginPath() | |
| pen.addPoint((0, 0), segmentType="move") | |
| pen.addPoint((0, 100), segmentType="line") | |
| pen.addPoint((3, 200)) | |
| pen.addPoint((300, 200)) | |
| pen.addPoint((400, 200), segmentType="curve") | |
| pen.endPath() | |
| self.assertEqual( | |
| "beginPath() addPoint((0, 0), segmentType='move') " | |
| "addPoint((0, 100), segmentType='line', smooth=True) " | |
| "addPoint((3, 200)) addPoint((300, 200)) " | |
| "addPoint((400, 200), segmentType='curve') endPath()", | |
| repr(tpen), | |
| ) | |
| class TestReverseContourPointPen(unittest.TestCase): | |
| def test_singlePoint(self): | |
| tpen = _TestPointPen() | |
| pen = ReverseContourPointPen(tpen) | |
| pen.beginPath() | |
| pen.addPoint((0, 0), segmentType="move") | |
| pen.endPath() | |
| self.assertEqual( | |
| "beginPath() " "addPoint((0, 0), segmentType='move') " "endPath()", | |
| repr(tpen), | |
| ) | |
| def test_line(self): | |
| tpen = _TestPointPen() | |
| pen = ReverseContourPointPen(tpen) | |
| pen.beginPath() | |
| pen.addPoint((0, 0), segmentType="move") | |
| pen.addPoint((0, 100), segmentType="line") | |
| pen.endPath() | |
| self.assertEqual( | |
| "beginPath() " | |
| "addPoint((0, 100), segmentType='move') " | |
| "addPoint((0, 0), segmentType='line') " | |
| "endPath()", | |
| repr(tpen), | |
| ) | |
| def test_triangle(self): | |
| tpen = _TestPointPen() | |
| pen = ReverseContourPointPen(tpen) | |
| pen.beginPath() | |
| pen.addPoint((0, 0), segmentType="line") | |
| pen.addPoint((0, 100), segmentType="line") | |
| pen.addPoint((100, 100), segmentType="line") | |
| pen.endPath() | |
| self.assertEqual( | |
| "beginPath() " | |
| "addPoint((0, 0), segmentType='line') " | |
| "addPoint((100, 100), segmentType='line') " | |
| "addPoint((0, 100), segmentType='line') " | |
| "endPath()", | |
| repr(tpen), | |
| ) | |
| def test_cubicOpen(self): | |
| tpen = _TestPointPen() | |
| pen = ReverseContourPointPen(tpen) | |
| pen.beginPath() | |
| pen.addPoint((0, 0), segmentType="move") | |
| pen.addPoint((0, 100)) | |
| pen.addPoint((100, 200)) | |
| pen.addPoint((200, 200), segmentType="curve") | |
| pen.endPath() | |
| self.assertEqual( | |
| "beginPath() " | |
| "addPoint((200, 200), segmentType='move') " | |
| "addPoint((100, 200)) " | |
| "addPoint((0, 100)) " | |
| "addPoint((0, 0), segmentType='curve') " | |
| "endPath()", | |
| repr(tpen), | |
| ) | |
| def test_quadOpen(self): | |
| tpen = _TestPointPen() | |
| pen = ReverseContourPointPen(tpen) | |
| pen.beginPath() | |
| pen.addPoint((0, 0), segmentType="move") | |
| pen.addPoint((0, 100)) | |
| pen.addPoint((100, 200)) | |
| pen.addPoint((200, 200), segmentType="qcurve") | |
| pen.endPath() | |
| self.assertEqual( | |
| "beginPath() " | |
| "addPoint((200, 200), segmentType='move') " | |
| "addPoint((100, 200)) " | |
| "addPoint((0, 100)) " | |
| "addPoint((0, 0), segmentType='qcurve') " | |
| "endPath()", | |
| repr(tpen), | |
| ) | |
| def test_cubicClosed(self): | |
| tpen = _TestPointPen() | |
| pen = ReverseContourPointPen(tpen) | |
| pen.beginPath() | |
| pen.addPoint((0, 0), segmentType="line") | |
| pen.addPoint((0, 100)) | |
| pen.addPoint((100, 200)) | |
| pen.addPoint((200, 200), segmentType="curve") | |
| pen.endPath() | |
| self.assertEqual( | |
| "beginPath() " | |
| "addPoint((0, 0), segmentType='curve') " | |
| "addPoint((200, 200), segmentType='line') " | |
| "addPoint((100, 200)) " | |
| "addPoint((0, 100)) " | |
| "endPath()", | |
| repr(tpen), | |
| ) | |
| def test_quadClosedOffCurveStart(self): | |
| tpen = _TestPointPen() | |
| pen = ReverseContourPointPen(tpen) | |
| pen.beginPath() | |
| pen.addPoint((100, 200)) | |
| pen.addPoint((200, 200), segmentType="qcurve") | |
| pen.addPoint((0, 0), segmentType="line") | |
| pen.addPoint((0, 100)) | |
| pen.endPath() | |
| self.assertEqual( | |
| "beginPath() " | |
| "addPoint((100, 200)) " | |
| "addPoint((0, 100)) " | |
| "addPoint((0, 0), segmentType='qcurve') " | |
| "addPoint((200, 200), segmentType='line') " | |
| "endPath()", | |
| repr(tpen), | |
| ) | |
| def test_quadNoOnCurve(self): | |
| tpen = _TestPointPen() | |
| pen = ReverseContourPointPen(tpen) | |
| pen.beginPath(identifier="bar") | |
| pen.addPoint((0, 0)) | |
| pen.addPoint((0, 100), identifier="foo", arbitrary="foo") | |
| pen.addPoint((100, 200), arbitrary=123) | |
| pen.addPoint((200, 200)) | |
| pen.endPath() | |
| pen.addComponent("base", [1, 0, 0, 1, 0, 0], identifier="foo") | |
| self.assertEqual( | |
| "beginPath(identifier='bar') " | |
| "addPoint((0, 0)) " | |
| "addPoint((200, 200)) " | |
| "addPoint((100, 200), arbitrary=123) " | |
| "addPoint((0, 100), identifier='foo', arbitrary='foo') " | |
| "endPath() " | |
| "addComponent('base', [1, 0, 0, 1, 0, 0], identifier='foo')", | |
| repr(tpen), | |
| ) | |
| def test_closed_line_overlapping_start_end_points(self): | |
| # Test case from https://github.com/googlefonts/fontmake/issues/572 | |
| tpen = _TestPointPen() | |
| pen = ReverseContourPointPen(tpen) | |
| pen.beginPath() | |
| pen.addPoint((0, 651), segmentType="line") | |
| pen.addPoint((0, 101), segmentType="line") | |
| pen.addPoint((0, 101), segmentType="line") | |
| pen.addPoint((0, 651), segmentType="line") | |
| pen.endPath() | |
| self.assertEqual( | |
| "beginPath() " | |
| "addPoint((0, 651), segmentType='line') " | |
| "addPoint((0, 651), segmentType='line') " | |
| "addPoint((0, 101), segmentType='line') " | |
| "addPoint((0, 101), segmentType='line') " | |
| "endPath()", | |
| repr(tpen), | |
| ) |