diff --git a/svgpathtools.egg-info/PKG-INFO b/svgpathtools.egg-info/PKG-INFO
index 29ff89b..9409782 100644
--- a/svgpathtools.egg-info/PKG-INFO
+++ b/svgpathtools.egg-info/PKG-INFO
@@ -7,7 +7,6 @@ Author: Andy Port
Author-email: AndyAPort@gmail.com
License: MIT
Download-URL: http://github.com/mathandy/svgpathtools/tarball/1.3.2
-Description-Content-Type: UNKNOWN
Description:
svgpathtools
============
@@ -595,9 +594,8 @@ Description:
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/svgpathtools.egg-info/SOURCES.txt b/svgpathtools.egg-info/SOURCES.txt
index c1dde0f..1eb4a50 100644
--- a/svgpathtools.egg-info/SOURCES.txt
+++ b/svgpathtools.egg-info/SOURCES.txt
@@ -15,6 +15,7 @@ test.svg
vectorframes.svg
svgpathtools/__init__.py
svgpathtools/bezier.py
+svgpathtools/document.py
svgpathtools/misctools.py
svgpathtools/parser.py
svgpathtools/path.py
@@ -29,11 +30,13 @@ svgpathtools.egg-info/requires.txt
svgpathtools.egg-info/top_level.txt
test/circle.svg
test/ellipse.svg
+test/groups.svg
test/polygons.svg
test/rects.svg
test/test.svg
test/test_bezier.py
test/test_generation.py
+test/test_groups.py
test/test_parsing.py
test/test_path.py
test/test_polytools.py
diff --git a/svgpathtools/__init__.py b/svgpathtools/__init__.py
index 207b33a..e678bb2 100644
--- a/svgpathtools/__init__.py
+++ b/svgpathtools/__init__.py
@@ -17,4 +17,4 @@ from .document import Document
try:
from .svg2paths import svg2paths, svg2paths2
except ImportError:
- pass
\ No newline at end of file
+ pass
diff --git a/svgpathtools/document.py b/svgpathtools/document.py
index 1dbaf18..69ef34f 100644
--- a/svgpathtools/document.py
+++ b/svgpathtools/document.py
@@ -52,9 +52,8 @@ A Big Problem:
from __future__ import division, absolute_import, print_function
import os
import collections
-import xml.etree.cElementTree as etree
-import xml.etree.ElementTree.Element as Element
-import xml.etree.ElementTree.SubElement as SubElement
+import xml.etree.ElementTree as etree
+from xml.etree.ElementTree import Element, SubElement, register_namespace, _namespace_map
import warnings
# Internal dependencies
@@ -65,6 +64,11 @@ from .svg2paths import (path2pathd, ellipse2pathd, line2pathd, polyline2pathd,
from .misctools import open_in_browser
from .path import *
+# Let xml.etree.ElementTree know about the SVG namespace
+print(' ------------------ about to register the svg namespace')
+register_namespace('svg', 'http://www.w3.org/2000/svg')
+print('namespace map: {0}'.format(_namespace_map))
+
# THESE MUST BE WRAPPED TO OUTPUT ElementTree.element objects
CONVERSIONS = {'path': path2pathd,
'circle': ellipse2pathd,
@@ -74,12 +78,15 @@ CONVERSIONS = {'path': path2pathd,
'polygon': polygon2pathd,
'rect': rect2pathd}
+ONLY_PATHS = {'path': path2pathd}
+
def flatten_all_paths(
group,
group_filter=lambda x: True,
path_filter=lambda x: True,
- path_conversions=CONVERSIONS):
+ path_conversions=CONVERSIONS,
+ search_xpath='{http://www.w3.org/2000/svg}g'):
"""Returns the paths inside a group (recursively), expressing the paths in the base coordinates.
Note that if the group being passed in is nested inside some parent group(s), we cannot take the parent group(s)
@@ -92,7 +99,8 @@ def flatten_all_paths(
that are not included in this dictionary will be ignored (including the `path` tag).
"""
if not isinstance(group, Element):
- raise TypeError('Must provide an xml.etree.Element object')
+ raise TypeError('Must provide an xml.etree.Element object. Instead you provided {0} : compared to {1}'
+ .format(type(group), type(Element('some tag'))))
# Stop right away if the group_selector rejects this group
if not group_filter(group):
@@ -108,7 +116,7 @@ def flatten_all_paths(
def get_relevant_children(parent, last_tf):
children = []
- for elem in filter(group_filter, parent.iterfind('g')):
+ for elem in filter(group_filter, parent.iterfind(search_xpath)):
children.append(new_stack_element(elem, last_tf))
return children
@@ -120,10 +128,13 @@ def flatten_all_paths(
while stack:
top = stack.pop()
+ print('popping group {0}'.format(top.group.attrib))
+ print('has children: {0}'.format(list(elem.tag for elem in top.group.iter())))
+
# For each element type that we know how to convert into path data, parse the element after confirming that
# the path_filter accepts it.
- for key, converter in path_conversions:
- for path_elem in filter(path_filter, top.group.iterfind(key)):
+ for key, converter in path_conversions.iteritems():
+ for path_elem in filter(path_filter, top.group.iterfind('{http://www.w3.org/2000/svg}'+key)):
path_tf = top.transform * parse_transform(path_elem.get('transform'))
path = transform(parse_path(converter(path_elem)), path_tf)
paths.append(FlattenedPath(path, path_elem.attrib, path_tf))
@@ -139,7 +150,8 @@ def flatten_group(
recursive=True,
group_filter=lambda x: True,
path_filter=lambda x: True,
- path_conversions=CONVERSIONS):
+ path_conversions=CONVERSIONS,
+ search_xpath='g'):
"""Flatten all the paths in a specific group.
The paths will be flattened into the 'root' frame. Note that root needs to be
@@ -150,7 +162,7 @@ def flatten_group(
# We will shortcut here, because it is impossible for any paths to be returned anyhow.
return []
- # We create a set of the unique IDs of each group object that we want to flatten.
+ # We create a set of the unique IDs of each element that we wish to flatten, if those elements are groups.
# Any groups outside of this set will be skipped while we flatten the paths.
desired_groups = set()
if recursive:
@@ -162,7 +174,7 @@ def flatten_group(
def desired_group_filter(x):
return (id(x) in desired_groups) and group_filter(x)
- return flatten_all_paths(root, desired_group_filter, path_filter, path_conversions)
+ return flatten_all_paths(root, desired_group_filter, path_filter, path_conversions, search_xpath)
class Document:
@@ -190,14 +202,6 @@ class Document:
self.tree = etree.parse(filename)
self.root = self.tree.getroot()
- # get URI namespace (only necessary in OS X?)
- root_tag = self.tree.getroot().tag
- if root_tag[0] == "{":
- self._prefix = root_tag[:root_tag.find('}') + 1]
- else:
- self._prefix = ''
- # etree.register_namespace('', prefix)
-
def flatten_all_paths(self,
group_filter=lambda x: True,
path_filter=lambda x: True,
@@ -215,7 +219,7 @@ class Document:
group = self.get_or_add_group(group)
elif not isinstance(group, Element):
raise TypeError('Must provide a list of strings that represent a nested group name, '
- 'or provide an xml.etree.Element object')
+ 'or provide an xml.etree.Element object. Instead you provided {0}'.format(group))
return flatten_group(group, self.tree.getroot(), recursive, group_filter, path_filter, path_conversions)
@@ -247,7 +251,8 @@ class Document:
group = self.get_or_add_group(group)
elif not isinstance(group, Element):
- raise TypeError('Must provide a list of strings or an xml.etree.Element object')
+ raise TypeError('Must provide a list of strings or an xml.etree.Element object. '
+ 'Instead you provided {0}'.format(group))
else:
# Make sure that the group belongs to this Document object
@@ -262,7 +267,8 @@ class Document:
# Assume this is a valid d-string. TODO: Should we sanity check the input string?
path_svg = path
else:
- raise TypeError('Must provide a Path, a path segment type, or a valid SVG path d-string')
+ raise TypeError('Must provide a Path, a path segment type, or a valid SVG path d-string. '
+ 'Instead you provided {0}'.format(path))
if attribs is None:
attribs = {}
@@ -313,7 +319,7 @@ class Document:
if parent is None:
parent = self.tree.getroot()
elif not self.contains_group(parent):
- warnings.warn('The requested group does not belong to this Document')
+ warnings.warn('The requested group {0} does not belong to this Document'.format(parent))
if group_attribs is None:
group_attribs = {}
diff --git a/svgpathtools/parser.py b/svgpathtools/parser.py
index 70f0358..f01aee5 100644
--- a/svgpathtools/parser.py
+++ b/svgpathtools/parser.py
@@ -206,30 +206,38 @@ def _check_num_parsed_values(values, allowed):
if len(allowed) > 1:
warnings.warn('Expected one of the following number of values {0}, found {1}: {2}'
.format(allowed, len(values), values))
- elif allowed != 1:
- warnings.warn('Expected {0} values, found {1}: {2}'.format(allowed, len(values), values))
+ elif allowed[0] != 1:
+ warnings.warn('Expected {0} values, found {1}: {2}'.format(allowed[0], len(values), values))
else:
warnings.warn('Expected 1 value, found {0}: {1}'.format(len(values), values))
+ return False
+ return True
def _parse_transform_substr(transform_substr):
+
type_str, value_str = transform_substr.split('(')
- values = list(map(float, value_str.split(',')))
+ value_str = value_str.replace(',', ' ')
+ values = list(map(float, filter(None, value_str.split(' '))))
+
transform = np.identity(3)
if 'matrix' in type_str:
- _check_num_parsed_values(values, 6)
+ if not _check_num_parsed_values(values, [6]):
+ return transform
transform[0:2, 0:3] = np.matrix([values[0:6:2], values[1:6:2]])
elif 'translate' in transform_substr:
- _check_num_parsed_values(values, [1, 2])
+ if not _check_num_parsed_values(values, [1, 2]):
+ return transform
transform[0, 2] = values[0]
if len(values) > 1:
transform[1, 2] = values[1]
elif 'scale' in transform_substr:
- _check_num_parsed_values(values, [1, 2])
+ if not _check_num_parsed_values(values, [1, 2]):
+ return transform
x_scale = values[0]
y_scale = values[1] if (len(values) > 1) else x_scale
@@ -237,26 +245,31 @@ def _parse_transform_substr(transform_substr):
transform[1, 1] = y_scale
elif 'rotate' in transform_substr:
- _check_num_parsed_values(values, [1, 3])
+ if not _check_num_parsed_values(values, [1, 3]):
+ return transform
angle = values[0] * np.pi / 180.0
if len(values) == 3:
offset = values[1:3]
else:
offset = (0, 0)
- T_offset = np.identity(3)
- T_offset[0:2, 2] = np.matrix([[offset[0]], [offset[1]]])
- T_rotate = np.identity(3)
- T_rotate[0:2, 0:2] = np.matrix([np.cos(angle), -np.sin(angle)], [np.sin(angle), np.cos(angle)])
+ tf_offset = np.identity(3)
+ tf_offset[0:2, 2:3] = np.matrix([[offset[0]], [offset[1]]])
+ tf_rotate = np.identity(3)
+ tf_rotate[0:2, 0:2] = np.matrix([[np.cos(angle), -np.sin(angle)], [np.sin(angle), np.cos(angle)]])
- transform = T_offset * T_rotate * (-T_offset)
+ transform = tf_offset * tf_rotate * (-tf_offset)
elif 'skewX' in transform_substr:
- _check_num_parsed_values(values, 1)
+ if not _check_num_parsed_values(values, [1]):
+ return transform
+
transform[0, 1] = np.tan(values[0] * np.pi / 180.0)
elif 'skewY' in transform_substr:
- _check_num_parsed_values(values, 1)
+ if not _check_num_parsed_values(values, [1]):
+ return transform
+
transform[1, 0] = np.tan(values[0] * np.pi / 180.0)
else:
# Return an identity matrix if the type of transform is unknown, and warn the user
@@ -274,6 +287,8 @@ def parse_transform(transform_str):
raise TypeError('Must provide a string to parse')
total_transform = np.identity(3)
- transform_substrs = transform_str.split(')')
+ transform_substrs = transform_str.split(')')[:-1] # Skip the last element, because it should be empty
for substr in transform_substrs:
total_transform *= _parse_transform_substr(substr)
+
+ return total_transform
diff --git a/test/groups.svg b/test/groups.svg
index 6b3cc8e..f5492d1 100644
--- a/test/groups.svg
+++ b/test/groups.svg
@@ -5,13 +5,11 @@
viewBox="0 0 365 365"
height="100%"
width="100%"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:ev="http://www.w3.org/2001/xml-events"
- xmlns:xlink="http://www.w3.org/1999/xlink">
+ xmlns="http://www.w3.org/2000/svg">
+ transform="matrix(1.5 0.0 0.0 0.5 -40.0 20.0)">