| 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_to_cubic_bezier(): |
| pen = RecordingPen() |
| parse_path("M300,200 h-150 a150,150 0 1,0 150,-150 z", pen) |
| expected = [ |
| ('moveTo', ((300.0, 200.0),)), |
| ('lineTo', ((150.0, 200.0),)), |
| ( |
| 'curveTo', |
| ( |
| (150.0, 282.842), |
| (217.157, 350.0), |
| (300.0, 350.0) |
| ) |
| ), |
| ( |
| 'curveTo', |
| ( |
| (382.842, 350.0), |
| (450.0, 282.842), |
| (450.0, 200.0) |
| ) |
| ), |
| ( |
| 'curveTo', |
| ( |
| (450.0, 117.157), |
| (382.842, 50.0), |
| (300.0, 50.0) |
| ) |
| ), |
| ('lineTo', ((300.0, 200.0),)), |
| ('closePath', ()) |
| ] |
| |
| result = list(pen.value) |
| assert len(result) == len(expected) |
| for (cmd1, points1), (cmd2, points2) in zip(result, expected): |
| assert cmd1 == cmd2 |
| assert len(points1) == len(points2) |
| for pt1, pt2 in zip(points1, points2): |
| assert pt1 == pytest.approx(pt2, rel=1e-5) |
| |
| |
| |
| class ArcRecordingPen(RecordingPen): |
| |
| def arcTo(self, rx, ry, rotation, arc_large, arc_sweep, end_point): |
| self.value.append( |
| ("arcTo", (rx, ry, rotation, arc_large, arc_sweep, end_point)) |
| ) |
| |
| |
| def test_arc_pen_with_arcTo(): |
| pen = ArcRecordingPen() |
| parse_path("M300,200 h-150 a150,150 0 1,0 150,-150 z", pen) |
| expected = [ |
| ('moveTo', ((300.0, 200.0),)), |
| ('lineTo', ((150.0, 200.0),)), |
| ('arcTo', (150.0, 150.0, 0.0, True, False, (300.0, 50.0))), |
| ('lineTo', ((300.0, 200.0),)), |
| ('closePath', ()) |
| ] |
| |
| assert pen.value == expected |