Updated with master

ElementTree
Andy Port 2018-05-28 19:35:40 -07:00
commit 30d511577b
15 changed files with 141 additions and 107 deletions

View File

@ -8,8 +8,8 @@ to make it easy to contribute.
We need better automated testing coverage. Please, submit unittests! See the
Testing Style section below for info.
Here's a list of things that need (more) unittests
* OK, well... maybe you could help by filling out this list
Here's a list of things that need (more) unittests:
* TBA (feel free to help)
## Submitting Bugs
If you find a bug, please submit an issue along with an **easily reproducible

View File

@ -766,7 +766,7 @@
" for distances in offset_distances:\n",
" offset_paths.append(offset_curve(path, distances))\n",
"\n",
"# Note: This will take a few moments\n",
"# Let's take a look\n",
"wsvg(paths + offset_paths, 'g'*len(paths) + 'r'*len(offset_paths), filename='offset_curves.svg')"
]
},
@ -830,7 +830,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.13"
"version": "2.7.12"
}
},
"nbformat": 4,

View File

@ -585,9 +585,8 @@ curve <https://en.wikipedia.org/wiki/Parallel_curve>`__ for a few paths.
of the 'parallel' offset curve."""
nls = []
for seg in path:
ct = 1
for k in range(steps):
t = k / steps
t = k / float(steps)
offset_vector = offset_distance * seg.normal(t)
nl = Line(seg.point(t), seg.point(t) + offset_vector)
nls.append(nl)

Binary file not shown.

BIN
dist/svgpathtools-1.3.2.tar.gz vendored Normal file

Binary file not shown.

View File

@ -3,7 +3,7 @@ import codecs
import os
VERSION = '1.3.2beta'
VERSION = '1.3.2'
AUTHOR_NAME = 'Andy Port'
AUTHOR_EMAIL = 'AndyAPort@gmail.com'
@ -31,7 +31,7 @@ setup(name='svgpathtools',
download_url = 'http://github.com/mathandy/svgpathtools/tarball/'+VERSION,
license='MIT',
# install_requires=['numpy', 'svgwrite'],
install_requires=['numpy', 'svgwrite'],
platforms="OS Independent",
# test_suite='tests',
requires=['numpy', 'svgwrite'],

View File

@ -1,13 +1,15 @@
Metadata-Version: 1.1
Name: svgpathtools
Version: 1.3.1
Version: 1.3.2
Summary: A collection of tools for manipulating and analyzing SVG Path objects and Bezier curves.
Home-page: https://github.com/mathandy/svgpathtools
Author: Andy Port
Author-email: AndyAPort@gmail.com
License: MIT
Download-URL: http://github.com/mathandy/svgpathtools/tarball/1.3.1
Description: svgpathtools
Download-URL: http://github.com/mathandy/svgpathtools/tarball/1.3.2
Description-Content-Type: UNKNOWN
Description:
svgpathtools
============
svgpathtools is a collection of tools for manipulating and analyzing SVG
@ -46,11 +48,6 @@ Description: svgpathtools
- compute **inverse arc length**
- convert RGB color tuples to hexadecimal color strings and back
Note on Python 3
----------------
While I am hopeful that this package entirely works with Python 3, it was born from a larger project coded in Python 2 and has not been thoroughly tested in
Python 3. Please let me know if you find any incompatibilities.
Prerequisites
-------------
@ -94,8 +91,6 @@ Description: svgpathtools
module <https://github.com/regebro/svg.path>`__. Interested svg.path
users should see the compatibility notes at bottom of this readme.
Also, a big thanks to the author(s) of `A Primer on Bézier Curves <http://pomax.github.io/bezierinfo/>`_, an outstanding resource for learning about Bézier curves and Bézier curve-related algorithms.
Basic Usage
-----------
@ -126,11 +121,11 @@ Description: svgpathtools
on discontinuous Path objects. A simple workaround is provided, however,
by the ``Path.continuous_subpaths()`` method. `↩ <#a1>`__
.. code:: python
.. code:: ipython2
from __future__ import division, print_function
.. code:: python
.. code:: ipython2
# Coordinates are given as points in the complex plane
from svgpathtools import Path, Line, QuadraticBezier, CubicBezier, Arc
@ -167,7 +162,7 @@ Description: svgpathtools
list. So segments can **append**\ ed, **insert**\ ed, set by index,
**del**\ eted, **enumerate**\ d, **slice**\ d out, etc.
.. code:: python
.. code:: ipython2
# Let's append another to the end of it
path.append(CubicBezier(250+350j, 275+350j, 250+225j, 200+100j))
@ -234,7 +229,7 @@ Description: svgpathtools
| Note: Line, Polyline, Polygon, and Path SVG elements can all be
converted to Path objects using this function.
.. code:: python
.. code:: ipython2
# Read SVG into a list of path objects and list of dictionaries of attributes
from svgpathtools import svg2paths, wsvg
@ -271,7 +266,7 @@ Description: svgpathtools
automatically attempt to open the created svg file in your default SVG
viewer.
.. code:: python
.. code:: ipython2
# Let's make a new SVG that's identical to the first
wsvg(paths, attributes=attributes, svg_attributes=svg_attributes, filename='output1.svg')
@ -303,7 +298,7 @@ Description: svgpathtools
that ``path.point(T)=path[k].point(t)``.
| There is also a ``Path.t2T()`` method to solve the inverse problem.
.. code:: python
.. code:: ipython2
# Example:
@ -333,11 +328,11 @@ Description: svgpathtools
True
Tangent vectors and Bezier curves as numpy polynomial objects
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Bezier curves as NumPy polynomial objects
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| Another great way to work with the parameterizations for Line,
QuadraticBezier, and CubicBezier objects is to convert them to
| Another great way to work with the parameterizations for ``Line``,
``QuadraticBezier``, and ``CubicBezier`` objects is to convert them to
``numpy.poly1d`` objects. This is done easily using the
``Line.poly()``, ``QuadraticBezier.poly()`` and ``CubicBezier.poly()``
methods.
@ -369,9 +364,10 @@ Description: svgpathtools
\end{bmatrix}
\begin{bmatrix}P_0\\P_1\\P_2\\P_3\end{bmatrix}
QuadraticBezier.poly() and Line.poly() are defined similarly.
``QuadraticBezier.poly()`` and ``Line.poly()`` are `defined
similarly <https://en.wikipedia.org/wiki/B%C3%A9zier_curve#General_definition>`__.
.. code:: python
.. code:: ipython2
# Example:
b = CubicBezier(300+100j, 100+100j, 200+200j, 200+300j)
@ -401,15 +397,25 @@ Description: svgpathtools
(-400 + -100j) t + (900 + 300j) t - 600 t + (300 + 100j)
To illustrate the awesomeness of being able to convert our Bezier curve
objects to numpy.poly1d objects and back, lets compute the unit tangent
vector of the above CubicBezier object, b, at t=0.5 in four different
ways.
The ability to convert between Bezier objects to NumPy polynomial
objects is very useful. For starters, we can take turn a list of Bézier
segments into a NumPy array
Tangent vectors (and more on polynomials)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Numpy Array operations on Bézier path segments
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code:: python
`Example available
here <https://github.com/mathandy/svgpathtools/blob/master/examples/compute-many-points-quickly-using-numpy-arrays.py>`__
To further illustrate the power of being able to convert our Bezier
curve objects to numpy.poly1d objects and back, lets compute the unit
tangent vector of the above CubicBezier object, b, at t=0.5 in four
different ways.
Tangent vectors (and more on NumPy polynomials)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code:: ipython2
t = 0.5
### Method 1: the easy way
@ -451,7 +457,7 @@ Description: svgpathtools
Translations (shifts), reversing orientation, and normal vectors
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code:: python
.. code:: ipython2
# Speaking of tangents, let's add a normal vector to the picture
n = b.normal(t)
@ -481,7 +487,7 @@ Description: svgpathtools
Rotations and Translations
~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code:: python
.. code:: ipython2
# Let's take a Line and an Arc and make some pictures
top_half = Arc(start=-1, radius=1+2j, rotation=0, large_arc=1, sweep=1, end=1)
@ -514,7 +520,7 @@ Description: svgpathtools
``CubicBezier.length()``, and ``Arc.length()`` methods, as well as the
related inverse arc length methods ``.ilength()`` function to do this.
.. code:: python
.. code:: ipython2
# First we'll load the path data from the file test.svg
paths, attributes = svg2paths('test.svg')
@ -556,7 +562,7 @@ Description: svgpathtools
Intersections between Bezier curves
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code:: python
.. code:: ipython2
# Let's find all intersections between redpath and the other
redpath = paths[0]
@ -580,7 +586,7 @@ Description: svgpathtools
Here we'll find the `offset
curve <https://en.wikipedia.org/wiki/Parallel_curve>`__ for a few paths.
.. code:: python
.. code:: ipython2
from svgpathtools import parse_path, Line, Path, wsvg
def offset_curve(path, offset_distance, steps=1000):
@ -638,6 +644,7 @@ Description: svgpathtools
This module is under a MIT License.
Keywords: svg,svg path,svg.path,bezier,parse svg path,display svg
Platform: OS Independent
Classifier: Development Status :: 4 - Beta

View File

@ -15,23 +15,26 @@ test.svg
vectorframes.svg
svgpathtools/__init__.py
svgpathtools/bezier.py
svgpathtools/directional_field.py
svgpathtools/misctools.py
svgpathtools/parser.py
svgpathtools/path.py
svgpathtools/paths2svg.py
svgpathtools/pathtools.py
svgpathtools/polytools.py
svgpathtools/smoothing.py
svgpathtools/svg2paths.py
svgpathtools.egg-info/PKG-INFO
svgpathtools.egg-info/SOURCES.txt
svgpathtools.egg-info/dependency_links.txt
svgpathtools.egg-info/requires.txt
svgpathtools.egg-info/top_level.txt
test/circle.svg
test/ellipse.svg
test/polygons.svg
test/rects.svg
test/test.svg
test/test_bezier.py
test/test_generation.py
test/test_parsing.py
test/test_path.py
test/test_pathtools.py
test/test_polytools.py
test/test_svg2paths.py

View File

@ -0,0 +1,2 @@
numpy
svgwrite

View File

@ -572,7 +572,7 @@ class Line(object):
d = (other_seg.start.imag, other_seg.end.imag)
denom = ((a[1] - a[0])*(d[0] - d[1]) -
(b[1] - b[0])*(c[0] - c[1]))
if denom == 0:
if np.isclose(denom, 0):
return []
t1 = (c[0]*(b[0] - d[1]) -
c[1]*(b[0] - d[0]) -
@ -1300,10 +1300,13 @@ class Arc(object):
# delta is the angular distance of the arc (w.r.t the circle)
# theta is the angle between the positive x'-axis and the start point
# on the circle
u1_real_rounded = u1.real
if u1.real > 1 or u1.real < -1:
u1_real_rounded = round(u1.real)
if u1.imag > 0:
self.theta = degrees(acos(u1.real))
self.theta = degrees(acos(u1_real_rounded))
elif u1.imag < 0:
self.theta = -degrees(acos(u1.real))
self.theta = -degrees(acos(u1_real_rounded))
else:
if u1.real > 0: # start is on pos u_x axis
self.theta = 0
@ -2060,7 +2063,7 @@ class Path(MutableSequence):
if np.isclose(t, 0) and (seg_idx != 0 or self.end==self.start):
previous_seg_in_path = self._segments[
(seg_idx - 1) % len(self._segments)]
if not seg.joins_smoothl_with(previous_seg_in_path):
if not seg.joins_smoothly_with(previous_seg_in_path):
return float('inf')
elif np.isclose(t, 1) and (seg_idx != len(self) - 1 or self.end==self.start):
next_seg_in_path = self._segments[

View File

@ -88,7 +88,7 @@ def disvg(paths=None, colors=None,
openinbrowser=True, timestamp=False,
margin_size=0.1, mindim=600, dimensions=None,
viewbox=None, text=None, text_path=None, font_size=None,
attributes=None, svg_attributes=None):
attributes=None, svg_attributes=None, svgwrite_debug=False):
"""Takes in a list of paths and creates an SVG file containing said paths.
REQUIRED INPUTS:
:param paths - a list of paths
@ -152,14 +152,22 @@ def disvg(paths=None, colors=None,
paths. Note: This will override any other conflicting settings.
:param svg_attributes - a dictionary of attributes for output svg.
Note 1: This will override any other conflicting settings.
Note 2: Setting `svg_attributes={'debug': False}` may result in a
significant increase in speed.
:param svgwrite_debug - This parameter turns on/off `svgwrite`'s
debugging mode. By default svgwrite_debug=False. This increases
speed and also prevents `svgwrite` from raising of an error when not
all `svg_attributes` key-value pairs are understood.
NOTES:
-The unit of length here is assumed to be pixels in all variables.
* The `svg_attributes` parameter will override any other conflicting
settings.
-If this function is used multiple times in quick succession to
* Any `extra` parameters that `svgwrite.Drawing()` accepts can be
controlled by passing them in through `svg_attributes`.
* The unit of length here is assumed to be pixels in all variables.
* If this function is used multiple times in quick succession to
display multiple SVGs (all using the default filename), the
svgviewer/browser will likely fail to load some of the SVGs in time.
To fix this, use the timestamp attribute, or give the files unique
@ -277,12 +285,15 @@ def disvg(paths=None, colors=None,
szy = str(mindim) + 'px'
# Create an SVG file
if svg_attributes:
if svg_attributes is not None:
szx = svg_attributes.get("width", szx)
szy = svg_attributes.get("height", szy)
dwg = Drawing(filename=filename, size=(szx, szy), **svg_attributes)
debug = svg_attributes.get("debug", svgwrite_debug)
dwg = Drawing(filename=filename, size=(szx, szy), debug=debug,
**svg_attributes)
else:
dwg = Drawing(filename=filename, size=(szx, szy), viewBox=viewbox)
dwg = Drawing(filename=filename, size=(szx, szy), debug=svgwrite_debug,
viewBox=viewbox)
# add paths
if paths:
@ -377,7 +388,7 @@ def wsvg(paths=None, colors=None,
openinbrowser=False, timestamp=False,
margin_size=0.1, mindim=600, dimensions=None,
viewbox=None, text=None, text_path=None, font_size=None,
attributes=None, svg_attributes=None):
attributes=None, svg_attributes=None, svgwrite_debug=False):
"""Convenience function; identical to disvg() except that
openinbrowser=False by default. See disvg() docstring for more info."""
disvg(paths, colors=colors, filename=filename,
@ -386,4 +397,5 @@ def wsvg(paths=None, colors=None,
openinbrowser=openinbrowser, timestamp=timestamp,
margin_size=margin_size, mindim=mindim, dimensions=dimensions,
viewbox=viewbox, text=text, text_path=text_path, font_size=font_size,
attributes=attributes, svg_attributes=svg_attributes)
attributes=attributes, svg_attributes=svg_attributes,
svgwrite_debug=svgwrite_debug)

View File

@ -10,6 +10,13 @@ import xml.etree.cElementTree as etree
from .parser import parse_path
COORD_PAIR_TMPLT = re.compile(
r'([\+-]?\d*[\.\d]\d*[eE][\+-]?\d+|[\+-]?\d*[\.\d]\d*)' +
r'(?:\s*,\s*|\s+|(?=-))' +
r'([\+-]?\d*[\.\d]\d*[eE][\+-]?\d+|[\+-]?\d*[\.\d]\d*)'
)
def ellipse2pathd(ellipse):
"""converts the parameters from an ellipse or a circle to a string
for a Path object d-attribute"""
@ -37,23 +44,21 @@ def ellipse2pathd(ellipse):
return d
def polyline2pathd(polyline_d):
"""converts the string from a polyline points-attribute to a string
for a Path object d-attribute"""
try:
points = polyline_d['points']
except:
pass
points = polyline_d.replace(', ', ',')
points = points.replace(' ,', ',')
points = points.split()
def polyline2pathd(polyline_d, is_polygon=False):
"""converts the string from a polyline points-attribute to a string for a
Path object d-attribute"""
points = COORD_PAIR_TMPLT.findall(polyline_d)
closed = (float(points[0][0]) == float(points[-1][0]) and
float(points[0][1]) == float(points[-1][1]))
closed = points[0] == points[-1]
# The `parse_path` call ignores redundant 'z' (closure) commands
# e.g. `parse_path('M0 0L100 100Z') == parse_path('M0 0L100 100L0 0Z')`
# This check ensures that an n-point polygon is converted to an n-Line path.
if is_polygon and closed:
points.append(points[0])
d = 'M' + points.pop(0).replace(',', ' ')
for p in points:
d += 'L' + p.replace(',', ' ')
if closed:
d = 'M' + 'L'.join('{0} {1}'.format(x,y) for x,y in points)
if is_polygon or closed:
d += 'z'
return d
@ -62,31 +67,8 @@ def polygon2pathd(polyline_d):
"""converts the string from a polygon points-attribute to a string
for a Path object d-attribute.
Note: For a polygon made from n points, the resulting path will be
composed of n lines (even if some of these lines have length zero).
"""
try:
points = polyline_d['points']
except:
pass
points = polyline_d.replace(', ', ',')
points = points.replace(' ,', ',')
points = points.split()
reduntantly_closed = points[0] == points[-1]
d = 'M' + points[0].replace(',', ' ')
for p in points[1:]:
d += 'L' + p.replace(',', ' ')
# The `parse_path` call ignores redundant 'z' (closure) commands
# e.g.
# `parse_path('M0 0L100 100Z') == parse_path('M0 0L100 100L0 0Z')`
# This check ensures that an n-point polygon is converted to an
# n-Line path.
if reduntantly_closed:
d += 'L' + points[0].replace(',', ' ')
return d + 'z'
composed of n lines (even if some of these lines have length zero)."""
return polyline2pathd(polyline_d, True)
def rect2pathd(rect):
@ -104,9 +86,11 @@ def rect2pathd(rect):
"".format(x0, y0, x1, y1, x2, y2, x3, y3))
return d
def line2pathd(l):
return 'M' + l['x1'] + ' ' + l['y1'] + 'L' + l['x2'] + ' ' + l['y2']
CONVERSIONS = {'circle': ellipse2pathd,
'ellipse': ellipse2pathd,
'line': line2pathd,
@ -114,6 +98,7 @@ CONVERSIONS = {'circle': ellipse2pathd,
'polygon': polygon2pathd,
'rect': rect2pathd}
def svg2paths(svg_file_location, return_svg_attributes=False,
conversions=CONVERSIONS, return_tree=False):
"""Converts SVG to list of Path objects and attribute dictionaries.
@ -203,5 +188,3 @@ def svg2paths3(svg_file_location, return_svg_attributes=True,
return svg2paths(svg_file_location=svg_file_location,
return_svg_attributes=return_svg_attributes,
conversions=conversions, return_tree=return_tree)

View File

@ -1,5 +1,5 @@
<?xml version="1.0" ?>
<svg baseProfile="full" height="600px" version="1.1" viewBox="-10.05 -10.05 120.1 120.1" width="600px" xmlns="http://www.w3.org/2000/svg" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xlink="http://www.w3.org/1999/xlink">
<polygon points="55.5,0 55.5, 50 105.5,50" style="stroke:purple;stroke-width:1"/>
<polygon points="0,0 0,100 100,100 0,0" style="stroke:purple;stroke-width:1"/>
<polygon points="0,0.0 0,-100 , 1.0e-1-1e2,0,0" style="stroke:purple;stroke-width:1"/>
</svg>

Before

Width:  |  Height:  |  Size: 422 B

After

Width:  |  Height:  |  Size: 431 B

View File

@ -353,6 +353,18 @@ class QuadraticBezierTest(unittest.TestCase):
class ArcTest(unittest.TestCase):
def test_trusting_acos(self):
"""`u1.real` is > 1 in this arc due to numerical error."""
try:
a1 = Arc(start=(160.197+102.925j),
radius=(0.025+0.025j),
rotation=0.0,
large_arc=False,
sweep=True,
end=(160.172+102.95j))
except ValueError:
self.fail("Arc() raised ValueError unexpectedly!")
def test_points(self):
arc1 = Arc(0j, 100 + 50j, 0, 0, 0, 100 + 50j)
self.assertAlmostEqual(arc1.center, 100 + 0j)
@ -979,6 +991,19 @@ class Test_intersect(unittest.TestCase):
self.assertTrue(len(yix), 1)
###################################################################
def test_line_line_0(self):
l0 = Line(start=(25.389999999999997+99.989999999999995j), end=(25.389999999999997+90.484999999999999j))
l1 = Line(start=(25.390000000000001+84.114999999999995j), end=(25.389999999999997+74.604202137430320j))
i = l0.intersect(l1)
assert(len(i)) == 0
def test_line_line_1(self):
l0 = Line(start=(-124.705378549+327.696674827j), end=(12.4926214511+121.261674827j))
l1 = Line(start=(-12.4926214511+121.261674827j), end=(124.705378549+327.696674827j))
i = l0.intersect(l1)
assert(len(i)) == 1
assert(abs(l0.point(i[0][0])-l1.point(i[0][1])) < 1e-9)
class TestPathTools(unittest.TestCase):
# moved from test_pathtools.py

View File

@ -20,9 +20,9 @@ class TestSVG2Paths(unittest.TestCase):
# triangular quadrilateral (with a redundant 4th "closure" point)
path = paths[1]
path_correct = Path(Line(0+0j, 0+100j),
Line(0+100j, 100+100j),
Line(100+100j, 0+0j),
path_correct = Path(Line(0+0j, 0-100j),
Line(0-100j, 0.1-100j),
Line(0.1-100j, 0+0j),
Line(0+0j, 0+0j) # result of redundant point
)
self.assertTrue(path.isclosed())