xref: /aosp_15_r20/external/fonttools/Tests/pens/utils.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughes# Copyright 2016 Google Inc. All Rights Reserved.
2*e1fe3e4aSElliott Hughes#
3*e1fe3e4aSElliott Hughes# Licensed under the Apache License, Version 2.0 (the "License");
4*e1fe3e4aSElliott Hughes# you may not use this file except in compliance with the License.
5*e1fe3e4aSElliott Hughes# You may obtain a copy of the License at
6*e1fe3e4aSElliott Hughes#
7*e1fe3e4aSElliott Hughes#     http://www.apache.org/licenses/LICENSE-2.0
8*e1fe3e4aSElliott Hughes#
9*e1fe3e4aSElliott Hughes# Unless required by applicable law or agreed to in writing, software
10*e1fe3e4aSElliott Hughes# distributed under the License is distributed on an "AS IS" BASIS,
11*e1fe3e4aSElliott Hughes# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*e1fe3e4aSElliott Hughes# See the License for the specific language governing permissions and
13*e1fe3e4aSElliott Hughes# limitations under the License.
14*e1fe3e4aSElliott Hughes
15*e1fe3e4aSElliott Hughesfrom fontTools.pens.pointPen import PointToSegmentPen, SegmentToPointPen
16*e1fe3e4aSElliott Hughesfrom fontTools.ufoLib.glifLib import GlyphSet
17*e1fe3e4aSElliott Hughesfrom math import isclose
18*e1fe3e4aSElliott Hughesimport os
19*e1fe3e4aSElliott Hughesimport unittest
20*e1fe3e4aSElliott Hughes
21*e1fe3e4aSElliott Hughes
22*e1fe3e4aSElliott HughesDATADIR = os.path.join(os.path.dirname(__file__), "data")
23*e1fe3e4aSElliott HughesCUBIC_GLYPHS = GlyphSet(os.path.join(DATADIR, "cubic"))
24*e1fe3e4aSElliott HughesQUAD_GLYPHS = GlyphSet(os.path.join(DATADIR, "quadratic"))
25*e1fe3e4aSElliott Hughes
26*e1fe3e4aSElliott Hughes
27*e1fe3e4aSElliott Hughesclass BaseDummyPen(object):
28*e1fe3e4aSElliott Hughes    """Base class for pens that record the commands they are called with."""
29*e1fe3e4aSElliott Hughes
30*e1fe3e4aSElliott Hughes    def __init__(self, *args, **kwargs):
31*e1fe3e4aSElliott Hughes        self.commands = []
32*e1fe3e4aSElliott Hughes
33*e1fe3e4aSElliott Hughes    def __str__(self):
34*e1fe3e4aSElliott Hughes        """Return the pen commands as a string of python code."""
35*e1fe3e4aSElliott Hughes        return _repr_pen_commands(self.commands)
36*e1fe3e4aSElliott Hughes
37*e1fe3e4aSElliott Hughes    def addComponent(self, glyphName, transformation, **kwargs):
38*e1fe3e4aSElliott Hughes        self.commands.append(("addComponent", (glyphName, transformation), kwargs))
39*e1fe3e4aSElliott Hughes
40*e1fe3e4aSElliott Hughes
41*e1fe3e4aSElliott Hughesclass DummyPen(BaseDummyPen):
42*e1fe3e4aSElliott Hughes    """A SegmentPen that records the commands it's called with."""
43*e1fe3e4aSElliott Hughes
44*e1fe3e4aSElliott Hughes    def moveTo(self, pt):
45*e1fe3e4aSElliott Hughes        self.commands.append(("moveTo", (pt,), {}))
46*e1fe3e4aSElliott Hughes
47*e1fe3e4aSElliott Hughes    def lineTo(self, pt):
48*e1fe3e4aSElliott Hughes        self.commands.append(("lineTo", (pt,), {}))
49*e1fe3e4aSElliott Hughes
50*e1fe3e4aSElliott Hughes    def curveTo(self, *points):
51*e1fe3e4aSElliott Hughes        self.commands.append(("curveTo", points, {}))
52*e1fe3e4aSElliott Hughes
53*e1fe3e4aSElliott Hughes    def qCurveTo(self, *points):
54*e1fe3e4aSElliott Hughes        self.commands.append(("qCurveTo", points, {}))
55*e1fe3e4aSElliott Hughes
56*e1fe3e4aSElliott Hughes    def closePath(self):
57*e1fe3e4aSElliott Hughes        self.commands.append(("closePath", tuple(), {}))
58*e1fe3e4aSElliott Hughes
59*e1fe3e4aSElliott Hughes    def endPath(self):
60*e1fe3e4aSElliott Hughes        self.commands.append(("endPath", tuple(), {}))
61*e1fe3e4aSElliott Hughes
62*e1fe3e4aSElliott Hughes
63*e1fe3e4aSElliott Hughesclass DummyPointPen(BaseDummyPen):
64*e1fe3e4aSElliott Hughes    """A PointPen that records the commands it's called with."""
65*e1fe3e4aSElliott Hughes
66*e1fe3e4aSElliott Hughes    def beginPath(self, **kwargs):
67*e1fe3e4aSElliott Hughes        self.commands.append(("beginPath", tuple(), kwargs))
68*e1fe3e4aSElliott Hughes
69*e1fe3e4aSElliott Hughes    def endPath(self):
70*e1fe3e4aSElliott Hughes        self.commands.append(("endPath", tuple(), {}))
71*e1fe3e4aSElliott Hughes
72*e1fe3e4aSElliott Hughes    def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
73*e1fe3e4aSElliott Hughes        kwargs["segmentType"] = str(segmentType) if segmentType else None
74*e1fe3e4aSElliott Hughes        kwargs["smooth"] = smooth
75*e1fe3e4aSElliott Hughes        kwargs["name"] = name
76*e1fe3e4aSElliott Hughes        self.commands.append(("addPoint", (pt,), kwargs))
77*e1fe3e4aSElliott Hughes
78*e1fe3e4aSElliott Hughes
79*e1fe3e4aSElliott Hughesclass DummyGlyph(object):
80*e1fe3e4aSElliott Hughes    """Provides a minimal interface for storing a glyph's outline data in a
81*e1fe3e4aSElliott Hughes    SegmentPen-oriented way. The glyph's outline consists in the list of
82*e1fe3e4aSElliott Hughes    SegmentPen commands required to draw it.
83*e1fe3e4aSElliott Hughes    """
84*e1fe3e4aSElliott Hughes
85*e1fe3e4aSElliott Hughes    # the SegmentPen class used to draw on this glyph type
86*e1fe3e4aSElliott Hughes    DrawingPen = DummyPen
87*e1fe3e4aSElliott Hughes
88*e1fe3e4aSElliott Hughes    def __init__(self, glyph=None):
89*e1fe3e4aSElliott Hughes        """If another glyph (i.e. any object having a 'draw' method) is given,
90*e1fe3e4aSElliott Hughes        its outline data is copied to self.
91*e1fe3e4aSElliott Hughes        """
92*e1fe3e4aSElliott Hughes        self._pen = self.DrawingPen()
93*e1fe3e4aSElliott Hughes        self.outline = self._pen.commands
94*e1fe3e4aSElliott Hughes        if glyph:
95*e1fe3e4aSElliott Hughes            self.appendGlyph(glyph)
96*e1fe3e4aSElliott Hughes
97*e1fe3e4aSElliott Hughes    def appendGlyph(self, glyph):
98*e1fe3e4aSElliott Hughes        """Copy another glyph's outline onto self."""
99*e1fe3e4aSElliott Hughes        glyph.draw(self._pen)
100*e1fe3e4aSElliott Hughes
101*e1fe3e4aSElliott Hughes    def getPen(self):
102*e1fe3e4aSElliott Hughes        """Return the SegmentPen that can 'draw' on this glyph."""
103*e1fe3e4aSElliott Hughes        return self._pen
104*e1fe3e4aSElliott Hughes
105*e1fe3e4aSElliott Hughes    def getPointPen(self):
106*e1fe3e4aSElliott Hughes        """Return a PointPen adapter that can 'draw' on this glyph."""
107*e1fe3e4aSElliott Hughes        return PointToSegmentPen(self._pen)
108*e1fe3e4aSElliott Hughes
109*e1fe3e4aSElliott Hughes    def draw(self, pen):
110*e1fe3e4aSElliott Hughes        """Use another SegmentPen to replay the glyph's outline commands."""
111*e1fe3e4aSElliott Hughes        if self.outline:
112*e1fe3e4aSElliott Hughes            for cmd, args, kwargs in self.outline:
113*e1fe3e4aSElliott Hughes                getattr(pen, cmd)(*args, **kwargs)
114*e1fe3e4aSElliott Hughes
115*e1fe3e4aSElliott Hughes    def drawPoints(self, pointPen):
116*e1fe3e4aSElliott Hughes        """Use another PointPen to replay the glyph's outline commands,
117*e1fe3e4aSElliott Hughes        indirectly through an adapter.
118*e1fe3e4aSElliott Hughes        """
119*e1fe3e4aSElliott Hughes        pen = SegmentToPointPen(pointPen)
120*e1fe3e4aSElliott Hughes        self.draw(pen)
121*e1fe3e4aSElliott Hughes
122*e1fe3e4aSElliott Hughes    def __eq__(self, other):
123*e1fe3e4aSElliott Hughes        """Return True if 'other' glyph's outline is the same as self."""
124*e1fe3e4aSElliott Hughes        if hasattr(other, "outline"):
125*e1fe3e4aSElliott Hughes            return self.outline == other.outline
126*e1fe3e4aSElliott Hughes        elif hasattr(other, "draw"):
127*e1fe3e4aSElliott Hughes            return self.outline == self.__class__(other).outline
128*e1fe3e4aSElliott Hughes        return NotImplemented
129*e1fe3e4aSElliott Hughes
130*e1fe3e4aSElliott Hughes    def __ne__(self, other):
131*e1fe3e4aSElliott Hughes        """Return True if 'other' glyph's outline is different from self."""
132*e1fe3e4aSElliott Hughes        return not (self == other)
133*e1fe3e4aSElliott Hughes
134*e1fe3e4aSElliott Hughes    def approx(self, other, rel_tol=1e-12):
135*e1fe3e4aSElliott Hughes        if hasattr(other, "outline"):
136*e1fe3e4aSElliott Hughes            outline2 == other.outline
137*e1fe3e4aSElliott Hughes        elif hasattr(other, "draw"):
138*e1fe3e4aSElliott Hughes            outline2 = self.__class__(other).outline
139*e1fe3e4aSElliott Hughes        else:
140*e1fe3e4aSElliott Hughes            raise TypeError(type(other).__name__)
141*e1fe3e4aSElliott Hughes        outline1 = self.outline
142*e1fe3e4aSElliott Hughes        if len(outline1) != len(outline2):
143*e1fe3e4aSElliott Hughes            return False
144*e1fe3e4aSElliott Hughes        for (cmd1, arg1, kwd1), (cmd2, arg2, kwd2) in zip(outline1, outline2):
145*e1fe3e4aSElliott Hughes            if cmd1 != cmd2:
146*e1fe3e4aSElliott Hughes                return False
147*e1fe3e4aSElliott Hughes            if kwd1 != kwd2:
148*e1fe3e4aSElliott Hughes                return False
149*e1fe3e4aSElliott Hughes            if arg1:
150*e1fe3e4aSElliott Hughes                if isinstance(arg1[0], tuple):
151*e1fe3e4aSElliott Hughes                    if not arg2 or not isinstance(arg2[0], tuple):
152*e1fe3e4aSElliott Hughes                        return False
153*e1fe3e4aSElliott Hughes                    for (x1, y1), (x2, y2) in zip(arg1, arg2):
154*e1fe3e4aSElliott Hughes                        if not isclose(x1, x2, rel_tol=rel_tol) or not isclose(
155*e1fe3e4aSElliott Hughes                            y1, y2, rel_tol=rel_tol
156*e1fe3e4aSElliott Hughes                        ):
157*e1fe3e4aSElliott Hughes                            return False
158*e1fe3e4aSElliott Hughes                elif arg1 != arg2:
159*e1fe3e4aSElliott Hughes                    return False
160*e1fe3e4aSElliott Hughes            elif arg2:
161*e1fe3e4aSElliott Hughes                return False
162*e1fe3e4aSElliott Hughes        return True
163*e1fe3e4aSElliott Hughes
164*e1fe3e4aSElliott Hughes    def __str__(self):
165*e1fe3e4aSElliott Hughes        """Return commands making up the glyph's outline as a string."""
166*e1fe3e4aSElliott Hughes        return str(self._pen)
167*e1fe3e4aSElliott Hughes
168*e1fe3e4aSElliott Hughes
169*e1fe3e4aSElliott Hughesclass DummyPointGlyph(DummyGlyph):
170*e1fe3e4aSElliott Hughes    """Provides a minimal interface for storing a glyph's outline data in a
171*e1fe3e4aSElliott Hughes    PointPen-oriented way. The glyph's outline consists in the list of
172*e1fe3e4aSElliott Hughes    PointPen commands required to draw it.
173*e1fe3e4aSElliott Hughes    """
174*e1fe3e4aSElliott Hughes
175*e1fe3e4aSElliott Hughes    # the PointPen class used to draw on this glyph type
176*e1fe3e4aSElliott Hughes    DrawingPen = DummyPointPen
177*e1fe3e4aSElliott Hughes
178*e1fe3e4aSElliott Hughes    def appendGlyph(self, glyph):
179*e1fe3e4aSElliott Hughes        """Copy another glyph's outline onto self."""
180*e1fe3e4aSElliott Hughes        glyph.drawPoints(self._pen)
181*e1fe3e4aSElliott Hughes
182*e1fe3e4aSElliott Hughes    def getPen(self):
183*e1fe3e4aSElliott Hughes        """Return a SegmentPen adapter that can 'draw' on this glyph."""
184*e1fe3e4aSElliott Hughes        return SegmentToPointPen(self._pen)
185*e1fe3e4aSElliott Hughes
186*e1fe3e4aSElliott Hughes    def getPointPen(self):
187*e1fe3e4aSElliott Hughes        """Return the PointPen that can 'draw' on this glyph."""
188*e1fe3e4aSElliott Hughes        return self._pen
189*e1fe3e4aSElliott Hughes
190*e1fe3e4aSElliott Hughes    def draw(self, pen):
191*e1fe3e4aSElliott Hughes        """Use another SegmentPen to replay the glyph's outline commands,
192*e1fe3e4aSElliott Hughes        indirectly through an adapter.
193*e1fe3e4aSElliott Hughes        """
194*e1fe3e4aSElliott Hughes        pointPen = PointToSegmentPen(pen)
195*e1fe3e4aSElliott Hughes        self.drawPoints(pointPen)
196*e1fe3e4aSElliott Hughes
197*e1fe3e4aSElliott Hughes    def drawPoints(self, pointPen):
198*e1fe3e4aSElliott Hughes        """Use another PointPen to replay the glyph's outline commands."""
199*e1fe3e4aSElliott Hughes        if self.outline:
200*e1fe3e4aSElliott Hughes            for cmd, args, kwargs in self.outline:
201*e1fe3e4aSElliott Hughes                getattr(pointPen, cmd)(*args, **kwargs)
202*e1fe3e4aSElliott Hughes
203*e1fe3e4aSElliott Hughes
204*e1fe3e4aSElliott Hughesdef _repr_pen_commands(commands):
205*e1fe3e4aSElliott Hughes    """
206*e1fe3e4aSElliott Hughes    >>> print(_repr_pen_commands([
207*e1fe3e4aSElliott Hughes    ...     ('moveTo', tuple(), {}),
208*e1fe3e4aSElliott Hughes    ...     ('lineTo', ((1.0, 0.1),), {}),
209*e1fe3e4aSElliott Hughes    ...     ('curveTo', ((1.0, 0.1), (2.0, 0.2), (3.0, 0.3)), {})
210*e1fe3e4aSElliott Hughes    ... ]))
211*e1fe3e4aSElliott Hughes    pen.moveTo()
212*e1fe3e4aSElliott Hughes    pen.lineTo((1, 0.1))
213*e1fe3e4aSElliott Hughes    pen.curveTo((1, 0.1), (2, 0.2), (3, 0.3))
214*e1fe3e4aSElliott Hughes
215*e1fe3e4aSElliott Hughes    >>> print(_repr_pen_commands([
216*e1fe3e4aSElliott Hughes    ...     ('beginPath', tuple(), {}),
217*e1fe3e4aSElliott Hughes    ...     ('addPoint', ((1.0, 0.1),),
218*e1fe3e4aSElliott Hughes    ...      {"segmentType":"line", "smooth":True, "name":"test", "z":1}),
219*e1fe3e4aSElliott Hughes    ... ]))
220*e1fe3e4aSElliott Hughes    pen.beginPath()
221*e1fe3e4aSElliott Hughes    pen.addPoint((1, 0.1), name='test', segmentType='line', smooth=True, z=1)
222*e1fe3e4aSElliott Hughes
223*e1fe3e4aSElliott Hughes    >>> print(_repr_pen_commands([
224*e1fe3e4aSElliott Hughes    ...    ('addComponent', ('A', (1, 0, 0, 1, 0, 0)), {})
225*e1fe3e4aSElliott Hughes    ... ]))
226*e1fe3e4aSElliott Hughes    pen.addComponent('A', (1, 0, 0, 1, 0, 0))
227*e1fe3e4aSElliott Hughes    """
228*e1fe3e4aSElliott Hughes    s = []
229*e1fe3e4aSElliott Hughes    for cmd, args, kwargs in commands:
230*e1fe3e4aSElliott Hughes        if args:
231*e1fe3e4aSElliott Hughes            if isinstance(args[0], tuple):
232*e1fe3e4aSElliott Hughes                # cast float to int if there're no digits after decimal point,
233*e1fe3e4aSElliott Hughes                # and round floats to 12 decimal digits (more than enough)
234*e1fe3e4aSElliott Hughes                args = [
235*e1fe3e4aSElliott Hughes                    (
236*e1fe3e4aSElliott Hughes                        tuple((int(v) if int(v) == v else round(v, 12)) for v in pt)
237*e1fe3e4aSElliott Hughes                        if pt is not None
238*e1fe3e4aSElliott Hughes                        else None
239*e1fe3e4aSElliott Hughes                    )
240*e1fe3e4aSElliott Hughes                    for pt in args
241*e1fe3e4aSElliott Hughes                ]
242*e1fe3e4aSElliott Hughes            args = ", ".join(repr(a) for a in args)
243*e1fe3e4aSElliott Hughes        if kwargs:
244*e1fe3e4aSElliott Hughes            kwargs = ", ".join("%s=%r" % (k, v) for k, v in sorted(kwargs.items()))
245*e1fe3e4aSElliott Hughes        if args and kwargs:
246*e1fe3e4aSElliott Hughes            s.append("pen.%s(%s, %s)" % (cmd, args, kwargs))
247*e1fe3e4aSElliott Hughes        elif args:
248*e1fe3e4aSElliott Hughes            s.append("pen.%s(%s)" % (cmd, args))
249*e1fe3e4aSElliott Hughes        elif kwargs:
250*e1fe3e4aSElliott Hughes            s.append("pen.%s(%s)" % (cmd, kwargs))
251*e1fe3e4aSElliott Hughes        else:
252*e1fe3e4aSElliott Hughes            s.append("pen.%s()" % cmd)
253*e1fe3e4aSElliott Hughes    return "\n".join(s)
254*e1fe3e4aSElliott Hughes
255*e1fe3e4aSElliott Hughes
256*e1fe3e4aSElliott Hughesclass TestDummyGlyph(unittest.TestCase):
257*e1fe3e4aSElliott Hughes    def test_equal(self):
258*e1fe3e4aSElliott Hughes        # verify that the copy and the copy of the copy are equal to
259*e1fe3e4aSElliott Hughes        # the source glyph's outline, as well as to each other
260*e1fe3e4aSElliott Hughes        source = CUBIC_GLYPHS["a"]
261*e1fe3e4aSElliott Hughes        copy = DummyGlyph(source)
262*e1fe3e4aSElliott Hughes        copy2 = DummyGlyph(copy)
263*e1fe3e4aSElliott Hughes        self.assertEqual(source, copy)
264*e1fe3e4aSElliott Hughes        self.assertEqual(source, copy2)
265*e1fe3e4aSElliott Hughes        self.assertEqual(copy, copy2)
266*e1fe3e4aSElliott Hughes        # assert equality doesn't hold any more after modification
267*e1fe3e4aSElliott Hughes        copy.outline.pop()
268*e1fe3e4aSElliott Hughes        self.assertNotEqual(source, copy)
269*e1fe3e4aSElliott Hughes        self.assertNotEqual(copy, copy2)
270*e1fe3e4aSElliott Hughes
271*e1fe3e4aSElliott Hughes
272*e1fe3e4aSElliott Hughesclass TestDummyPointGlyph(unittest.TestCase):
273*e1fe3e4aSElliott Hughes    def test_equal(self):
274*e1fe3e4aSElliott Hughes        # same as above but using the PointPen protocol
275*e1fe3e4aSElliott Hughes        source = CUBIC_GLYPHS["a"]
276*e1fe3e4aSElliott Hughes        copy = DummyPointGlyph(source)
277*e1fe3e4aSElliott Hughes        copy2 = DummyPointGlyph(copy)
278*e1fe3e4aSElliott Hughes        self.assertEqual(source, copy)
279*e1fe3e4aSElliott Hughes        self.assertEqual(source, copy2)
280*e1fe3e4aSElliott Hughes        self.assertEqual(copy, copy2)
281*e1fe3e4aSElliott Hughes        copy.outline.pop()
282*e1fe3e4aSElliott Hughes        self.assertNotEqual(source, copy)
283*e1fe3e4aSElliott Hughes        self.assertNotEqual(copy, copy2)
284*e1fe3e4aSElliott Hughes
285*e1fe3e4aSElliott Hughes
286*e1fe3e4aSElliott Hughesif __name__ == "__main__":
287*e1fe3e4aSElliott Hughes    unittest.main()
288