from __future__ import print_function, absolute_import, division from fontTools.misc.py23 import * from fontTools.pens.recordingPen import RecordingPen from fontTools.svgLib import parse_path import pytest @pytest.mark.parametrize( "pathdef, expected", [ # Examples from the SVG spec ( "M 100 100 L 300 100 L 200 300 z", [ ("moveTo", ((100.0, 100.0),)), ("lineTo", ((300.0, 100.0),)), ("lineTo", ((200.0, 300.0),)), ("lineTo", ((100.0, 100.0),)), ("closePath", ()), ] ), # for Z command behavior when there is multiple subpaths ( "M 0 0 L 50 20 M 100 100 L 300 100 L 200 300 z", [ ("moveTo", ((0.0, 0.0),)), ("lineTo", ((50.0, 20.0),)), ("endPath", ()), ("moveTo", ((100.0, 100.0),)), ("lineTo", ((300.0, 100.0),)), ("lineTo", ((200.0, 300.0),)), ("lineTo", ((100.0, 100.0),)), ("closePath", ()), ] ), ( "M100,200 C100,100 250,100 250,200 S400,300 400,200", [ ("moveTo", ((100.0, 200.0),)), ("curveTo", ((100.0, 100.0), (250.0, 100.0), (250.0, 200.0))), ("curveTo", ((250.0, 300.0), (400.0, 300.0), (400.0, 200.0))), ("endPath", ()), ] ), ( "M100,200 C100,100 400,100 400,200", [ ("moveTo", ((100.0, 200.0),)), ("curveTo", ((100.0, 100.0), (400.0, 100.0), (400.0, 200.0))), ("endPath", ()), ] ), ( "M100,500 C25,400 475,400 400,500", [ ("moveTo", ((100.0, 500.0),)), ("curveTo", ((25.0, 400.0), (475.0, 400.0), (400.0, 500.0))), ("endPath", ()), ] ), ( "M100,800 C175,700 325,700 400,800", [ ("moveTo", ((100.0, 800.0),)), ("curveTo", ((175.0, 700.0), (325.0, 700.0), (400.0, 800.0))), ("endPath", ()), ] ), ( "M600,200 C675,100 975,100 900,200", [ ("moveTo", ((600.0, 200.0),)), ("curveTo", ((675.0, 100.0), (975.0, 100.0), (900.0, 200.0))), ("endPath", ()), ] ), ( "M600,500 C600,350 900,650 900,500", [ ("moveTo", ((600.0, 500.0),)), ("curveTo", ((600.0, 350.0), (900.0, 650.0), (900.0, 500.0))), ("endPath", ()), ] ), ( "M600,800 C625,700 725,700 750,800 S875,900 900,800", [ ("moveTo", ((600.0, 800.0),)), ("curveTo", ((625.0, 700.0), (725.0, 700.0), (750.0, 800.0))), ("curveTo", ((775.0, 900.0), (875.0, 900.0), (900.0, 800.0))), ("endPath", ()), ] ), ( "M200,300 Q400,50 600,300 T1000,300", [ ("moveTo", ((200.0, 300.0),)), ("qCurveTo", ((400.0, 50.0), (600.0, 300.0))), ("qCurveTo", ((800.0, 550.0), (1000.0, 300.0))), ("endPath", ()), ] ), # End examples from SVG spec # Relative moveto ( "M 0 0 L 50 20 m 50 80 L 300 100 L 200 300 z", [ ("moveTo", ((0.0, 0.0),)), ("lineTo", ((50.0, 20.0),)), ("endPath", ()), ("moveTo", ((100.0, 100.0),)), ("lineTo", ((300.0, 100.0),)), ("lineTo", ((200.0, 300.0),)), ("lineTo", ((100.0, 100.0),)), ("closePath", ()), ] ), # Initial smooth and relative curveTo ( "M100,200 s 150,-100 150,0", [ ("moveTo", ((100.0, 200.0),)), ("curveTo", ((100.0, 200.0), (250.0, 100.0), (250.0, 200.0))), ("endPath", ()), ] ), # Initial smooth and relative qCurveTo ( "M100,200 t 150,0", [ ("moveTo", ((100.0, 200.0),)), ("qCurveTo", ((100.0, 200.0), (250.0, 200.0))), ("endPath", ()), ] ), # relative l command ( "M 100 100 L 300 100 l -100 200 z", [ ("moveTo", ((100.0, 100.0),)), ("lineTo", ((300.0, 100.0),)), ("lineTo", ((200.0, 300.0),)), ("lineTo", ((100.0, 100.0),)), ("closePath", ()), ] ), # relative q command ( "M200,300 q200,-250 400,0", [ ("moveTo", ((200.0, 300.0),)), ("qCurveTo", ((400.0, 50.0), (600.0, 300.0))), ("endPath", ()), ] ), # absolute H command ( "M 100 100 H 300 L 200 300 z", [ ("moveTo", ((100.0, 100.0),)), ("lineTo", ((300.0, 100.0),)), ("lineTo", ((200.0, 300.0),)), ("lineTo", ((100.0, 100.0),)), ("closePath", ()), ] ), # relative h command ( "M 100 100 h 200 L 200 300 z", [ ("moveTo", ((100.0, 100.0),)), ("lineTo", ((300.0, 100.0),)), ("lineTo", ((200.0, 300.0),)), ("lineTo", ((100.0, 100.0),)), ("closePath", ()), ] ), # absolute V command ( "M 100 100 V 300 L 200 300 z", [ ("moveTo", ((100.0, 100.0),)), ("lineTo", ((100.0, 300.0),)), ("lineTo", ((200.0, 300.0),)), ("lineTo", ((100.0, 100.0),)), ("closePath", ()), ] ), # relative v command ( "M 100 100 v 200 L 200 300 z", [ ("moveTo", ((100.0, 100.0),)), ("lineTo", ((100.0, 300.0),)), ("lineTo", ((200.0, 300.0),)), ("lineTo", ((100.0, 100.0),)), ("closePath", ()), ] ), ] ) def test_parse_path(pathdef, expected): pen = RecordingPen() parse_path(pathdef, pen) assert pen.value == expected @pytest.mark.parametrize( "pathdef1, pathdef2", [ # don't need spaces between numbers and commands ( "M 100 100 L 200 200", "M100 100L200 200", ), # repeated implicit command ( "M 100 200 L 200 100 L -100 -200", "M 100 200 L 200 100 -100 -200" ), # don't need spaces before a minus-sign ( "M100,200c10-5,20-10,30-20", "M 100 200 c 10 -5 20 -10 30 -20" ), # closed paths have an implicit lineTo if they don't # end on the same point as the initial moveTo ( "M 100 100 L 300 100 L 200 300 z", "M 100 100 L 300 100 L 200 300 L 100 100 z" ) ] ) def test_equivalent_paths(pathdef1, pathdef2): pen1 = RecordingPen() parse_path(pathdef1, pen1) pen2 = RecordingPen() parse_path(pathdef2, pen2) assert pen1.value == pen2.value def test_exponents(): # It can be e or E, the plus is optional, and a minimum of +/-3.4e38 must be supported. pen = RecordingPen() parse_path("M-3.4e38 3.4E+38L-3.4E-38,3.4e-38", pen) expected = [ ("moveTo", ((-3.4e+38, 3.4e+38),)), ("lineTo", ((-3.4e-38, 3.4e-38),)), ("endPath", ()), ] assert pen.value == expected def test_invalid_implicit_command(): with pytest.raises(ValueError) as exc_info: parse_path("M 100 100 L 200 200 Z 100 200", RecordingPen()) assert exc_info.match("Unallowed implicit command") def test_arc_not_implemented(): pathdef = "M300,200 h-150 a150,150 0 1,0 150,-150 z" with pytest.raises(NotImplementedError) as exc_info: parse_path(pathdef, RecordingPen()) assert exc_info.match("arcs are not supported")