Compare commits

..

1 Commits

Author SHA1 Message Date
Andrew Port 6db22d002a remove need for scipy req 2022-03-05 21:06:16 -08:00
25 changed files with 84 additions and 428 deletions

View File

@ -1,34 +0,0 @@
name: Github CI Unit Testing for Legacy Environments
on:
push:
pull_request:
workflow_dispatch:
jobs:
build:
runs-on: ${{ matrix.os }}
continue-on-error: true
strategy:
matrix:
os: [ubuntu-18.04, macos-10.15, windows-2019]
python-version: [2.7, 3.5, 3.6]
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
# configure python
- uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
# install deps
- name: Install dependencies for ${{ matrix.os }} Python ${{ matrix.python-version }}
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install scipy
# find and run all unit tests
- name: Run unit tests
run: python -m unittest discover test

View File

@ -12,7 +12,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest, macos-latest, windows-latest] os: [ubuntu-latest, macos-latest, windows-latest]
python-version: [3.7, 3.8, 3.9, "3.10", "3.11"] python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9, "3.10"]
steps: steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2 - uses: actions/checkout@v2

View File

@ -8,7 +8,7 @@ on:
jobs: jobs:
build-n-publish: build-n-publish:
name: Build and publish to TestPyPI name: Build and publish to TestPyPI
runs-on: ubuntu-latest runs-on: ubuntu-18.04
steps: steps:
- uses: actions/checkout@master - uses: actions/checkout@master
- name: Set up Python 3 - name: Set up Python 3
@ -30,7 +30,7 @@ jobs:
--outdir dist/ --outdir dist/
. .
- name: Publish to Test PyPI - name: Publish to Test PyPI
uses: pypa/gh-action-pypi-publish@release/v1 uses: pypa/gh-action-pypi-publish@master
with: with:
skip_existing: true skip_existing: true
password: ${{ secrets.TESTPYPI_API_TOKEN }} password: ${{ secrets.TESTPYPI_API_TOKEN }}

View File

@ -1,4 +1,4 @@
name: Publish to PyPI if new version name: Publish to TestPyPI and if new version PyPI
on: on:
push: push:
@ -8,7 +8,7 @@ on:
jobs: jobs:
build-n-publish: build-n-publish:
name: Build and publish to TestPyPI and PyPI name: Build and publish to TestPyPI and PyPI
runs-on: ubuntu-latest runs-on: ubuntu-18.04
steps: steps:
- uses: actions/checkout@master - uses: actions/checkout@master
- name: Set up Python 3 - name: Set up Python 3
@ -30,13 +30,13 @@ jobs:
--outdir dist/ --outdir dist/
. .
- name: Publish to Test PyPI - name: Publish to Test PyPI
uses: pypa/gh-action-pypi-publish@release/v1 uses: pypa/gh-action-pypi-publish@master
with: with:
skip_existing: true skip_existing: true
password: ${{ secrets.TESTPYPI_API_TOKEN }} password: ${{ secrets.TESTPYPI_API_TOKEN }}
repository_url: https://test.pypi.org/legacy/ repository_url: https://test.pypi.org/legacy/
- name: Publish to PyPI - name: Publish to PyPI
if: startsWith(github.ref, 'refs/tags') if: startsWith(github.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@release/v1 uses: pypa/gh-action-pypi-publish@master
with: with:
password: ${{ secrets.PYPI_API_TOKEN }} password: ${{ secrets.PYPI_API_TOKEN }}

View File

@ -7,6 +7,7 @@
"[![Donate](https://img.shields.io/badge/donate-paypal-brightgreen)](https://www.paypal.com/donate?business=4SKJ27AM4EYYA&no_recurring=0&item_name=Support+the+creator+of+svgpathtools?++He%27s+a+student+and+would+appreciate+it.&currency_code=USD)\n", "[![Donate](https://img.shields.io/badge/donate-paypal-brightgreen)](https://www.paypal.com/donate?business=4SKJ27AM4EYYA&no_recurring=0&item_name=Support+the+creator+of+svgpathtools?++He%27s+a+student+and+would+appreciate+it.&currency_code=USD)\n",
"![Python](https://img.shields.io/pypi/pyversions/svgpathtools.svg)\n", "![Python](https://img.shields.io/pypi/pyversions/svgpathtools.svg)\n",
"[![PyPI](https://img.shields.io/pypi/v/svgpathtools)](https://pypi.org/project/svgpathtools/)\n", "[![PyPI](https://img.shields.io/pypi/v/svgpathtools)](https://pypi.org/project/svgpathtools/)\n",
"![Build](https://img.shields.io/github/workflow/status/mathandy/svgpathtools/Github%20CI%20Unit%20Testing)\n",
"[![PyPI - Downloads](https://img.shields.io/pypi/dm/svgpathtools?color=yellow)](https://pypistats.org/packages/svgpathtools)\n", "[![PyPI - Downloads](https://img.shields.io/pypi/dm/svgpathtools?color=yellow)](https://pypistats.org/packages/svgpathtools)\n",
"# svgpathtools\n", "# svgpathtools\n",
"\n", "\n",

View File

@ -1,6 +1,7 @@
[![Donate](https://img.shields.io/badge/donate-paypal-brightgreen)](https://www.paypal.com/donate?business=4SKJ27AM4EYYA&no_recurring=0&item_name=Support+the+creator+of+svgpathtools?++He%27s+a+student+and+would+appreciate+it.&currency_code=USD) [![Donate](https://img.shields.io/badge/donate-paypal-brightgreen)](https://www.paypal.com/donate?business=4SKJ27AM4EYYA&no_recurring=0&item_name=Support+the+creator+of+svgpathtools?++He%27s+a+student+and+would+appreciate+it.&currency_code=USD)
![Python](https://img.shields.io/pypi/pyversions/svgpathtools.svg) ![Python](https://img.shields.io/pypi/pyversions/svgpathtools.svg)
[![PyPI](https://img.shields.io/pypi/v/svgpathtools)](https://pypi.org/project/svgpathtools/) [![PyPI](https://img.shields.io/pypi/v/svgpathtools)](https://pypi.org/project/svgpathtools/)
![Build](https://img.shields.io/github/workflow/status/mathandy/svgpathtools/Github%20CI%20Unit%20Testing)
[![PyPI - Downloads](https://img.shields.io/pypi/dm/svgpathtools?color=yellow)](https://pypistats.org/packages/svgpathtools) [![PyPI - Downloads](https://img.shields.io/pypi/dm/svgpathtools?color=yellow)](https://pypistats.org/packages/svgpathtools)
# svgpathtools # svgpathtools

View File

@ -8,7 +8,7 @@ Note: The relevant matrix transformation for quadratics can be found in the
svgpathtools.bezier module.""" svgpathtools.bezier module."""
from __future__ import print_function from __future__ import print_function
import numpy as np import numpy as np
from svgpathtools import bezier_point, Path, bpoints2bezier, polynomial2bezier from svgpathtools import *
class HigherOrderBezier: class HigherOrderBezier:

View File

@ -7,8 +7,7 @@ Path.continuous_subpaths() method to split a paths into a list of its
continuous subpaths. continuous subpaths.
""" """
from svgpathtools import Path, Line from svgpathtools import *
def path1_is_contained_in_path2(path1, path2): def path1_is_contained_in_path2(path1, path2):
assert path2.isclosed() # This question isn't well-defined otherwise assert path2.isclosed() # This question isn't well-defined otherwise
@ -17,11 +16,11 @@ def path1_is_contained_in_path2(path1, path2):
# find a point that's definitely outside path2 # find a point that's definitely outside path2
xmin, xmax, ymin, ymax = path2.bbox() xmin, xmax, ymin, ymax = path2.bbox()
b = (xmin + 1) + 1j*(ymax + 1) B = (xmin + 1) + 1j*(ymax + 1)
a = path1.start # pick an arbitrary point in path1 A = path1.start # pick an arbitrary point in path1
ab_line = Path(Line(a, b)) AB_line = Path(Line(A, B))
number_of_intersections = len(ab_line.intersect(path2)) number_of_intersections = len(AB_line.intersect(path2))
if number_of_intersections % 2: # if number of intersections is odd if number_of_intersections % 2: # if number of intersections is odd
return True return True
else: else:

View File

@ -1,16 +1,13 @@
from svgpathtools import disvg, Line, CubicBezier from svgpathtools import *
from scipy.optimize import fminbound
# create some example paths # create some example paths
path1 = CubicBezier(1,2+3j,3-5j,4+1j) path1 = CubicBezier(1,2+3j,3-5j,4+1j)
path2 = path1.rotated(60).translated(3) path2 = path1.rotated(60).translated(3)
# find minimizer
from scipy.optimize import fminbound
def dist(t): def dist(t):
return path1.radialrange(path2.point(t))[0][0] return path1.radialrange(path2.point(t))[0][0]
# find minimizer
T2 = fminbound(dist, 0, 1) T2 = fminbound(dist, 0, 1)
# Let's do a visual check # Let's do a visual check

View File

@ -3,7 +3,7 @@ import codecs
import os import os
VERSION = '1.6.1' VERSION = '1.4.4'
AUTHOR_NAME = 'Andy Port' AUTHOR_NAME = 'Andy Port'
AUTHOR_EMAIL = 'AndyAPort@gmail.com' AUTHOR_EMAIL = 'AndyAPort@gmail.com'
GITHUB = 'https://github.com/mathandy/svgpathtools' GITHUB = 'https://github.com/mathandy/svgpathtools'
@ -30,7 +30,8 @@ setup(name='svgpathtools',
download_url='{}/releases/download/{}/svgpathtools-{}-py2.py3-none-any.whl' download_url='{}/releases/download/{}/svgpathtools-{}-py2.py3-none-any.whl'
''.format(GITHUB, VERSION, VERSION), ''.format(GITHUB, VERSION, VERSION),
license='MIT', license='MIT',
install_requires=['numpy', 'svgwrite', 'scipy'], install_requires=['numpy', 'svgwrite'],
extras_require={'fast_accurate_lengths': ['scipy']},
platforms="OS Independent", platforms="OS Independent",
keywords=['svg', 'svg path', 'svg.path', 'bezier', 'parse svg path', 'display svg'], keywords=['svg', 'svg path', 'svg.path', 'bezier', 'parse svg path', 'display svg'],
classifiers=[ classifiers=[
@ -47,7 +48,6 @@ setup(name='svgpathtools',
"Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Topic :: Multimedia :: Graphics :: Editors :: Vector-Based", "Topic :: Multimedia :: Graphics :: Editors :: Vector-Based",
"Topic :: Scientific/Engineering", "Topic :: Scientific/Engineering",
"Topic :: Scientific/Engineering :: Image Recognition", "Topic :: Scientific/Engineering :: Image Recognition",

View File

@ -17,6 +17,6 @@ from .document import (Document, CONVERSIONS, CONVERT_ONLY_PATHS,
from .svg_io_sax import SaxDocument from .svg_io_sax import SaxDocument
try: try:
from .svg_to_paths import svg2paths, svg2paths2, svgstr2paths from .svg_to_paths import svg2paths, svg2paths2
except ImportError: except ImportError:
pass pass

View File

@ -13,7 +13,7 @@ An Historic Note:
Example: Example:
Typical usage looks something like the following. Typical usage looks something like the following.
>> from svgpathtools import Document >> from svgpathtools import *
>> doc = Document('my_file.html') >> doc = Document('my_file.html')
>> for path in doc.paths(): >> for path in doc.paths():
>> # Do something with the transformed Path object. >> # Do something with the transformed Path object.
@ -41,10 +41,8 @@ import xml.etree.ElementTree as etree
from xml.etree.ElementTree import Element, SubElement, register_namespace from xml.etree.ElementTree import Element, SubElement, register_namespace
from xml.dom.minidom import parseString from xml.dom.minidom import parseString
import warnings import warnings
from io import StringIO
from tempfile import gettempdir from tempfile import gettempdir
from time import time from time import time
import numpy as np
# Internal dependencies # Internal dependencies
from .parser import parse_path from .parser import parse_path
@ -52,17 +50,13 @@ from .parser import parse_transform
from .svg_to_paths import (path2pathd, ellipse2pathd, line2pathd, from .svg_to_paths import (path2pathd, ellipse2pathd, line2pathd,
polyline2pathd, polygon2pathd, rect2pathd) polyline2pathd, polygon2pathd, rect2pathd)
from .misctools import open_in_browser from .misctools import open_in_browser
from .path import transform, Path, is_path_segment from .path import *
# To maintain forward/backward compatibility # To maintain forward/backward compatibility
try: try:
string = basestring str = basestring
except NameError: except NameError:
string = str pass
try:
from os import PathLike
except ImportError:
PathLike = string
# Let xml.etree.ElementTree know about the SVG namespace # Let xml.etree.ElementTree know about the SVG namespace
SVG_NAMESPACE = {'svg': 'http://www.w3.org/2000/svg'} SVG_NAMESPACE = {'svg': 'http://www.w3.org/2000/svg'}
@ -241,14 +235,13 @@ class Document:
The output Path objects will be transformed based on their parent groups. The output Path objects will be transformed based on their parent groups.
Args: Args:
filepath (str or file-like): The filepath of the filepath (str): The filepath of the DOM-style object.
DOM-style object or a file-like object containing it.
""" """
# strings are interpreted as file location everything else is treated as # remember location of original svg file
# file-like object and passed to the xml parser directly self.original_filepath = filepath
from_filepath = isinstance(filepath, string) or isinstance(filepath, PathLike) if filepath is not None and os.path.dirname(filepath) == '':
self.original_filepath = os.path.abspath(filepath) if from_filepath else None self.original_filepath = os.path.join(os.getcwd(), filepath)
if filepath is None: if filepath is None:
self.tree = etree.ElementTree(Element('svg')) self.tree = etree.ElementTree(Element('svg'))
@ -258,14 +251,6 @@ class Document:
self.root = self.tree.getroot() self.root = self.tree.getroot()
@classmethod
def from_svg_string(cls, svg_string):
"""Constructor for creating a Document object from a string."""
# wrap string into StringIO object
svg_file_obj = StringIO(svg_string)
# create document from file object
return Document(svg_file_obj)
def paths(self, group_filter=lambda x: True, def paths(self, group_filter=lambda x: True,
path_filter=lambda x: True, path_conversions=CONVERSIONS): path_filter=lambda x: True, path_conversions=CONVERSIONS):
"""Returns a list of all paths in the document. """Returns a list of all paths in the document.
@ -278,7 +263,7 @@ class Document:
def paths_from_group(self, group, recursive=True, group_filter=lambda x: True, def paths_from_group(self, group, recursive=True, group_filter=lambda x: True,
path_filter=lambda x: True, path_conversions=CONVERSIONS): path_filter=lambda x: True, path_conversions=CONVERSIONS):
if all(isinstance(s, string) for s in group): if all(isinstance(s, str) for s in group):
# If we're given a list of strings, assume it represents a # If we're given a list of strings, assume it represents a
# nested sequence # nested sequence
group = self.get_group(group) group = self.get_group(group)
@ -323,7 +308,7 @@ class Document:
path_svg = path.d() path_svg = path.d()
elif is_path_segment(path): elif is_path_segment(path):
path_svg = Path(path).d() path_svg = Path(path).d()
elif isinstance(path, string): elif isinstance(path, str):
# Assume this is a valid d-string. # Assume this is a valid d-string.
# TODO: Should we sanity check the input string? # TODO: Should we sanity check the input string?
path_svg = path path_svg = path

View File

@ -21,11 +21,17 @@ from numpy import sqrt, cos, sin, tan, arccos as acos, arcsin as asin, \
degrees, radians, log, pi, ceil degrees, radians, log, pi, ceil
from numpy import exp, sqrt as csqrt, angle as phase, isnan from numpy import exp, sqrt as csqrt, angle as phase, isnan
_length_error_default = 1e-12
_length_error_default_without_scipy = 1e-9
try: try:
from scipy.integrate import quad from scipy.integrate import quad
_quad_available = True _quad_available = True
except: except:
_quad_available = False _quad_available = False
warn("SciPy not found. `LENGTH_ERROR`, `ILENGTH_ERROR`, and "
"`ILENGTH_S_TOL` defaults will be set to {} to avoid slow "
"computations (normally {})."
"".format(_length_error_default_without_scipy, _length_error_default))
# Internal dependencies # Internal dependencies
from .bezier import (bezier_intersections, bezier_bounding_box, split_bezier, from .bezier import (bezier_intersections, bezier_bounding_box, split_bezier,
@ -43,20 +49,20 @@ except NameError:
COMMANDS = set('MmZzLlHhVvCcSsQqTtAa') COMMANDS = set('MmZzLlHhVvCcSsQqTtAa')
UPPERCASE = set('MZLHVCSQTA') UPPERCASE = set('MZLHVCSQTA')
COMMAND_RE = re.compile(r"([MmZzLlHhVvCcSsQqTtAa])") COMMAND_RE = re.compile("([MmZzLlHhVvCcSsQqTtAa])")
FLOAT_RE = re.compile(r"[-+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?") FLOAT_RE = re.compile("[-+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?")
# Default Parameters ########################################################## # Default Parameters ##########################################################
# path segment .length() parameters for arc length computation # path segment .length() parameters for arc length computation
LENGTH_MIN_DEPTH = 5 LENGTH_MIN_DEPTH = 5
LENGTH_ERROR = 1e-12 LENGTH_ERROR = _length_error_default if _quad_available else _length_error_default_without_scipy
USE_SCIPY_QUAD = True # for elliptic Arc segment arc length computation USE_SCIPY_QUAD = True # for Arc and CubicBezier length computation
# path segment .ilength() parameters for inverse arc length computation # path segment .ilength() parameters for inverse arc length computation
ILENGTH_MIN_DEPTH = 5 ILENGTH_MIN_DEPTH = 5
ILENGTH_ERROR = 1e-12 ILENGTH_ERROR = _length_error_default if _quad_available else _length_error_default_without_scipy
ILENGTH_S_TOL = 1e-12 ILENGTH_S_TOL = _length_error_default if _quad_available else _length_error_default_without_scipy
ILENGTH_MAXITS = 10000 ILENGTH_MAXITS = 10000
# compatibility/implementation related warnings and parameters # compatibility/implementation related warnings and parameters
@ -189,6 +195,7 @@ def bez2poly(bez, numpy_ordering=True, return_poly1d=False):
def transform_segments_together(path, transformation): def transform_segments_together(path, transformation):
"""Makes sure that, if joints were continuous, they're kept that way.""" """Makes sure that, if joints were continuous, they're kept that way."""
transformed_segs = [transformation(seg) for seg in path] transformed_segs = [transformation(seg) for seg in path]
joint_was_continuous = [sa.end == sb.start for sa, sb in path.joints()]
for i, (sa, sb) in enumerate(path.joints()): for i, (sa, sb) in enumerate(path.joints()):
if sa.end == sb.start: if sa.end == sb.start:
@ -201,7 +208,7 @@ def rotate(curve, degs, origin=None):
(a complex number). By default origin is either `curve.point(0.5)`, or in (a complex number). By default origin is either `curve.point(0.5)`, or in
the case that curve is an Arc object, `origin` defaults to `curve.center`. the case that curve is an Arc object, `origin` defaults to `curve.center`.
""" """
def rotate_point(z): def transform(z):
return exp(1j*radians(degs))*(z - origin) + origin return exp(1j*radians(degs))*(z - origin) + origin
if origin is None: if origin is None:
@ -214,10 +221,10 @@ def rotate(curve, degs, origin=None):
transformation = lambda seg: rotate(seg, degs, origin=origin) transformation = lambda seg: rotate(seg, degs, origin=origin)
return transform_segments_together(curve, transformation) return transform_segments_together(curve, transformation)
elif is_bezier_segment(curve): elif is_bezier_segment(curve):
return bpoints2bezier([rotate_point(bpt) for bpt in curve.bpoints()]) return bpoints2bezier([transform(bpt) for bpt in curve.bpoints()])
elif isinstance(curve, Arc): elif isinstance(curve, Arc):
new_start = rotate_point(curve.start) new_start = transform(curve.start)
new_end = rotate_point(curve.end) new_end = transform(curve.end)
new_rotation = curve.rotation + degs new_rotation = curve.rotation + degs
return Arc(new_start, radius=curve.radius, rotation=new_rotation, return Arc(new_start, radius=curve.radius, rotation=new_rotation,
large_arc=curve.large_arc, sweep=curve.sweep, end=new_end) large_arc=curve.large_arc, sweep=curve.sweep, end=new_end)
@ -294,10 +301,6 @@ def scale(curve, sx, sy=None, origin=0j):
def transform(curve, tf): def transform(curve, tf):
"""Transforms the curve by the homogeneous transformation matrix tf""" """Transforms the curve by the homogeneous transformation matrix tf"""
if all((tf == np.eye(3)).ravel()):
return curve # tf is identity, return curve as is
def to_point(p): def to_point(p):
return np.array([[p.real], [p.imag], [1.0]]) return np.array([[p.real], [p.imag], [1.0]])
@ -318,7 +321,7 @@ def transform(curve, tf):
new_start = to_complex(tf.dot(to_point(curve.start))) new_start = to_complex(tf.dot(to_point(curve.start)))
new_end = to_complex(tf.dot(to_point(curve.end))) new_end = to_complex(tf.dot(to_point(curve.end)))
# Based on https://math.stackexchange.com/questions/2349726/ # Based on https://math.stackexchange.com/questions/2349726/compute-the-major-and-minor-axis-of-an-ellipse-after-linearly-transforming-it
rx2 = curve.radius.real ** 2 rx2 = curve.radius.real ** 2
ry2 = curve.radius.imag ** 2 ry2 = curve.radius.imag ** 2
@ -338,14 +341,10 @@ def transform(curve, tf):
if new_radius.real == 0 or new_radius.imag == 0 : if new_radius.real == 0 or new_radius.imag == 0 :
return Line(new_start, new_end) return Line(new_start, new_end)
else: else :
if tf[0][0] * tf[1][1] >= 0.0:
new_sweep = curve.sweep
else:
new_sweep = not curve.sweep
return Arc(new_start, radius=new_radius, rotation=curve.rotation + rot, return Arc(new_start, radius=new_radius, rotation=curve.rotation + rot,
large_arc=curve.large_arc, sweep=new_sweep, end=new_end, large_arc=curve.large_arc, sweep=curve.sweep, end=new_end,
autoscale_radius=True) autoscale_radius=False)
else: else:
raise TypeError("Input `curve` should be a Path, Line, " raise TypeError("Input `curve` should be a Path, Line, "
@ -715,19 +714,6 @@ class Line(object):
Note: This will fail if the two segments coincide for more than a Note: This will fail if the two segments coincide for more than a
finite collection of points. finite collection of points.
tol is not used.""" tol is not used."""
if isinstance(other_seg, (Line, QuadraticBezier, CubicBezier)):
ob = [e.real for e in other_seg.bpoints()]
sb = [e.real for e in self.bpoints()]
if min(ob) > max(sb):
return []
if max(ob) < min(sb):
return []
ob = [e.imag for e in other_seg.bpoints()]
sb = [e.imag for e in self.bpoints()]
if min(ob) > max(sb):
return []
if max(ob) < min(sb):
return []
if isinstance(other_seg, Line): if isinstance(other_seg, Line):
assert other_seg.end != other_seg.start and self.end != self.start assert other_seg.end != other_seg.start and self.end != self.start
assert self != other_seg assert self != other_seg
@ -1055,19 +1041,6 @@ class QuadraticBezier(object):
self.point(t1) == other_seg.point(t2). self.point(t1) == other_seg.point(t2).
Note: This will fail if the two segments coincide for more than a Note: This will fail if the two segments coincide for more than a
finite collection of points.""" finite collection of points."""
if isinstance(other_seg, (Line, QuadraticBezier, CubicBezier)):
ob = [e.real for e in other_seg.bpoints()]
sb = [e.real for e in self.bpoints()]
if min(ob) > max(sb):
return []
if max(ob) < min(sb):
return []
ob = [e.imag for e in other_seg.bpoints()]
sb = [e.imag for e in self.bpoints()]
if min(ob) > max(sb):
return []
if max(ob) < min(sb):
return []
if isinstance(other_seg, Line): if isinstance(other_seg, Line):
return bezier_by_line_intersections(self, other_seg) return bezier_by_line_intersections(self, other_seg)
elif isinstance(other_seg, QuadraticBezier): elif isinstance(other_seg, QuadraticBezier):
@ -1222,7 +1195,7 @@ class CubicBezier(object):
return self._length_info['length'] return self._length_info['length']
# using scipy.integrate.quad is quick # using scipy.integrate.quad is quick
if _quad_available: if _quad_available and USE_SCIPY_QUAD:
s = quad(lambda tau: abs(self.derivative(tau)), t0, t1, s = quad(lambda tau: abs(self.derivative(tau)), t0, t1,
epsabs=error, limit=1000)[0] epsabs=error, limit=1000)[0]
else: else:
@ -1328,19 +1301,6 @@ class CubicBezier(object):
This will fail if the two segments coincide for more than a This will fail if the two segments coincide for more than a
finite collection of points. finite collection of points.
""" """
if isinstance(other_seg, (Line, QuadraticBezier, CubicBezier)):
ob = [e.real for e in other_seg.bpoints()]
sb = [e.real for e in self.bpoints()]
if min(ob) > max(sb):
return []
if max(ob) < min(sb):
return []
ob = [e.imag for e in other_seg.bpoints()]
sb = [e.imag for e in self.bpoints()]
if min(ob) > max(sb):
return []
if max(ob) < min(sb):
return []
if isinstance(other_seg, Line): if isinstance(other_seg, Line):
return bezier_by_line_intersections(self, other_seg) return bezier_by_line_intersections(self, other_seg)
elif (isinstance(other_seg, QuadraticBezier) or elif (isinstance(other_seg, QuadraticBezier) or
@ -1398,7 +1358,7 @@ class CubicBezier(object):
class Arc(object): class Arc(object):
def __init__(self, start, radius, rotation, large_arc, sweep, end, def __init__(self, start, radius, rotation, large_arc, sweep, end,
autoscale_radius=True): autoscale_radius=True):
r""" """
This should be thought of as a part of an ellipse connecting two This should be thought of as a part of an ellipse connecting two
points on that ellipse, start and end. points on that ellipse, start and end.
Parameters Parameters
@ -1802,7 +1762,7 @@ class Arc(object):
h = hash(self) h = hash(self)
if self.segment_length_hash is None or self.segment_length_hash != h: if self.segment_length_hash is None or self.segment_length_hash != h:
self.segment_length_hash = h self.segment_length_hash = h
if _quad_available: if _quad_available and USE_SCIPY_QUAD:
self.segment_length = quad(lambda tau: abs(self.derivative(tau)), self.segment_length = quad(lambda tau: abs(self.derivative(tau)),
t0, t1, epsabs=error, limit=1000)[0] t0, t1, epsabs=error, limit=1000)[0]
else: else:
@ -1810,7 +1770,7 @@ class Arc(object):
self.point(t1), error, min_depth, 0) self.point(t1), error, min_depth, 0)
return self.segment_length return self.segment_length
if _quad_available: if _quad_available and USE_SCIPY_QUAD:
return quad(lambda tau: abs(self.derivative(tau)), t0, t1, return quad(lambda tau: abs(self.derivative(tau)), t0, t1,
epsabs=error, limit=1000)[0] epsabs=error, limit=1000)[0]
else: else:
@ -2572,7 +2532,7 @@ class Path(MutableSequence):
# Shortcuts # Shortcuts
if len(self._segments) == 0: if len(self._segments) == 0:
raise ValueError("This path contains no segments!") return None
if pos == 0.0: if pos == 0.0:
return self._segments[0].point(pos) return self._segments[0].point(pos)
if pos == 1.0: if pos == 1.0:
@ -2589,7 +2549,6 @@ class Path(MutableSequence):
segment_end - segment_start) segment_end - segment_start)
return segment.point(segment_pos) return segment.point(segment_pos)
segment_start = segment_end segment_start = segment_end
raise RuntimeError("Something has gone wrong. Could not compute Path.point({}) for path {}".format(pos, self))
def length(self, T0=0, T1=1, error=LENGTH_ERROR, min_depth=LENGTH_MIN_DEPTH): def length(self, T0=0, T1=1, error=LENGTH_ERROR, min_depth=LENGTH_MIN_DEPTH):
self._calc_lengths(error=error, min_depth=min_depth) self._calc_lengths(error=error, min_depth=min_depth)

View File

@ -6,7 +6,6 @@
from __future__ import division, absolute_import, print_function from __future__ import division, absolute_import, print_function
import os import os
from xml.etree.ElementTree import iterparse, Element, ElementTree, SubElement from xml.etree.ElementTree import iterparse, Element, ElementTree, SubElement
import numpy as np
# Internal dependencies # Internal dependencies
from .parser import parse_path from .parser import parse_path
@ -14,13 +13,13 @@ from .parser import parse_transform
from .svg_to_paths import (path2pathd, ellipse2pathd, line2pathd, from .svg_to_paths import (path2pathd, ellipse2pathd, line2pathd,
polyline2pathd, polygon2pathd, rect2pathd) polyline2pathd, polygon2pathd, rect2pathd)
from .misctools import open_in_browser from .misctools import open_in_browser
from .path import transform from .path import *
# To maintain forward/backward compatibility # To maintain forward/backward compatibility
try: try:
string = basestring str = basestring
except NameError: except NameError:
string = str pass
NAME_SVG = "svg" NAME_SVG = "svg"
ATTR_VERSION = "version" ATTR_VERSION = "version"
@ -165,17 +164,17 @@ class SaxDocument:
if matrix is not None and not np.all(np.equal(matrix, identity)): if matrix is not None and not np.all(np.equal(matrix, identity)):
matrix_string = "matrix(" matrix_string = "matrix("
matrix_string += " " matrix_string += " "
matrix_string += string(matrix[0][0]) matrix_string += str(matrix[0][0])
matrix_string += " " matrix_string += " "
matrix_string += string(matrix[1][0]) matrix_string += str(matrix[1][0])
matrix_string += " " matrix_string += " "
matrix_string += string(matrix[0][1]) matrix_string += str(matrix[0][1])
matrix_string += " " matrix_string += " "
matrix_string += string(matrix[1][1]) matrix_string += str(matrix[1][1])
matrix_string += " " matrix_string += " "
matrix_string += string(matrix[0][2]) matrix_string += str(matrix[0][2])
matrix_string += " " matrix_string += " "
matrix_string += string(matrix[1][2]) matrix_string += str(matrix[1][2])
matrix_string += ")" matrix_string += ")"
path.set(ATTR_TRANSFORM, matrix_string) path.set(ATTR_TRANSFORM, matrix_string)
if ATTR_DATA in values: if ATTR_DATA in values:

View File

@ -4,13 +4,8 @@ The main tool being the svg2paths() function."""
# External dependencies # External dependencies
from __future__ import division, absolute_import, print_function from __future__ import division, absolute_import, print_function
from xml.dom.minidom import parse from xml.dom.minidom import parse
import os from os import path as os_path, getcwd
from io import StringIO
import re import re
try:
from os import PathLike as FilePathLike
except ImportError:
FilePathLike = str
# Internal dependencies # Internal dependencies
from .parser import parse_path from .parser import parse_path
@ -51,7 +46,7 @@ def ellipse2pathd(ellipse):
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'
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' return d
def polyline2pathd(polyline, is_polygon=False): def polyline2pathd(polyline, is_polygon=False):
@ -149,9 +144,7 @@ def svg2paths(svg_file_location,
SVG Path, Line, Polyline, Polygon, Circle, and Ellipse elements. SVG Path, Line, Polyline, Polygon, Circle, and Ellipse elements.
Args: Args:
svg_file_location (string or file-like object): the location of the svg_file_location (string): the location of the svg file
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 return_svg_attributes (bool): Set to True and a dictionary of
svg-attributes will be extracted and returned. See also the svg-attributes will be extracted and returned. See also the
`svg2paths2()` function. `svg2paths2()` function.
@ -175,10 +168,8 @@ def svg2paths(svg_file_location,
list: The list of corresponding path attribute dictionaries. list: The list of corresponding path attribute dictionaries.
dict (optional): A dictionary of svg-attributes (see `svg2paths2()`). dict (optional): A dictionary of svg-attributes (see `svg2paths2()`).
""" """
# strings are interpreted as file location everything else is treated as if os_path.dirname(svg_file_location) == '':
# file-like object and passed to the xml parser directly 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) doc = parse(svg_file_location)
@ -258,26 +249,3 @@ def svg2paths2(svg_file_location,
convert_polylines_to_paths=convert_polylines_to_paths, convert_polylines_to_paths=convert_polylines_to_paths,
convert_polygons_to_paths=convert_polygons_to_paths, convert_polygons_to_paths=convert_polygons_to_paths,
convert_rectangles_to_paths=convert_rectangles_to_paths) convert_rectangles_to_paths=convert_rectangles_to_paths)
def svgstr2paths(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)
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,
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)

View File

@ -1,19 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100mm" height="100mm" viewBox="-100 -200 500 500" xmlns="http://www.w3.org/2000/svg" version="1.1">
<g id="Sketch" transform="scale(1,-1)">
<path id="slot" d="
M 0 10
L 0 80
A 30 30 0 1 0 0 140
A 10 10 0 0 1 0 100
L 100 100
A 10 10 0 1 1 100 140
A 30 30 0 0 0 100 80
L 100 10
A 10 10 0 0 0 90 0
L 10 0
A 10 10 0 0 0 0 10
" stroke="#ff0000" stroke-width="0.35 px"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 665 B

View File

@ -1,7 +1,7 @@
from __future__ import division, absolute_import, print_function from __future__ import division, absolute_import, print_function
import numpy as np import numpy as np
import unittest import unittest
from svgpathtools.bezier import bezier_point, bezier2polynomial, polynomial2bezier from svgpathtools.bezier import *
from svgpathtools.path import bpoints2bezier from svgpathtools.path import bpoints2bezier

View File

@ -1,54 +0,0 @@
from __future__ import division, absolute_import, print_function
import unittest
from svgpathtools import Document
from io import StringIO
from io import open # overrides build-in open for compatibility with python2
from os.path import join, dirname
from sys import version_info
class TestDocument(unittest.TestCase):
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"""
if version_info >= (3, 6):
import pathlib
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"""
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', encoding='utf-8') as file:
# read entire file into string
file_content = file.read()
# prepare stringio object
file_as_stringio = StringIO(file_content)
doc = Document(file_as_stringio)
self.assertEqual(len(doc.paths()), 2)
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()
doc = Document.from_svg_string(file_content)
self.assertEqual(len(doc.paths()), 2)

View File

@ -2,7 +2,7 @@
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
from __future__ import division, absolute_import, print_function from __future__ import division, absolute_import, print_function
import unittest import unittest
from svgpathtools import parse_path from svgpathtools import *
class TestGeneration(unittest.TestCase): class TestGeneration(unittest.TestCase):

View File

@ -5,15 +5,11 @@ $ python -m unittest test.test_groups.TestGroups.test_group_flatten
""" """
from __future__ import division, absolute_import, print_function from __future__ import division, absolute_import, print_function
import unittest import unittest
from svgpathtools import Document, SVG_NAMESPACE, parse_path, Line, Arc from svgpathtools import *
from os.path import join, dirname from os.path import join, dirname
import numpy as np import numpy as np
# When an assert fails, show the full error message, don't truncate it.
unittest.util._MAX_LENGTH = 999999999
def get_desired_path(name, paths): def get_desired_path(name, paths):
return next(p for p in paths return next(p for p in paths
if p.element.get('{some://testuri}name') == name) if p.element.get('{some://testuri}name') == name)
@ -46,22 +42,6 @@ class TestGroups(unittest.TestCase):
self.check_values(tf.dot(v_s), actual.start) self.check_values(tf.dot(v_s), actual.start)
self.check_values(tf.dot(v_e), actual.end) self.check_values(tf.dot(v_e), actual.end)
def test_group_transform(self):
# The input svg has a group transform of "scale(1,-1)", which
# can mess with Arc sweeps.
doc = Document(join(dirname(__file__), 'negative-scale.svg'))
path = doc.paths()[0]
self.assertEqual(path[0], Line(start=-10j, end=-80j))
self.assertEqual(path[1], Arc(start=-80j, radius=(30+30j), rotation=0.0, large_arc=True, sweep=True, end=-140j))
self.assertEqual(path[2], Arc(start=-140j, radius=(20+20j), rotation=0.0, large_arc=False, sweep=False, end=-100j))
self.assertEqual(path[3], Line(start=-100j, end=(100-100j)))
self.assertEqual(path[4], Arc(start=(100-100j), radius=(20+20j), rotation=0.0, large_arc=True, sweep=False, end=(100-140j)))
self.assertEqual(path[5], Arc(start=(100-140j), radius=(30+30j), rotation=0.0, large_arc=False, sweep=True, end=(100-80j)))
self.assertEqual(path[6], Line(start=(100-80j), end=(100-10j)))
self.assertEqual(path[7], Arc(start=(100-10j), radius=(10+10j), rotation=0.0, large_arc=False, sweep=True, end=(90+0j)))
self.assertEqual(path[8], Line(start=(90+0j), end=(10+0j)))
self.assertEqual(path[9], Arc(start=(10+0j), radius=(10+10j), rotation=0.0, large_arc=False, sweep=True, end=-10j))
def test_group_flatten(self): def test_group_flatten(self):
# Test the Document.paths() function against the # Test the Document.paths() function against the
# groups.svg test file. # groups.svg test file.

View File

@ -1,9 +1,8 @@
# Note: This file was taken mostly as is from the svg.path module (v 2.0) # Note: This file was taken mostly as is from the svg.path module (v 2.0)
from __future__ import division, absolute_import, print_function from __future__ import division, absolute_import, print_function
import unittest import unittest
from svgpathtools import Path, Line, QuadraticBezier, CubicBezier, Arc, parse_path from svgpathtools import *
import svgpathtools import svgpathtools
import numpy as np import numpy as np

View File

@ -1,7 +1,6 @@
# External dependencies # External dependencies
from __future__ import division, absolute_import, print_function from __future__ import division, absolute_import, print_function
import os import os
import time
from sys import version_info from sys import version_info
import unittest import unittest
from math import sqrt, pi from math import sqrt, pi
@ -11,12 +10,8 @@ import random
import warnings import warnings
# Internal dependencies # Internal dependencies
from svgpathtools import ( from svgpathtools import *
Line, QuadraticBezier, CubicBezier, Arc, Path, poly2bez, path_encloses_pt, from svgpathtools.path import _NotImplemented4ArcException, bezier_radialrange
bpoints2bezier, closest_point_in_path, farthest_point_in_path,
is_bezier_segment, is_bezier_path, parse_path
)
from svgpathtools.path import bezier_radialrange
# An important note for those doing any debugging: # An important note for those doing any debugging:
# ------------------------------------------------ # ------------------------------------------------
@ -1496,50 +1491,6 @@ class Test_intersect(unittest.TestCase):
self.assertTrue(len(yix) == 1) self.assertTrue(len(yix) == 1)
################################################################### ###################################################################
def test_random_intersections(self):
from random import Random
r = Random()
distance = 100
distribution = 10000
count = 500
def random_complex(offset_x=0.0, offset_y=0.0):
return complex(r.random() * distance + offset_x, r.random() * distance + offset_y)
def random_line():
offset_x = r.random() * distribution
offset_y = r.random() * distribution
return Line(random_complex(offset_x, offset_y), random_complex(offset_x, offset_y))
def random_quad():
offset_x = r.random() * distribution
offset_y = r.random() * distribution
return QuadraticBezier(random_complex(offset_x, offset_y), random_complex(offset_x, offset_y), random_complex(offset_x, offset_y))
def random_cubic():
offset_x = r.random() * distribution
offset_y = r.random() * distribution
return CubicBezier(random_complex(offset_x, offset_y), random_complex(offset_x, offset_y), random_complex(offset_x, offset_y), random_complex(offset_x, offset_y))
def random_path():
path = Path()
for i in range(count):
type_segment = random.randint(0, 3)
if type_segment == 0:
path.append(random_line())
if type_segment == 1:
path.append(random_quad())
if type_segment == 2:
path.append(random_cubic())
return path
path1 = random_path()
path2 = random_path()
t = time.time()
intersections = path1.intersect(path2)
print("\nFound {} intersections in {} seconds.\n"
"".format(len(intersections), time.time() - t))
def test_line_line_0(self): def test_line_line_0(self):
l0 = Line(start=(25.389999999999997+99.989999999999995j), l0 = Line(start=(25.389999999999997+99.989999999999995j),
end=(25.389999999999997+90.484999999999999j)) end=(25.389999999999997+90.484999999999999j))

View File

@ -4,7 +4,7 @@ import unittest
import numpy as np import numpy as np
# Internal dependencies # Internal dependencies
from svgpathtools import rational_limit from svgpathtools import *
class Test_polytools(unittest.TestCase): class Test_polytools(unittest.TestCase):

View File

@ -1,6 +1,6 @@
from __future__ import division, absolute_import, print_function from __future__ import division, absolute_import, print_function
import unittest import unittest
from svgpathtools import SaxDocument from svgpathtools import *
from os.path import join, dirname from os.path import join, dirname

View File

@ -1,17 +1,10 @@
from __future__ import division, absolute_import, print_function from __future__ import division, absolute_import, print_function
import unittest import unittest
from svgpathtools import Path, Line, Arc, svg2paths, svgstr2paths from svgpathtools import *
from io import StringIO
from io import open # overrides build-in open for compatibility with python2
import os
from os.path import join, dirname from os.path import join, dirname
from sys import version_info
import tempfile
import shutil
from svgpathtools.svg_to_paths import rect2pathd from svgpathtools.svg_to_paths import rect2pathd
class TestSVG2Paths(unittest.TestCase): class TestSVG2Paths(unittest.TestCase):
def test_svg2paths_polygons(self): def test_svg2paths_polygons(self):
@ -60,77 +53,8 @@ class TestSVG2Paths(unittest.TestCase):
self.assertTrue(path_circle==path_circle_correct) self.assertTrue(path_circle==path_circle_correct)
self.assertTrue(path_circle.isclosed()) self.assertTrue(path_circle.isclosed())
# test for issue #198 (circles not being closed)
svg = u"""<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" width="40mm" height="40mm"
viewBox="0 0 40 40" version="1.1">
<g id="layer">
<circle id="c1" cx="20.000" cy="20.000" r="11.000" />
<circle id="c2" cx="20.000" cy="20.000" r="5.15" />
</g>
</svg>"""
tmpdir = tempfile.mkdtemp()
svgfile = os.path.join(tmpdir, 'test.svg')
with open(svgfile, 'w') as f:
f.write(svg)
paths, _ = svg2paths(svgfile)
self.assertEqual(len(paths), 2)
self.assertTrue(paths[0].isclosed())
self.assertTrue(paths[1].isclosed())
shutil.rmtree(tmpdir)
def test_rect2pathd(self): def test_rect2pathd(self):
non_rounded = {"x":"10", "y":"10", "width":"100","height":"100"} 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') 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"} 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") 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"""
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"""
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"""
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"""
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
file_as_stringio = StringIO(file_content)
paths, _ = svg2paths(file_as_stringio)
self.assertEqual(len(paths), 2)
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()
paths, _ = svgstr2paths(file_content)
self.assertEqual(len(paths), 2)
if __name__ == '__main__':
unittest.main()