Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-svgpathtools for 
openSUSE:Factory checked in at 2025-06-10 09:08:16
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-svgpathtools (Old)
 and      /work/SRC/openSUSE:Factory/.python-svgpathtools.new.19631 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-svgpathtools"

Tue Jun 10 09:08:16 2025 rev:9 rq:1284231 version:1.7.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-svgpathtools/python-svgpathtools.changes  
2025-05-09 18:54:02.727785868 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-svgpathtools.new.19631/python-svgpathtools.changes
       2025-06-10 09:10:31.284422460 +0200
@@ -1,0 +2,14 @@
+Mon Jun  9 13:55:49 UTC 2025 - Mia Herkt <m...@0x0.st>
+
+- Switch to pip-based build
+- Update to 1.7.1
+Fixes:
+  * Rotation angle calculation when transforming arcs
+  * Converting rounded rect to a d-string path
+  * Floating point error in bezier bbox calculation
+  * Skip end path in polyline if no dedicated end path is provided
+Changes:
+  * When converting ellipses to d-string paths, use arcs by default
+  * Add various new test cases
+
+-------------------------------------------------------------------

Old:
----
  svgpathtools-1.7.0.tar.gz

New:
----
  svgpathtools-1.7.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-svgpathtools.spec ++++++
--- /var/tmp/diff_new_pack.iV6W2s/_old  2025-06-10 09:10:31.940449588 +0200
+++ /var/tmp/diff_new_pack.iV6W2s/_new  2025-06-10 09:10:31.940449588 +0200
@@ -18,13 +18,14 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-svgpathtools
-Version:        1.7.0
+Version:        1.7.1
 Release:        0
 Summary:        Tools for manipulating and analyzing SVG Path objects and 
Bézier curves
 License:        MIT
 URL:            https://github.com/mathandy/svgpathtools
 Source:         
https://files.pythonhosted.org/packages/source/s/svgpathtools/svgpathtools-%{version}.tar.gz
 BuildRequires:  %{python_module numpy}
+BuildRequires:  %{python_module pip}
 BuildRequires:  %{python_module pytest}
 BuildRequires:  %{python_module scipy}
 BuildRequires:  %{python_module setuptools}
@@ -45,10 +46,10 @@
 %setup -q -n svgpathtools-%{version}
 
 %build
-%python_build
+%pyproject_wheel
 
 %install
-%python_install
+%pyproject_install
 %python_expand %fdupes %{buildroot}%{$python_sitelib}
 
 %check

++++++ svgpathtools-1.7.0.tar.gz -> svgpathtools-1.7.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/svgpathtools-1.7.0/PKG-INFO 
new/svgpathtools-1.7.1/PKG-INFO
--- old/svgpathtools-1.7.0/PKG-INFO     2025-05-08 02:17:50.233075600 +0200
+++ new/svgpathtools-1.7.1/PKG-INFO     2025-05-28 01:31:28.195323500 +0200
@@ -1,9 +1,9 @@
 Metadata-Version: 2.1
 Name: svgpathtools
-Version: 1.7.0
+Version: 1.7.1
 Summary: A collection of tools for manipulating and analyzing SVG Path objects 
and Bezier curves.
 Home-page: https://github.com/mathandy/svgpathtools
-Download-URL: 
https://github.com/mathandy/svgpathtools/releases/download/1.7.0/svgpathtools-1.7.0-py3-none-any.whl
+Download-URL: 
https://github.com/mathandy/svgpathtools/releases/download/1.7.1/svgpathtools-1.7.1-py3-none-any.whl
 Author: Andy Port
 Author-email: andyap...@gmail.com
 License: MIT
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/svgpathtools-1.7.0/setup.py 
new/svgpathtools-1.7.1/setup.py
--- old/svgpathtools-1.7.0/setup.py     2025-05-08 02:17:39.000000000 +0200
+++ new/svgpathtools-1.7.1/setup.py     2025-05-28 01:31:17.000000000 +0200
@@ -3,7 +3,7 @@
 import os
 
 
