• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from fontTools.pens.recordingPen import RecordingPen
2from fontTools.pens.reverseContourPen import ReverseContourPen
3import pytest
4
5
6TEST_DATA = [
7    (
8        [
9            ('moveTo', ((0, 0),)),
10            ('lineTo', ((1, 1),)),
11            ('lineTo', ((2, 2),)),
12            ('lineTo', ((3, 3),)),  # last not on move, line is implied
13            ('closePath', ()),
14        ],
15        [
16            ('moveTo', ((0, 0),)),
17            ('lineTo', ((3, 3),)),
18            ('lineTo', ((2, 2),)),
19            ('lineTo', ((1, 1),)),
20            ('closePath', ()),
21        ]
22    ),
23    (
24        [
25            ('moveTo', ((0, 0),)),
26            ('lineTo', ((1, 1),)),
27            ('lineTo', ((2, 2),)),
28            ('lineTo', ((0, 0),)),  # last on move, no implied line
29            ('closePath', ()),
30        ],
31        [
32            ('moveTo', ((0, 0),)),
33            ('lineTo', ((2, 2),)),
34            ('lineTo', ((1, 1),)),
35            ('closePath', ()),
36        ]
37    ),
38    (
39        [
40            ('moveTo', ((0, 0),)),
41            ('lineTo', ((0, 0),)),
42            ('lineTo', ((1, 1),)),
43            ('lineTo', ((2, 2),)),
44            ('closePath', ()),
45        ],
46        [
47            ('moveTo', ((0, 0),)),
48            ('lineTo', ((2, 2),)),
49            ('lineTo', ((1, 1),)),
50            ('lineTo', ((0, 0),)),
51            ('lineTo', ((0, 0),)),
52            ('closePath', ()),
53        ]
54    ),
55    (
56        [
57            ('moveTo', ((0, 0),)),
58            ('lineTo', ((1, 1),)),
59            ('closePath', ()),
60        ],
61        [
62            ('moveTo', ((0, 0),)),
63            ('lineTo', ((1, 1),)),
64            ('closePath', ()),
65        ]
66    ),
67    (
68        [
69            ('moveTo', ((0, 0),)),
70            ('curveTo', ((1, 1), (2, 2), (3, 3))),
71            ('curveTo', ((4, 4), (5, 5), (0, 0))),
72            ('closePath', ()),
73        ],
74        [
75            ('moveTo', ((0, 0),)),
76            ('curveTo', ((5, 5), (4, 4), (3, 3))),
77            ('curveTo', ((2, 2), (1, 1), (0, 0))),
78            ('closePath', ()),
79        ]
80    ),
81    (
82        [
83            ('moveTo', ((0, 0),)),
84            ('curveTo', ((1, 1), (2, 2), (3, 3))),
85            ('curveTo', ((4, 4), (5, 5), (6, 6))),
86            ('closePath', ()),
87        ],
88        [
89            ('moveTo', ((0, 0),)),
90            ('lineTo', ((6, 6),)),  # implied line
91            ('curveTo', ((5, 5), (4, 4), (3, 3))),
92            ('curveTo', ((2, 2), (1, 1), (0, 0))),
93            ('closePath', ()),
94        ]
95    ),
96    (
97        [
98            ('moveTo', ((0, 0),)),
99            ('lineTo', ((1, 1),)),  # this line becomes implied
100            ('curveTo', ((2, 2), (3, 3), (4, 4))),
101            ('curveTo', ((5, 5), (6, 6), (7, 7))),
102            ('closePath', ()),
103        ],
104        [
105            ('moveTo', ((0, 0),)),
106            ('lineTo', ((7, 7),)),
107            ('curveTo', ((6, 6), (5, 5), (4, 4))),
108            ('curveTo', ((3, 3), (2, 2), (1, 1))),
109            ('closePath', ()),
110        ]
111    ),
112    (
113        [
114            ('moveTo', ((0, 0),)),
115            ('qCurveTo', ((1, 1), (2, 2))),
116            ('qCurveTo', ((3, 3), (0, 0))),
117            ('closePath', ()),
118        ],
119        [
120            ('moveTo', ((0, 0),)),
121            ('qCurveTo', ((3, 3), (2, 2))),
122            ('qCurveTo', ((1, 1), (0, 0))),
123            ('closePath', ()),
124        ]
125    ),
126    (
127        [
128            ('moveTo', ((0, 0),)),
129            ('qCurveTo', ((1, 1), (2, 2))),
130            ('qCurveTo', ((3, 3), (4, 4))),
131            ('closePath', ()),
132        ],
133        [
134            ('moveTo', ((0, 0),)),
135            ('lineTo', ((4, 4),)),
136            ('qCurveTo', ((3, 3), (2, 2))),
137            ('qCurveTo', ((1, 1), (0, 0))),
138            ('closePath', ()),
139        ]
140    ),
141    (
142        [
143            ('moveTo', ((0, 0),)),
144            ('lineTo', ((1, 1),)),
145            ('qCurveTo', ((2, 2), (3, 3))),
146            ('closePath', ()),
147        ],
148        [
149            ('moveTo', ((0, 0),)),
150            ('lineTo', ((3, 3),)),
151            ('qCurveTo', ((2, 2), (1, 1))),
152            ('closePath', ()),
153        ]
154    ),
155    (
156        [
157            ('addComponent', ('a', (1, 0, 0, 1, 0, 0)))
158        ],
159        [
160            ('addComponent', ('a', (1, 0, 0, 1, 0, 0)))
161        ]
162    ),
163    (
164        [], []
165    ),
166    (
167        [
168            ('moveTo', ((0, 0),)),
169            ('endPath', ()),
170        ],
171        [
172            ('moveTo', ((0, 0),)),
173            ('endPath', ()),
174        ],
175    ),
176    (
177        [
178            ('moveTo', ((0, 0),)),
179            ('closePath', ()),
180        ],
181        [
182            ('moveTo', ((0, 0),)),
183            ('endPath', ()),  # single-point paths is always open
184        ],
185    ),
186    (
187        [
188            ('moveTo', ((0, 0),)),
189            ('lineTo', ((1, 1),)),
190            ('endPath', ())
191        ],
192        [
193            ('moveTo', ((1, 1),)),
194            ('lineTo', ((0, 0),)),
195            ('endPath', ())
196        ]
197    ),
198    (
199        [
200            ('moveTo', ((0, 0),)),
201            ('curveTo', ((1, 1), (2, 2), (3, 3))),
202            ('endPath', ())
203        ],
204        [
205            ('moveTo', ((3, 3),)),
206            ('curveTo', ((2, 2), (1, 1), (0, 0))),
207            ('endPath', ())
208        ]
209    ),
210    (
211        [
212            ('moveTo', ((0, 0),)),
213            ('curveTo', ((1, 1), (2, 2), (3, 3))),
214            ('lineTo', ((4, 4),)),
215            ('endPath', ())
216        ],
217        [
218            ('moveTo', ((4, 4),)),
219            ('lineTo', ((3, 3),)),
220            ('curveTo', ((2, 2), (1, 1), (0, 0))),
221            ('endPath', ())
222        ]
223    ),
224    (
225        [
226            ('moveTo', ((0, 0),)),
227            ('lineTo', ((1, 1),)),
228            ('curveTo', ((2, 2), (3, 3), (4, 4))),
229            ('endPath', ())
230        ],
231        [
232            ('moveTo', ((4, 4),)),
233            ('curveTo', ((3, 3), (2, 2), (1, 1))),
234            ('lineTo', ((0, 0),)),
235            ('endPath', ())
236        ]
237    ),
238    (
239        [
240            ('qCurveTo', ((0, 0), (1, 1), (2, 2), None)),
241            ('closePath', ())
242        ],
243        [
244            ('qCurveTo', ((0, 0), (2, 2), (1, 1), None)),
245            ('closePath', ())
246        ]
247    ),
248    (
249        [
250            ('qCurveTo', ((0, 0), (1, 1), (2, 2), None)),
251            ('endPath', ())
252        ],
253        [
254            ('qCurveTo', ((0, 0), (2, 2), (1, 1), None)),
255            ('closePath', ())  # this is always "closed"
256        ]
257    ),
258    # Test case from:
259    # https://github.com/googlei18n/cu2qu/issues/51#issue-179370514
260    (
261        [
262            ('moveTo', ((848, 348),)),
263            ('lineTo', ((848, 348),)),  # duplicate lineTo point after moveTo
264            ('qCurveTo', ((848, 526), (649, 704), (449, 704))),
265            ('qCurveTo', ((449, 704), (248, 704), (50, 526), (50, 348))),
266            ('lineTo', ((50, 348),)),
267            ('qCurveTo', ((50, 348), (50, 171), (248, -3), (449, -3))),
268            ('qCurveTo', ((449, -3), (649, -3), (848, 171), (848, 348))),
269            ('closePath', ())
270        ],
271        [
272            ('moveTo', ((848, 348),)),
273            ('qCurveTo', ((848, 171), (649, -3), (449, -3), (449, -3))),
274            ('qCurveTo', ((248, -3), (50, 171), (50, 348), (50, 348))),
275            ('lineTo', ((50, 348),)),
276            ('qCurveTo', ((50, 526), (248, 704), (449, 704), (449, 704))),
277            ('qCurveTo', ((649, 704), (848, 526), (848, 348))),
278            ('lineTo', ((848, 348),)),  # the duplicate point is kept
279            ('closePath', ())
280        ]
281    ),
282    # Test case from https://github.com/googlefonts/fontmake/issues/572
283    # An additional closing lineTo is required to disambiguate a duplicate
284    # point at the end of a contour from the implied closing line.
285    (
286        [
287            ('moveTo', ((0, 651),)),
288            ('lineTo', ((0, 101),)),
289            ('lineTo', ((0, 101),)),
290            ('lineTo', ((0, 651),)),
291            ('lineTo', ((0, 651),)),
292            ('closePath', ())
293        ],
294        [
295            ('moveTo', ((0, 651),)),
296            ('lineTo', ((0, 651),)),
297            ('lineTo', ((0, 101),)),
298            ('lineTo', ((0, 101),)),
299            ('closePath', ())
300        ]
301    )
302]
303
304
305@pytest.mark.parametrize("contour, expected", TEST_DATA)
306def test_reverse_pen(contour, expected):
307    recpen = RecordingPen()
308    revpen = ReverseContourPen(recpen)
309    for operator, operands in contour:
310        getattr(revpen, operator)(*operands)
311    assert recpen.value == expected
312
313
314@pytest.mark.parametrize("contour, expected", TEST_DATA)
315def test_reverse_point_pen(contour, expected):
316    from fontTools.ufoLib.pointPen import (
317        ReverseContourPointPen, PointToSegmentPen, SegmentToPointPen)
318
319    recpen = RecordingPen()
320    pt2seg = PointToSegmentPen(recpen, outputImpliedClosingLine=True)
321    revpen = ReverseContourPointPen(pt2seg)
322    seg2pt = SegmentToPointPen(revpen)
323    for operator, operands in contour:
324        getattr(seg2pt, operator)(*operands)
325
326    # for closed contours that have a lineTo following the moveTo,
327    # and whose points don't overlap, our current implementation diverges
328    # from the ReverseContourPointPen as wrapped by ufoLib's pen converters.
329    # In the latter case, an extra lineTo is added because of
330    # outputImpliedClosingLine=True. This is redundant but not incorrect,
331    # as the number of points is the same in both.
332    if (contour and contour[-1][0] == "closePath" and
333            contour[1][0] == "lineTo" and contour[1][1] != contour[0][1]):
334        expected = expected[:-1] + [("lineTo", contour[0][1])] + expected[-1:]
335
336    assert recpen.value == expected
337