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