-VERSION = '1.7.0'
+VERSION = '1.7.1'
 AUTHOR_NAME = 'Andy Port'
 AUTHOR_EMAIL = 'andyap...@gmail.com'
 GITHUB = 'https://github.com/mathandy/svgpathtools'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/svgpathtools-1.7.0/svgpathtools/bezier.py 
new/svgpathtools-1.7.1/svgpathtools/bezier.py
--- old/svgpathtools-1.7.0/svgpathtools/bezier.py       2025-05-08 
02:17:39.000000000 +0200
+++ new/svgpathtools-1.7.1/svgpathtools/bezier.py       2025-05-28 
01:31:17.000000000 +0200
@@ -10,6 +10,7 @@
 
 # Internal dependencies
 from .polytools import real, imag, polyroots, polyroots01
+from .constants import FLOAT_EPSILON
 
 
 # Evaluation ##################################################################
@@ -171,7 +172,7 @@
     if len(p) == 4:  # cubic case
         a = [p.real for p in p]
         denom = a[0] - 3*a[1] + 3*a[2] - a[3]
-        if denom != 0:
+        if abs(denom) > FLOAT_EPSILON:  # check that denom != 0 accounting for 
floating point error
             delta = a[1]**2 - (a[0] + a[1])*a[2] + a[2]**2 + (a[0] - a[1])*a[3]
             if delta >= 0:  # otherwise no local extrema
                 sqdelta = sqrt(delta)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/svgpathtools-1.7.0/svgpathtools/constants.py 
new/svgpathtools-1.7.1/svgpathtools/constants.py
--- old/svgpathtools-1.7.0/svgpathtools/constants.py    1970-01-01 
01:00:00.000000000 +0100
+++ new/svgpathtools-1.7.1/svgpathtools/constants.py    2025-05-28 
01:31:17.000000000 +0200
@@ -0,0 +1,3 @@
+"""This submodule contains constants used throughout the project."""
+
+FLOAT_EPSILON = 1e-12
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/svgpathtools-1.7.0/svgpathtools/path.py 
new/svgpathtools-1.7.1/svgpathtools/path.py
--- old/svgpathtools-1.7.0/svgpathtools/path.py 2025-05-08 02:17:39.000000000 
+0200
+++ new/svgpathtools-1.7.1/svgpathtools/path.py 2025-05-28 01:31:17.000000000 
+0200
@@ -335,7 +335,7 @@
         new_radius = complex(rx, ry)
 
         xeigvec = eigvecs[:, 0]
-        rot = np.degrees(np.arccos(xeigvec[0]))
+        rot = np.degrees(np.arctan2(xeigvec[1], xeigvec[0]))
 
         if new_radius.real == 0 or new_radius.imag == 0 :
             return Line(new_start, new_end)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/svgpathtools-1.7.0/svgpathtools/svg_to_paths.py 
new/svgpathtools-1.7.1/svgpathtools/svg_to_paths.py
--- old/svgpathtools-1.7.0/svgpathtools/svg_to_paths.py 2025-05-08 
02:17:39.000000000 +0200
+++ new/svgpathtools-1.7.1/svgpathtools/svg_to_paths.py 2025-05-28 
01:31:17.000000000 +0200
@@ -27,7 +27,7 @@
     return path.get('d', '')
 
 
-def ellipse2pathd(ellipse):
+def ellipse2pathd(ellipse, use_cubics=False):
     """converts the parameters from an ellipse or a circle to a string for a 
     Path object d-attribute"""
 
@@ -46,10 +46,32 @@
     cx = float(cx)
     cy = float(cy)
 
