From bbf75d0b5a072bd931f71fcb5d631b6cddda1637 Mon Sep 17 00:00:00 2001 From: Andrew Port Date: Tue, 9 Nov 2021 18:45:31 -0800 Subject: [PATCH 01/29] Remove deprecated 'requires' setuptools parameter --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 1c86ae8..054eb20 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,6 @@ setup(name='svgpathtools', license='MIT', install_requires=['numpy', 'svgwrite', 'scipy'], platforms="OS Independent", - requires=['numpy', 'svgwrite', 'scipy'], keywords=['svg', 'svg path', 'svg.path', 'bezier', 'parse svg path', 'display svg'], classifiers=[ "Development Status :: 4 - Beta", From 3a6711a5e74743cf17ecf8938374399fa01583ae Mon Sep 17 00:00:00 2001 From: Andrew Port Date: Tue, 9 Nov 2021 18:47:17 -0800 Subject: [PATCH 02/29] add scipy to requirements file --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 2c3af04..8013573 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ numpy svgwrite +scipy From ca094feea99fcd8d7036d2da919c3fc7ceacd94d Mon Sep 17 00:00:00 2001 From: Andrew Port Date: Tue, 9 Nov 2021 19:48:41 -0800 Subject: [PATCH 03/29] Add 3.10 CI check and disable test_hash unittest for Windows Python 2 environments --- .github/workflows/github-ci.yml | 2 +- setup.py | 1 + test/test_path.py | 10 ++++++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/github-ci.yml b/.github/workflows/github-ci.yml index 70555c8..41ccecc 100644 --- a/.github/workflows/github-ci.yml +++ b/.github/workflows/github-ci.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9] + python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9, "3.10"] steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v2 diff --git a/setup.py b/setup.py index 054eb20..1c6a456 100644 --- a/setup.py +++ b/setup.py @@ -46,6 +46,7 @@ setup(name='svgpathtools', "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Topic :: Multimedia :: Graphics :: Editors :: Vector-Based", "Topic :: Scientific/Engineering", "Topic :: Scientific/Engineering :: Image Recognition", diff --git a/test/test_path.py b/test/test_path.py index 8285555..db77dc9 100644 --- a/test/test_path.py +++ b/test/test_path.py @@ -743,7 +743,7 @@ class TestPath(unittest.TestCase): # this is necessary due to changes to the builtin `hash` function user_hash_seed = os.environ.get("PYTHONHASHSEED", "") os.environ["PYTHONHASHSEED"] = "314" - if version_info.major >= 3 and version_info.minor >= 8: + if version_info >= (3, 8): expected_hashes = [ -6073024107272494569, -2519772625496438197, 8726412907710383506, 2132930052750006195, 3112548573593977871, 991446120749438306, @@ -751,7 +751,7 @@ class TestPath(unittest.TestCase): -4418099728831808951, 702646573139378041, -6331016786776229094, 5053050772929443013, 6102272282813527681, -5385294438006156225 ] - elif version_info.major == 3 and 2 <= version_info.minor < 8: + elif (3, 2) <= version_info < (3, 8): expected_hashes = [ -5662973462929734898, 5166874115671195563, 5223434942701471389, -7224979960884350294, -5178990533869800243, -4003140762934044601, @@ -760,6 +760,7 @@ class TestPath(unittest.TestCase): -7093907105533857815, 2036243740727202243, -8108488067585685407 ] else: + expected_hashes = [ -5762846476463470127, -138736730317965290, -2005041722222729058, 8448700906794235291, -5178990533869800243, -4003140762934044601, @@ -768,6 +769,11 @@ class TestPath(unittest.TestCase): -7093907105533857815, 2036243740727202243, -8108488067585685407 ] + if version_info.major == 2 and os.name == 'nt': + # the expected hash values for 2.7 apparently differed on Windows + # if you work in Windows and want to fix this test, please do + return + for c, h in zip(test_curves, expected_hashes): self.assertTrue(hash(c) == h, msg="hash {} was expected for curve = {}".format(h, c)) os.environ["PYTHONHASHSEED"] = user_hash_seed # restore user's hash seed From 8ad7458b31dacbdb9edf7fde91b938da2341aeb8 Mon Sep 17 00:00:00 2001 From: Andrew Port Date: Tue, 9 Nov 2021 20:45:13 -0800 Subject: [PATCH 04/29] fix github action syntax for publishing to PyPI --- .github/workflows/publish-on-pypi-test.yml | 37 ++++++++++++++++++++++ .github/workflows/publish-on-pypi.yml | 6 ++-- 2 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/publish-on-pypi-test.yml diff --git a/.github/workflows/publish-on-pypi-test.yml b/.github/workflows/publish-on-pypi-test.yml new file mode 100644 index 0000000..9f12f52 --- /dev/null +++ b/.github/workflows/publish-on-pypi-test.yml @@ -0,0 +1,37 @@ +name: Publish to TestPyPI + +on: + push: + branches: + - master + +jobs: + build-n-publish: + name: Build and publish to TestPyPI + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@master + - name: Set up Python 3 + uses: actions/setup-python@v1 + with: + python-version: 3 + - name: Install pypa/build + run: >- + python -m + pip install + build + --user + - name: Build a binary wheel and a source tarball + run: >- + python -m + build + --sdist + --wheel + --outdir dist/ + . + - name: Publish to Test PyPI + uses: pypa/gh-action-pypi-publish@master + with: + skip_existing: true + password: ${{ secrets.TESTPYPI_API_TOKEN }} + repository_url: https://test.pypi.org/legacy/ diff --git a/.github/workflows/publish-on-pypi.yml b/.github/workflows/publish-on-pypi.yml index 85495d4..5108006 100644 --- a/.github/workflows/publish-on-pypi.yml +++ b/.github/workflows/publish-on-pypi.yml @@ -2,7 +2,9 @@ name: Publish to TestPyPI and if new version PyPI on: push: - branches: [ master ] + tags: + - 'v*' + jobs: build-n-publish: name: Build and publish to TestPyPI and PyPI @@ -37,4 +39,4 @@ jobs: if: startsWith(github.ref, 'refs/tags') uses: pypa/gh-action-pypi-publish@master with: - password: ${{ secrets.PYPI_API_TOKEN }} \ No newline at end of file + password: ${{ secrets.PYPI_API_TOKEN }} From d1421d62865faab88e25ce9157cf2bbdc03944d0 Mon Sep 17 00:00:00 2001 From: Andrew Port Date: Fri, 26 Nov 2021 18:23:07 -0800 Subject: [PATCH 05/29] fix issue with filenames with no directory causing error --- svgpathtools/paths2svg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/svgpathtools/paths2svg.py b/svgpathtools/paths2svg.py index f9c461f..8a88f0a 100644 --- a/svgpathtools/paths2svg.py +++ b/svgpathtools/paths2svg.py @@ -217,7 +217,7 @@ def disvg(paths=None, colors=None, filename=None, stroke_widths=None, # append time stamp to filename if timestamp: fbname, fext = os_path.splitext(filename) - dirname = os_path.dirname(filename) + dirname = os_path.abspath(os_path.dirname(filename)) tstamp = str(time()).replace('.', '') stfilename = os_path.split(fbname)[1] + '_' + tstamp + fext filename = os_path.join(dirname, stfilename) From abd99f0846ea636b9c33ce28453348bd662b98c7 Mon Sep 17 00:00:00 2001 From: Andrew Port Date: Fri, 26 Nov 2021 18:31:38 -0800 Subject: [PATCH 06/29] fix issue with filenames with no directory causing error --- svgpathtools/paths2svg.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/svgpathtools/paths2svg.py b/svgpathtools/paths2svg.py index 8a88f0a..d936ca0 100644 --- a/svgpathtools/paths2svg.py +++ b/svgpathtools/paths2svg.py @@ -214,10 +214,13 @@ def disvg(paths=None, colors=None, filename=None, stroke_widths=None, timestamp = True if timestamp is None else timestamp filename = os_path.join(gettempdir(), 'disvg_output.svg') + dirname = os_path.abspath(os_path.dirname(filename)) + if not os_path.exists(dirname): + makedirs(dirname) + # append time stamp to filename if timestamp: fbname, fext = os_path.splitext(filename) - dirname = os_path.abspath(os_path.dirname(filename)) tstamp = str(time()).replace('.', '') stfilename = os_path.split(fbname)[1] + '_' + tstamp + fext filename = os_path.join(dirname, stfilename) @@ -407,9 +410,6 @@ def disvg(paths=None, colors=None, filename=None, stroke_widths=None, if paths2Drawing: return dwg - # save svg - if not os_path.exists(os_path.dirname(filename)): - makedirs(os_path.dirname(filename)) dwg.save() # re-open the svg, make the xml pretty, and save it again From 5037fac57481136af2c11375e6fce896374cba6b Mon Sep 17 00:00:00 2001 From: Andrew Port Date: Fri, 26 Nov 2021 18:34:58 -0800 Subject: [PATCH 07/29] fix issue with filenames with no directory causing error --- setup.py | 2 +- svgpathtools/paths2svg.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 1c6a456..ab6d68b 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ import codecs import os -VERSION = '1.4.3' +VERSION = '1.4.4' AUTHOR_NAME = 'Andy Port' AUTHOR_EMAIL = 'AndyAPort@gmail.com' GITHUB = 'https://github.com/mathandy/svgpathtools' diff --git a/svgpathtools/paths2svg.py b/svgpathtools/paths2svg.py index d936ca0..6e9c10e 100644 --- a/svgpathtools/paths2svg.py +++ b/svgpathtools/paths2svg.py @@ -217,7 +217,7 @@ def disvg(paths=None, colors=None, filename=None, stroke_widths=None, dirname = os_path.abspath(os_path.dirname(filename)) if not os_path.exists(dirname): makedirs(dirname) - + # append time stamp to filename if timestamp: fbname, fext = os_path.splitext(filename) From 6c655ad2200dae008e315bcdc05b5f73a8daeb35 Mon Sep 17 00:00:00 2001 From: Catherine Holloway Date: Tue, 25 Jan 2022 20:23:04 -0500 Subject: [PATCH 08/29] add support for rounded rectangles (#161) --- svgpathtools/svg_to_paths.py | 24 ++++++++++++++++++++---- test/test_svg2paths.py | 8 ++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/svgpathtools/svg_to_paths.py b/svgpathtools/svg_to_paths.py index 2dff80a..bcd23a9 100644 --- a/svgpathtools/svg_to_paths.py +++ b/svgpathtools/svg_to_paths.py @@ -84,14 +84,30 @@ def rect2pathd(rect): The rectangle will start at the (x,y) coordinate specified by the rectangle object and proceed counter-clockwise.""" - x0, y0 = float(rect.get('x', 0)), float(rect.get('y', 0)) + x, y = float(rect.get('x', 0)), float(rect.get('y', 0)) w, h = float(rect.get('width', 0)), float(rect.get('height', 0)) - x1, y1 = x0 + w, y0 - x2, y2 = x0 + w, y0 + h - x3, y3 = x0, y0 + h + if 'rx' in rect or 'ry' in rect: + rx = float(rect.get('rx', 0)) + ry = float(rect.get('ry', 0)) + d = "M {} {} ".format(x + rx, y) # right of p0 + d += "L {} {} ".format(x + w - rx, y) # go to p1 + d += "A {} {} 0 0 1 {} {} ".format(rx, ry, x+w, y+ry) # arc for p1 + d += "L {} {} ".format(x+w, y+h-ry) # above p2 + d += "A {} {} 0 0 1 {} {} ".format(rx, ry, x+w-rx, y+h) # arc for p2 + d += "L {} {} ".format(x+rx, y+h) # right of p3 + d += "A {} {} 0 0 1 {} {} ".format(rx, ry, x, y+h-ry) # arc for p3 + d += "L {} {} ".format(x, y+ry) # below p0 + d += "A {} {} 0 0 1 {} {} z".format(rx, ry, x+rx, y) # arc for p0 + return d + + x0, y0 = x, y + x1, y1 = x + w, y + x2, y2 = x + w, y + h + x3, y3 = x, y + h d = ("M{} {} L {} {} L {} {} L {} {} z" "".format(x0, y0, x1, y1, x2, y2, x3, y3)) + return d diff --git a/test/test_svg2paths.py b/test/test_svg2paths.py index 90a23e0..5a0dab0 100644 --- a/test/test_svg2paths.py +++ b/test/test_svg2paths.py @@ -3,6 +3,8 @@ import unittest from svgpathtools import * from os.path import join, dirname +from svgpathtools.svg_to_paths import rect2pathd + class TestSVG2Paths(unittest.TestCase): def test_svg2paths_polygons(self): @@ -50,3 +52,9 @@ class TestSVG2Paths(unittest.TestCase): self.assertTrue(len(path_circle)==2) self.assertTrue(path_circle==path_circle_correct) self.assertTrue(path_circle.isclosed()) + + 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") \ No newline at end of file From 2dc06df20f03110be0303524af7c8fe29b45e268 Mon Sep 17 00:00:00 2001 From: Andrew Port Date: Thu, 3 Feb 2022 18:09:09 -0800 Subject: [PATCH 09/29] rounded rect now parsed properly if only rx or only ry is included --- svgpathtools/svg_to_paths.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/svgpathtools/svg_to_paths.py b/svgpathtools/svg_to_paths.py index bcd23a9..b0303b0 100644 --- a/svgpathtools/svg_to_paths.py +++ b/svgpathtools/svg_to_paths.py @@ -87,8 +87,17 @@ def rect2pathd(rect): 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: - rx = float(rect.get('rx', 0)) - ry = float(rect.get('ry', 0)) + + # if only one, rx or ry, is present, use that value for both + # https://developer.mozilla.org/en-US/docs/Web/SVG/Element/rect + rx = rect.get('rx', None) + ry = rect.get('ry', None) + if rx is None: + rx = ry or 0. + if ry is None: + ry = rx or 0. + rx, ry = float(rx), float(ry) + d = "M {} {} ".format(x + rx, y) # right of p0 d += "L {} {} ".format(x + w - rx, y) # go to p1 d += "A {} {} 0 0 1 {} {} ".format(rx, ry, x+w, y+ry) # arc for p1 From ac138b8e5d4d65f439c1616becd6be326608e46f Mon Sep 17 00:00:00 2001 From: Andrew Port Date: Thu, 3 Feb 2022 18:10:00 -0800 Subject: [PATCH 10/29] aesthetic cleanup --- svgpathtools/svg_to_paths.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/svgpathtools/svg_to_paths.py b/svgpathtools/svg_to_paths.py index b0303b0..af14da5 100644 --- a/svgpathtools/svg_to_paths.py +++ b/svgpathtools/svg_to_paths.py @@ -98,15 +98,15 @@ def rect2pathd(rect): ry = rx or 0. rx, ry = float(rx), float(ry) - d = "M {} {} ".format(x + rx, y) # right of p0 - d += "L {} {} ".format(x + w - rx, y) # go to p1 - d += "A {} {} 0 0 1 {} {} ".format(rx, ry, x+w, y+ry) # arc for p1 - d += "L {} {} ".format(x+w, y+h-ry) # above p2 - d += "A {} {} 0 0 1 {} {} ".format(rx, ry, x+w-rx, y+h) # arc for p2 - d += "L {} {} ".format(x+rx, y+h) # right of p3 - d += "A {} {} 0 0 1 {} {} ".format(rx, ry, x, y+h-ry) # arc for p3 - d += "L {} {} ".format(x, y+ry) # below p0 - d += "A {} {} 0 0 1 {} {} z".format(rx, ry, x+rx, y) # arc for p0 + d = "M {} {} ".format(x + rx, y) # right of p0 + d += "L {} {} ".format(x + w - rx, y) # go to p1 + d += "A {} {} 0 0 1 {} {} ".format(rx, ry, x+w, y+ry) # arc for p1 + d += "L {} {} ".format(x+w, y+h-ry) # above p2 + d += "A {} {} 0 0 1 {} {} ".format(rx, ry, x+w-rx, y+h) # arc for p2 + d += "L {} {} ".format(x+rx, y+h) # right of p3 + d += "A {} {} 0 0 1 {} {} ".format(rx, ry, x, y+h-ry) # arc for p3 + d += "L {} {} ".format(x, y+ry) # below p0 + d += "A {} {} 0 0 1 {} {} z".format(rx, ry, x+rx, y) # arc for p0 return d x0, y0 = x, y From c84c897bf2121ed86ceed45b4e027785351c2fd5 Mon Sep 17 00:00:00 2001 From: Andrew Port Date: Thu, 3 Feb 2022 18:11:55 -0800 Subject: [PATCH 11/29] aesthetic cleanup --- svgpathtools/svg_to_paths.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/svgpathtools/svg_to_paths.py b/svgpathtools/svg_to_paths.py index af14da5..f4ca07b 100644 --- a/svgpathtools/svg_to_paths.py +++ b/svgpathtools/svg_to_paths.py @@ -17,9 +17,11 @@ COORD_PAIR_TMPLT = re.compile( r'([\+-]?\d*[\.\d]\d*[eE][\+-]?\d+|[\+-]?\d*[\.\d]\d*)' ) + def path2pathd(path): return path.get('d', '') + def ellipse2pathd(ellipse): """converts the parameters from an ellipse or a circle to a string for a Path object d-attribute""" From 19df25b99b405ec4fc7616b58384eca7879b6fd4 Mon Sep 17 00:00:00 2001 From: chanicpanic <51764816+chanicpanic@users.noreply.github.com> Date: Sun, 27 Feb 2022 18:48:50 -0800 Subject: [PATCH 12/29] Fix Document.add_path for empty groups (#170) --- svgpathtools/document.py | 2 +- test/test_groups.py | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/svgpathtools/document.py b/svgpathtools/document.py index f88f5ba..1dd9077 100644 --- a/svgpathtools/document.py +++ b/svgpathtools/document.py @@ -289,7 +289,7 @@ class Document: # If given a list of strings (one or more), assume it represents # a sequence of nested group names - elif all(isinstance(elem, str) for elem in group): + elif len(group) > 0 and all(isinstance(elem, str) for elem in group): group = self.get_or_add_group(group) elif not isinstance(group, Element): diff --git a/test/test_groups.py b/test/test_groups.py index 44b6cb9..aeb3393 100644 --- a/test/test_groups.py +++ b/test/test_groups.py @@ -235,4 +235,11 @@ class TestGroups(unittest.TestCase): path = parse_path(path_d) svg_path = doc.add_path(path, group=new_leaf) - self.assertEqual(path_d, svg_path.get('d')) \ No newline at end of file + self.assertEqual(path_d, svg_path.get('d')) + + # Test that paths are added to the correct group + new_sibling = doc.get_or_add_group( + ['base_group', 'new_parent', 'new_sibling']) + doc.add_path(path, group=new_sibling) + self.assertEqual(len(new_sibling), 1) + self.assertEqual(path_d, new_sibling[0].get('d')) From ce43c75cd8f5ce13a446a014fbbaec9b1df73296 Mon Sep 17 00:00:00 2001 From: FlyingSamson Date: Sun, 22 May 2022 13:09:13 +0200 Subject: [PATCH 13/29] Allow file-like object as input to Documents ctor and svg2paths function --- svgpathtools/document.py | 22 ++++++++++++++-------- svgpathtools/svg_to_paths.py | 20 +++++++++++++------- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/svgpathtools/document.py b/svgpathtools/document.py index 1dd9077..8371c85 100644 --- a/svgpathtools/document.py +++ b/svgpathtools/document.py @@ -224,7 +224,7 @@ def flattened_paths_from_group(group_to_flatten, root, recursive=True, class Document: - def __init__(self, filepath=None): + def __init__(self, svg_file_name_or_file=None): """A container for a DOM-style SVG document. The `Document` class provides a simple interface to modify and analyze @@ -235,19 +235,25 @@ class Document: The output Path objects will be transformed based on their parent groups. Args: - filepath (str): The filepath of the DOM-style object. + svg_file_name_or_file (str or file-like): The filepath of the + DOM-style object or a file-like object containing it. """ + self.original_filepath = None - # remember location of original svg file - self.original_filepath = filepath - if filepath is not None and os.path.dirname(filepath) == '': - self.original_filepath = os.path.join(os.getcwd(), filepath) + # strings are interpreted as file location everything else is treated as + # file-like object and passed to the xml parser directly + if isinstance(svg_file_name_or_file, str): + # remember location of original svg file if any + self.original_filepath = svg_file_name_or_file + if os.path.dirname(svg_file_name_or_file) == '': + self.original_filepath = os.path.join( + os.getcwd(), svg_file_name_or_file) - if filepath is None: + if svg_file_name_or_file is None: self.tree = etree.ElementTree(Element('svg')) else: # parse svg to ElementTree object - self.tree = etree.parse(filepath) + self.tree = etree.parse(svg_file_name_or_file) self.root = self.tree.getroot() diff --git a/svgpathtools/svg_to_paths.py b/svgpathtools/svg_to_paths.py index f4ca07b..a3a68c9 100644 --- a/svgpathtools/svg_to_paths.py +++ b/svgpathtools/svg_to_paths.py @@ -129,7 +129,7 @@ def line2pathd(l): ) -def svg2paths(svg_file_location, +def svg2paths(svg_file_name_or_file, return_svg_attributes=False, convert_circles_to_paths=True, convert_ellipses_to_paths=True, @@ -144,7 +144,9 @@ def svg2paths(svg_file_location, SVG Path, Line, Polyline, Polygon, Circle, and Ellipse elements. Args: - svg_file_location (string): the location of the svg file + svg_file_name_or_file (string or file-like object): the location of the + svg file on disk or a file-like object containing the content of a + svg file return_svg_attributes (bool): Set to True and a dictionary of svg-attributes will be extracted and returned. See also the `svg2paths2()` function. @@ -168,10 +170,14 @@ def svg2paths(svg_file_location, list: The list of corresponding path attribute dictionaries. dict (optional): A dictionary of svg-attributes (see `svg2paths2()`). """ - if os_path.dirname(svg_file_location) == '': - svg_file_location = os_path.join(getcwd(), svg_file_location) + # strings are interpreted as file location everything else is treated as + # file-like object and passed to the xml parser directly + if isinstance(svg_file_name_or_file, str): + if os_path.dirname(svg_file_name_or_file) == '': + svg_file_name_or_file = os_path.join( + getcwd(), svg_file_name_or_file) - doc = parse(svg_file_location) + doc = parse(svg_file_name_or_file) def dom2dict(element): """Converts DOM elements to dictionaries of attributes.""" @@ -230,7 +236,7 @@ def svg2paths(svg_file_location, return path_list, attribute_dictionary_list -def svg2paths2(svg_file_location, +def svg2paths2(svg_file_name_or_file, return_svg_attributes=True, convert_circles_to_paths=True, convert_ellipses_to_paths=True, @@ -241,7 +247,7 @@ def svg2paths2(svg_file_location, """Convenience function; identical to svg2paths() except that return_svg_attributes=True by default. See svg2paths() docstring for more info.""" - return svg2paths(svg_file_location=svg_file_location, + return svg2paths(svg_file_name_or_file=svg_file_name_or_file, return_svg_attributes=return_svg_attributes, convert_circles_to_paths=convert_circles_to_paths, convert_ellipses_to_paths=convert_ellipses_to_paths, From ccdd10212c64d06970975467a9eadf1b388c3f24 Mon Sep 17 00:00:00 2001 From: FlyingSamson Date: Sun, 22 May 2022 13:32:09 +0200 Subject: [PATCH 14/29] Add unit tests for reading from different sources in svg2paths --- test/test_svg2paths.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/test/test_svg2paths.py b/test/test_svg2paths.py index 5a0dab0..b5cf5b4 100644 --- a/test/test_svg2paths.py +++ b/test/test_svg2paths.py @@ -1,6 +1,7 @@ from __future__ import division, absolute_import, print_function import unittest from svgpathtools import * +from io import StringIO from os.path import join, dirname from svgpathtools.svg_to_paths import rect2pathd @@ -57,4 +58,33 @@ class TestSVG2Paths(unittest.TestCase): 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") \ No newline at end of file + 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") + + def test_from_file_path(self): + """ Test reading svg from file provided as path """ + paths, _ = svg2paths(join(dirname(__file__), 'polygons.svg')) + + self.assertEqual(len(paths), 2) + + def test_from_file_object(self): + """ Test reading svg from file object that has already been opened """ + with open(join(dirname(__file__), 'polygons.svg'), 'r') as file: + paths, _ = svg2paths(file) + + self.assertEqual(len(paths), 2) + + def test_from_stringio(self): + """ Test reading svg object contained in an StringIO object """ + with open(join(dirname(__file__), 'polygons.svg'), 'r') as file: + # read entire file into string + file_content: str = file.read() + # prepare stringio object + file_as_stringio = StringIO() + # paste file content into it + file_as_stringio.write(file_content) + # reset curser to its beginning + file_as_stringio.seek(0) + + paths, _ = svg2paths(file_as_stringio) + + self.assertEqual(len(paths), 2) From 50b335f3da4d9f6ea06568bb22f0e8d5bb9632d7 Mon Sep 17 00:00:00 2001 From: FlyingSamson Date: Sun, 22 May 2022 14:37:27 +0200 Subject: [PATCH 15/29] Add convenience functions for converting svgs contained in a string to paths --- svgpathtools/__init__.py | 2 +- svgpathtools/svg_to_paths.py | 47 ++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/svgpathtools/__init__.py b/svgpathtools/__init__.py index f043731..91f1afb 100644 --- a/svgpathtools/__init__.py +++ b/svgpathtools/__init__.py @@ -17,6 +17,6 @@ from .document import (Document, CONVERSIONS, CONVERT_ONLY_PATHS, from .svg_io_sax import SaxDocument try: - from .svg_to_paths import svg2paths, svg2paths2 + from .svg_to_paths import svg2paths, svg2paths2, svg_string2paths, svg_string2paths2 except ImportError: pass diff --git a/svgpathtools/svg_to_paths.py b/svgpathtools/svg_to_paths.py index a3a68c9..f8d5d88 100644 --- a/svgpathtools/svg_to_paths.py +++ b/svgpathtools/svg_to_paths.py @@ -5,6 +5,7 @@ The main tool being the svg2paths() function.""" from __future__ import division, absolute_import, print_function from xml.dom.minidom import parse from os import path as os_path, getcwd +from io import StringIO import re # Internal dependencies @@ -255,3 +256,49 @@ def svg2paths2(svg_file_name_or_file, convert_polylines_to_paths=convert_polylines_to_paths, convert_polygons_to_paths=convert_polygons_to_paths, convert_rectangles_to_paths=convert_rectangles_to_paths) + + +def svg_string2paths(svg_string, + return_svg_attributes=False, + convert_circles_to_paths=True, + convert_ellipses_to_paths=True, + convert_lines_to_paths=True, + convert_polylines_to_paths=True, + convert_polygons_to_paths=True, + convert_rectangles_to_paths=True): + """Convenience function; identical to svg2paths() except that it takes the + svg object as string. See svg2paths() docstring for more + info.""" + # wrap string into StringIO object + svg_file_obj = StringIO(svg_string) + # reset cursor to the beginning of the buffer + svg_file_obj.seek(0) + return svg2paths(svg_file_name_or_file=svg_file_obj, + return_svg_attributes=return_svg_attributes, + convert_circles_to_paths=convert_circles_to_paths, + convert_ellipses_to_paths=convert_ellipses_to_paths, + convert_lines_to_paths=convert_lines_to_paths, + convert_polylines_to_paths=convert_polylines_to_paths, + convert_polygons_to_paths=convert_polygons_to_paths, + convert_rectangles_to_paths=convert_rectangles_to_paths) + + +def svg_string2paths2(svg_string, + return_svg_attributes=True, + convert_circles_to_paths=True, + convert_ellipses_to_paths=True, + convert_lines_to_paths=True, + convert_polylines_to_paths=True, + convert_polygons_to_paths=True, + convert_rectangles_to_paths=True): + """Convenience function; identical to svg2paths2() except that it takes the + svg object as string. See svg2paths() docstring for more + info.""" + return svg_string2paths(svg_string=svg_string, + return_svg_attributes=return_svg_attributes, + convert_circles_to_paths=convert_circles_to_paths, + convert_ellipses_to_paths=convert_ellipses_to_paths, + convert_lines_to_paths=convert_lines_to_paths, + convert_polylines_to_paths=convert_polylines_to_paths, + convert_polygons_to_paths=convert_polygons_to_paths, + convert_rectangles_to_paths=convert_rectangles_to_paths) From 33f4639bbf2f526f6a0dade6d351cb91f129cffd Mon Sep 17 00:00:00 2001 From: FlyingSamson Date: Sun, 22 May 2022 14:51:16 +0200 Subject: [PATCH 16/29] Add tests for functions taking svg objects as string --- test/test_svg2paths.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/test/test_svg2paths.py b/test/test_svg2paths.py index b5cf5b4..06220bb 100644 --- a/test/test_svg2paths.py +++ b/test/test_svg2paths.py @@ -74,7 +74,7 @@ class TestSVG2Paths(unittest.TestCase): self.assertEqual(len(paths), 2) def test_from_stringio(self): - """ Test reading svg object contained in an StringIO object """ + """ Test reading svg object contained in a StringIO object """ with open(join(dirname(__file__), 'polygons.svg'), 'r') as file: # read entire file into string file_content: str = file.read() @@ -88,3 +88,23 @@ class TestSVG2Paths(unittest.TestCase): paths, _ = svg2paths(file_as_stringio) self.assertEqual(len(paths), 2) + + def test_from_string_without_svg_attrs(self): + """ Test reading svg object contained in a string without svg attributes""" + with open(join(dirname(__file__), 'polygons.svg'), 'r') as file: + # read entire file into string + file_content: str = file.read() + + paths, _ = svg_string2paths(file_content) + + self.assertEqual(len(paths), 2) + + def test_from_string_with_svg_attrs(self): + """ Test reading svg object contained in a string with svg attributes""" + with open(join(dirname(__file__), 'polygons.svg'), 'r') as file: + # read entire file into string + file_content: str = file.read() + + paths, _, _ = svg_string2paths2(file_content) + + self.assertEqual(len(paths), 2) From 1771fbfb06e3a1ec92628859d2bb2c9dec85adf4 Mon Sep 17 00:00:00 2001 From: FlyingSamson Date: Sun, 22 May 2022 15:30:48 +0200 Subject: [PATCH 17/29] Add factory method for creating from string holding svg object --- svgpathtools/document.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/svgpathtools/document.py b/svgpathtools/document.py index 8371c85..3d2b7c1 100644 --- a/svgpathtools/document.py +++ b/svgpathtools/document.py @@ -41,6 +41,7 @@ import xml.etree.ElementTree as etree from xml.etree.ElementTree import Element, SubElement, register_namespace from xml.dom.minidom import parseString import warnings +from io import StringIO from tempfile import gettempdir from time import time @@ -257,6 +258,18 @@ class Document: self.root = self.tree.getroot() + @staticmethod + def from_svg_string(svg_string): + """Factory method for creating a document from a string holding a svg + object + """ + # wrap string into StringIO object + svg_file_obj = StringIO(svg_string) + # reset cursor to the beginning of the buffer + svg_file_obj.seek(0) + # create document from file object + return Document(svg_file_obj) + def paths(self, group_filter=lambda x: True, path_filter=lambda x: True, path_conversions=CONVERSIONS): """Returns a list of all paths in the document. From a743e0293c3ca04e7b5e4cdb7059085e2b4fdb6c Mon Sep 17 00:00:00 2001 From: FlyingSamson Date: Sun, 22 May 2022 15:46:56 +0200 Subject: [PATCH 18/29] Add tests for creating from file location, file, StringIO, and string --- test/test_document.py | 45 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 test/test_document.py diff --git a/test/test_document.py b/test/test_document.py new file mode 100644 index 0000000..f1004bb --- /dev/null +++ b/test/test_document.py @@ -0,0 +1,45 @@ +from __future__ import division, absolute_import, print_function +import unittest +from svgpathtools import * +from io import StringIO +from os.path import join, dirname + +class TestDocument(unittest.TestCase): + def test_from_file_path(self): + """ Test reading svg from file provided as path """ + doc = Document(join(dirname(__file__), 'polygons.svg')) + + self.assertEqual(len(doc.paths()), 2) + + def test_from_file_object(self): + """ Test reading svg from file object that has already been opened """ + with open(join(dirname(__file__), 'polygons.svg'), 'r') as file: + doc = Document(file) + + self.assertEqual(len(doc.paths()), 2) + + def test_from_stringio(self): + """ Test reading svg object contained in a StringIO object """ + with open(join(dirname(__file__), 'polygons.svg'), 'r') as file: + # read entire file into string + file_content: str = file.read() + # prepare stringio object + file_as_stringio = StringIO() + # paste file content into it + file_as_stringio.write(file_content) + # reset curser to its beginning + file_as_stringio.seek(0) + + doc = Document(file_as_stringio) + + self.assertEqual(len(doc.paths()), 2) + + def test_from_string_without_svg_attrs(self): + """ Test reading svg object contained in a string without svg attributes""" + with open(join(dirname(__file__), 'polygons.svg'), 'r') as file: + # read entire file into string + file_content: str = file.read() + + doc = Document.from_svg_string(file_content) + + self.assertEqual(len(doc.paths()), 2) From 68e0d1f30d34f2efc95ed95c11927461852c7956 Mon Sep 17 00:00:00 2001 From: FlyingSamson Date: Sun, 22 May 2022 15:51:03 +0200 Subject: [PATCH 19/29] Fix tests for old python versions not supporting type hints --- test/test_document.py | 4 ++-- test/test_svg2paths.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/test_document.py b/test/test_document.py index f1004bb..6b4a795 100644 --- a/test/test_document.py +++ b/test/test_document.py @@ -22,7 +22,7 @@ class TestDocument(unittest.TestCase): """ Test reading svg object contained in a StringIO object """ with open(join(dirname(__file__), 'polygons.svg'), 'r') as file: # read entire file into string - file_content: str = file.read() + file_content = file.read() # prepare stringio object file_as_stringio = StringIO() # paste file content into it @@ -38,7 +38,7 @@ class TestDocument(unittest.TestCase): """ Test reading svg object contained in a string without svg attributes""" with open(join(dirname(__file__), 'polygons.svg'), 'r') as file: # read entire file into string - file_content: str = file.read() + file_content = file.read() doc = Document.from_svg_string(file_content) diff --git a/test/test_svg2paths.py b/test/test_svg2paths.py index 06220bb..8944586 100644 --- a/test/test_svg2paths.py +++ b/test/test_svg2paths.py @@ -77,7 +77,7 @@ class TestSVG2Paths(unittest.TestCase): """ Test reading svg object contained in a StringIO object """ with open(join(dirname(__file__), 'polygons.svg'), 'r') as file: # read entire file into string - file_content: str = file.read() + file_content = file.read() # prepare stringio object file_as_stringio = StringIO() # paste file content into it @@ -93,7 +93,7 @@ class TestSVG2Paths(unittest.TestCase): """ Test reading svg object contained in a string without svg attributes""" with open(join(dirname(__file__), 'polygons.svg'), 'r') as file: # read entire file into string - file_content: str = file.read() + file_content = file.read() paths, _ = svg_string2paths(file_content) @@ -103,7 +103,7 @@ class TestSVG2Paths(unittest.TestCase): """ Test reading svg object contained in a string with svg attributes""" with open(join(dirname(__file__), 'polygons.svg'), 'r') as file: # read entire file into string - file_content: str = file.read() + file_content = file.read() paths, _, _ = svg_string2paths2(file_content) From 02a223c2202284456eccb74ddea6f760e24a24ed Mon Sep 17 00:00:00 2001 From: FlyingSamson Date: Sun, 22 May 2022 16:17:43 +0200 Subject: [PATCH 20/29] Fix fileio for test compatibility with python2.7 --- test/test_document.py | 11 +++++++---- test/test_svg2paths.py | 10 +++++++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/test/test_document.py b/test/test_document.py index 6b4a795..8837e8b 100644 --- a/test/test_document.py +++ b/test/test_document.py @@ -2,6 +2,7 @@ from __future__ import division, absolute_import, print_function import unittest from svgpathtools import * from io import StringIO +from io import open # overrides build-in open for compatibility with python2 from os.path import join, dirname class TestDocument(unittest.TestCase): @@ -20,7 +21,8 @@ class TestDocument(unittest.TestCase): def test_from_stringio(self): """ Test reading svg object contained in a StringIO object """ - with open(join(dirname(__file__), 'polygons.svg'), 'r') as file: + with open(join(dirname(__file__), 'polygons.svg'), + 'r', encoding='utf-8') as file: # read entire file into string file_content = file.read() # prepare stringio object @@ -34,9 +36,10 @@ class TestDocument(unittest.TestCase): self.assertEqual(len(doc.paths()), 2) - def test_from_string_without_svg_attrs(self): - """ Test reading svg object contained in a string without svg attributes""" - with open(join(dirname(__file__), 'polygons.svg'), 'r') as file: + def test_from_string(self): + """ Test reading svg object contained in a string""" + with open(join(dirname(__file__), 'polygons.svg'), + 'r', encoding='utf-8') as file: # read entire file into string file_content = file.read() diff --git a/test/test_svg2paths.py b/test/test_svg2paths.py index 8944586..1a48bd7 100644 --- a/test/test_svg2paths.py +++ b/test/test_svg2paths.py @@ -2,6 +2,7 @@ from __future__ import division, absolute_import, print_function import unittest from svgpathtools import * from io import StringIO +from io import open # overrides build-in open for compatibility with python2 from os.path import join, dirname from svgpathtools.svg_to_paths import rect2pathd @@ -75,7 +76,8 @@ class TestSVG2Paths(unittest.TestCase): def test_from_stringio(self): """ Test reading svg object contained in a StringIO object """ - with open(join(dirname(__file__), 'polygons.svg'), 'r') as file: + with open(join(dirname(__file__), 'polygons.svg'), + 'r', encoding='utf-8') as file: # read entire file into string file_content = file.read() # prepare stringio object @@ -91,7 +93,8 @@ class TestSVG2Paths(unittest.TestCase): def test_from_string_without_svg_attrs(self): """ Test reading svg object contained in a string without svg attributes""" - with open(join(dirname(__file__), 'polygons.svg'), 'r') as file: + with open(join(dirname(__file__), 'polygons.svg'), + 'r', encoding='utf-8') as file: # read entire file into string file_content = file.read() @@ -101,7 +104,8 @@ class TestSVG2Paths(unittest.TestCase): def test_from_string_with_svg_attrs(self): """ Test reading svg object contained in a string with svg attributes""" - with open(join(dirname(__file__), 'polygons.svg'), 'r') as file: + with open(join(dirname(__file__), 'polygons.svg'), + 'r', encoding='utf-8') as file: # read entire file into string file_content = file.read() From a473ee3f4cae5f8b339cd1ed5371d334824a0e22 Mon Sep 17 00:00:00 2001 From: FlyingSamson Date: Tue, 24 May 2022 18:15:52 +0200 Subject: [PATCH 21/29] Remove unnecessary seek commands --- svgpathtools/document.py | 2 -- svgpathtools/svg_to_paths.py | 2 -- test/test_document.py | 6 +----- test/test_svg2paths.py | 6 +----- 4 files changed, 2 insertions(+), 14 deletions(-) diff --git a/svgpathtools/document.py b/svgpathtools/document.py index 3d2b7c1..2ee0d69 100644 --- a/svgpathtools/document.py +++ b/svgpathtools/document.py @@ -265,8 +265,6 @@ class Document: """ # wrap string into StringIO object svg_file_obj = StringIO(svg_string) - # reset cursor to the beginning of the buffer - svg_file_obj.seek(0) # create document from file object return Document(svg_file_obj) diff --git a/svgpathtools/svg_to_paths.py b/svgpathtools/svg_to_paths.py index f8d5d88..617e622 100644 --- a/svgpathtools/svg_to_paths.py +++ b/svgpathtools/svg_to_paths.py @@ -271,8 +271,6 @@ def svg_string2paths(svg_string, info.""" # wrap string into StringIO object svg_file_obj = StringIO(svg_string) - # reset cursor to the beginning of the buffer - svg_file_obj.seek(0) return svg2paths(svg_file_name_or_file=svg_file_obj, return_svg_attributes=return_svg_attributes, convert_circles_to_paths=convert_circles_to_paths, diff --git a/test/test_document.py b/test/test_document.py index 8837e8b..d32bb3b 100644 --- a/test/test_document.py +++ b/test/test_document.py @@ -26,11 +26,7 @@ class TestDocument(unittest.TestCase): # read entire file into string file_content = file.read() # prepare stringio object - file_as_stringio = StringIO() - # paste file content into it - file_as_stringio.write(file_content) - # reset curser to its beginning - file_as_stringio.seek(0) + file_as_stringio = StringIO(file_content) doc = Document(file_as_stringio) diff --git a/test/test_svg2paths.py b/test/test_svg2paths.py index 1a48bd7..4533cc9 100644 --- a/test/test_svg2paths.py +++ b/test/test_svg2paths.py @@ -81,11 +81,7 @@ class TestSVG2Paths(unittest.TestCase): # read entire file into string file_content = file.read() # prepare stringio object - file_as_stringio = StringIO() - # paste file content into it - file_as_stringio.write(file_content) - # reset curser to its beginning - file_as_stringio.seek(0) + file_as_stringio = StringIO(file_content) paths, _ = svg2paths(file_as_stringio) From db5200f460f9a570ea8b15cfc59a213aa9bcc978 Mon Sep 17 00:00:00 2001 From: FlyingSamson Date: Wed, 25 May 2022 17:39:10 +0200 Subject: [PATCH 22/29] Switch back to previous function parameter names --- svgpathtools/document.py | 16 ++++++++-------- svgpathtools/svg_to_paths.py | 20 ++++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/svgpathtools/document.py b/svgpathtools/document.py index 2ee0d69..974b1f3 100644 --- a/svgpathtools/document.py +++ b/svgpathtools/document.py @@ -225,7 +225,7 @@ def flattened_paths_from_group(group_to_flatten, root, recursive=True, class Document: - def __init__(self, svg_file_name_or_file=None): + def __init__(self, filepath=None): """A container for a DOM-style SVG document. The `Document` class provides a simple interface to modify and analyze @@ -236,25 +236,25 @@ class Document: The output Path objects will be transformed based on their parent groups. Args: - svg_file_name_or_file (str or file-like): The filepath of the + filepath (str or file-like): The filepath of the DOM-style object or a file-like object containing it. """ self.original_filepath = None # strings are interpreted as file location everything else is treated as # file-like object and passed to the xml parser directly - if isinstance(svg_file_name_or_file, str): + if isinstance(filepath, str): # remember location of original svg file if any - self.original_filepath = svg_file_name_or_file - if os.path.dirname(svg_file_name_or_file) == '': + self.original_filepath = filepath + if os.path.dirname(filepath) == '': self.original_filepath = os.path.join( - os.getcwd(), svg_file_name_or_file) + os.getcwd(), filepath) - if svg_file_name_or_file is None: + if filepath is None: self.tree = etree.ElementTree(Element('svg')) else: # parse svg to ElementTree object - self.tree = etree.parse(svg_file_name_or_file) + self.tree = etree.parse(filepath) self.root = self.tree.getroot() diff --git a/svgpathtools/svg_to_paths.py b/svgpathtools/svg_to_paths.py index 617e622..98d2b1d 100644 --- a/svgpathtools/svg_to_paths.py +++ b/svgpathtools/svg_to_paths.py @@ -130,7 +130,7 @@ def line2pathd(l): ) -def svg2paths(svg_file_name_or_file, +def svg2paths(svg_file_location, return_svg_attributes=False, convert_circles_to_paths=True, convert_ellipses_to_paths=True, @@ -145,7 +145,7 @@ def svg2paths(svg_file_name_or_file, SVG Path, Line, Polyline, Polygon, Circle, and Ellipse elements. Args: - svg_file_name_or_file (string or file-like object): the location of the + svg_file_location (string or file-like object): the location of the svg file on disk or a file-like object containing the content of a svg file return_svg_attributes (bool): Set to True and a dictionary of @@ -173,12 +173,12 @@ def svg2paths(svg_file_name_or_file, """ # strings are interpreted as file location everything else is treated as # file-like object and passed to the xml parser directly - if isinstance(svg_file_name_or_file, str): - if os_path.dirname(svg_file_name_or_file) == '': - svg_file_name_or_file = os_path.join( - getcwd(), svg_file_name_or_file) + if isinstance(svg_file_location, str): + if os_path.dirname(svg_file_location) == '': + svg_file_location = os_path.join( + getcwd(), svg_file_location) - doc = parse(svg_file_name_or_file) + doc = parse(svg_file_location) def dom2dict(element): """Converts DOM elements to dictionaries of attributes.""" @@ -237,7 +237,7 @@ def svg2paths(svg_file_name_or_file, return path_list, attribute_dictionary_list -def svg2paths2(svg_file_name_or_file, +def svg2paths2(svg_file_location, return_svg_attributes=True, convert_circles_to_paths=True, convert_ellipses_to_paths=True, @@ -248,7 +248,7 @@ def svg2paths2(svg_file_name_or_file, """Convenience function; identical to svg2paths() except that return_svg_attributes=True by default. See svg2paths() docstring for more info.""" - return svg2paths(svg_file_name_or_file=svg_file_name_or_file, + return svg2paths(svg_file_location=svg_file_location, return_svg_attributes=return_svg_attributes, convert_circles_to_paths=convert_circles_to_paths, convert_ellipses_to_paths=convert_ellipses_to_paths, @@ -271,7 +271,7 @@ def svg_string2paths(svg_string, info.""" # wrap string into StringIO object svg_file_obj = StringIO(svg_string) - return svg2paths(svg_file_name_or_file=svg_file_obj, + return svg2paths(svg_file_location=svg_file_obj, return_svg_attributes=return_svg_attributes, convert_circles_to_paths=convert_circles_to_paths, convert_ellipses_to_paths=convert_ellipses_to_paths, From aacd5fa96d57676d2e7a5ee35215cc9e082e5d08 Mon Sep 17 00:00:00 2001 From: FlyingSamson Date: Wed, 25 May 2022 17:45:31 +0200 Subject: [PATCH 23/29] Remove second version of function returning the svg_attributes by default --- svgpathtools/svg_to_paths.py | 21 --------------------- test/test_svg2paths.py | 15 ++------------- 2 files changed, 2 insertions(+), 34 deletions(-) diff --git a/svgpathtools/svg_to_paths.py b/svgpathtools/svg_to_paths.py index 98d2b1d..d8c9a4b 100644 --- a/svgpathtools/svg_to_paths.py +++ b/svgpathtools/svg_to_paths.py @@ -279,24 +279,3 @@ def svg_string2paths(svg_string, convert_polylines_to_paths=convert_polylines_to_paths, convert_polygons_to_paths=convert_polygons_to_paths, convert_rectangles_to_paths=convert_rectangles_to_paths) - - -def svg_string2paths2(svg_string, - return_svg_attributes=True, - convert_circles_to_paths=True, - convert_ellipses_to_paths=True, - convert_lines_to_paths=True, - convert_polylines_to_paths=True, - convert_polygons_to_paths=True, - convert_rectangles_to_paths=True): - """Convenience function; identical to svg2paths2() except that it takes the - svg object as string. See svg2paths() docstring for more - info.""" - return svg_string2paths(svg_string=svg_string, - return_svg_attributes=return_svg_attributes, - convert_circles_to_paths=convert_circles_to_paths, - convert_ellipses_to_paths=convert_ellipses_to_paths, - convert_lines_to_paths=convert_lines_to_paths, - convert_polylines_to_paths=convert_polylines_to_paths, - convert_polygons_to_paths=convert_polygons_to_paths, - convert_rectangles_to_paths=convert_rectangles_to_paths) diff --git a/test/test_svg2paths.py b/test/test_svg2paths.py index 4533cc9..faeccc1 100644 --- a/test/test_svg2paths.py +++ b/test/test_svg2paths.py @@ -87,8 +87,8 @@ class TestSVG2Paths(unittest.TestCase): self.assertEqual(len(paths), 2) - def test_from_string_without_svg_attrs(self): - """ Test reading svg object contained in a string without svg attributes""" + def test_from_string(self): + """ Test reading svg object contained in a string """ with open(join(dirname(__file__), 'polygons.svg'), 'r', encoding='utf-8') as file: # read entire file into string @@ -97,14 +97,3 @@ class TestSVG2Paths(unittest.TestCase): paths, _ = svg_string2paths(file_content) self.assertEqual(len(paths), 2) - - def test_from_string_with_svg_attrs(self): - """ Test reading svg object contained in a string with svg attributes""" - with open(join(dirname(__file__), 'polygons.svg'), - 'r', encoding='utf-8') as file: - # read entire file into string - file_content = file.read() - - paths, _, _ = svg_string2paths2(file_content) - - self.assertEqual(len(paths), 2) From 2fc016d48ff9ef3412d4720a9d2cafc60770fb57 Mon Sep 17 00:00:00 2001 From: FlyingSamson Date: Wed, 25 May 2022 17:58:14 +0200 Subject: [PATCH 24/29] Make factory method a classmethod --- svgpathtools/document.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/svgpathtools/document.py b/svgpathtools/document.py index 974b1f3..3b98599 100644 --- a/svgpathtools/document.py +++ b/svgpathtools/document.py @@ -258,8 +258,8 @@ class Document: self.root = self.tree.getroot() - @staticmethod - def from_svg_string(svg_string): + @classmethod + def from_svg_string(cls, svg_string): """Factory method for creating a document from a string holding a svg object """ From 07f46d41f865b2bcc1c054cce35814dc8ffd32c4 Mon Sep 17 00:00:00 2001 From: FlyingSamson Date: Wed, 25 May 2022 19:24:50 +0200 Subject: [PATCH 25/29] Rename svg_string2paths to svgstr2paths --- svgpathtools/__init__.py | 2 +- svgpathtools/svg_to_paths.py | 2 +- test/test_svg2paths.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/svgpathtools/__init__.py b/svgpathtools/__init__.py index 91f1afb..7e5da65 100644 --- a/svgpathtools/__init__.py +++ b/svgpathtools/__init__.py @@ -17,6 +17,6 @@ from .document import (Document, CONVERSIONS, CONVERT_ONLY_PATHS, from .svg_io_sax import SaxDocument try: - from .svg_to_paths import svg2paths, svg2paths2, svg_string2paths, svg_string2paths2 + from .svg_to_paths import svg2paths, svg2paths2, svgstr2paths except ImportError: pass diff --git a/svgpathtools/svg_to_paths.py b/svgpathtools/svg_to_paths.py index d8c9a4b..dec0e44 100644 --- a/svgpathtools/svg_to_paths.py +++ b/svgpathtools/svg_to_paths.py @@ -258,7 +258,7 @@ def svg2paths2(svg_file_location, convert_rectangles_to_paths=convert_rectangles_to_paths) -def svg_string2paths(svg_string, +def svgstr2paths(svg_string, return_svg_attributes=False, convert_circles_to_paths=True, convert_ellipses_to_paths=True, diff --git a/test/test_svg2paths.py b/test/test_svg2paths.py index faeccc1..67a5751 100644 --- a/test/test_svg2paths.py +++ b/test/test_svg2paths.py @@ -94,6 +94,6 @@ class TestSVG2Paths(unittest.TestCase): # read entire file into string file_content = file.read() - paths, _ = svg_string2paths(file_content) + paths, _ = svgstr2paths(file_content) self.assertEqual(len(paths), 2) From 356d86df78c6473d5f4c8737d36464698c812bc1 Mon Sep 17 00:00:00 2001 From: Andrew Port Date: Sun, 5 Jun 2022 21:00:07 -0700 Subject: [PATCH 26/29] restore support for PosixPath inputs for Document --- svgpathtools/document.py | 21 ++++++++++----------- test/test_document.py | 23 +++++++++++++++++------ test/test_svg2paths.py | 15 +++++++++++++-- 3 files changed, 40 insertions(+), 19 deletions(-) diff --git a/svgpathtools/document.py b/svgpathtools/document.py index 3b98599..f3810f9 100644 --- a/svgpathtools/document.py +++ b/svgpathtools/document.py @@ -55,9 +55,13 @@ from .path import * # To maintain forward/backward compatibility try: - str = basestring + string = basestring except NameError: - pass + string = str +try: + from os import PathLike +except ImportError: + PathLike = string # Let xml.etree.ElementTree know about the SVG namespace SVG_NAMESPACE = {'svg': 'http://www.w3.org/2000/svg'} @@ -239,16 +243,11 @@ class Document: filepath (str or file-like): The filepath of the DOM-style object or a file-like object containing it. """ - self.original_filepath = None # strings are interpreted as file location everything else is treated as # file-like object and passed to the xml parser directly - if isinstance(filepath, str): - # remember location of original svg file if any - self.original_filepath = filepath - if os.path.dirname(filepath) == '': - self.original_filepath = os.path.join( - os.getcwd(), filepath) + from_filepath = isinstance(filepath, string) or isinstance(filepath, PathLike) + self.original_filepath = os.path.abspath(filepath) if from_filepath else None if filepath is None: self.tree = etree.ElementTree(Element('svg')) @@ -280,7 +279,7 @@ class Document: def paths_from_group(self, group, recursive=True, group_filter=lambda x: True, path_filter=lambda x: True, path_conversions=CONVERSIONS): - if all(isinstance(s, str) for s in group): + if all(isinstance(s, string) for s in group): # If we're given a list of strings, assume it represents a # nested sequence group = self.get_group(group) @@ -325,7 +324,7 @@ class Document: path_svg = path.d() elif is_path_segment(path): path_svg = Path(path).d() - elif isinstance(path, str): + elif isinstance(path, string): # Assume this is a valid d-string. # TODO: Should we sanity check the input string? path_svg = path diff --git a/test/test_document.py b/test/test_document.py index d32bb3b..d96160d 100644 --- a/test/test_document.py +++ b/test/test_document.py @@ -2,25 +2,36 @@ from __future__ import division, absolute_import, print_function import unittest from svgpathtools import * from io import StringIO -from io import open # overrides build-in open for compatibility with python2 +from io import open # overrides build-in open for compatibility with python2 from os.path import join, dirname +try: + import pathlib +except ImportError: + import pathlib2 as pathlib + class TestDocument(unittest.TestCase): - def test_from_file_path(self): - """ Test reading svg from file provided as path """ + def test_from_file_path_string(self): + """Test reading svg from file provided as path""" doc = Document(join(dirname(__file__), 'polygons.svg')) self.assertEqual(len(doc.paths()), 2) + def test_from_file_path(self): + """Test reading svg from file provided as path""" + doc = Document(pathlib.Path(__file__).parent / 'polygons.svg') + + self.assertEqual(len(doc.paths()), 2) + def test_from_file_object(self): - """ Test reading svg from file object that has already been opened """ + """Test reading svg from file object that has already been opened""" with open(join(dirname(__file__), 'polygons.svg'), 'r') as file: doc = Document(file) self.assertEqual(len(doc.paths()), 2) def test_from_stringio(self): - """ Test reading svg object contained in a StringIO object """ + """Test reading svg object contained in a StringIO object""" with open(join(dirname(__file__), 'polygons.svg'), 'r', encoding='utf-8') as file: # read entire file into string @@ -33,7 +44,7 @@ class TestDocument(unittest.TestCase): self.assertEqual(len(doc.paths()), 2) def test_from_string(self): - """ Test reading svg object contained in a string""" + """Test reading svg object contained in a string""" with open(join(dirname(__file__), 'polygons.svg'), 'r', encoding='utf-8') as file: # read entire file into string diff --git a/test/test_svg2paths.py b/test/test_svg2paths.py index 67a5751..2b5d459 100644 --- a/test/test_svg2paths.py +++ b/test/test_svg2paths.py @@ -2,11 +2,16 @@ from __future__ import division, absolute_import, print_function import unittest from svgpathtools import * from io import StringIO -from io import open # overrides build-in open for compatibility with python2 +from io import open # overrides build-in open for compatibility with python2 from os.path import join, dirname +# try: +# import pathlib +# except ImportError: +# import pathlib2 as pathlib from svgpathtools.svg_to_paths import rect2pathd + class TestSVG2Paths(unittest.TestCase): def test_svg2paths_polygons(self): @@ -61,12 +66,18 @@ class TestSVG2Paths(unittest.TestCase): 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") - def test_from_file_path(self): + def test_from_file_path_string(self): """ Test reading svg from file provided as path """ paths, _ = svg2paths(join(dirname(__file__), 'polygons.svg')) self.assertEqual(len(paths), 2) + # def test_from_file_path(self): + # """ Test reading svg from file provided as path """ + # paths, _ = svg2paths(pathlib.Path(__file__) / 'polygons.svg') + # + # self.assertEqual(len(paths), 2) + def test_from_file_object(self): """ Test reading svg from file object that has already been opened """ with open(join(dirname(__file__), 'polygons.svg'), 'r') as file: From d3a66f0bbd59dfeabef43df5e416eb6abd0e6a6b Mon Sep 17 00:00:00 2001 From: Andrew Port Date: Sun, 5 Jun 2022 21:17:28 -0700 Subject: [PATCH 27/29] skip pathlib support for python < 3.6 --- test/test_document.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/test/test_document.py b/test/test_document.py index d96160d..6642a49 100644 --- a/test/test_document.py +++ b/test/test_document.py @@ -4,10 +4,7 @@ from svgpathtools import * from io import StringIO from io import open # overrides build-in open for compatibility with python2 from os.path import join, dirname -try: - import pathlib -except ImportError: - import pathlib2 as pathlib +from sys import version_info class TestDocument(unittest.TestCase): @@ -19,9 +16,11 @@ class TestDocument(unittest.TestCase): def test_from_file_path(self): """Test reading svg from file provided as path""" - doc = Document(pathlib.Path(__file__).parent / 'polygons.svg') + if version_info >= (3, 6): + import pathlib + doc = Document(pathlib.Path(__file__).parent / 'polygons.svg') - self.assertEqual(len(doc.paths()), 2) + self.assertEqual(len(doc.paths()), 2) def test_from_file_object(self): """Test reading svg from file object that has already been opened""" From e8792f4d2dee5264d982053e2ef4576fe4b26741 Mon Sep 17 00:00:00 2001 From: Andrew Port Date: Sun, 5 Jun 2022 21:47:37 -0700 Subject: [PATCH 28/29] add support for os.PathLike arguments --- svgpathtools/svg_to_paths.py | 12 +++++++----- test/test_svg2paths.py | 19 +++++++++---------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/svgpathtools/svg_to_paths.py b/svgpathtools/svg_to_paths.py index dec0e44..a8decba 100644 --- a/svgpathtools/svg_to_paths.py +++ b/svgpathtools/svg_to_paths.py @@ -4,9 +4,13 @@ The main tool being the svg2paths() function.""" # External dependencies from __future__ import division, absolute_import, print_function from xml.dom.minidom import parse -from os import path as os_path, getcwd +import os from io import StringIO import re +try: + from os import PathLike as FilePathLike +except ImportError: + FilePathLike = str # Internal dependencies from .parser import parse_path @@ -173,10 +177,8 @@ def svg2paths(svg_file_location, """ # strings are interpreted as file location everything else is treated as # file-like object and passed to the xml parser directly - if isinstance(svg_file_location, str): - if os_path.dirname(svg_file_location) == '': - svg_file_location = os_path.join( - getcwd(), svg_file_location) + from_filepath = isinstance(svg_file_location, str) or isinstance(svg_file_location, FilePathLike) + svg_file_location = os.path.abspath(svg_file_location) if from_filepath else svg_file_location doc = parse(svg_file_location) diff --git a/test/test_svg2paths.py b/test/test_svg2paths.py index 2b5d459..ed91f06 100644 --- a/test/test_svg2paths.py +++ b/test/test_svg2paths.py @@ -4,10 +4,7 @@ from svgpathtools import * from io import StringIO from io import open # overrides build-in open for compatibility with python2 from os.path import join, dirname -# try: -# import pathlib -# except ImportError: -# import pathlib2 as pathlib +from sys import version_info from svgpathtools.svg_to_paths import rect2pathd @@ -67,16 +64,18 @@ class TestSVG2Paths(unittest.TestCase): 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") def test_from_file_path_string(self): - """ Test reading svg from file provided as path """ + """ Test reading svg from file provided as path""" paths, _ = svg2paths(join(dirname(__file__), 'polygons.svg')) self.assertEqual(len(paths), 2) - # def test_from_file_path(self): - # """ Test reading svg from file provided as path """ - # paths, _ = svg2paths(pathlib.Path(__file__) / 'polygons.svg') - # - # self.assertEqual(len(paths), 2) + def test_from_file_path(self): + """ Test reading svg from file provided as pathlib POSIXPath""" + if version_info >= (3, 6): + import pathlib + paths, _ = svg2paths(pathlib.Path(__file__).parent / 'polygons.svg') + + self.assertEqual(len(paths), 2) def test_from_file_object(self): """ Test reading svg from file object that has already been opened """ From b82530aaac00321a62ffb242b0d489a226df7a2f Mon Sep 17 00:00:00 2001 From: Andrew Port Date: Sun, 5 Jun 2022 21:49:21 -0700 Subject: [PATCH 29/29] minor fix to docstring formatting --- test/test_svg2paths.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/test_svg2paths.py b/test/test_svg2paths.py index ed91f06..31058ca 100644 --- a/test/test_svg2paths.py +++ b/test/test_svg2paths.py @@ -64,13 +64,13 @@ class TestSVG2Paths(unittest.TestCase): 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") def test_from_file_path_string(self): - """ Test reading svg from file provided as path""" + """Test reading svg from file provided as path""" paths, _ = svg2paths(join(dirname(__file__), 'polygons.svg')) self.assertEqual(len(paths), 2) def test_from_file_path(self): - """ Test reading svg from file provided as pathlib POSIXPath""" + """Test reading svg from file provided as pathlib POSIXPath""" if version_info >= (3, 6): import pathlib paths, _ = svg2paths(pathlib.Path(__file__).parent / 'polygons.svg') @@ -78,14 +78,14 @@ class TestSVG2Paths(unittest.TestCase): self.assertEqual(len(paths), 2) def test_from_file_object(self): - """ Test reading svg from file object that has already been opened """ + """Test reading svg from file object that has already been opened""" with open(join(dirname(__file__), 'polygons.svg'), 'r') as file: paths, _ = svg2paths(file) self.assertEqual(len(paths), 2) def test_from_stringio(self): - """ Test reading svg object contained in a StringIO object """ + """Test reading svg object contained in a StringIO object""" with open(join(dirname(__file__), 'polygons.svg'), 'r', encoding='utf-8') as file: # read entire file into string @@ -98,7 +98,7 @@ class TestSVG2Paths(unittest.TestCase): self.assertEqual(len(paths), 2) def test_from_string(self): - """ Test reading svg object contained in a string """ + """Test reading svg object contained in a string""" with open(join(dirname(__file__), 'polygons.svg'), 'r', encoding='utf-8') as file: # read entire file into string