Merge pull request #1720 from anthrotype/point-to-segment-pen-overlapping-pts

PointToSegmentPen: preserve duplicate last point
This commit is contained in:
Cosimo Lupo 2019-09-10 16:17:31 +02:00 committed by GitHub
commit b75cf069cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 120 additions and 1 deletions

View File

@ -180,18 +180,36 @@ class PointToSegmentPen(BasePointToSegmentPen):
pen.moveTo(movePt)
outputImpliedClosingLine = self.outputImpliedClosingLine
nSegments = len(segments)
lastPt = movePt
for i in range(nSegments):
segmentType, points = segments[i]
points = [pt for pt, smooth, name, kwargs in points]
if segmentType == "line":
assert len(points) == 1, "illegal line segment point count: %d" % len(points)
pt = points[0]
if i + 1 != nSegments or outputImpliedClosingLine or not closed:
# For closed contours, a 'lineTo' is always implied from the last oncurve
# point to the starting point, thus we can omit it when the last and
# starting point don't overlap.
# However, when the last oncurve point is a "line" segment and has same
# coordinates as the starting point of a closed contour, we need to output
# the closing 'lineTo' explicitly (regardless of the value of the
# 'outputImpliedClosingLine' option) in order to disambiguate this case from
# the implied closing 'lineTo', otherwise the duplicate point would be lost.
# See https://github.com/googlefonts/fontmake/issues/572.
if (
i + 1 != nSegments
or outputImpliedClosingLine
or not closed
or pt == lastPt
):
pen.lineTo(pt)
lastPt = pt
elif segmentType == "curve":
pen.curveTo(*points)
lastPt = points[-1]
elif segmentType == "qcurve":
pen.qCurveTo(*points)
lastPt = points[-1]
else:
assert 0, "illegal segmentType: %s" % segmentType
if closed:

View File

@ -156,6 +156,67 @@ class PointToSegmentPenTest(unittest.TestCase):
"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):
@ -409,3 +470,23 @@ class TestReverseContourPointPen(unittest.TestCase):
"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)
)

View File

@ -278,6 +278,26 @@ TEST_DATA = [
('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', ())
]
)
]