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]
283
284
285@pytest.mark.parametrize("contour, expected", TEST_DATA)
286def test_reverse_pen(contour, expected):
287    recpen = RecordingPen()
288    revpen = ReverseContourPen(recpen)
289    for operator, operands in contour:
290        getattr(revpen, operator)(*operands)
291    assert recpen.value == expected
292
293
294@pytest.mark.parametrize("contour, expected", TEST_DATA)
295def test_reverse_point_pen(contour, expected):
296    from fontTools.ufoLib.pointPen import (
297        ReverseContourPointPen, PointToSegmentPen, SegmentToPointPen)
298
299    recpen = RecordingPen()
300    pt2seg = PointToSegmentPen(recpen, outputImpliedClosingLine=True)
301    revpen = ReverseContourPointPen(pt2seg)
302    seg2pt = SegmentToPointPen(revpen)
303    for operator, operands in contour:
304        getattr(seg2pt, operator)(*operands)
305
306    # for closed contours that have a lineTo following the moveTo,
307    # and whose points don't overlap, our current implementation diverges
308    # from the ReverseContourPointPen as wrapped by ufoLib's pen converters.
309    # In the latter case, an extra lineTo is added because of
310    # outputImpliedClosingLine=True. This is redundant but not incorrect,
311    # as the number of points is the same in both.
312    if (contour and contour[-1][0] == "closePath" and
313            contour[1][0] == "lineTo" and contour[1][1] != contour[0][1]):
314        expected = expected[:-1] + [("lineTo", contour[0][1])] + expected[-1:]
315
316    assert recpen.value == expected
317