1from __future__ import print_function, absolute_import, division 2 3from fontTools.misc.py23 import * 4from fontTools.pens.recordingPen import RecordingPen 5from fontTools.svgLib import parse_path 6 7import pytest 8 9 10@pytest.mark.parametrize( 11 "pathdef, expected", 12 [ 13 14 # Examples from the SVG spec 15 16 ( 17 "M 100 100 L 300 100 L 200 300 z", 18 [ 19 ("moveTo", ((100.0, 100.0),)), 20 ("lineTo", ((300.0, 100.0),)), 21 ("lineTo", ((200.0, 300.0),)), 22 ("lineTo", ((100.0, 100.0),)), 23 ("closePath", ()), 24 ] 25 ), 26 # for Z command behavior when there is multiple subpaths 27 ( 28 "M 0 0 L 50 20 M 100 100 L 300 100 L 200 300 z", 29 [ 30 ("moveTo", ((0.0, 0.0),)), 31 ("lineTo", ((50.0, 20.0),)), 32 ("endPath", ()), 33 ("moveTo", ((100.0, 100.0),)), 34 ("lineTo", ((300.0, 100.0),)), 35 ("lineTo", ((200.0, 300.0),)), 36 ("lineTo", ((100.0, 100.0),)), 37 ("closePath", ()), 38 ] 39 ), 40 ( 41 "M100,200 C100,100 250,100 250,200 S400,300 400,200", 42 [ 43 ("moveTo", ((100.0, 200.0),)), 44 ("curveTo", ((100.0, 100.0), 45 (250.0, 100.0), 46 (250.0, 200.0))), 47 ("curveTo", ((250.0, 300.0), 48 (400.0, 300.0), 49 (400.0, 200.0))), 50 ("endPath", ()), 51 ] 52 ), 53 ( 54 "M100,200 C100,100 400,100 400,200", 55 [ 56 ("moveTo", ((100.0, 200.0),)), 57 ("curveTo", ((100.0, 100.0), 58 (400.0, 100.0), 59 (400.0, 200.0))), 60 ("endPath", ()), 61 ] 62 ), 63 ( 64 "M100,500 C25,400 475,400 400,500", 65 [ 66 ("moveTo", ((100.0, 500.0),)), 67 ("curveTo", ((25.0, 400.0), 68 (475.0, 400.0), 69 (400.0, 500.0))), 70 ("endPath", ()), 71 ] 72 ), 73 ( 74 "M100,800 C175,700 325,700 400,800", 75 [ 76 ("moveTo", ((100.0, 800.0),)), 77 ("curveTo", ((175.0, 700.0), 78 (325.0, 700.0), 79 (400.0, 800.0))), 80 ("endPath", ()), 81 ] 82 ), 83 ( 84 "M600,200 C675,100 975,100 900,200", 85 [ 86 ("moveTo", ((600.0, 200.0),)), 87 ("curveTo", ((675.0, 100.0), 88 (975.0, 100.0), 89 (900.0, 200.0))), 90 ("endPath", ()), 91 ] 92 ), 93 ( 94 "M600,500 C600,350 900,650 900,500", 95 [ 96 ("moveTo", ((600.0, 500.0),)), 97 ("curveTo", ((600.0, 350.0), 98 (900.0, 650.0), 99 (900.0, 500.0))), 100 ("endPath", ()), 101 ] 102 ), 103 ( 104 "M600,800 C625,700 725,700 750,800 S875,900 900,800", 105 [ 106 ("moveTo", ((600.0, 800.0),)), 107 ("curveTo", ((625.0, 700.0), 108 (725.0, 700.0), 109 (750.0, 800.0))), 110 ("curveTo", ((775.0, 900.0), 111 (875.0, 900.0), 112 (900.0, 800.0))), 113 ("endPath", ()), 114 ] 115 ), 116 ( 117 "M200,300 Q400,50 600,300 T1000,300", 118 [ 119 ("moveTo", ((200.0, 300.0),)), 120 ("qCurveTo", ((400.0, 50.0), 121 (600.0, 300.0))), 122 ("qCurveTo", ((800.0, 550.0), 123 (1000.0, 300.0))), 124 ("endPath", ()), 125 ] 126 ), 127 # End examples from SVG spec 128 129 # Relative moveto 130 ( 131 "M 0 0 L 50 20 m 50 80 L 300 100 L 200 300 z", 132 [ 133 ("moveTo", ((0.0, 0.0),)), 134 ("lineTo", ((50.0, 20.0),)), 135 ("endPath", ()), 136 ("moveTo", ((100.0, 100.0),)), 137 ("lineTo", ((300.0, 100.0),)), 138 ("lineTo", ((200.0, 300.0),)), 139 ("lineTo", ((100.0, 100.0),)), 140 ("closePath", ()), 141 ] 142 ), 143 # Initial smooth and relative curveTo 144 ( 145 "M100,200 s 150,-100 150,0", 146 [ 147 ("moveTo", ((100.0, 200.0),)), 148 ("curveTo", ((100.0, 200.0), 149 (250.0, 100.0), 150 (250.0, 200.0))), 151 ("endPath", ()), 152 ] 153 ), 154 # Initial smooth and relative qCurveTo 155 ( 156 "M100,200 t 150,0", 157 [ 158 ("moveTo", ((100.0, 200.0),)), 159 ("qCurveTo", ((100.0, 200.0), 160 (250.0, 200.0))), 161 ("endPath", ()), 162 ] 163 ), 164 # relative l command 165 ( 166 "M 100 100 L 300 100 l -100 200 z", 167 [ 168 ("moveTo", ((100.0, 100.0),)), 169 ("lineTo", ((300.0, 100.0),)), 170 ("lineTo", ((200.0, 300.0),)), 171 ("lineTo", ((100.0, 100.0),)), 172 ("closePath", ()), 173 ] 174 ), 175 # relative q command 176 ( 177 "M200,300 q200,-250 400,0", 178 [ 179 ("moveTo", ((200.0, 300.0),)), 180 ("qCurveTo", ((400.0, 50.0), 181 (600.0, 300.0))), 182 ("endPath", ()), 183 ] 184 ), 185 # absolute H command 186 ( 187 "M 100 100 H 300 L 200 300 z", 188 [ 189 ("moveTo", ((100.0, 100.0),)), 190 ("lineTo", ((300.0, 100.0),)), 191 ("lineTo", ((200.0, 300.0),)), 192 ("lineTo", ((100.0, 100.0),)), 193 ("closePath", ()), 194 ] 195 ), 196 # relative h command 197 ( 198 "M 100 100 h 200 L 200 300 z", 199 [ 200 ("moveTo", ((100.0, 100.0),)), 201 ("lineTo", ((300.0, 100.0),)), 202 ("lineTo", ((200.0, 300.0),)), 203 ("lineTo", ((100.0, 100.0),)), 204 ("closePath", ()), 205 ] 206 ), 207 # absolute V command 208 ( 209 "M 100 100 V 300 L 200 300 z", 210 [ 211 ("moveTo", ((100.0, 100.0),)), 212 ("lineTo", ((100.0, 300.0),)), 213 ("lineTo", ((200.0, 300.0),)), 214 ("lineTo", ((100.0, 100.0),)), 215 ("closePath", ()), 216 ] 217 ), 218 # relative v command 219 ( 220 "M 100 100 v 200 L 200 300 z", 221 [ 222 ("moveTo", ((100.0, 100.0),)), 223 ("lineTo", ((100.0, 300.0),)), 224 ("lineTo", ((200.0, 300.0),)), 225 ("lineTo", ((100.0, 100.0),)), 226 ("closePath", ()), 227 ] 228 ), 229 ] 230) 231def test_parse_path(pathdef, expected): 232 pen = RecordingPen() 233 parse_path(pathdef, pen) 234 235 assert pen.value == expected 236 237 238@pytest.mark.parametrize( 239 "pathdef1, pathdef2", 240 [ 241 # don't need spaces between numbers and commands 242 ( 243 "M 100 100 L 200 200", 244 "M100 100L200 200", 245 ), 246 # repeated implicit command 247 ( 248 "M 100 200 L 200 100 L -100 -200", 249 "M 100 200 L 200 100 -100 -200" 250 ), 251 # don't need spaces before a minus-sign 252 ( 253 "M100,200c10-5,20-10,30-20", 254 "M 100 200 c 10 -5 20 -10 30 -20" 255 ), 256 # closed paths have an implicit lineTo if they don't 257 # end on the same point as the initial moveTo 258 ( 259 "M 100 100 L 300 100 L 200 300 z", 260 "M 100 100 L 300 100 L 200 300 L 100 100 z" 261 ) 262 ] 263) 264def test_equivalent_paths(pathdef1, pathdef2): 265 pen1 = RecordingPen() 266 parse_path(pathdef1, pen1) 267 268 pen2 = RecordingPen() 269 parse_path(pathdef2, pen2) 270 271 assert pen1.value == pen2.value 272 273 274def test_exponents(): 275 # It can be e or E, the plus is optional, and a minimum of +/-3.4e38 must be supported. 276 pen = RecordingPen() 277 parse_path("M-3.4e38 3.4E+38L-3.4E-38,3.4e-38", pen) 278 expected = [ 279 ("moveTo", ((-3.4e+38, 3.4e+38),)), 280 ("lineTo", ((-3.4e-38, 3.4e-38),)), 281 ("endPath", ()), 282 ] 283 284 assert pen.value == expected 285 286 287def test_invalid_implicit_command(): 288 with pytest.raises(ValueError) as exc_info: 289 parse_path("M 100 100 L 200 200 Z 100 200", RecordingPen()) 290 assert exc_info.match("Unallowed implicit command") 291 292 293def test_arc_to_cubic_bezier(): 294 pen = RecordingPen() 295 parse_path("M300,200 h-150 a150,150 0 1,0 150,-150 z", pen) 296 expected = [ 297 ('moveTo', ((300.0, 200.0),)), 298 ('lineTo', ((150.0, 200.0),)), 299 ( 300 'curveTo', 301 ( 302 (150.0, 282.842), 303 (217.157, 350.0), 304 (300.0, 350.0) 305 ) 306 ), 307 ( 308 'curveTo', 309 ( 310 (382.842, 350.0), 311 (450.0, 282.842), 312 (450.0, 200.0) 313 ) 314 ), 315 ( 316 'curveTo', 317 ( 318 (450.0, 117.157), 319 (382.842, 50.0), 320 (300.0, 50.0) 321 ) 322 ), 323 ('lineTo', ((300.0, 200.0),)), 324 ('closePath', ()) 325 ] 326 327 result = list(pen.value) 328 assert len(result) == len(expected) 329 for (cmd1, points1), (cmd2, points2) in zip(result, expected): 330 assert cmd1 == cmd2 331 assert len(points1) == len(points2) 332 for pt1, pt2 in zip(points1, points2): 333 assert pt1 == pytest.approx(pt2, rel=1e-5) 334 335 336 337class ArcRecordingPen(RecordingPen): 338 339 def arcTo(self, rx, ry, rotation, arc_large, arc_sweep, end_point): 340 self.value.append( 341 ("arcTo", (rx, ry, rotation, arc_large, arc_sweep, end_point)) 342 ) 343 344 345def test_arc_pen_with_arcTo(): 346 pen = ArcRecordingPen() 347 parse_path("M300,200 h-150 a150,150 0 1,0 150,-150 z", pen) 348 expected = [ 349 ('moveTo', ((300.0, 200.0),)), 350 ('lineTo', ((150.0, 200.0),)), 351 ('arcTo', (150.0, 150.0, 0.0, True, False, (300.0, 50.0))), 352 ('lineTo', ((300.0, 200.0),)), 353 ('closePath', ()) 354 ] 355 356 assert pen.value == expected 357