From 6864a9ea04d4550ec54540f136cc10a194f661c5 Mon Sep 17 00:00:00 2001 From: tatarize Date: Sun, 14 Jun 2020 02:42:27 -0900 Subject: [PATCH] Corrections for near collinear quads Fixes svg.path issue 61 --- setup.cfg | 2 +- svgelements/svgelements.py | 22 +++++++++++----------- test/test_angle.py | 3 ++- test/test_color.py | 1 + test/test_element.py | 1 + test/test_length.py | 1 + test/test_matrix.py | 1 + test/test_path.py | 1 + test/test_path_dunder.py | 1 + test/test_paths.py | 22 ++++++++++++++++++++++ 10 files changed, 42 insertions(+), 13 deletions(-) diff --git a/setup.cfg b/setup.cfg index b3d15026..8bc63b34 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = svgelements -version = 1.1.1 +version = 1.1.2 description = Svg Elements Parsing long_description_content_type=text/markdown long_description = file: README.md diff --git a/svgelements/svgelements.py b/svgelements/svgelements.py index 04342f3c..1393f5ed 100644 --- a/svgelements/svgelements.py +++ b/svgelements/svgelements.py @@ -3308,17 +3308,7 @@ def length(self, error=None, min_depth=None): """Calculate the length of the path up to a certain position""" a = self.start - 2 * self.control + self.end b = 2 * (self.control - self.start) - a_dot_b = a.real * b.real + a.imag * b.imag - - if abs(a) < 1e-12: - s = abs(b) - elif abs(a_dot_b + abs(a) * abs(b)) < 1e-12: - k = abs(b) / abs(a) - if k >= 2: - s = abs(b) - abs(a) - else: - s = abs(a) * (k ** 2 / 2 - k + 1) - else: + try: # For an explanation of this case, see # http://www.malczak.info/blog/quadratic-bezier-curve-length/ A = 4 * (a.real ** 2 + a.imag ** 2) @@ -3333,6 +3323,16 @@ def length(self, error=None, min_depth=None): s = (A32 * Sabc + A2 * B * (Sabc - C2) + (4 * C * A - B ** 2) * log((2 * A2 + BA + Sabc) / (BA + C2))) / (4 * A32) + except (ZeroDivisionError, ValueError): + # a_dot_b = a.real * b.real + a.imag * b.imag + if abs(a) < 1e-10: + s = abs(b) + else: + k = abs(b) / abs(a) + if k >= 2: + s = abs(b) - abs(a) + else: + s = abs(a) * (k ** 2 / 2 - k + 1) return s def is_smooth_from(self, previous): diff --git a/test/test_angle.py b/test/test_angle.py index 6cbe5414..448249d7 100644 --- a/test/test_angle.py +++ b/test/test_angle.py @@ -6,6 +6,7 @@ class TestElementAngle(unittest.TestCase): + """These tests ensure the basic functions of the Angle element.""" def test_angle_init(self): self.assertEqual(Angle.degrees(90).as_turns, 0.25) @@ -40,4 +41,4 @@ def test_orth(self): self.assertFalse(Angle.degrees(91).is_orthogonal()) self.assertFalse(Angle.degrees(181).is_orthogonal()) self.assertFalse(Angle.degrees(271).is_orthogonal()) - self.assertFalse(Angle.degrees(361).is_orthogonal()) \ No newline at end of file + self.assertFalse(Angle.degrees(361).is_orthogonal()) diff --git a/test/test_color.py b/test/test_color.py index 2a1dbc7c..12b36857 100644 --- a/test/test_color.py +++ b/test/test_color.py @@ -6,6 +6,7 @@ class TestElementColor(unittest.TestCase): + """These tests test the basic functions of the Color element.""" def test_color_red(self): r0 = Color('red') diff --git a/test/test_element.py b/test/test_element.py index be160e5a..330bea79 100644 --- a/test/test_element.py +++ b/test/test_element.py @@ -6,6 +6,7 @@ class TestElementElement(unittest.TestCase): + """These tests ensure the performance of the SVGElement basecase.""" def test_element_id(self): values = {'id': 'my_id', 'random': True} diff --git a/test/test_length.py b/test/test_length.py index 3ccc90e8..e0d47463 100644 --- a/test/test_length.py +++ b/test/test_length.py @@ -6,6 +6,7 @@ class TestElementLength(unittest.TestCase): + """Tests the functionality of the Length Element.""" def test_length_parsing(self): self.assertAlmostEqual(Length('10cm'), (Length('100mm'))) diff --git a/test/test_matrix.py b/test/test_matrix.py index 441574f0..4978107f 100644 --- a/test/test_matrix.py +++ b/test/test_matrix.py @@ -6,6 +6,7 @@ class TestPathMatrix(unittest.TestCase): + """Tests the functionality of the Matrix element.""" def test_rotate_css_angles(self): matrix = Matrix("rotate(90, 100,100)") diff --git a/test/test_path.py b/test/test_path.py index d3e33d42..8ca93937 100644 --- a/test/test_path.py +++ b/test/test_path.py @@ -6,6 +6,7 @@ class TestPath(unittest.TestCase): + """Tests of the SVG Path element.""" def test_subpaths(self): path = Path("M0,0 50,50 100,100z M0,100 50,50, 100,0") diff --git a/test/test_path_dunder.py b/test/test_path_dunder.py index 36b6abd1..936792c0 100644 --- a/test/test_path_dunder.py +++ b/test/test_path_dunder.py @@ -6,6 +6,7 @@ class TestPath(unittest.TestCase): + """Tests of dunder methods of the SVG Path element.""" def test_path_iadd_str(self): p1 = Path("M0,0") diff --git a/test/test_paths.py b/test/test_paths.py index 4df3d574..19b9a2fc 100644 --- a/test/test_paths.py +++ b/test/test_paths.py @@ -34,6 +34,7 @@ # these points religiously. They might be subtly wrong, unless otherwise # noted. + class LineTest(unittest.TestCase): def test_lines(self): @@ -291,6 +292,9 @@ def test_equality(self): CubicBezier(600 + 501j, 600 + 350j, 900 + 650j, 900 + 500j)) self.assertTrue(segment != Line(0, 400)) + def test_colinear(self): + p = Path("M0,0C5,0 15,0 15,0") + self.assertAlmostEqual(p.length(), 15) class QuadraticBezierTest(unittest.TestCase): @@ -340,6 +344,24 @@ def test_equality(self): self.assertFalse(segment == Arc(0j, 100 + 50j, 0, 0, 0, 100 + 50j)) self.assertTrue(Arc(0j, 100 + 50j, 0, 0, 0, 100 + 50j) != segment) + def test_issue_61(self): + p = Path('M 206.5,525 Q 162.5,583 162.5,583') + self.assertAlmostEqual(p.length(), 72.80109889280519) + p = Path('M 425.781 446.289 Q 410.40000000000003 373.047 410.4 373.047') + self.assertAlmostEqual(p.length(), 74.83959997888816) + p = Path('M 639.648 568.115 Q 606.6890000000001 507.568 606.689 507.568') + self.assertAlmostEqual(p.length(), 68.93645544992873) + p = Path('M 288.818 616.699 Q 301.025 547.3629999999999 301.025 547.363') + self.assertAlmostEqual(p.length(), 70.40235610403947) + p = Path('M 339.927 706.25 Q 243.92700000000002 806.25 243.927 806.25') + self.assertAlmostEqual(p.length(), 138.6217876093077) + p = Path('M 539.795 702.637 Q 548.0959999999999 803.4669999999999 548.096 803.467') + self.assertAlmostEqual(p.length(), 101.17111989594662) + p = Path('M 537.815 555.042 Q 570.1680000000001 499.1600000000001 570.168 499.16') + self.assertAlmostEqual(p.length(), 64.57177814649368) + p = Path('M 615.297 470.503 Q 538.797 694.5029999999999 538.797 694.503') + self.assertAlmostEqual(p.length(), 236.70287281737836) + class ArcTest(unittest.TestCase):