-    d = ''
-    d += 'M' + str(cx - rx) + ',' + str(cy)
-    d += 'a' + str(rx) + ',' + str(ry) + ' 0 1,0 ' + str(2 * rx) + ',0'
-    d += 'a' + str(rx) + ',' + str(ry) + ' 0 1,0 ' + str(-2 * rx) + ',0'
+    if use_cubics:
+        # Modified by NXP 2024, 2025
+        PATH_KAPPA = 0.552284
+        rxKappa = rx * PATH_KAPPA;
+        ryKappa = ry * PATH_KAPPA;
+
+        #According to the SVG specification 
(https://lists.w3.org/Archives/Public/www-archive/2005May/att-0005/SVGT12_Main.pdf),
+        #Section 9.4, "The 'ellipse' element": "The arc of an 'ellipse' 
element begins at the "3 o'clock" point on
+        #the radius and progresses towards the "9 o'clock". Therefore, the 
ellipse begins at the rightmost point
+        #and progresses clockwise.
+        d = ''
+        # Move to the rightmost point
+        d += 'M' + str(cx + rx) + ' ' + str(cy)
+        # Draw bottom-right quadrant
+        d += 'C' + str(cx + rx) + ' ' + str(cy + ryKappa) + ' ' + str(cx + 
rxKappa) + ' ' + str(cy + ry) + ' ' + str(cx) + ' ' + str(cy + ry)
+        # Draw bottom-left quadrant
+        d += 'C' + str(cx - rxKappa) + ' ' + str(cy + ry) + ' ' + str(cx - rx) 
+ ' ' + str(cy + ryKappa) + ' ' + str(cx - rx) + ' ' + str(cy)
+        # Draw top-left quadrant
+        d += 'C' + str(cx - rx) + ' ' + str(cy - ryKappa) + ' ' + str(cx - 
rxKappa) + ' ' + str(cy - ry) + ' ' + str(cx) + ' ' + str(cy - ry)
+        # Draw top-right quadrant
+        d += 'C' + str(cx + rxKappa) + ' ' + str(cy - ry) + ' ' + str(cx + rx) 
+ ' ' + str(cy - ryKappa) + ' ' + str(cx + rx) + ' ' + str(cy)
+    else:
+        d = ''
+        d += 'M' + str(cx - rx) + ',' + str(cy)
+        d += 'a' + str(rx) + ',' + str(ry) + ' 0 1,0 ' + str(2 * rx) + ',0'
+        d += 'a' + str(rx) + ',' + str(ry) + ' 0 1,0 ' + str(-2 * rx) + ',0'
 
     return d + 'z'
 
@@ -62,6 +84,9 @@
     else:
         points = COORD_PAIR_TMPLT.findall(polyline.get('points', ''))
 
+    if len(points) == 0:
+        return ''
+
     closed = (float(points[0][0]) == float(points[-1][0]) and
               float(points[0][1]) == float(points[-1][1]))
 
@@ -77,13 +102,13 @@
     return d
 
 
-def polygon2pathd(polyline):
+def polygon2pathd(polyline, is_polygon=True):
     """converts the string from a polygon points-attribute to a string 
     for a Path object d-attribute.
     Note:  For a polygon made from n points, the resulting path will be
     composed of n lines (even if some of these lines have length zero).
     """
-    return polyline2pathd(polyline, True)
+    return polyline2pathd(polyline, is_polygon)
 
 
 def rect2pathd(rect):
@@ -93,7 +118,8 @@
     rectangle object and proceed counter-clockwise."""
     x, y = float(rect.get('x', 0)), float(rect.get('y', 0))
     w, h = float(rect.get('width', 0)), float(rect.get('height', 0))
-    if 'rx' in rect or 'ry' in rect:
+
+    if 'rx' in rect.keys() or 'ry' in rect.keys():
 
         # if only one, rx or ry, is present, use that value for both
         # https://developer.mozilla.org/en-US/docs/Web/SVG/Element/rect
@@ -204,7 +230,7 @@
     # path strings, add to list
     if convert_polygons_to_paths:
         pgons = [dom2dict(el) for el in doc.getElementsByTagName('polygon')]
-        d_strings += [polygon2pathd(pg) for pg in pgons]
+        d_strings += [polygon2pathd(pg, True) for pg in pgons]
         attribute_dictionary_list += pgons
 
     if convert_lines_to_paths:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/svgpathtools-1.7.0/svgpathtools.egg-info/PKG-INFO 
new/svgpathtools-1.7.1/svgpathtools.egg-info/PKG-INFO
--- old/svgpathtools-1.7.0/svgpathtools.egg-info/PKG-INFO       2025-05-08 
02:17:50.000000000 +0200
+++ new/svgpathtools-1.7.1/svgpathtools.egg-info/PKG-INFO       2025-05-28 
01:31:28.000000000 +0200
@@ -1,9 +1,9 @@
 Metadata-Version: 2.1
 Name: svgpathtools
-Version: 1.7.0
+Version: 1.7.1
 Summary: A collection of tools for manipulating and analyzing SVG Path objects 
and Bezier curves.
 Home-page: https://github.com/mathandy/svgpathtools
-Download-URL: 
https://github.com/mathandy/svgpathtools/releases/download/1.7.0/svgpathtools-1.7.0-py3-none-any.whl
+Download-URL: 
https://github.com/mathandy/svgpathtools/releases/download/1.7.1/svgpathtools-1.7.1-py3-none-any.whl
 Author: Andy Port
 Author-email: andyap...@gmail.com
 License: MIT
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/svgpathtools-1.7.0/svgpathtools.egg-info/SOURCES.txt 
new/svgpathtools-1.7.1/svgpathtools.egg-info/SOURCES.txt
--- old/svgpathtools-1.7.0/svgpathtools.egg-info/SOURCES.txt    2025-05-08 
02:17:50.000000000 +0200
+++ new/svgpathtools-1.7.1/svgpathtools.egg-info/SOURCES.txt    2025-05-28 
01:31:28.000000000 +0200
@@ -15,6 +15,7 @@
 vectorframes.svg
 svgpathtools/__init__.py
 svgpathtools/bezier.py
+svgpathtools/constants.py
 svgpathtools/document.py
 svgpathtools/misctools.py
 svgpathtools/parser.py
@@ -35,6 +36,8 @@
 test/groups.svg
 test/negative-scale.svg
 test/polygons.svg
+test/polygons_no_points.svg
+test/polyline.svg
 test/rects.svg
 test/test.svg
 test/test_bezier.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/svgpathtools-1.7.0/test/polygons_no_points.svg 
new/svgpathtools-1.7.1/test/polygons_no_points.svg
--- old/svgpathtools-1.7.0/test/polygons_no_points.svg  1970-01-01 
01:00:00.000000000 +0100
+++ new/svgpathtools-1.7.1/test/polygons_no_points.svg  2025-05-28 
01:31:17.000000000 +0200
@@ -0,0 +1,5 @@
+<?xml version="1.0" ?>
+<svg baseProfile="full" height="600px" version="1.1" viewBox="-10.05 -10.05 
120.1 120.1" width="600px" xmlns="http://www.w3.org/2000/svg"; 
xmlns:ev="http://www.w3.org/2001/xml-events"; 
xmlns:xlink="http://www.w3.org/1999/xlink";>
+  <polygon xml:id="polygon-01" fill="red" stroke="black" />
+  <polygon xml:id="polygon-02" fill="red" stroke="black" points="" />
+</svg>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/svgpathtools-1.7.0/test/polyline.svg 
new/svgpathtools-1.7.1/test/polyline.svg
--- old/svgpathtools-1.7.0/test/polyline.svg    1970-01-01 01:00:00.000000000 
+0100
+++ new/svgpathtools-1.7.1/test/polyline.svg    2025-05-28 01:31:17.000000000 
+0200
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg version="1.2" baseProfile="tiny" xml:id="svg-root" width="100%" 
height="100%"
+  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg";
+  xmlns:xlink="http://www.w3.org/1999/xlink"; 
xmlns:xe="http://www.w3.org/2001/xml-events";>
+    <polyline xml:id="polyline-04" fill="none" stroke="red" stroke-width="8" 
points="59,185,98,203,108,245,82,279,39,280,11,247,19,205" />
+    <polyline xml:id="polyline-02" fill="none" stroke="blue" stroke-width="8" 
points="220,50,267,84,249,140,190,140,172,84,220,50" />
+</svg>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/svgpathtools-1.7.0/test/test_bezier.py 
new/svgpathtools-1.7.1/test/test_bezier.py
--- old/svgpathtools-1.7.0/test/test_bezier.py  2025-05-08 02:17:39.000000000 
+0200
+++ new/svgpathtools-1.7.1/test/test_bezier.py  2025-05-28 01:31:17.000000000 
+0200
@@ -1,8 +1,8 @@
 from __future__ import division, absolute_import, print_function
 import numpy as np
 import unittest
-from svgpathtools.bezier import bezier_point, bezier2polynomial, 
polynomial2bezier
-from svgpathtools.path import bpoints2bezier
+from svgpathtools.bezier import bezier_point, bezier2polynomial, 
polynomial2bezier, bezier_bounding_box, bezier_real_minmax
+from svgpathtools.path import bpoints2bezier, CubicBezier
 
 
 seed = 2718
@@ -58,5 +58,53 @@
                 self.assertAlmostEqual(b.point(t), p(t), msg=msg)
 
 
+class TestBezierBoundingBox(unittest.TestCase):
+    def test_bezier_bounding_box(self):
+        # This bezier curve has denominator == 0 but due to floating point 
arithmetic error it is not exactly 0
+        zero_denominator_bezier_curve = CubicBezier(612.547 + 109.3261j, 
579.967 - 19.4422j, 428.0344 - 19.4422j, 395.4374 + 109.3261j)
+        zero_denom_xmin, zero_denom_xmax, zero_denom_ymin, zero_denom_ymax = 
bezier_bounding_box(zero_denominator_bezier_curve)
+        self.assertAlmostEqual(zero_denom_xmin, 395.437400, 5)
+        self.assertAlmostEqual(zero_denom_xmax, 612.547, 5)
+        self.assertAlmostEqual(zero_denom_ymin, 12.7498749, 5)
+        self.assertAlmostEqual(zero_denom_ymax, 109.3261, 5)
+
+        # This bezier curve has global extrema at the start and end points
+        start_end_bbox_bezier_curve = CubicBezier(886.8238 + 354.8439j, 
884.4765 + 340.5983j, 877.6258 + 330.0518j, 868.2909 + 323.2453j)
+        start_end_xmin, start_end_xmax, start_end_ymin, start_end_ymax = 
bezier_bounding_box(start_end_bbox_bezier_curve)
+        self.assertAlmostEqual(start_end_xmin, 868.2909, 5)
+        self.assertAlmostEqual(start_end_xmax, 886.8238, 5)
+        self.assertAlmostEqual(start_end_ymin, 323.2453, 5)
+        self.assertAlmostEqual(start_end_ymax, 354.8439, 5)
+
+        # This bezier curve is to cover some random case where at least one of 
the global extrema is not the start or end point
+        general_bezier_curve = CubicBezier(295.2282 + 402.0233j, 310.3734 + 
355.5329j, 343.547 + 340.5983j, 390.122 + 355.7018j)
+        general_xmin, general_xmax, general_ymin, general_ymax = 
bezier_bounding_box(general_bezier_curve)
+        self.assertAlmostEqual(general_xmin, 295.2282, 5)
+        self.assertAlmostEqual(general_xmax, 390.121999999, 5)
+        self.assertAlmostEqual(general_ymin, 350.030030142, 5)
+        self.assertAlmostEqual(general_ymax, 402.0233, 5)
+
+
+class TestBezierRealMinMax(unittest.TestCase):
+    def test_bezier_real_minmax(self):
+        # This bezier curve has denominator == 0 but due to floating point 
arithmetic error it is not exactly 0
+        zero_denominator_bezier_curve = [109.3261, -19.4422, -19.4422, 
109.3261]
+        zero_denominator_minmax = 
bezier_real_minmax(zero_denominator_bezier_curve)
+        self.assertAlmostEqual(zero_denominator_minmax[0], 12.7498749, 5)
+        self.assertAlmostEqual(zero_denominator_minmax[1], 109.3261, 5)
+
+        # This bezier curve has global extrema at the start and end points
+        start_end_bbox_bezier_curve = [354.8439, 340.5983, 330.0518, 323.2453]
+        start_end_bbox_minmax = bezier_real_minmax(start_end_bbox_bezier_curve)
+        self.assertAlmostEqual(start_end_bbox_minmax[0], 323.2453, 5)
+        self.assertAlmostEqual(start_end_bbox_minmax[1], 354.8439, 5)
+
+        # This bezier curve is to cover some random case where at least one of 
the global extrema is not the start or end point
+        general_bezier_curve = [402.0233, 355.5329, 340.5983, 355.7018]
+        general_minmax = bezier_real_minmax(general_bezier_curve)
+        self.assertAlmostEqual(general_minmax[0], 350.030030142, 5)
+        self.assertAlmostEqual(general_minmax[1], 402.0233, 5)
+
+
 if __name__ == '__main__':
     unittest.main()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/svgpathtools-1.7.0/test/test_generation.py 
new/svgpathtools-1.7.1/test/test_generation.py
--- old/svgpathtools-1.7.0/test/test_generation.py      2025-05-08 
02:17:39.000000000 +0200
+++ new/svgpathtools-1.7.1/test/test_generation.py      2025-05-28 
01:31:17.000000000 +0200
@@ -82,6 +82,12 @@
         psf = 'M 0.0,0.0 L 340.0,-10.0 L 100.0,100.0 L 200.0,0.0'
         self.assertTrue(parse_path(path).d() in (ps, psf))
 
+    def test_floating_point_stability(self):
+        # Check that reading and then outputting a d-string
+        # does not introduce floating point error noise.
+        path = "M 70.63,10.42 C 0.11,0.33 -0.89,2.09 -1.54,2.45 C -4.95,2.73 
-17.52,7.24 -39.46,11.04"
+        self.assertEqual(parse_path(path).d(), path)
+
 
 if __name__ == '__main__':
     unittest.main()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/svgpathtools-1.7.0/test/test_groups.py 
new/svgpathtools-1.7.1/test/test_groups.py
--- old/svgpathtools-1.7.0/test/test_groups.py  2025-05-08 02:17:39.000000000 
+0200
+++ new/svgpathtools-1.7.1/test/test_groups.py  2025-05-28 01:31:17.000000000 
+0200
@@ -46,6 +46,65 @@
         self.check_values(tf.dot(v_s), actual.start)
         self.check_values(tf.dot(v_e), actual.end)
 
+    def test_nonrounded_rect(self):
+        # Check that (nonrounded) rect is parsed properly
+
+        x, y = 10, 10
+        w, h = 100, 100
+
+        doc = Document.from_svg_string(
+            "\n".join(
+                [
+                    '<svg width="200" height="200" 
xmlns="http://www.w3.org/2000/svg";',
+                    '     style="fill:green;stroke:black;stroke-width:1.5">',
+                    f'  <rect x="{x}" y="{y}" width="{w}" height="{h}"/>',
+                    "</svg>",
+                ]
+            )
+        )
+
+        line_count, arc_count = 0, 0
+
+        for p in doc.paths():
+            for s in p:
+                if isinstance(s, Line):
+                    line_count += 1
+                if isinstance(s, Arc):
+                    arc_count += 1
+
+        self.assertEqual(line_count, 4)
+        self.assertEqual(arc_count, 0)
+
+    def test_rounded_rect(self):
+        # Check that rounded rect is parsed properly
+
+        x, y = 10, 10
+        rx, ry = 15, 12
+        w, h = 100, 100
+
+        doc = Document.from_svg_string(
+            "\n".join(
+                [
+                    '<svg width="200" height="200" 
xmlns="http://www.w3.org/2000/svg";',
+                    '     style="fill:green;stroke:black;stroke-width:1.5">',
+                    f'  <rect x="{x}" y="{y}" rx="{rx}" ry="{ry}" width="{w}" 
height="{h}"/>',
+                    "</svg>",
+                ]
+            )
+        )
+
+        line_count, arc_count = 0, 0
+
+        for p in doc.paths():
+            for s in p:
+                if isinstance(s, Line):
+                    line_count += 1
+                if isinstance(s, Arc):
+                    arc_count += 1
+
+        self.assertEqual(line_count, 4)
+        self.assertEqual(arc_count, 4)
+
     def test_group_transform(self):
         # The input svg has a group transform of "scale(1,-1)", which
         # can mess with Arc sweeps.
@@ -62,6 +121,42 @@
         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_ellipse_transform(self):
+        # Check that ellipse to path conversion respects rotation transforms
+
+        cx, cy = 40, 80
+        rx, ry = 15, 20
+
+        def dist_to_ellipse(angle, pt):
+            rot = np.exp(-1j * np.radians(angle))
+            transformed_pt = rot * complex(pt.real - cx, pt.imag - cy)
+            return transformed_pt.real**2 / rx**2 + transformed_pt.imag**2 / 
ry**2 - 1
+
+        for angle in np.linspace(-179, 180, num=123):
+            svgstring = "\n".join(
+                [
+                    '<svg width="200" height="200" 
xmlns="http://www.w3.org/2000/svg";',
+                    '     style="fill:green;stroke:black;stroke-width:1.5">',
+                    f'  <ellipse cx="{cx}" cy="{cy}" rx="{rx}" ry="{ry}" 
transform="rotate({angle} {cx} {cy})"/>',
+                    "</svg>",
+                ]
+            )
+
+            doc = Document.from_svg_string(svgstring)
+
+            for p in doc.paths():
+                subtended_angle = 0.0
+                for s in p:
+                    if isinstance(s, Arc):
+                        # check that several points lie on the original ellipse
+                        for t in [0.0, 1 / 3.0, 0.5, 2 / 3.0, 1.0]:
+                            dist = dist_to_ellipse(angle, s.point(t))
+                            self.assertAlmostEqual(dist, 0)
+
+                        # and that the subtended angles sum to 2*pi
+                        subtended_angle = subtended_angle + s.delta
+                self.assertAlmostEqual(np.abs(subtended_angle), 360)
+
     def test_group_flatten(self):
         # Test the Document.paths() function against the
         # groups.svg test file.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/svgpathtools-1.7.0/test/test_svg2paths.py 
new/svgpathtools-1.7.1/test/test_svg2paths.py
--- old/svgpathtools-1.7.0/test/test_svg2paths.py       2025-05-08 
02:17:39.000000000 +0200
+++ new/svgpathtools-1.7.1/test/test_svg2paths.py       2025-05-28 
01:31:17.000000000 +0200
@@ -81,10 +81,47 @@
         shutil.rmtree(tmpdir)
 
     def test_rect2pathd(self):
-        non_rounded = {"x":"10", "y":"10", "width":"100","height":"100"}
-        self.assertEqual(rect2pathd(non_rounded), 'M10.0 10.0 L 110.0 10.0 L 
110.0 110.0 L 10.0 110.0 z')
-        rounded = {"x":"10", "y":"10", "width":"100","height":"100", 
"rx":"15", "ry": "12"}
-        self.assertEqual(rect2pathd(rounded), "M 25.0 10.0 L 95.0 10.0 A 15.0 
12.0 0 0 1 110.0 22.0 L 110.0 98.0 A 15.0 12.0 0 0 1 95.0 110.0 L 25.0 110.0 A 
15.0 12.0 0 0 1 10.0 98.0 L 10.0 22.0 A 15.0 12.0 0 0 1 25.0 10.0 z")
+        non_rounded_dict = {"x": "10", "y": "10", "width": "100", "height": 
"100"}
+        self.assertEqual(
+            rect2pathd(non_rounded_dict),
+            "M10.0 10.0 L 110.0 10.0 L 110.0 110.0 L 10.0 110.0 z",
+        )
+
+        non_rounded_svg = """<?xml version="1.0" encoding="UTF-8"?>
+          <svg xmlns="http://www.w3.org/2000/svg"; 
xmlns:svg="http://www.w3.org/2000/svg"; width="200mm" height="200mm" 
version="1.1">
+              <rect id="non_rounded" x="10" y="10" width="100" height="100" />
+          </svg>"""
+
+        paths, _ = svg2paths(StringIO(non_rounded_svg))
+        self.assertEqual(len(paths), 1)
+        self.assertTrue(paths[0].isclosed())
+        self.assertEqual(
+            paths[0].d(use_closed_attrib=True),
+            "M 10.0,10.0 L 110.0,10.0 L 110.0,110.0 L 10.0,110.0 Z",
+        )
+        self.assertEqual(
+            paths[0].d(use_closed_attrib=False),
+            "M 10.0,10.0 L 110.0,10.0 L 110.0,110.0 L 10.0,110.0 L 10.0,10.0",
+        )
+
+        rounded_dict = {"x": "10", "y": "10", "width": "100","height": "100", 
"rx": "15", "ry": "12"}
+        self.assertEqual(
+            rect2pathd(rounded_dict),
+            "M 25.0 10.0 L 95.0 10.0 A 15.0 12.0 0 0 1 110.0 22.0 L 110.0 98.0 
A 15.0 12.0 0 0 1 95.0 110.0 L 25.0 110.0 A 15.0 12.0 0 0 1 10.0 98.0 L 10.0 
22.0 A 15.0 12.0 0 0 1 25.0 10.0 z",
+        )
+
+        rounded_svg = """<?xml version="1.0" encoding="UTF-8"?>
+          <svg xmlns="http://www.w3.org/2000/svg"; 
xmlns:svg="http://www.w3.org/2000/svg"; width="200mm" height="200mm" 
version="1.1">
+              <rect id="rounded" x="10" y="10" width="100" height ="100" 
rx="15" ry="12" />
+          </svg>"""
+
+        paths, _ = svg2paths(StringIO(rounded_svg))
+        self.assertEqual(len(paths), 1)
+        self.assertTrue(paths[0].isclosed())
+        self.assertEqual(
+            paths[0].d(),
+            "M 25.0,10.0 L 95.0,10.0 A 15.0,12.0 0.0 0,1 110.0,22.0 L 
110.0,98.0 A 15.0,12.0 0.0 0,1 95.0,110.0 L 25.0,110.0 A 15.0,12.0 0.0 0,1 
10.0,98.0 L 10.0,22.0 A 15.0,12.0 0.0 0,1 25.0,10.0",
+        )
 
     def test_from_file_path_string(self):
         """Test reading svg from file provided as path"""
@@ -131,6 +168,44 @@
 
             self.assertEqual(len(paths), 2)
 
+    def test_svg2paths_polygon_no_points(self):
+
+        paths, _ = svg2paths(join(dirname(__file__), 'polygons_no_points.svg'))
+
+        path = paths[0]
+        path_correct = Path()
+        self.assertTrue(len(path)==0)
+        self.assertTrue(path==path_correct)
+
+        path = paths[1]
+        self.assertTrue(len(path)==0)
+        self.assertTrue(path==path_correct)
+
+    def test_svg2paths_polyline_tests(self):
+
+        paths, _ = svg2paths(join(dirname(__file__), 'polyline.svg'))
+
+        path = paths[0]
+        path_correct = Path(Line(59+185j, 98+203j),
+                            Line(98+203j, 108+245j),
+                            Line(108+245j, 82+279j),
+                            Line(82+279j, 39+280j),
+                            Line(39+280j, 11+247j),
+                            Line(11+247j, 19+205j))
+        self.assertFalse(path.isclosed())
+        self.assertTrue(len(path)==6)
+        self.assertTrue(path==path_correct)
+
+        path = paths[1]
+        path_correct = Path(Line(220+50j, 267+84j),
+                            Line(267+84j, 249+140j),
+                            Line(249+140j, 190+140j),
+                            Line(190+140j, 172+84j),
+                            Line(172+84j, 220+50j))
+        self.assertTrue(path.isclosed())
+        self.assertTrue(len(path)==5)
+        self.assertTrue(path==path_correct)
+
 
 if __name__ == '__main__':
     unittest.main()

Reply via email to