xref: /aosp_15_r20/external/fonttools/Lib/fontTools/pens/explicitClosingLinePen.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1from fontTools.pens.filterPen import ContourFilterPen
2
3
4class ExplicitClosingLinePen(ContourFilterPen):
5    """A filter pen that adds an explicit lineTo to the first point of each closed
6    contour if the end point of the last segment is not already the same as the first point.
7    Otherwise, it passes the contour through unchanged.
8
9    >>> from pprint import pprint
10    >>> from fontTools.pens.recordingPen import RecordingPen
11    >>> rec = RecordingPen()
12    >>> pen = ExplicitClosingLinePen(rec)
13    >>> pen.moveTo((0, 0))
14    >>> pen.lineTo((100, 0))
15    >>> pen.lineTo((100, 100))
16    >>> pen.closePath()
17    >>> pprint(rec.value)
18    [('moveTo', ((0, 0),)),
19     ('lineTo', ((100, 0),)),
20     ('lineTo', ((100, 100),)),
21     ('lineTo', ((0, 0),)),
22     ('closePath', ())]
23    >>> rec = RecordingPen()
24    >>> pen = ExplicitClosingLinePen(rec)
25    >>> pen.moveTo((0, 0))
26    >>> pen.lineTo((100, 0))
27    >>> pen.lineTo((100, 100))
28    >>> pen.lineTo((0, 0))
29    >>> pen.closePath()
30    >>> pprint(rec.value)
31    [('moveTo', ((0, 0),)),
32     ('lineTo', ((100, 0),)),
33     ('lineTo', ((100, 100),)),
34     ('lineTo', ((0, 0),)),
35     ('closePath', ())]
36    >>> rec = RecordingPen()
37    >>> pen = ExplicitClosingLinePen(rec)
38    >>> pen.moveTo((0, 0))
39    >>> pen.curveTo((100, 0), (0, 100), (100, 100))
40    >>> pen.closePath()
41    >>> pprint(rec.value)
42    [('moveTo', ((0, 0),)),
43     ('curveTo', ((100, 0), (0, 100), (100, 100))),
44     ('lineTo', ((0, 0),)),
45     ('closePath', ())]
46    >>> rec = RecordingPen()
47    >>> pen = ExplicitClosingLinePen(rec)
48    >>> pen.moveTo((0, 0))
49    >>> pen.curveTo((100, 0), (0, 100), (100, 100))
50    >>> pen.lineTo((0, 0))
51    >>> pen.closePath()
52    >>> pprint(rec.value)
53    [('moveTo', ((0, 0),)),
54     ('curveTo', ((100, 0), (0, 100), (100, 100))),
55     ('lineTo', ((0, 0),)),
56     ('closePath', ())]
57    >>> rec = RecordingPen()
58    >>> pen = ExplicitClosingLinePen(rec)
59    >>> pen.moveTo((0, 0))
60    >>> pen.curveTo((100, 0), (0, 100), (0, 0))
61    >>> pen.closePath()
62    >>> pprint(rec.value)
63    [('moveTo', ((0, 0),)),
64     ('curveTo', ((100, 0), (0, 100), (0, 0))),
65     ('closePath', ())]
66    >>> rec = RecordingPen()
67    >>> pen = ExplicitClosingLinePen(rec)
68    >>> pen.moveTo((0, 0))
69    >>> pen.closePath()
70    >>> pprint(rec.value)
71    [('moveTo', ((0, 0),)), ('closePath', ())]
72    >>> rec = RecordingPen()
73    >>> pen = ExplicitClosingLinePen(rec)
74    >>> pen.closePath()
75    >>> pprint(rec.value)
76    [('closePath', ())]
77    >>> rec = RecordingPen()
78    >>> pen = ExplicitClosingLinePen(rec)
79    >>> pen.moveTo((0, 0))
80    >>> pen.lineTo((100, 0))
81    >>> pen.lineTo((100, 100))
82    >>> pen.endPath()
83    >>> pprint(rec.value)
84    [('moveTo', ((0, 0),)),
85     ('lineTo', ((100, 0),)),
86     ('lineTo', ((100, 100),)),
87     ('endPath', ())]
88    """
89
90    def filterContour(self, contour):
91        if (
92            not contour
93            or contour[0][0] != "moveTo"
94            or contour[-1][0] != "closePath"
95            or len(contour) < 3
96        ):
97            return
98        movePt = contour[0][1][0]
99        lastSeg = contour[-2][1]
100        if lastSeg and movePt != lastSeg[-1]:
101            contour[-1:] = [("lineTo", (movePt,)), ("closePath", ())]
102