[reverseContourPen] Add outputImpliedClosingLine

Fixes https://github.com/fonttools/fonttools/issues/2914
This commit is contained in:
Behdad Esfahbod 2022-12-09 15:27:07 -07:00
parent 6aca5be9b7
commit ddbbef2257
2 changed files with 36 additions and 12 deletions

View File

@ -14,11 +14,15 @@ class ReverseContourPen(ContourFilterPen):
the first point. the first point.
""" """
def __init__(self, outPen, outputImpliedClosingLine=False):
super().__init__(outPen)
self.outputImpliedClosingLine = outputImpliedClosingLine
def filterContour(self, contour): def filterContour(self, contour):
return reversedContour(contour) return reversedContour(contour, self.outputImpliedClosingLine)
def reversedContour(contour): def reversedContour(contour, outputImpliedClosingLine=False):
""" Generator that takes a list of pen's (operator, operands) tuples, """ Generator that takes a list of pen's (operator, operands) tuples,
and yields them with the winding direction reversed. and yields them with the winding direction reversed.
""" """
@ -60,7 +64,7 @@ def reversedContour(contour):
if closed: if closed:
# for closed paths, we keep the starting point # for closed paths, we keep the starting point
yield firstType, firstPts yield firstType, firstPts
if firstOnCurve != lastOnCurve: if outputImpliedClosingLine or firstOnCurve != lastOnCurve:
# emit an implied line between the last and first points # emit an implied line between the last and first points
yield "lineTo", (lastOnCurve,) yield "lineTo", (lastOnCurve,)
contour[-1] = (lastType, contour[-1] = (lastType,
@ -71,6 +75,8 @@ def reversedContour(contour):
else: else:
# contour has only two points, the second and last are the same # contour has only two points, the second and last are the same
secondType, secondPts = lastType, lastPts secondType, secondPts = lastType, lastPts
if not outputImpliedClosingLine:
# if a lineTo follows the initial moveTo, after reversing it # if a lineTo follows the initial moveTo, after reversing it
# will be implied by the closePath, so we don't emit one; # will be implied by the closePath, so we don't emit one;
# unless the lineTo and moveTo overlap, in which case we keep the # unless the lineTo and moveTo overlap, in which case we keep the

View File

@ -310,6 +310,24 @@ def test_reverse_pen(contour, expected):
getattr(revpen, operator)(*operands) getattr(revpen, operator)(*operands)
assert recpen.value == expected 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 len(recpen.value) == 4
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 len(recpen.value) == 6
@pytest.mark.parametrize("contour, expected", TEST_DATA) @pytest.mark.parametrize("contour, expected", TEST_DATA)
def test_reverse_point_pen(contour, expected): def test_reverse_point_pen(contour, expected):