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