diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 815c3a1..61878fe 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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 diff --git a/README.ipynb b/README.ipynb index 237ca9d..2ede45e 100644 --- a/README.ipynb +++ b/README.ipynb @@ -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, diff --git a/README.rst b/README.rst index 71867f5..b0ad6b5 100644 --- a/README.rst +++ b/README.rst @@ -585,9 +585,8 @@ 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) diff --git a/dist/svgpathtools-1.3.2-py2.py3-none-any.whl b/dist/svgpathtools-1.3.2-py2.py3-none-any.whl new file mode 100644 index 0000000..8d07955 Binary files /dev/null and b/dist/svgpathtools-1.3.2-py2.py3-none-any.whl differ diff --git a/dist/svgpathtools-1.3.2.tar.gz b/dist/svgpathtools-1.3.2.tar.gz new file mode 100644 index 0000000..941613f Binary files /dev/null and b/dist/svgpathtools-1.3.2.tar.gz differ diff --git a/setup.py b/setup.py index 9ebcf95..10b7dbf 100644 --- a/setup.py +++ b/setup.py @@ -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'], diff --git a/svgpathtools.egg-info/PKG-INFO b/svgpathtools.egg-info/PKG-INFO index edc988c..29ff89b 100644 --- a/svgpathtools.egg-info/PKG-INFO +++ b/svgpathtools.egg-info/PKG-INFO @@ -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 `__. 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 `_, 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 `__. - .. 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 `__ + + 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 `__ 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 diff --git a/svgpathtools.egg-info/SOURCES.txt b/svgpathtools.egg-info/SOURCES.txt index 99a5197..c1dde0f 100644 --- a/svgpathtools.egg-info/SOURCES.txt +++ b/svgpathtools.egg-info/SOURCES.txt @@ -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 \ No newline at end of file +test/test_polytools.py +test/test_svg2paths.py \ No newline at end of file diff --git a/svgpathtools.egg-info/requires.txt b/svgpathtools.egg-info/requires.txt new file mode 100644 index 0000000..2c3af04 --- /dev/null +++ b/svgpathtools.egg-info/requires.txt @@ -0,0 +1,2 @@ +numpy +svgwrite diff --git a/svgpathtools/path.py b/svgpathtools/path.py index d5234f1..13ec198 100644 --- a/svgpathtools/path.py +++ b/svgpathtools/path.py @@ -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[ diff --git a/svgpathtools/paths2svg.py b/svgpathtools/paths2svg.py index 935c5ec..4443a2e 100644 --- a/svgpathtools/paths2svg.py +++ b/svgpathtools/paths2svg.py @@ -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) diff --git a/test/test_path.py b/test/test_path.py index e69499b..8474b26 100644 --- a/test/test_path.py +++ b/test/test_path.py @@ -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