xref: /aosp_15_r20/external/fonttools/Lib/fontTools/pens/filterPen.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1from fontTools.pens.basePen import AbstractPen
2from fontTools.pens.pointPen import AbstractPointPen
3from fontTools.pens.recordingPen import RecordingPen
4
5
6class _PassThruComponentsMixin(object):
7    def addComponent(self, glyphName, transformation, **kwargs):
8        self._outPen.addComponent(glyphName, transformation, **kwargs)
9
10
11class FilterPen(_PassThruComponentsMixin, AbstractPen):
12    """Base class for pens that apply some transformation to the coordinates
13    they receive and pass them to another pen.
14
15    You can override any of its methods. The default implementation does
16    nothing, but passes the commands unmodified to the other pen.
17
18    >>> from fontTools.pens.recordingPen import RecordingPen
19    >>> rec = RecordingPen()
20    >>> pen = FilterPen(rec)
21    >>> v = iter(rec.value)
22
23    >>> pen.moveTo((0, 0))
24    >>> next(v)
25    ('moveTo', ((0, 0),))
26
27    >>> pen.lineTo((1, 1))
28    >>> next(v)
29    ('lineTo', ((1, 1),))
30
31    >>> pen.curveTo((2, 2), (3, 3), (4, 4))
32    >>> next(v)
33    ('curveTo', ((2, 2), (3, 3), (4, 4)))
34
35    >>> pen.qCurveTo((5, 5), (6, 6), (7, 7), (8, 8))
36    >>> next(v)
37    ('qCurveTo', ((5, 5), (6, 6), (7, 7), (8, 8)))
38
39    >>> pen.closePath()
40    >>> next(v)
41    ('closePath', ())
42
43    >>> pen.moveTo((9, 9))
44    >>> next(v)
45    ('moveTo', ((9, 9),))
46
47    >>> pen.endPath()
48    >>> next(v)
49    ('endPath', ())
50
51    >>> pen.addComponent('foo', (1, 0, 0, 1, 0, 0))
52    >>> next(v)
53    ('addComponent', ('foo', (1, 0, 0, 1, 0, 0)))
54    """
55
56    def __init__(self, outPen):
57        self._outPen = outPen
58        self.current_pt = None
59
60    def moveTo(self, pt):
61        self._outPen.moveTo(pt)
62        self.current_pt = pt
63
64    def lineTo(self, pt):
65        self._outPen.lineTo(pt)
66        self.current_pt = pt
67
68    def curveTo(self, *points):
69        self._outPen.curveTo(*points)
70        self.current_pt = points[-1]
71
72    def qCurveTo(self, *points):
73        self._outPen.qCurveTo(*points)
74        self.current_pt = points[-1]
75
76    def closePath(self):
77        self._outPen.closePath()
78        self.current_pt = None
79
80    def endPath(self):
81        self._outPen.endPath()
82        self.current_pt = None
83
84
85class ContourFilterPen(_PassThruComponentsMixin, RecordingPen):
86    """A "buffered" filter pen that accumulates contour data, passes
87    it through a ``filterContour`` method when the contour is closed or ended,
88    and finally draws the result with the output pen.
89
90    Components are passed through unchanged.
91    """
92
93    def __init__(self, outPen):
94        super(ContourFilterPen, self).__init__()
95        self._outPen = outPen
96
97    def closePath(self):
98        super(ContourFilterPen, self).closePath()
99        self._flushContour()
100
101    def endPath(self):
102        super(ContourFilterPen, self).endPath()
103        self._flushContour()
104
105    def _flushContour(self):
106        result = self.filterContour(self.value)
107        if result is not None:
108            self.value = result
109        self.replay(self._outPen)
110        self.value = []
111
112    def filterContour(self, contour):
113        """Subclasses must override this to perform the filtering.
114
115        The contour is a list of pen (operator, operands) tuples.
116        Operators are strings corresponding to the AbstractPen methods:
117        "moveTo", "lineTo", "curveTo", "qCurveTo", "closePath" and
118        "endPath". The operands are the positional arguments that are
119        passed to each method.
120
121        If the method doesn't return a value (i.e. returns None), it's
122        assumed that the argument was modified in-place.
123        Otherwise, the return value is drawn with the output pen.
124        """
125        return  # or return contour
126
127
128class FilterPointPen(_PassThruComponentsMixin, AbstractPointPen):
129    """Baseclass for point pens that apply some transformation to the
130    coordinates they receive and pass them to another point pen.
131
132    You can override any of its methods. The default implementation does
133    nothing, but passes the commands unmodified to the other pen.
134
135    >>> from fontTools.pens.recordingPen import RecordingPointPen
136    >>> rec = RecordingPointPen()
137    >>> pen = FilterPointPen(rec)
138    >>> v = iter(rec.value)
139    >>> pen.beginPath(identifier="abc")
140    >>> next(v)
141    ('beginPath', (), {'identifier': 'abc'})
142    >>> pen.addPoint((1, 2), "line", False)
143    >>> next(v)
144    ('addPoint', ((1, 2), 'line', False, None), {})
145    >>> pen.addComponent("a", (2, 0, 0, 2, 10, -10), identifier="0001")
146    >>> next(v)
147    ('addComponent', ('a', (2, 0, 0, 2, 10, -10)), {'identifier': '0001'})
148    >>> pen.endPath()
149    >>> next(v)
150    ('endPath', (), {})
151    """
152
153    def __init__(self, outPointPen):
154        self._outPen = outPointPen
155
156    def beginPath(self, **kwargs):
157        self._outPen.beginPath(**kwargs)
158
159    def endPath(self):
160        self._outPen.endPath()
161
162    def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
163        self._outPen.addPoint(pt, segmentType, smooth, name, **kwargs)
164