You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
491 lines
18 KiB
491 lines
18 KiB
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)
|
|
)
|