xref: /aosp_15_r20/external/fonttools/Tests/pens/qu2cuPen_test.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1# Copyright 2016 Google Inc. All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import sys
16import unittest
17
18from fontTools.pens.qu2cuPen import Qu2CuPen
19from fontTools.pens.recordingPen import RecordingPen
20from textwrap import dedent
21import pytest
22
23try:
24    from .utils import CUBIC_GLYPHS, QUAD_GLYPHS
25    from .utils import DummyGlyph
26    from .utils import DummyPen
27except ImportError as e:
28    pytest.skip(str(e), allow_module_level=True)
29
30MAX_ERR = 1.0
31
32
33class _TestPenMixin(object):
34    """Collection of tests that are shared by both the SegmentPen and the
35    PointPen test cases, plus some helper methods.
36    Note: We currently don't have a PointPen.
37    """
38
39    maxDiff = None
40
41    def diff(self, expected, actual):
42        import difflib
43
44        expected = str(self.Glyph(expected)).splitlines(True)
45        actual = str(self.Glyph(actual)).splitlines(True)
46        diff = difflib.unified_diff(
47            expected, actual, fromfile="expected", tofile="actual"
48        )
49        return "".join(diff)
50
51    def convert_glyph(self, glyph, **kwargs):
52        # draw source glyph onto a new glyph using a Cu2Qu pen and return it
53        converted = self.Glyph()
54        pen = getattr(converted, self.pen_getter_name)()
55        cubicpen = self.Qu2CuPen(pen, MAX_ERR, all_cubic=True, **kwargs)
56        getattr(glyph, self.draw_method_name)(cubicpen)
57        return converted
58
59    def expect_glyph(self, source, expected):
60        converted = self.convert_glyph(source)
61        self.assertNotEqual(converted, source)
62        if not converted.approx(expected):
63            print(self.diff(expected, converted))
64            self.fail("converted glyph is different from expected")
65
66    def test_convert_simple_glyph(self):
67        self.expect_glyph(QUAD_GLYPHS["a"], CUBIC_GLYPHS["a"])
68        self.expect_glyph(QUAD_GLYPHS["A"], CUBIC_GLYPHS["A"])
69
70    def test_convert_composite_glyph(self):
71        source = CUBIC_GLYPHS["Aacute"]
72        converted = self.convert_glyph(source)
73        # components don't change after quadratic conversion
74        self.assertEqual(converted, source)
75
76    def test_reverse_direction(self):
77        for name in ("a", "A", "Eacute"):
78            source = QUAD_GLYPHS[name]
79            normal_glyph = self.convert_glyph(source)
80            reversed_glyph = self.convert_glyph(source, reverse_direction=True)
81
82            # the number of commands is the same, just their order is iverted
83            self.assertTrue(len(normal_glyph.outline), len(reversed_glyph.outline))
84            self.assertNotEqual(normal_glyph, reversed_glyph)
85
86    def test_stats(self):
87        stats = {}
88        for name in QUAD_GLYPHS.keys():
89            source = QUAD_GLYPHS[name]
90            self.convert_glyph(source, stats=stats)
91
92        self.assertTrue(stats)
93        self.assertTrue("2" in stats)
94        self.assertEqual(type(stats["2"]), int)
95
96    def test_addComponent(self):
97        pen = self.Pen()
98        cubicpen = self.Qu2CuPen(pen, MAX_ERR)
99        cubicpen.addComponent("a", (1, 2, 3, 4, 5.0, 6.0))
100
101        # components are passed through without changes
102        self.assertEqual(
103            str(pen).splitlines(),
104            [
105                "pen.addComponent('a', (1, 2, 3, 4, 5.0, 6.0))",
106            ],
107        )
108
109
110class TestQu2CuPen(unittest.TestCase, _TestPenMixin):
111    def __init__(self, *args, **kwargs):
112        super(TestQu2CuPen, self).__init__(*args, **kwargs)
113        self.Glyph = DummyGlyph
114        self.Pen = DummyPen
115        self.Qu2CuPen = Qu2CuPen
116        self.pen_getter_name = "getPen"
117        self.draw_method_name = "draw"
118
119    def test_qCurveTo_1_point(self):
120        pen = DummyPen()
121        cubicpen = Qu2CuPen(pen, MAX_ERR)
122        cubicpen.moveTo((0, 0))
123        cubicpen.qCurveTo((1, 1))
124        cubicpen.closePath()
125
126        self.assertEqual(
127            str(pen).splitlines(),
128            [
129                "pen.moveTo((0, 0))",
130                "pen.qCurveTo((1, 1))",
131                "pen.closePath()",
132            ],
133        )
134
135    def test_qCurveTo_2_points(self):
136        pen = DummyPen()
137        cubicpen = Qu2CuPen(pen, MAX_ERR)
138        cubicpen.moveTo((0, 0))
139        cubicpen.qCurveTo((1, 1), (2, 2))
140        cubicpen.closePath()
141
142        self.assertEqual(
143            str(pen).splitlines(),
144            [
145                "pen.moveTo((0, 0))",
146                "pen.qCurveTo((1, 1), (2, 2))",
147                "pen.closePath()",
148            ],
149        )
150
151    def test_qCurveTo_3_points_no_conversion(self):
152        pen = DummyPen()
153        cubicpen = Qu2CuPen(pen, MAX_ERR)
154        cubicpen.moveTo((0, 0))
155        cubicpen.qCurveTo((0, 3), (1, 3), (1, 0))
156        cubicpen.closePath()
157
158        self.assertEqual(
159            str(pen).splitlines(),
160            [
161                "pen.moveTo((0, 0))",
162                "pen.qCurveTo((0, 3), (1, 3), (1, 0))",
163                "pen.closePath()",
164            ],
165        )
166
167    def test_qCurveTo_no_oncurve_points(self):
168        pen = DummyPen()
169        cubicpen = Qu2CuPen(pen, MAX_ERR)
170        cubicpen.qCurveTo((0, 0), (1, 0), (1, 1), (0, 1), None)
171        cubicpen.closePath()
172
173        self.assertEqual(
174            str(pen).splitlines(),
175            ["pen.qCurveTo((0, 0), (1, 0), (1, 1), (0, 1), None)", "pen.closePath()"],
176        )
177
178    def test_curveTo_1_point(self):
179        pen = DummyPen()
180        cubicpen = Qu2CuPen(pen, MAX_ERR)
181        cubicpen.moveTo((0, 0))
182        cubicpen.curveTo((1, 1))
183        cubicpen.closePath()
184
185        self.assertEqual(
186            str(pen).splitlines(),
187            [
188                "pen.moveTo((0, 0))",
189                "pen.curveTo((1, 1))",
190                "pen.closePath()",
191            ],
192        )
193
194    def test_curveTo_2_points(self):
195        pen = DummyPen()
196        cubicpen = Qu2CuPen(pen, MAX_ERR)
197        cubicpen.moveTo((0, 0))
198        cubicpen.curveTo((1, 1), (2, 2))
199        cubicpen.closePath()
200
201        self.assertEqual(
202            str(pen).splitlines(),
203            [
204                "pen.moveTo((0, 0))",
205                "pen.curveTo((1, 1), (2, 2))",
206                "pen.closePath()",
207            ],
208        )
209
210    def test_curveTo_3_points(self):
211        pen = DummyPen()
212        cubicpen = Qu2CuPen(pen, MAX_ERR)
213        cubicpen.moveTo((0, 0))
214        cubicpen.curveTo((1, 1), (2, 2), (3, 3))
215        cubicpen.closePath()
216
217        self.assertEqual(
218            str(pen).splitlines(),
219            [
220                "pen.moveTo((0, 0))",
221                "pen.curveTo((1, 1), (2, 2), (3, 3))",
222                "pen.closePath()",
223            ],
224        )
225
226    def test_all_cubic(self):
227        inPen = RecordingPen()
228        inPen.value = [
229            ("moveTo", ((1204, 347),)),
230            ("qCurveTo", ((1255, 347), (1323, 433), (1323, 467))),
231            ("qCurveTo", ((1323, 478), (1310, 492), (1302, 492))),
232            ("qCurveTo", ((1295, 492), (1289, 484))),
233            ("lineTo", ((1272, 461),)),
234            ("qCurveTo", ((1256, 439), (1221, 416), (1200, 416))),
235            ("qCurveTo", ((1181, 416), (1141, 440), (1141, 462))),
236            ("qCurveTo", ((1141, 484), (1190, 565), (1190, 594))),
237            ("qCurveTo", ((1190, 607), (1181, 634), (1168, 634))),
238            ("qCurveTo", ((1149, 634), (1146, 583), (1081, 496), (1081, 463))),
239            ("qCurveTo", ((1081, 417), (1164, 347), (1204, 347))),
240            ("closePath", ()),
241        ]
242
243        outPen = RecordingPen()
244        q2cPen = Qu2CuPen(outPen, 1.0, all_cubic=True)
245        inPen.replay(q2cPen)
246
247        print(outPen.value)
248
249        assert not any(typ == "qCurveTo" for typ, _ in outPen.value)
250
251
252if __name__ == "__main__":
253    unittest.main()
254