From 6abda09d1c94c2e449d5b35fe750a3b4ba5df97d Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 5 May 2023 00:18:36 -0600 Subject: [PATCH 1/2] add a test loading arcs with negative scale This test currently fails, fix in the following commit. The test loads an svg with a group with a transform with "scale(1,-1)". This situation can mess up arc sweeps, resulting in corrupted paths. --- test/negative-scale.svg | 19 +++++++++++++++++++ test/test_groups.py | 22 +++++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 test/negative-scale.svg diff --git a/test/negative-scale.svg b/test/negative-scale.svg new file mode 100644 index 0000000..108f2ee --- /dev/null +++ b/test/negative-scale.svg @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/test/test_groups.py b/test/test_groups.py index 7544433..8fdbf1d 100644 --- a/test/test_groups.py +++ b/test/test_groups.py @@ -5,11 +5,15 @@ $ python -m unittest test.test_groups.TestGroups.test_group_flatten """ from __future__ import division, absolute_import, print_function import unittest -from svgpathtools import Document, SVG_NAMESPACE, parse_path +from svgpathtools import Document, SVG_NAMESPACE, parse_path, Line, Arc from os.path import join, dirname import numpy as np +# When an assert fails, show the full error message, don't truncate it. +unittest.util._MAX_LENGTH = 999999999 + + def get_desired_path(name, paths): return next(p for p in paths if p.element.get('{some://testuri}name') == name) @@ -42,6 +46,22 @@ class TestGroups(unittest.TestCase): self.check_values(tf.dot(v_s), actual.start) self.check_values(tf.dot(v_e), actual.end) + def test_group_transform(self): + # The input svg has a group transform of "scale(1,-1)", which + # can mess with Arc sweeps. + doc = Document(join(dirname(__file__), 'negative-scale.svg')) + path = doc.paths()[0] + self.assertEqual(path[0], Line(start=-10j, end=-80j)) + self.assertEqual(path[1], Arc(start=-80j, radius=(30+30j), rotation=0.0, large_arc=True, sweep=True, end=-140j)) + self.assertEqual(path[2], Arc(start=-140j, radius=(20+20j), rotation=0.0, large_arc=False, sweep=False, end=-100j)) + self.assertEqual(path[3], Line(start=-100j, end=(100-100j))) + self.assertEqual(path[4], Arc(start=(100-100j), radius=(20+20j), rotation=0.0, large_arc=True, sweep=False, end=(100-140j))) + self.assertEqual(path[5], Arc(start=(100-140j), radius=(30+30j), rotation=0.0, large_arc=False, sweep=True, end=(100-80j))) + self.assertEqual(path[6], Line(start=(100-80j), end=(100-10j))) + self.assertEqual(path[7], Arc(start=(100-10j), radius=(10+10j), rotation=0.0, large_arc=False, sweep=True, end=(90+0j))) + self.assertEqual(path[8], Line(start=(90+0j), end=(10+0j))) + self.assertEqual(path[9], Arc(start=(10+0j), radius=(10+10j), rotation=0.0, large_arc=False, sweep=True, end=-10j)) + def test_group_flatten(self): # Test the Document.paths() function against the # groups.svg test file. From e94483510ec3ff63f4e941657109ee32844c038d Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 5 May 2023 00:35:22 -0600 Subject: [PATCH 2/2] path.transform: Arc sweep is reversed by negative scale When transforming an Arc, negative scale reverses the sweep. --- svgpathtools/path.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/svgpathtools/path.py b/svgpathtools/path.py index 5903efe..b65d11b 100644 --- a/svgpathtools/path.py +++ b/svgpathtools/path.py @@ -338,9 +338,13 @@ def transform(curve, tf): if new_radius.real == 0 or new_radius.imag == 0 : return Line(new_start, new_end) - else : + else: + if tf[0][0] * tf[1][1] >= 0.0: + new_sweep = curve.sweep + else: + new_sweep = not curve.sweep return Arc(new_start, radius=new_radius, rotation=curve.rotation + rot, - large_arc=curve.large_arc, sweep=curve.sweep, end=new_end, + large_arc=curve.large_arc, sweep=new_sweep, end=new_end, autoscale_radius=True) else: