Compare commits

...

20 Commits

Author SHA1 Message Date
Andrew Port 67fd6e885d remove error-causing type check 2021-10-18 22:36:24 -07:00
Andrew Port f2eb3d0596 update official python version support 2021-10-18 22:23:13 -07:00
Andrew Port 2422d15251 update 2021-10-18 21:24:36 -07:00
Andrew Port 2368627a17 update 2021-09-23 02:18:32 -07:00
Andrew Port 0c5dc9de1a fixed ElementTree issue in python3 2021-09-23 00:59:10 -07:00
Andrew Port 657a9d6745 updated from master 2021-09-23 00:22:55 -07:00
Andrew Port 3e1f8e00a5 minor cleanup 2021-09-23 00:21:41 -07:00
Andrew Port 05408cfa26 update (tests not passing in python2, need to investigate) 2021-09-22 21:17:35 -07:00
Andrew Port e71d2d4282 remove ambiguous except blocks 2021-09-21 02:49:00 -07:00
Andrew Port 413a2864f6 cleanup to avoid linting issues 2021-09-21 02:44:39 -07:00
Andrew Port a2b62fc011 add docstring 2021-09-21 02:40:35 -07:00
Andrew Port d86c63214b clean up docstrings 2021-09-21 02:34:34 -07:00
Andrew Port d2b1ea5770 make imports explicit 2021-09-21 02:22:12 -07:00
Andrew Port da050a2eeb replace xml parsers with defusedxml versions 2021-09-21 01:54:58 -07:00
Andrew Port 0a31f348d6 remove warning 2021-09-21 01:54:25 -07:00
Andrew Port 9863e7050a add python2 compatible warning check for closed property 2021-09-21 01:54:11 -07:00
Andrew Port 11682a3363 suppress unneeded numpy warnings from QuadraticBezier.length() 2021-09-21 01:39:25 -07:00
Andrew Port 4f615f9a9d replace ambiguous except block with if-statement 2021-09-20 23:41:55 -07:00
Andrew Port ace8522c19 fix most linter warnings in test_path.py 2021-09-20 23:41:15 -07:00
Andrew Port d881b21b47 remove unused import 2021-09-20 22:40:38 -07:00
18 changed files with 218 additions and 172 deletions

View File

@ -1,17 +1,23 @@
"""The goal of this gist is to show how to compute many points on a path """ An example of how to speed up point() calculations with vectorization.
The goal of this gist is to show how to compute many points on a path
quickly using NumPy arrays. I.e. there's a much faster way than using, say quickly using NumPy arrays. I.e. there's a much faster way than using, say
[some_path.point(t) for t in many_tvals]. The example below assumes the [some_path.point(t) for t in many_tvals]. The example below assumes the
`Path` object is composed entirely of `CubicBezier` objects, but this can `Path` object is composed entirely of `CubicBezier` objects, but this can
easily be generalized to paths containing `Line` and `QuadraticBezier` objects easily be generalized to paths containing `Line` and `QuadraticBezier` objects
also. also.
Note: The relevant matrix transformation for quadratics can be found in the 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 * from svgpathtools import bezier_point, bpoints2bezier, polynomial2bezier, Path
class HigherOrderBezier: class HigherOrderBezier:
"""Bezier curve of arbitrary degree"""
def __init__(self, bpoints): def __init__(self, bpoints):
self.bpts = bpoints self.bpts = bpoints
@ -38,7 +44,7 @@ def points_in_each_seg_slow(path, tvals):
def points_in_each_seg(path, tvals): def points_in_each_seg(path, tvals):
"""Compute seg.point(t) for each seg in path and each t in tvals.""" """Compute seg.point(t) for each seg in path and each t in tvals."""
A = np.array([[-1, 3, -3, 1], # transforms cubic bez to standard poly A = np.array([[-1, 3, -3, 1], # transforms cubic bez to standard poly
[ 3, -6, 3, 0], [ 3, -6, 3, 0],
[-3, 3, 0, 0], [-3, 3, 0, 0],
[ 1, 0, 0, 0]]) [ 1, 0, 0, 0]])

View File

@ -1,2 +1,3 @@
numpy numpy
svgwrite svgwrite
defusedxml

View File

@ -30,9 +30,9 @@ 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'], install_requires=['numpy', 'svgwrite', 'defusedxml'],
platforms="OS Independent", platforms="OS Independent",
requires=['numpy', 'svgwrite'], requires=['numpy', 'svgwrite', 'defusedxml'],
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=[
"Development Status :: 4 - Beta", "Development Status :: 4 - Beta",

View File

@ -31,13 +31,9 @@ def bezier_point(p, t):
Warning: Be concerned about numerical stability when using this function Warning: Be concerned about numerical stability when using this function
with high order curves.""" with high order curves."""
# begin arc support block ######################## # for Arc support
try: if hasattr(p, 'radius'):
p.large_arc
return p.point(t) return p.point(t)
except:
pass
# end arc support block ##########################
deg = len(p) - 1 deg = len(p) - 1
if deg == 3: if deg == 3:
@ -145,14 +141,11 @@ def split_bezier(bpoints, t):
def halve_bezier(p): def halve_bezier(p):
"""split path segment into two halves at t=0.5"""
# begin arc support block ######################## # for Arc support
try: if hasattr(p, 'radius'):
p.large_arc
return p.split(0.5) return p.split(0.5)
except:
pass
# end arc support block ##########################
if len(p) == 4: if len(p) == 4:
return ([p[0], (p[0] + p[1])/2, (p[0] + 2*p[1] + p[2])/4, return ([p[0], (p[0] + p[1])/2, (p[0] + 2*p[1] + p[2])/4,
@ -199,13 +192,9 @@ def bezier_bounding_box(bez):
(xmin, xmax, ymin, ymax). (xmin, xmax, ymin, ymax).
Warning: For the non-cubic case this is not particularly efficient.""" Warning: For the non-cubic case this is not particularly efficient."""
# begin arc support block ######################## # for Arc support
try: if hasattr(bez, 'radius'):
bla = bez.large_arc return bez.bbox()
return bez.bbox() # added to support Arc objects
except:
pass
# end arc support block ##########################
if len(bez) == 4: if len(bez) == 4:
xmin, xmax = bezier_real_minmax([p.real for p in bez]) xmin, xmax = bezier_real_minmax([p.real for p in bez])

View File

@ -36,10 +36,15 @@ A Big Problem:
# 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 sys
import collections import collections
import xml.etree.ElementTree as etree from defusedxml.cElementTree import parse, tostring
from xml.etree.ElementTree import Element, SubElement, register_namespace from xml.etree.cElementTree import register_namespace
from xml.dom.minidom import parseString if sys.version_info.major == 2:
from xml.etree.ElementTree import Element, SubElement, ElementTree
else:
from xml.etree.cElementTree import Element, SubElement, ElementTree
from defusedxml.minidom import parseString
import warnings import warnings
from tempfile import gettempdir from tempfile import gettempdir
from time import time from time import time
@ -97,9 +102,6 @@ def flattened_paths(group, group_filter=lambda x: True,
only convert explicit path elements, pass in only convert explicit path elements, pass in
`path_conversions=CONVERT_ONLY_PATHS`. `path_conversions=CONVERT_ONLY_PATHS`.
""" """
if not isinstance(group, Element):
raise TypeError('Must provide an xml.etree.Element object. '
'Instead you provided {0}'.format(type(group)))
# Stop right away if the group_selector rejects this group # Stop right away if the group_selector rejects this group
if not group_filter(group): if not group_filter(group):
@ -244,10 +246,10 @@ class Document:
self.original_filepath = os.path.join(os.getcwd(), filepath) 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 = ElementTree(Element('svg'))
else: else:
# parse svg to ElementTree object # parse svg to ElementTree object
self.tree = etree.parse(filepath) self.tree = parse(filepath)
self.root = self.tree.getroot() self.root = self.tree.getroot()
@ -416,7 +418,7 @@ class Document:
SVG_NAMESPACE['svg']), group_attribs) SVG_NAMESPACE['svg']), group_attribs)
def __repr__(self): def __repr__(self):
return etree.tostring(self.tree.getroot()).decode() return tostring(self.tree.getroot()).decode()
def pretty(self, **kwargs): def pretty(self, **kwargs):
return parseString(repr(self)).toprettyxml(**kwargs) return parseString(repr(self)).toprettyxml(**kwargs)

View File

@ -920,6 +920,7 @@ class QuadraticBezier(object):
if t0 == 1 and t1 == 0: if t0 == 1 and t1 == 0:
if self._length_info['bpoints'] == self.bpoints(): if self._length_info['bpoints'] == self.bpoints():
return self._length_info['length'] return self._length_info['length']
a = self.start - 2*self.control + self.end a = self.start - 2*self.control + self.end
b = 2*(self.control - self.start) b = 2*(self.control - self.start)
a_dot_b = a.real*b.real + a.imag*b.imag a_dot_b = a.real*b.real + a.imag*b.imag
@ -927,20 +928,23 @@ class QuadraticBezier(object):
if abs(a) < 1e-12: if abs(a) < 1e-12:
s = abs(b)*(t1 - t0) s = abs(b)*(t1 - t0)
else: else:
c2 = 4 * (a.real ** 2 + a.imag ** 2) with np.testing.suppress_warnings() as sup:
c1 = 4 * a_dot_b sup.filter(RuntimeWarning)
c0 = b.real ** 2 + b.imag ** 2 c2 = 4 * (a.real ** 2 + a.imag ** 2)
c1 = 4 * a_dot_b
c0 = b.real ** 2 + b.imag ** 2
beta = c1 / (2 * c2) beta = c1 / (2 * c2)
gamma = c0 / c2 - beta ** 2 gamma = c0 / c2 - beta ** 2
dq1_mag = sqrt(c2 * t1 ** 2 + c1 * t1 + c0)
dq0_mag = sqrt(c2 * t0 ** 2 + c1 * t0 + c0)
logarand = (sqrt(c2) * (t1 + beta) + dq1_mag) / \
(sqrt(c2) * (t0 + beta) + dq0_mag)
s = (t1 + beta) * dq1_mag - (t0 + beta) * dq0_mag + \
gamma * sqrt(c2) * log(logarand)
s /= 2
dq1_mag = sqrt(c2 * t1 ** 2 + c1 * t1 + c0)
dq0_mag = sqrt(c2 * t0 ** 2 + c1 * t0 + c0)
logarand = (sqrt(c2) * (t1 + beta) + dq1_mag) / \
(sqrt(c2) * (t0 + beta) + dq0_mag)
s = (t1 + beta) * dq1_mag - (t0 + beta) * dq0_mag + \
gamma * sqrt(c2) * log(logarand)
s /= 2
if isnan(s): if isnan(s):
tstar = abs(b) / (2 * abs(a)) tstar = abs(b) / (2 * abs(a))
if t1 < tstar: if t1 < tstar:

View File

@ -8,7 +8,7 @@ from __future__ import division, absolute_import, print_function
from math import ceil from math import ceil
from os import path as os_path, makedirs from os import path as os_path, makedirs
from tempfile import gettempdir from tempfile import gettempdir
from xml.dom.minidom import parse as md_xml_parse from defusedxml.minidom import parse as md_xml_parse
from svgwrite import Drawing, text as txt from svgwrite import Drawing, text as txt
from time import time from time import time
from warnings import warn from warnings import warn

View File

@ -10,19 +10,26 @@ from .misctools import isclose
def polyroots(p, realroots=False, condition=lambda r: True): def polyroots(p, realroots=False, condition=lambda r: True):
"""Returns the roots of a polynomial with coefficients given in p.
p[0] * x**n + p[1] * x**(n-1) + ... + p[n-1]*x + p[n]
Args:
p: 1D array-like object of polynomial coefficients.
realroots: a boolean. If true, only real roots will be returned
and the condition function can be written assuming all roots
are real.
condition: a boolean-valued function. Only roots satisfying
this will be returned. If realroots==True, these conditions
should assume the roots are real.
Returns:
(list) A list containing the roots of the polynomial.
Notes:
* This uses np.isclose and np.roots
""" """
Returns the roots of a polynomial with coefficients given in p.
p[0] * x**n + p[1] * x**(n-1) + ... + p[n-1]*x + p[n]
INPUT:
p - Rank-1 array-like object of polynomial coefficients.
realroots - a boolean. If true, only real roots will be returned and the
condition function can be written assuming all roots are real.
condition - a boolean-valued function. Only roots satisfying this will be
returned. If realroots==True, these conditions should assume the roots
are real.
OUTPUT:
A list containing the roots of the polynomial.
NOTE: This uses np.isclose and np.roots"""
roots = np.roots(p) roots = np.roots(p)
if realroots: if realroots:
roots = [r.real for r in roots if isclose(r.imag, 0)] roots = [r.real for r in roots if isclose(r.imag, 0)]
@ -36,16 +43,18 @@ def polyroots(p, realroots=False, condition=lambda r: True):
def polyroots01(p): def polyroots01(p):
"""Returns the real roots between 0 and 1 of the polynomial with """Returns the real roots 0 < x < 1 of the polynomial given by `p`.
coefficients given in p,
p[0] * x**n + p[1] * x**(n-1) + ... + p[n-1]*x + p[n] p[0] * x**n + p[1] * x**(n-1) + ... + p[n-1]*x + p[n]
p can also be a np.poly1d object. See polyroots for more information."""
Notes:
p can also be a np.poly1d object. See polyroots for more information.
"""
return polyroots(p, realroots=True, condition=lambda tval: 0 <= tval <= 1) return polyroots(p, realroots=True, condition=lambda tval: 0 <= tval <= 1)
def rational_limit(f, g, t0): def rational_limit(f, g, t0):
"""Computes the limit of the rational function (f/g)(t) """Computes the limit of the rational function (f/g)(t) as t approaches t0."""
as t approaches t0."""
assert isinstance(f, np.poly1d) and isinstance(g, np.poly1d) assert isinstance(f, np.poly1d) and isinstance(g, np.poly1d)
assert g != np.poly1d([0]) assert g != np.poly1d([0])
if g(t0) != 0: if g(t0) != 0:

View File

@ -5,7 +5,8 @@
# External dependencies # External dependencies
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.cElementTree import Element, ElementTree, SubElement
from defusedxml.cElementTree import iterparse
# Internal dependencies # Internal dependencies
from .parser import parse_path from .parser import parse_path

View File

@ -3,7 +3,7 @@ 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 defusedxml.minidom import parse
from os import path as os_path, getcwd from os import path as os_path, getcwd
import re import re
@ -17,9 +17,11 @@ COORD_PAIR_TMPLT = re.compile(
r'([\+-]?\d*[\.\d]\d*[eE][\+-]?\d+|[\+-]?\d*[\.\d]\d*)' r'([\+-]?\d*[\.\d]\d*[eE][\+-]?\d+|[\+-]?\d*[\.\d]\d*)'
) )
def path2pathd(path): def path2pathd(path):
return path.get('d', '') return path.get('d', '')
def ellipse2pathd(ellipse): def ellipse2pathd(ellipse):
"""converts the parameters from an ellipse or a circle to a string for a """converts the parameters from an ellipse or a circle to a string for a
Path object d-attribute""" Path object d-attribute"""

View File

@ -1,11 +1,12 @@
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 * from svgpathtools.bezier import bezier_point, bezier2polynomial, polynomial2bezier
from svgpathtools.path import bpoints2bezier from svgpathtools.path import bpoints2bezier
class HigherOrderBezier: class HigherOrderBezier:
"""To help test Bezier curves of arbitrary degree"""
def __init__(self, bpoints): def __init__(self, bpoints):
self.bpts = bpoints self.bpts = bpoints

View File

@ -1,8 +1,7 @@
# Note: This file was taken mostly as is from the svg.path module (v 2.0) """credit: This was modified from a file in 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 * from svgpathtools import parse_path
class TestGeneration(unittest.TestCase): class TestGeneration(unittest.TestCase):

View File

@ -5,7 +5,7 @@ $ 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 * from svgpathtools import Document, SVG_NAMESPACE, parse_path
from os.path import join, dirname from os.path import join, dirname
import numpy as np import numpy as np

View File

@ -1,7 +1,7 @@
# 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 * from svgpathtools import parse_path, Path, Line, QuadraticBezier, CubicBezier, Arc
import svgpathtools import svgpathtools
import numpy as np import numpy as np

View File

@ -1,8 +1,6 @@
# External dependencies # External dependencies
from __future__ import division, absolute_import, print_function from __future__ import division, absolute_import, print_function
import os from unittest import TestCase
import sys
import unittest
from math import sqrt, pi from math import sqrt, pi
from operator import itemgetter from operator import itemgetter
import numpy as np import numpy as np
@ -10,8 +8,11 @@ import random
import warnings import warnings
# Internal dependencies # Internal dependencies
from svgpathtools import * from svgpathtools import (
from svgpathtools.path import _NotImplemented4ArcException, bezier_radialrange Line, QuadraticBezier, CubicBezier, Arc, Path, parse_path,
is_bezier_segment, is_bezier_path, poly2bez, bpoints2bezier,
closest_point_in_path, farthest_point_in_path, path_encloses_pt)
from svgpathtools.path import bezier_radialrange
# An important note for those doing any debugging: # An important note for those doing any debugging:
# ------------------------------------------------ # ------------------------------------------------
@ -66,7 +67,25 @@ def assert_intersections(test_case, a_seg, b_seg, intersections, count, msg=None
test_case.assertAlmostEqual(a_seg.point(i[0]), b_seg.point(i[1]), msg=msg, delta=tol) test_case.assertAlmostEqual(a_seg.point(i[0]), b_seg.point(i[1]), msg=msg, delta=tol)
class LineTest(unittest.TestCase): class AssertWarns(warnings.catch_warnings):
"""A python 2 compatible version of assertWarns."""
def __init__(self, test_case, warning):
self.test_case = test_case
self.warning_type = warning
self.log = None
super(AssertWarns, self).__init__(record=True, module=None)
def __enter__(self):
self.log = super(AssertWarns, self).__enter__()
return self.log
def __exit__(self, *exc_info):
super(AssertWarns, self).__exit__(*exc_info)
self.test_case.assertEqual(type(self.log[0]), self.warning_type)
# noinspection PyTypeChecker
class LineTest(TestCase):
def test_lines(self): def test_lines(self):
# These points are calculated, and not just regression tests. # These points are calculated, and not just regression tests.
@ -160,9 +179,9 @@ class LineTest(unittest.TestCase):
self.assertIsNone(l.point_to_t(-0.001-0j)) self.assertIsNone(l.point_to_t(-0.001-0j))
random.seed() random.seed()
for line_index in range(100): for _ in range(100):
l = random_line() l = random_line()
for t_index in range(100): for __ in range(100):
orig_t = random.random() orig_t = random.random()
p = l.point(orig_t) p = l.point(orig_t)
computed_t = l.point_to_t(p) computed_t = l.point_to_t(p)
@ -183,7 +202,8 @@ class LineTest(unittest.TestCase):
self.assertAlmostEqual(max_ta, max_tb, delta=TOL) self.assertAlmostEqual(max_ta, max_tb, delta=TOL)
class CubicBezierTest(unittest.TestCase): # noinspection PyTypeChecker
class CubicBezierTest(TestCase):
def test_approx_circle(self): def test_approx_circle(self):
"""This is a approximate circle drawn in Inkscape""" """This is a approximate circle drawn in Inkscape"""
@ -419,14 +439,13 @@ class CubicBezierTest(unittest.TestCase):
segment = CubicBezier(complex(600, 500), complex(600, 350), segment = CubicBezier(complex(600, 500), complex(600, 350),
complex(900, 650), complex(900, 500)) complex(900, 650), complex(900, 500))
self.assertTrue(segment == self.assertTrue(segment == CubicBezier(600 + 500j, 600 + 350j, 900 + 650j, 900 + 500j))
CubicBezier(600 + 500j, 600 + 350j, 900 + 650j, 900 + 500j)) self.assertTrue(segment != CubicBezier(600 + 501j, 600 + 350j, 900 + 650j, 900 + 500j))
self.assertTrue(segment !=
CubicBezier(600 + 501j, 600 + 350j, 900 + 650j, 900 + 500j))
self.assertTrue(segment != Line(0, 400)) self.assertTrue(segment != Line(0, 400))
class QuadraticBezierTest(unittest.TestCase): # noinspection PyTypeChecker
class QuadraticBezierTest(TestCase):
def test_svg_examples(self): def test_svg_examples(self):
"""These is the path in the SVG specs""" """These is the path in the SVG specs"""
@ -495,25 +514,24 @@ class QuadraticBezierTest(unittest.TestCase):
# This is to test the __eq__ and __ne__ methods, so we can't use # This is to test the __eq__ and __ne__ methods, so we can't use
# assertEqual and assertNotEqual # assertEqual and assertNotEqual
segment = QuadraticBezier(200 + 300j, 400 + 50j, 600 + 300j) segment = QuadraticBezier(200 + 300j, 400 + 50j, 600 + 300j)
self.assertTrue(segment == self.assertTrue(segment == QuadraticBezier(200 + 300j, 400 + 50j, 600 + 300j))
QuadraticBezier(200 + 300j, 400 + 50j, 600 + 300j)) self.assertTrue(segment != QuadraticBezier(200 + 301j, 400 + 50j, 600 + 300j))
self.assertTrue(segment !=
QuadraticBezier(200 + 301j, 400 + 50j, 600 + 300j))
self.assertFalse(segment == Arc(0j, 100 + 50j, 0, 0, 0, 100 + 50j)) self.assertFalse(segment == Arc(0j, 100 + 50j, 0, 0, 0, 100 + 50j))
self.assertTrue(Arc(0j, 100 + 50j, 0, 0, 0, 100 + 50j) != segment) self.assertTrue(Arc(0j, 100 + 50j, 0, 0, 0, 100 + 50j) != segment)
class ArcTest(unittest.TestCase): # noinspection PyTypeChecker
class ArcTest(TestCase):
def test_trusting_acos(self): def test_trusting_acos(self):
"""`u1.real` is > 1 in this arc due to numerical error.""" """`u1.real` is > 1 in this arc due to numerical error."""
try: try:
a1 = Arc(start=(160.197+102.925j), _ = Arc(start=(160.197+102.925j),
radius=(0.025+0.025j), radius=(0.025+0.025j),
rotation=0.0, rotation=0.0,
large_arc=False, large_arc=False,
sweep=True, sweep=True,
end=(160.172+102.95j)) end=(160.172+102.95j))
except ValueError: except ValueError:
self.fail("Arc() raised ValueError unexpectedly!") self.fail("Arc() raised ValueError unexpectedly!")
@ -680,9 +698,9 @@ class ArcTest(unittest.TestCase):
self.assertIsNone(a.point_to_t(730.5212132777968+171j)) self.assertIsNone(a.point_to_t(730.5212132777968+171j))
random.seed() random.seed()
for arc_index in range(100): for _ in range(100):
a = random_arc() a = random_arc()
for t_index in np.linspace(0, 1, 100): for __ in np.linspace(0, 1, 100):
orig_t = random.random() orig_t = random.random()
p = a.point(orig_t) p = a.point(orig_t)
computed_t = a.point_to_t(p) computed_t = a.point_to_t(p)
@ -692,7 +710,7 @@ class ArcTest(unittest.TestCase):
def test_approx_quad(self): def test_approx_quad(self):
n = 100 n = 100
for i in range(n): for _ in range(n):
arc = random_arc() arc = random_arc()
if arc.radius.real > 2000 or arc.radius.imag > 2000: if arc.radius.real > 2000 or arc.radius.imag > 2000:
continue # Random Arc too large, by autoscale. continue # Random Arc too large, by autoscale.
@ -705,7 +723,7 @@ class ArcTest(unittest.TestCase):
def test_approx_cubic(self): def test_approx_cubic(self):
n = 100 n = 100
for i in range(n): for _ in range(n):
arc = random_arc() arc = random_arc()
if arc.radius.real > 2000 or arc.radius.imag > 2000: if arc.radius.real > 2000 or arc.radius.imag > 2000:
continue # Random Arc too large, by autoscale. continue # Random Arc too large, by autoscale.
@ -717,7 +735,8 @@ class ArcTest(unittest.TestCase):
self.assertAlmostEqual(d, 0.0, delta=2) self.assertAlmostEqual(d, 0.0, delta=2)
class TestPath(unittest.TestCase): # noinspection PyTypeChecker
class TestPath(TestCase):
# def test_hash(self): # def test_hash(self):
# line1 = Line(600.5 + 350.5j, 650.5 + 325.5j) # line1 = Line(600.5 + 350.5j, 650.5 + 325.5j)
@ -810,8 +829,7 @@ class TestPath(unittest.TestCase):
# regression tests. # regression tests.
self.assertAlmostEqual(path.point(0.0), (275 + 175j), delta=TOL) self.assertAlmostEqual(path.point(0.0), (275 + 175j), delta=TOL)
self.assertAlmostEqual(path.point(0.2800495767557787), (275 + 25j), delta=TOL) self.assertAlmostEqual(path.point(0.2800495767557787), (275 + 25j), delta=TOL)
self.assertAlmostEqual(path.point(0.5), self.assertAlmostEqual(path.point(0.5), (168.93398282201787 + 68.93398282201787j))
(168.93398282201787 + 68.93398282201787j))
self.assertAlmostEqual(path.point(1 - 0.2800495767557787), (125 + 175j), delta=TOL) self.assertAlmostEqual(path.point(1 - 0.2800495767557787), (125 + 175j), delta=TOL)
self.assertAlmostEqual(path.point(1.0), (275 + 175j), delta=TOL) self.assertAlmostEqual(path.point(1.0), (275 + 175j), delta=TOL)
# The errors seem to accumulate. Still 6 decimal places is more # The errors seem to accumulate. Still 6 decimal places is more
@ -852,7 +870,7 @@ class TestPath(unittest.TestCase):
Arc(start=650 + 325j, radius=25 + 25j, rotation=-30, Arc(start=650 + 325j, radius=25 + 25j, rotation=-30,
large_arc=0, sweep=1, end=700 + 300j), large_arc=0, sweep=1, end=700 + 300j),
CubicBezier(start=700 + 300j, control1=800 + 400j, CubicBezier(start=700 + 300j, control1=800 + 400j,
control2=750 + 200j, end=600 + 100j), control2=750 + 200j, end=600 + 100j),
QuadraticBezier(start=600 + 100j, control=600, end=600 + 300j)) QuadraticBezier(start=600 + 100j, control=600, end=600 + 300j))
self.assertEqual(eval(repr(path)), path) self.assertEqual(eval(repr(path)), path)
@ -864,14 +882,14 @@ class TestPath(unittest.TestCase):
Arc(start=650 + 325j, radius=25 + 25j, rotation=-30, Arc(start=650 + 325j, radius=25 + 25j, rotation=-30,
large_arc=0, sweep=1, end=700 + 300j), large_arc=0, sweep=1, end=700 + 300j),
CubicBezier(start=700 + 300j, control1=800 + 400j, CubicBezier(start=700 + 300j, control1=800 + 400j,
control2=750 + 200j, end=600 + 100j), control2=750 + 200j, end=600 + 100j),
QuadraticBezier(start=600 + 100j, control=600, end=600 + 300j)) QuadraticBezier(start=600 + 100j, control=600, end=600 + 300j))
path2 = Path( path2 = Path(
Line(start=600 + 350j, end=650 + 325j), Line(start=600 + 350j, end=650 + 325j),
Arc(start=650 + 325j, radius=25 + 25j, rotation=-30, Arc(start=650 + 325j, radius=25 + 25j, rotation=-30,
large_arc=0, sweep=1, end=700 + 300j), large_arc=0, sweep=1, end=700 + 300j),
CubicBezier(start=700 + 300j, control1=800 + 400j, CubicBezier(start=700 + 300j, control1=800 + 400j,
control2=750 + 200j, end=600 + 100j), control2=750 + 200j, end=600 + 100j),
QuadraticBezier(start=600 + 100j, control=600, end=600 + 300j)) QuadraticBezier(start=600 + 100j, control=600, end=600 + 300j))
self.assertTrue(path1 == path2) self.assertTrue(path1 == path2)
@ -1041,17 +1059,17 @@ class TestPath(unittest.TestCase):
test_curves = [bezpath, bezpathz, path, pathz, lpath, qpath, cpath, test_curves = [bezpath, bezpathz, path, pathz, lpath, qpath, cpath,
apath, line1, arc1, arc2, cub1, cub2, quad3, linez] apath, line1, arc1, arc2, cub1, cub2, quad3, linez]
def scale_a_point(pt, sx, sy=None, origin=0j): def scale_a_point(pt_, sx_, sy_=None, origin_=0j):
if sy is None: if sy_ is None:
sy = sx sy_ = sx_
zeta = pt - origin zeta = pt_ - origin_
pt_vec = [[zeta.real], pt_vec = [[zeta.real],
[zeta.imag], [zeta.imag],
[1]] [1]]
transform = [[sx, 0, origin.real], transform = [[sx_, 0, origin_.real],
[0, sy, origin.imag]] [0, sy_, origin_.imag]]
return complex(*np.dot(transform, pt_vec).ravel()) return complex(*np.dot(transform, pt_vec).ravel())
@ -1075,6 +1093,8 @@ class TestPath(unittest.TestCase):
# find seg which t lands on for failure reporting # find seg which t lands on for failure reporting
seg = curve seg = curve
seg_idx = None
seg_t = None
if isinstance(curve, Path): if isinstance(curve, Path):
seg_idx, seg_t = curve.T2t(t) seg_idx, seg_t = curve.T2t(t)
seg = curve[seg_idx] seg = curve[seg_idx]
@ -1113,7 +1133,7 @@ class TestPath(unittest.TestCase):
curve.scaled(sx, sy).point(t) curve.scaled(sx, sy).point(t)
else: else:
curve_scaled = curve.scaled(sx, sy) curve_scaled = curve.scaled(sx, sy)
seg_scaled = seg.scaled(sx, sy) _ = seg.scaled(sx, sy)
if isinstance(curve, Path): if isinstance(curve, Path):
res = curve_scaled[seg_idx].point(seg_t) res = curve_scaled[seg_idx].point(seg_t)
else: else:
@ -1187,20 +1207,21 @@ class TestPath(unittest.TestCase):
self.assertEqual(path2.d(use_closed_attrib=True, rel=True), rel_s) self.assertEqual(path2.d(use_closed_attrib=True, rel=True), rel_s)
class Test_ilength(unittest.TestCase): # noinspection PyTypeChecker
class Test_ilength(TestCase):
# See svgpathtools.notes.inv_arclength.py for information on how these # See svgpathtools.notes.inv_arclength.py for information on how these
# test values were generated (using the .length() method). # test values were generated (using the .length() method).
############################################################## ##############################################################
def test_ilength_lines(self): def test_ilength_lines(self):
l = Line(1, 3-1j) l = Line(1, 3-1j)
nodall = Line(1+1j, 1+1j) # nodall = Line(1+1j, 1+1j)
tests = [(l, 0.01, 0.022360679774997897), tests = [(l, 0.01, 0.022360679774997897),
(l, 0.1, 0.223606797749979), (l, 0.1, 0.223606797749979),
(l, 0.5, 1.118033988749895), (l, 0.5, 1.118033988749895),
(l, 0.9, 2.012461179749811), (l, 0.9, 2.012461179749811),
(l, 0.99, 2.213707297724792)] (l, 0.99, 2.213707297724792)]
for (l, t, s) in tests: for (l, t, s) in tests:
self.assertAlmostEqual(l.ilength(s), t, delta=TOL) self.assertAlmostEqual(l.ilength(s), t, delta=TOL)
@ -1210,37 +1231,31 @@ class Test_ilength(unittest.TestCase):
q2 = QuadraticBezier(200 + 300j, 400 + 50j, 500 + 200j) q2 = QuadraticBezier(200 + 300j, 400 + 50j, 500 + 200j)
closedq = QuadraticBezier(6 + 2j, 5 - 1j, 6 + 2j) closedq = QuadraticBezier(6 + 2j, 5 - 1j, 6 + 2j)
linq = QuadraticBezier(1+3j, 2+5j, -9 - 17j) linq = QuadraticBezier(1+3j, 2+5j, -9 - 17j)
nodalq = QuadraticBezier(1, 1, 1) # nodalq = QuadraticBezier(1, 1, 1)
tests = [(q1, 0.01, 6.364183310105577), tests = [(q1, 0.01, 6.364183310105577),
(q1, 0.1, 60.23857499635088), (q1, 0.1, 60.23857499635088),
(q1, 0.5, 243.8855469477619), (q1, 0.5, 243.8855469477619),
(q1, 0.9, 427.53251889917294), (q1, 0.9, 427.53251889917294),
(q1, 0.99, 481.40691058541813), (q1, 0.99, 481.40691058541813),
(q2, 0.01, 6.365673533661836), (q2, 0.01, 6.365673533661836),
(q2, 0.1, 60.31675895732397), (q2, 0.1, 60.31675895732397),
(q2, 0.5, 233.24592830045907), (q2, 0.5, 233.24592830045907),
(q2, 0.9, 346.42891253298706), (q2, 0.9, 346.42891253298706),
(q2, 0.99, 376.32659156736844), (q2, 0.99, 376.32659156736844),
(closedq, 0.01, 0.06261309767133393), (closedq, 0.01, 0.06261309767133393),
(closedq, 0.1, 0.5692099788303084), (closedq, 0.1, 0.5692099788303084),
(closedq, 0.5, 1.5811388300841898), (closedq, 0.5, 1.5811388300841898),
(closedq, 0.9, 2.5930676813380713), (closedq, 0.9, 2.5930676813380713),
(closedq, 0.99, 3.0996645624970456), (closedq, 0.99, 3.0996645624970456),
(linq, 0.01, 0.04203807797699605), (linq, 0.01, 0.04203807797699605),
(linq, 0.1, 0.19379255804998186), (linq, 0.1, 0.19379255804998186),
(linq, 0.5, 4.844813951249544), (linq, 0.5, 4.844813951249544),
(linq, 0.9, 18.0823363780483), (linq, 0.9, 18.0823363780483),
(linq, 0.99, 22.24410609777091)] (linq, 0.99, 22.24410609777091)]
for q, t, s in tests: for q, t, s in tests:
try: self.assertAlmostEqual(q.ilength(s), t, delta=TOL)
self.assertAlmostEqual(q.ilength(s), t, delta=TOL)
except:
print(q)
print(s)
print(t)
raise
def test_ilength_cubics(self): def test_ilength_cubics(self):
c1 = CubicBezier(200 + 300j, 400 + 50j, 600+100j, -200) c1 = CubicBezier(200 + 300j, 400 + 50j, 600+100j, -200)
@ -1415,7 +1430,7 @@ class Test_ilength(unittest.TestCase):
for (c, t, s) in tests: for (c, t, s) in tests:
try: try:
self.assertAlmostEqual(c.ilength(s), t, msg=str((c, t, s)), delta=TOL) self.assertAlmostEqual(c.ilength(s), t, msg=str((c, t, s)), delta=TOL)
except: except ValueError:
# These test case values were generated using a system # These test case values were generated using a system
# with scipy installed -- if scipy is not installed, # with scipy installed -- if scipy is not installed,
# then in cases where `t == 1`, `s` may be slightly # then in cases where `t == 1`, `s` may be slightly
@ -1438,7 +1453,8 @@ class Test_ilength(unittest.TestCase):
lin.ilength(1) lin.ilength(1)
class Test_intersect(unittest.TestCase): # noinspection PyTypeChecker
class Test_intersect(TestCase):
def test_intersect(self): def test_intersect(self):
################################################################### ###################################################################
@ -1579,9 +1595,9 @@ class Test_intersect(unittest.TestCase):
assert_intersections(self, a, l, intersections, 0) assert_intersections(self, a, l, intersections, 0)
random.seed() random.seed()
for arc_index in range(50): for _ in range(50):
a = random_arc() a = random_arc()
for line_index in range(100): for __ in range(100):
l = random_line() l = random_line()
intersections = a.intersect(l) intersections = a.intersect(l)
msg = 'Generated: arc = {}, line = {}'.format(a, l) msg = 'Generated: arc = {}, line = {}'.format(a, l)
@ -1738,7 +1754,8 @@ class Test_intersect(unittest.TestCase):
assert_intersections(self, a0, a1, intersections, 0) assert_intersections(self, a0, a1, intersections, 0)
class TestPathTools(unittest.TestCase): # noinspection PyTypeChecker
class TestPathTools(TestCase):
# moved from test_pathtools.py # moved from test_pathtools.py
def setUp(self): def setUp(self):
@ -1973,7 +1990,7 @@ class TestPathTools(unittest.TestCase):
def test_path_area(self): def test_path_area(self):
if not RUN_SLOW_TESTS: if not RUN_SLOW_TESTS:
warnings.warn("Skipping `test_path_area` as RUN_SLOW_TESTS is false.") # warnings.warn("Skipping `test_path_area` as RUN_SLOW_TESTS is false.")
return return
cw_square = Path() cw_square = Path()
cw_square.append(Line((0+0j), (0+100j))) cw_square.append(Line((0+0j), (0+100j)))
@ -2029,7 +2046,8 @@ class TestPathTools(unittest.TestCase):
self.assertTrue(enclosing_shape.is_contained_by(larger_shape)) self.assertTrue(enclosing_shape.is_contained_by(larger_shape))
class TestPathBugs(unittest.TestCase): # noinspection PyTypeChecker
class TestPathBugs(TestCase):
def test_issue_113(self): def test_issue_113(self):
""" """
@ -2053,9 +2071,21 @@ class TestPathBugs(unittest.TestCase):
self.assertAlmostEqual(p.length(), 236.70287281737836, delta=TOL) self.assertAlmostEqual(p.length(), 236.70287281737836, delta=TOL)
def test_issue_71(self): def test_issue_71(self):
p = Path("M327 468z") """Test that degenerate (point-like) paths behave properly."""
m = p.closed # degenerate (point-like) closed path
q = p.d() # Failing to Crash is good. d_string = "M327 468z"
path = Path(d_string)
warning_type = warnings.WarningMessage
with AssertWarns(self, warning_type):
self.assertTrue(path.closed)
# test the Path.d() method reproduces an empty d-string
# note that ideally this would reproduce the original, but
# as a Path is a sequence of Bezier segments and arcs, and this
# d-string contains no Bezier segments or arcs, this output seems
# like an acceptable compromise
self.assertEqual(path.d(), '')
def test_issue_95(self): def test_issue_95(self):
""" """
@ -2074,4 +2104,5 @@ class TestPathBugs(unittest.TestCase):
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() from unittest import main
main()

View File

@ -4,7 +4,7 @@ import unittest
import numpy as np import numpy as np
# Internal dependencies # Internal dependencies
from svgpathtools import * from svgpathtools import rational_limit
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 * from svgpathtools import SaxDocument
from os.path import join, dirname from os.path import join, dirname

View File

@ -1,8 +1,9 @@
from __future__ import division, absolute_import, print_function from __future__ import division, absolute_import, print_function
import unittest import unittest
from svgpathtools import * from svgpathtools import svg2paths, Path, Line, Arc
from os.path import join, dirname from os.path import join, dirname
class TestSVG2Paths(unittest.TestCase): class TestSVG2Paths(unittest.TestCase):
def test_svg2paths_polygons(self): def test_svg2paths_polygons(self):
@ -15,8 +16,8 @@ class TestSVG2Paths(unittest.TestCase):
Line(105.5+50j, 55.5+0j) Line(105.5+50j, 55.5+0j)
) )
self.assertTrue(path.isclosed()) self.assertTrue(path.isclosed())
self.assertTrue(len(path)==3) self.assertEqual(len(path), 3)
self.assertTrue(path==path_correct) self.assertEqual(path, path_correct)
# triangular quadrilateral (with a redundant 4th "closure" point) # triangular quadrilateral (with a redundant 4th "closure" point)
path = paths[1] path = paths[1]
@ -26,8 +27,8 @@ class TestSVG2Paths(unittest.TestCase):
Line(0+0j, 0+0j) # result of redundant point Line(0+0j, 0+0j) # result of redundant point
) )
self.assertTrue(path.isclosed()) self.assertTrue(path.isclosed())
self.assertTrue(len(path)==4) self.assertEqual(len(path), 4)
self.assertTrue(path==path_correct) self.assertEqual(path, path_correct)
def test_svg2paths_ellipses(self): def test_svg2paths_ellipses(self):
@ -37,8 +38,8 @@ class TestSVG2Paths(unittest.TestCase):
path_ellipse = paths[0] path_ellipse = paths[0]
path_ellipse_correct = Path(Arc(50+100j, 50+50j, 0.0, True, False, 150+100j), path_ellipse_correct = Path(Arc(50+100j, 50+50j, 0.0, True, False, 150+100j),
Arc(150+100j, 50+50j, 0.0, True, False, 50+100j)) Arc(150+100j, 50+50j, 0.0, True, False, 50+100j))
self.assertTrue(len(path_ellipse)==2) self.assertEqual(len(path_ellipse), 2)
self.assertTrue(path_ellipse==path_ellipse_correct) self.assertEqual(path_ellipse, path_ellipse_correct)
self.assertTrue(path_ellipse.isclosed()) self.assertTrue(path_ellipse.isclosed())
# circle tests # circle tests
@ -46,7 +47,7 @@ class TestSVG2Paths(unittest.TestCase):
path_circle = paths[0] path_circle = paths[0]
path_circle_correct = Path(Arc(50+100j, 50+50j, 0.0, True, False, 150+100j), path_circle_correct = Path(Arc(50+100j, 50+50j, 0.0, True, False, 150+100j),
Arc(150+100j, 50+50j, 0.0, True, False, 50+100j)) Arc(150+100j, 50+50j, 0.0, True, False, 50+100j))
self.assertTrue(len(path_circle)==2) self.assertEqual(len(path_circle), 2)
self.assertTrue(path_circle==path_circle_correct) self.assertEqual(path_circle, path_circle_correct)
self.assertTrue(path_circle.isclosed()) self.assertTrue(path_circle.isclosed())