Repository: meerk40t/svgelements

Issue:
Control Point Arc with Colinear Points Crashes in unhelpful way.
An Arc with Colinear control points should be a line segment, this should crash with a better error than divide by zero.

Discussion:

Patch:
diff --git a/setup.cfg b/setup.cfg
index bf387e8..7b05ac3 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
 [metadata]
 name = svgelements
-version = 1.6.15
+version = 1.6.16
 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 c919e9a..feb2d26 100644
--- a/svgelements/svgelements.py
+++ b/svgelements/svgelements.py
@@ -43,7 +43,7 @@ Though not required the Image class acquires new functionality if provided with
 and the Arc can do exact arc calculations if scipy is installed.
 """
 
-SVGELEMENTS_VERSION = "1.6.15"
+SVGELEMENTS_VERSION = "1.6.16"
 
 MIN_DEPTH = 5
 ERROR = 1e-12
@@ -220,6 +220,7 @@ REGEX_COLOR_HSL = re.compile(
     % (PATTERN_FLOAT, PATTERN_FLOAT, PATTERN_FLOAT, PATTERN_FLOAT)
 )
 REGEX_LENGTH = re.compile(r"(%s)([A-Za-z%%]*)" % PATTERN_FLOAT)
+REGEX_CSS_COMMENT = re.compile(r"\/\*[\s\S]*?\*\/|\/\/.*$", re.MULTILINE)
 REGEX_CSS_STYLE = re.compile(r"([^{]+)\s*\{\s*([^}]+)\s*\}")
 REGEX_CSS_FONT = re.compile(
     r"(?:(normal|italic|oblique)\s|(normal|small-caps)\s|(normal|bold|bolder|lighter|\d{3})\s|(normal|ultra-condensed|extra-condensed|condensed|semi-condensed|semi-expanded|expanded|extra-expanded|ultra-expanded)\s)*\s*(xx-small|x-small|small|medium|large|x-large|xx-large|larger|smaller|\d+(?:em|pt|pc|px|%))(?:/(xx-small|x-small|small|medium|large|x-large|xx-large|larger|smaller|\d+(?:em|pt|pc|px|%)))?\s*(.*),?\s+(serif|sans-serif|cursive|fantasy|monospace);?"
@@ -4771,44 +4772,52 @@ class Arc(Curve):
             if control is not None:
                 delta_a = control - self.start
                 delta_b = self.end - control
-                if abs(delta_a.x) > 1e-12:
-                    slope_a = delta_a.y / delta_a.x
-                else:
-                    slope_a = float("inf")
-                if abs(delta_b.x) > 1e-12:
-                    slope_b = delta_b.y / delta_b.x
-                else:
-                    slope_b = float("inf")
                 ab_mid = Point.towards(self.start, control, 0.5)
                 bc_mid = Point.towards(control, self.end, 0.5)
-                if abs(delta_a.y) < 1e-12:  # slope_a == 0
+                if self.start == self.end:
                     cx = ab_mid.x
-                    if abs(delta_b.x) < 1e-12:  # slope_b == inf
-                        cy = bc_mid.y
+                    cy = ab_mid.y
+                    self.sweep = tau
+                else:
+                    if abs(delta_a.x) > 1e-12:
+                        slope_a = delta_a.y / delta_a.x
+                    else:
+                        slope_a = float("inf")
+                    if abs(delta_b.x) > 1e-12:
+                        slope_b = delta_b.y / delta_b.x
                     else:
-                        cy = bc_mid.y + (bc_mid.x - cx) / slope_b
-                elif abs(delta_b.y) < 1e-12:  # slope_b == 0
-                    cx = bc_mid.x
-                    if abs(delta_a.y) < 1e-12:  # slope_a == inf
+                        slope_b = float("inf")
+                    if abs(delta_a.y) < 1e-12:  # slope_a == 0
+                        cx = ab_mid.x
+                        if abs(delta_b.x) < 1e-12:  # slope_b == inf
+                            cy = bc_mid.y
+                        else:
+                            if abs(slope_b) > 1e-12:
+                                cy = bc_mid.y + (bc_mid.x - cx) / slope_b
+                            else:
+                                cy = float("inf")
+                    elif abs(delta_b.y) < 1e-12:  # slope_b == 0
+                        cx = bc_mid.x
+                        if abs(delta_a.y) < 1e-12:  # slope_a == inf
+                            cy = ab_mid.y
+                        else:
+                            cy = ab_mid.y + (ab_mid.x - cx) / slope_a
+                    elif abs(delta_a.x) < 1e-12:  # slope_a == inf
+                        cy = ab_mid.y
+                        cx = slope_b * (bc_mid.y - cy) + bc_mid.x
+                    elif abs(delta_b.x) < 1e-12:  # slope_b == inf
+                        cy = bc_mid.y
+                        cx = slope_a * (ab_mid.y - cy) + ab_mid.x
+                    elif abs(slope_a - slope_b) < 1e-12:
+                        cx = ab_mid.x
                         cy = ab_mid.y
                     else:
-                        cy = ab_mid.y + (ab_mid.x - cx) / slope_a
-                elif abs(delta_a.x) < 1e-12:  # slope_a == inf
-                    cy = ab_mid.y
-                    cx = slope_b * (bc_mid.y - cy) + bc_mid.x
-                elif abs(delta_b.x) < 1e-12:  # slope_b == inf
-                    cy = bc_mid.y
-                    cx = slope_a * (ab_mid.y - cy) + ab_mid.x
-                elif abs(slope_a - slope_b) < 1e-12:
-                    cx = ab_mid.x
-                    cy = ab_mid.y
-                else:
-                    cx = (
-                        slope_a * slope_b * (ab_mid.y - bc_mid.y)
-                        - slope_a * bc_mid.x
-                        + slope_b * ab_mid.x
-                    ) / (slope_b - slope_a)
-                    cy = ab_mid.y - (cx - ab_mid.x) / slope_a
+                        cx = (
+                                     slope_a * slope_b * (ab_mid.y - bc_mid.y)
+                                     - slope_a * bc_mid.x
+                                     + slope_b * ab_mid.x
+                             ) / (slope_b - slope_a)
+                        cy = ab_mid.y - (cx - ab_mid.x) / slope_a
                 self.center = Point(cx, cy)
                 cw = bool(Point.orientation(self.start, control, self.end) == 2)
             elif "r" in kwargs:
@@ -8834,7 +8843,9 @@ class SVG(Group):
                     s = Title(values, title=elem.text)
                     context.append(s)
                 elif SVG_TAG_STYLE == tag:
-                    assignments = list(re.findall(REGEX_CSS_STYLE, elem.text))
+                    textstyle = elem.text
+                    textstyle = re.sub(REGEX_CSS_COMMENT, '', textstyle)
+                    assignments = list(re.findall(REGEX_CSS_STYLE, textstyle.strip()))
                     for key, value in assignments:
                         key = key.strip()
                         value = value.strip()


Test patch:
diff --git a/test/test_css.py b/test/test_css.py
index b16939f..86442bc 100644
--- a/test/test_css.py
+++ b/test/test_css.py
@@ -36,3 +36,38 @@ class TestSVGCSS(unittest.TestCase):
 
         self.assertEqual(circ2.fill, "none")
         self.assertEqual(circ2.stroke, "red")
+
+    def test_issue_178(self):
+        """Testing Issue 178 css comment parsing
+        """
+
+        q = io.StringIO(u'''<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80">
+        <defs>
+        <style>
+        //this is a comment.
+        .cls-1,.cls-2{fill:none;stroke-miterlimit:10;}.cls-1{stroke:blue;}.cls-2{stroke:red;}
+        //.cls-2{stroke:pink;}
+        /* Testing this should be functional */
+        </style>
+        </defs>
+        <g id="Layer_2" data-name="Layer 2">
+        <g id="Layer_1-2" data-name="Layer 1">
+        <polygon points="56.59 67.4 39.86 57.28 23.01 67.22 26.34 45.99 12.83 30.88 31.62 27.88 40.12 8.6 48.41 27.97 67.17 31.17 53.5 46.14 56.59 67.4"/>
+        <circle class="cls-1" cx="40" cy="40" r="35"/>
+        <circle class="cls-2" cx="40" cy="40" r="39.5"/>
+        </g>
+        </g>
+        </svg>''')
+        m = SVG.parse(q)
+        poly = m[0][0][0]
+        circ1 = m[0][0][1]
+        circ2 = m[0][0][2]
+
+        self.assertEqual(poly.fill, "black")
+        self.assertEqual(poly.stroke, "none")
+
+        self.assertEqual(circ1.fill, "none")
+        self.assertEqual(circ1.stroke, "blue")
+
+        self.assertEqual(circ2.fill, "none")
+        self.assertEqual(circ2.stroke, "red")
diff --git a/test/test_path_segments.py b/test/test_path_segments.py
index 5af098d..8656fdc 100644
--- a/test/test_path_segments.py
+++ b/test/test_path_segments.py
@@ -40,3 +40,21 @@ class TestBoundingBox(unittest.TestCase):
     def test_null_arc_bbox(self):
         self.assertEqual(Path("M0,0A0,0 0 0 0 0,0z").bbox(), (0,0,0,0))
 
+
+class TestArcControlPoints(unittest.TestCase):
+
+    def test_coincident_end_arc(self):
+        """
+        Tests the creation of a control point with a coincident start and end.
+        """
+        arc = Arc(start=(0,0), control=(50,0), end=(0,0))
+        self.assertAlmostEqual(arc.rx, 25)
+
+    def test_linear_arc(self):
+        """
+        Colinear Arcs should raise value errors.
+        """
+        arc_vertical = Arc(start=(0, 0), control=(25, 0), end=(50, 0))
+        # print(arc_vertical)
+        arc_horizontal = Arc(start=(0, 0), control=(0, 25), end=(0, 50))
+        # print(arc_horizontal)