xref: /aosp_15_r20/external/fonttools/Lib/fontTools/pens/roundingPen.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1from fontTools.misc.roundTools import noRound, otRound
2from fontTools.misc.transform import Transform
3from fontTools.pens.filterPen import FilterPen, FilterPointPen
4
5
6__all__ = ["RoundingPen", "RoundingPointPen"]
7
8
9class RoundingPen(FilterPen):
10    """
11    Filter pen that rounds point coordinates and component XY offsets to integer. For
12    rounding the component transform values, a separate round function can be passed to
13    the pen.
14
15    >>> from fontTools.pens.recordingPen import RecordingPen
16    >>> recpen = RecordingPen()
17    >>> roundpen = RoundingPen(recpen)
18    >>> roundpen.moveTo((0.4, 0.6))
19    >>> roundpen.lineTo((1.6, 2.5))
20    >>> roundpen.qCurveTo((2.4, 4.6), (3.3, 5.7), (4.9, 6.1))
21    >>> roundpen.curveTo((6.4, 8.6), (7.3, 9.7), (8.9, 10.1))
22    >>> roundpen.addComponent("a", (1.5, 0, 0, 1.5, 10.5, -10.5))
23    >>> recpen.value == [
24    ...     ('moveTo', ((0, 1),)),
25    ...     ('lineTo', ((2, 3),)),
26    ...     ('qCurveTo', ((2, 5), (3, 6), (5, 6))),
27    ...     ('curveTo', ((6, 9), (7, 10), (9, 10))),
28    ...     ('addComponent', ('a', (1.5, 0, 0, 1.5, 11, -10))),
29    ... ]
30    True
31    """
32
33    def __init__(self, outPen, roundFunc=otRound, transformRoundFunc=noRound):
34        super().__init__(outPen)
35        self.roundFunc = roundFunc
36        self.transformRoundFunc = transformRoundFunc
37
38    def moveTo(self, pt):
39        self._outPen.moveTo((self.roundFunc(pt[0]), self.roundFunc(pt[1])))
40
41    def lineTo(self, pt):
42        self._outPen.lineTo((self.roundFunc(pt[0]), self.roundFunc(pt[1])))
43
44    def curveTo(self, *points):
45        self._outPen.curveTo(
46            *((self.roundFunc(x), self.roundFunc(y)) for x, y in points)
47        )
48
49    def qCurveTo(self, *points):
50        self._outPen.qCurveTo(
51            *((self.roundFunc(x), self.roundFunc(y)) for x, y in points)
52        )
53
54    def addComponent(self, glyphName, transformation):
55        xx, xy, yx, yy, dx, dy = transformation
56        self._outPen.addComponent(
57            glyphName,
58            Transform(
59                self.transformRoundFunc(xx),
60                self.transformRoundFunc(xy),
61                self.transformRoundFunc(yx),
62                self.transformRoundFunc(yy),
63                self.roundFunc(dx),
64                self.roundFunc(dy),
65            ),
66        )
67
68
69class RoundingPointPen(FilterPointPen):
70    """
71    Filter point pen that rounds point coordinates and component XY offsets to integer.
72    For rounding the component scale values, a separate round function can be passed to
73    the pen.
74
75    >>> from fontTools.pens.recordingPen import RecordingPointPen
76    >>> recpen = RecordingPointPen()
77    >>> roundpen = RoundingPointPen(recpen)
78    >>> roundpen.beginPath()
79    >>> roundpen.addPoint((0.4, 0.6), 'line')
80    >>> roundpen.addPoint((1.6, 2.5), 'line')
81    >>> roundpen.addPoint((2.4, 4.6))
82    >>> roundpen.addPoint((3.3, 5.7))
83    >>> roundpen.addPoint((4.9, 6.1), 'qcurve')
84    >>> roundpen.endPath()
85    >>> roundpen.addComponent("a", (1.5, 0, 0, 1.5, 10.5, -10.5))
86    >>> recpen.value == [
87    ...     ('beginPath', (), {}),
88    ...     ('addPoint', ((0, 1), 'line', False, None), {}),
89    ...     ('addPoint', ((2, 3), 'line', False, None), {}),
90    ...     ('addPoint', ((2, 5), None, False, None), {}),
91    ...     ('addPoint', ((3, 6), None, False, None), {}),
92    ...     ('addPoint', ((5, 6), 'qcurve', False, None), {}),
93    ...     ('endPath', (), {}),
94    ...     ('addComponent', ('a', (1.5, 0, 0, 1.5, 11, -10)), {}),
95    ... ]
96    True
97    """
98
99    def __init__(self, outPen, roundFunc=otRound, transformRoundFunc=noRound):
100        super().__init__(outPen)
101        self.roundFunc = roundFunc
102        self.transformRoundFunc = transformRoundFunc
103
104    def addPoint(
105        self, pt, segmentType=None, smooth=False, name=None, identifier=None, **kwargs
106    ):
107        self._outPen.addPoint(
108            (self.roundFunc(pt[0]), self.roundFunc(pt[1])),
109            segmentType=segmentType,
110            smooth=smooth,
111            name=name,
112            identifier=identifier,
113            **kwargs,
114        )
115
116    def addComponent(self, baseGlyphName, transformation, identifier=None, **kwargs):
117        xx, xy, yx, yy, dx, dy = transformation
118        self._outPen.addComponent(
119            baseGlyphName=baseGlyphName,
120            transformation=Transform(
121                self.transformRoundFunc(xx),
122                self.transformRoundFunc(xy),
123                self.transformRoundFunc(yx),
124                self.transformRoundFunc(yy),
125                self.roundFunc(dx),
126                self.roundFunc(dy),
127            ),
128            identifier=identifier,
129            **kwargs,
130        )
131