Some progress (and added CONTRIBUTING.md)
parent
e8367d463a
commit
d20ef060aa
|
@ -0,0 +1,50 @@
|
|||
# Contributing to svgpathtools
|
||||
|
||||
The following is a few and guidelines regarding the current philosophy, style,
|
||||
flaws, and the future directions of svgpathtools. These guidelines are meant
|
||||
to make it easy to contribute.
|
||||
|
||||
## Basic Considerations
|
||||
|
||||
### New features should come with unittests and docstrings.
|
||||
If you want to add a cool/useful feature to svgpathtools, that's great! Just
|
||||
make sure your pull-request includes both thorough unittests and well-written
|
||||
docstrings. See relevant sections below on "Testing Style" and
|
||||
"Docstring Style" below.
|
||||
|
||||
|
||||
### Modifications to old code may require additional unittests.
|
||||
Certain submodules of svgpathtools are poorly covered by the current set of
|
||||
unittests. That said, most functionality in svgpathtools has been tested quite
|
||||
a bit through use.
|
||||
The point being, if you're working on functionality not currently covered by
|
||||
unittests (and your changes replace more than a few lines), then please include
|
||||
unittests designed to verify that any affected functionary still works.
|
||||
|
||||
|
||||
## Style
|
||||
### Coding Style
|
||||
* Follow the PEP8 guidelines unless you have good reason to violate them (e.g.
|
||||
you want your code's variable names to match some official documentation, or
|
||||
PEP8 guidelines contradict those present in this document).
|
||||
* Include docstrings and in-line comments where appropriate. See
|
||||
"Docstring Style" section below for more info.
|
||||
* Use explicit, uncontracted names (e.g. "parse_transform" instead of
|
||||
"parse_trafo"). The ideal names should be something a user can guess
|
||||
* Use a capital 'T' denote a Path object's parameter, use a lower case 't' to
|
||||
denote a Path segment's parameter. See the methods `Path.t2T` and `Path.T2t`
|
||||
if you're unsure what I mean. In the ambiguous case, use either 't' or another
|
||||
appropriate option (e.g. "tau").
|
||||
|
||||
|
||||
### Testing Style
|
||||
See the svgpathtools/test folder for examples.
|
||||
|
||||
|
||||
### Docstring Style
|
||||
All docstrings in svgpathtools should (roughly) adhere to the Google Python
|
||||
Style Guide. Currently, this is not the case... but for the sake of
|
||||
consistency, Google Style is the officially preferred docstring style of
|
||||
svgpathtools.
|
||||
[Some nice examples of Google Python Style docstrings](
|
||||
https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html)
|
|
@ -69,29 +69,29 @@ def svg2paths(svg_file_location,
|
|||
return dict(list(zip(keys, values)))
|
||||
|
||||
# Use minidom to extract path strings from input SVG
|
||||
paths = [dom2dict(el) for el in doc.getElementsByTagName('path')]
|
||||
paths = [dom2dict(el) for el in doc.get_elements_by_tag('path')]
|
||||
d_strings = [el['d'] for el in paths]
|
||||
attribute_dictionary_list = paths
|
||||
# if pathless_svg:
|
||||
# for el in doc.getElementsByTagName('path'):
|
||||
# for el in doc.get_elements_by_tag('path'):
|
||||
# el.parentNode.removeChild(el)
|
||||
|
||||
# Use minidom to extract polyline strings from input SVG, convert to
|
||||
# path strings, add to list
|
||||
if convert_polylines_to_paths:
|
||||
plins = [dom2dict(el) for el in doc.getElementsByTagName('polyline')]
|
||||
plins = [dom2dict(el) for el in doc.get_elements_by_tag('polyline')]
|
||||
d_strings += [polyline2pathd(pl['points']) for pl in plins]
|
||||
attribute_dictionary_list += plins
|
||||
|
||||
# Use minidom to extract polygon strings from input SVG, convert to
|
||||
# path strings, add to list
|
||||
if convert_polygons_to_paths:
|
||||
pgons = [dom2dict(el) for el in doc.getElementsByTagName('polygon')]
|
||||
pgons = [dom2dict(el) for el in doc.get_elements_by_tag('polygon')]
|
||||
d_strings += [polyline2pathd(pg['points']) + 'z' for pg in pgons]
|
||||
attribute_dictionary_list += pgons
|
||||
|
||||
if convert_lines_to_paths:
|
||||
lines = [dom2dict(el) for el in doc.getElementsByTagName('line')]
|
||||
lines = [dom2dict(el) for el in doc.get_elements_by_tag('line')]
|
||||
d_strings += [('M' + l['x1'] + ' ' + l['y1'] +
|
||||
'L' + l['x2'] + ' ' + l['y2']) for l in lines]
|
||||
attribute_dictionary_list += lines
|
||||
|
@ -101,7 +101,7 @@ def svg2paths(svg_file_location,
|
|||
# doc.writexml(f)
|
||||
|
||||
if return_svg_attributes:
|
||||
svg_attributes = dom2dict(doc.getElementsByTagName('svg')[0])
|
||||
svg_attributes = dom2dict(doc.get_elements_by_tag('svg')[0])
|
||||
doc.unlink()
|
||||
path_list = [parse_path(d) for d in d_strings]
|
||||
return path_list, attribute_dictionary_list, svg_attributes
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
"""An (experimental) replacement for the svg2paths and paths2svg functionality.
|
||||
|
||||
This module contains the `Document` class, a container for a DOM-style document
|
||||
(e.g. svg, html, xml, etc.) designed to replace and improve upon the IO functionality of svgpathtools (i.e.
|
||||
the svg2paths and disvg/wsvg functions).
|
||||
|
||||
An Historic Note:
|
||||
The functionality in this module is meant to replace and improve upon the
|
||||
IO functionality previously provided by the the `svg2paths` and
|
||||
`disvg`/`wsvg` functions.
|
||||
|
||||
Example:
|
||||
Typical usage looks something the following.
|
||||
|
||||
>> from svgpathtools import *
|
||||
>> doc = Document('my_file.html')
|
||||
>> for p in doc.paths:
|
||||
>> foo(p) # do some stuff using svgpathtools functionality
|
||||
>> foo2(doc.tree) # do some stuff using ElementTree's functionality
|
||||
>> doc.display() # open modified document in OS's default application
|
||||
>> doc.save('my_new_file.html')
|
||||
|
||||
Attributes:
|
||||
CONVERSIONS (dict): A dictionary whose keys are tag-names (of path-like
|
||||
objects to be converted to paths during parsing) and whose values are
|
||||
functions that take in a dictionary (of attributes) and return a string
|
||||
(the path d-string). See the `Document` class docstring for more info.
|
||||
|
||||
Todo: (please see contributor guidelines in CONTRIBUTING.md)
|
||||
* Finish "NotImplemented" methods.
|
||||
* Find some clever (and easy to implement) way to create a thorough set of
|
||||
unittests.
|
||||
* Finish Documentation for each method (approximately following the Google
|
||||
Python Style Guide, see [1]_ for some nice examples).
|
||||
For nice style examples, see [1]_.
|
||||
|
||||
Some thoughts on this module's direction:
|
||||
* The `Document` class should ONLY grab path elements that are inside an
|
||||
SVG.
|
||||
* To handle transforms... there should be a "get_transform" function and
|
||||
also a "flatten_transforms" tool that removes any present transform
|
||||
attributes from all SVG-Path elements in the document (applying the
|
||||
transformations before to the svgpathtools Path objects).
|
||||
Note: This ability to "flatten" will ignore CSS files (and any relevant
|
||||
files that are not parsed into the tree automatically by ElementTree)...
|
||||
that is unless you have any bright ideas on this. I really know very
|
||||
little about DOM-style documents.
|
||||
"""
|
||||
|
||||
# External dependencies
|
||||
from __future__ import division, absolute_import, print_function
|
||||
import os
|
||||
import xml.etree.cElementTree as etree
|
||||
|
||||
# Internal dependencies
|
||||
from .parser import parse_path
|
||||
from .svg2paths import (ellipse2pathd, line2pathd, polyline2pathd,
|
||||
polygon2pathd, rect2pathd)
|
||||
from .misctools import open_in_browser
|
||||
|
||||
|
||||
CONVERSIONS = {'circle': ellipse2pathd,
|
||||
'ellipse': ellipse2pathd,
|
||||
'line': line2pathd,
|
||||
'polyline': polyline2pathd,
|
||||
'polygon': polygon2pathd,
|
||||
'rect': rect2pathd}
|
||||
|
||||
|
||||
class Document:
|
||||
def __init__(self, filename=None, conversions=CONVERSIONS):
|
||||
"""A container for a DOM-style document.
|
||||
|
||||
The `Document` class is meant to be used to parse, create, save, and
|
||||
modify DOM-style documents. Given the `filename` of a DOM-style
|
||||
document, it parses the document into an ElementTree object, extracts
|
||||
all SVG-Path and Path-like (see `conversions` below) objects into
|
||||
a list of svgpathtools Path objects."""
|
||||
|
||||
# remember location of original svg file
|
||||
if filename is not None and os.path.dirname(filename) == '':
|
||||
self.original_filename = os.path.join(os.getcwd(), filename)
|
||||
else:
|
||||
self.original_filename = filename
|
||||
|
||||
# parse svg to ElementTree object
|
||||
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)
|
||||
|
||||
self.paths = self._get_paths(conversions)
|
||||
|
||||
def get_elements_by_tag(self, tag):
|
||||
"""Returns a generator of all elements with the give tag.
|
||||
|
||||
Note: for more advanced XML-related functionality, use the `tree`
|
||||
attribute (an ElementTree object).
|
||||
"""
|
||||
return self.tree.iter(tag=self._prefix + tag)
|
||||
|
||||
def _get_paths(self, conversions):
|
||||
paths = []
|
||||
|
||||
# Get d-strings for SVG-Path elements
|
||||
paths += [el.attrib for el in self.get_elements_by_tag('path')]
|
||||
d_strings = [el['d'] for el in paths]
|
||||
attribute_dictionary_list = paths
|
||||
|
||||
# Get d-strings for path-like elements (using `conversions` dictionary)
|
||||
if conversions:
|
||||
for tag, fcn in conversions.items():
|
||||
attributes = [el.attrib for el in self.get_elements_by_tag(tag)]
|
||||
d_strings += [fcn(d) for d in attributes]
|
||||
|
||||
path_list = [parse_path(d) for d in d_strings]
|
||||
return path_list
|
||||
|
||||
def get_svg_attributes(self):
|
||||
"""To help with backwards compatibility."""
|
||||
return self.get_elements_by_tag('svg')[0].attrib
|
||||
|
||||
def get_path_attributes(self):
|
||||
"""To help with backwards compatibility."""
|
||||
return [p.tree_element.attrib for p in self.paths]
|
||||
|
||||
def add(self, path, attribs={}, parent=None):
|
||||
"""Add a new path to the SVG."""
|
||||
if parent is None:
|
||||
parent = self.tree.getroot()
|
||||
# just get root
|
||||
# then add new path
|
||||
# then record element_tree object in path
|
||||
raise NotImplementedError
|
||||
|
||||
def add_group(self, group_attribs={}, parent=None):
|
||||
"""Add an empty group element to the SVG."""
|
||||
if parent is None:
|
||||
parent = self.tree.getroot()
|
||||
raise NotImplementedError
|
||||
|
||||
def update_tree(self):
|
||||
"""Rewrite the d-string's for each path in the `tree` attribute."""
|
||||
raise NotImplementedError
|
||||
|
||||
def save(self, filename, update=True):
|
||||
"""Write to svg to a file."""
|
||||
if update:
|
||||
self.update_tree()
|
||||
|
||||
with open(filename, 'w') as output_svg:
|
||||
output_svg.write(etree.tostring(self.tree.getroot()))
|
||||
|
||||
def display(self, filename=None, update=True):
|
||||
"""Display the document.
|
||||
|
||||
Opens the document """
|
||||
if update:
|
||||
self.update_tree()
|
||||
|
||||
if filename is None:
|
||||
raise NotImplementedError
|
||||
|
||||
# write to a (by default temporary) file
|
||||
with open(filename, 'w') as output_svg:
|
||||
output_svg.write(etree.tostring(self.tree.getroot()))
|
||||
|
||||
open_in_browser(filename)
|
|
@ -1,7 +1,6 @@
|
|||
"""This submodule contains the path_parse() function used to convert SVG path
|
||||
element d-strings into svgpathtools Path objects.
|
||||
Note: This file was taken (nearly) as is from the svg.path module
|
||||
(v 2.0)."""
|
||||
Note: This file was taken (nearly) as is from the svg.path module (v 2.0)."""
|
||||
|
||||
# External dependencies
|
||||
from __future__ import division, absolute_import, print_function
|
||||
|
@ -26,7 +25,7 @@ def _tokenize_path(pathdef):
|
|||
yield token
|
||||
|
||||
|
||||
def parse_path(pathdef, current_pos=0j):
|
||||
def parse_path(pathdef, current_pos=0j, tree_element=None):
|
||||
# In the SVG specs, initial movetos are absolute, even if
|
||||
# specified as 'm'. This is the default behavior here as well.
|
||||
# But if you pass in a current_pos variable, the initial moveto
|
||||
|
@ -35,7 +34,11 @@ def parse_path(pathdef, current_pos=0j):
|
|||
# Reverse for easy use of .pop()
|
||||
elements.reverse()
|
||||
|
||||
if tree_element is None:
|
||||
segments = Path()
|
||||
else:
|
||||
segments = Path(tree_element=tree_element)
|
||||
|
||||
start_pos = None
|
||||
command = None
|
||||
|
||||
|
|
|
@ -1715,6 +1715,9 @@ class Path(MutableSequence):
|
|||
self._start = None
|
||||
self._end = None
|
||||
|
||||
if 'tree_element' in kw:
|
||||
self._tree_element = kw['tree_element']
|
||||
|
||||
def __getitem__(self, index):
|
||||
return self._segments[index]
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@ The main tool being the svg2paths() function."""
|
|||
|
||||
# External dependencies
|
||||
from __future__ import division, absolute_import, print_function
|
||||
from xml.dom.minidom import parse
|
||||
from os import path as os_path, getcwd
|
||||
import os
|
||||
import xml.etree.cElementTree as etree
|
||||
|
||||
# Internal dependencies
|
||||
from .parser import parse_path
|
||||
|
@ -40,6 +40,10 @@ def ellipse2pathd(ellipse):
|
|||
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()
|
||||
|
@ -59,6 +63,10 @@ def polygon2pathd(polyline_d):
|
|||
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()
|
||||
|
@ -93,15 +101,18 @@ 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']
|
||||
|
||||
def svg2paths(svg_file_location,
|
||||
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):
|
||||
CONVERSIONS = {'circle': ellipse2pathd,
|
||||
'ellipse': ellipse2pathd,
|
||||
'line': line2pathd,
|
||||
'polyline': polyline2pathd,
|
||||
'polygon': polygon2pathd,
|
||||
'rect': rect2pathd}
|
||||
|
||||
def svg2paths(svg_file_location, return_svg_attributes=False,
|
||||
conversions=CONVERSIONS, return_tree=False):
|
||||
"""Converts an SVG into a list of Path objects and attribute dictionaries.
|
||||
|
||||
Converts an SVG file into a list of Path objects and a list of
|
||||
|
@ -133,84 +144,60 @@ def svg2paths(svg_file_location,
|
|||
list: The list of corresponding path attribute dictionaries.
|
||||
dict (optional): A dictionary of svg-attributes (see `svg2paths2()`).
|
||||
"""
|
||||
if os_path.dirname(svg_file_location) == '':
|
||||
svg_file_location = os_path.join(getcwd(), svg_file_location)
|
||||
if os.path.dirname(svg_file_location) == '':
|
||||
svg_file_location = os.path.join(getcwd(), svg_file_location)
|
||||
|
||||
doc = parse(svg_file_location)
|
||||
tree = etree.parse(svg_file_location)
|
||||
|
||||
def dom2dict(element):
|
||||
"""Converts DOM elements to dictionaries of attributes."""
|
||||
keys = list(element.attributes.keys())
|
||||
values = [val.value for val in list(element.attributes.values())]
|
||||
return dict(list(zip(keys, values)))
|
||||
# get URI namespace
|
||||
root_tag = tree.getroot().tag
|
||||
if root_tag[0] == "{":
|
||||
prefix = root_tag[:root_tag.find('}') + 1]
|
||||
else:
|
||||
prefix = ''
|
||||
# etree.register_namespace('', prefix)
|
||||
|
||||
# Use minidom to extract path strings from input SVG
|
||||
paths = [dom2dict(el) for el in doc.getElementsByTagName('path')]
|
||||
def getElementsByTagName(tag):
|
||||
return tree.iter(tag=prefix+tag)
|
||||
|
||||
# Get d-strings for Path elements
|
||||
paths = [el.attrib for el in getElementsByTagName('path')]
|
||||
d_strings = [el['d'] for el in paths]
|
||||
attribute_dictionary_list = paths
|
||||
|
||||
# Use minidom to extract polyline strings from input SVG, convert to
|
||||
# path strings, add to list
|
||||
if convert_polylines_to_paths:
|
||||
plins = [dom2dict(el) for el in doc.getElementsByTagName('polyline')]
|
||||
d_strings += [polyline2pathd(pl['points']) for pl in plins]
|
||||
attribute_dictionary_list += plins
|
||||
# Get d-strings for Path-like elements (using `conversions` dictionary)
|
||||
for tag, fcn in conversions.items():
|
||||
attributes = [el.attrib for el in getElementsByTagName(tag)]
|
||||
d_strings += [fcn(d) for d in attributes]
|
||||
|
||||
# Use minidom to extract polygon strings from input SVG, convert to
|
||||
# path strings, add to list
|
||||
if convert_polygons_to_paths:
|
||||
pgons = [dom2dict(el) for el in doc.getElementsByTagName('polygon')]
|
||||
d_strings += [polygon2pathd(pg['points']) for pg in pgons]
|
||||
attribute_dictionary_list += pgons
|
||||
|
||||
if convert_lines_to_paths:
|
||||
lines = [dom2dict(el) for el in doc.getElementsByTagName('line')]
|
||||
d_strings += [('M' + l['x1'] + ' ' + l['y1'] +
|
||||
'L' + l['x2'] + ' ' + l['y2']) for l in lines]
|
||||
attribute_dictionary_list += lines
|
||||
|
||||
if convert_ellipses_to_paths:
|
||||
ellipses = [dom2dict(el) for el in doc.getElementsByTagName('ellipse')]
|
||||
d_strings += [ellipse2pathd(e) for e in ellipses]
|
||||
attribute_dictionary_list += ellipses
|
||||
|
||||
if convert_circles_to_paths:
|
||||
circles = [dom2dict(el) for el in doc.getElementsByTagName('circle')]
|
||||
d_strings += [ellipse2pathd(c) for c in circles]
|
||||
attribute_dictionary_list += circles
|
||||
|
||||
if convert_rectangles_to_paths:
|
||||
rectangles = [dom2dict(el) for el in doc.getElementsByTagName('rect')]
|
||||
d_strings += [rect2pathd(r) for r in rectangles]
|
||||
attribute_dictionary_list += rectangles
|
||||
|
||||
if return_svg_attributes:
|
||||
svg_attributes = dom2dict(doc.getElementsByTagName('svg')[0])
|
||||
doc.unlink()
|
||||
path_list = [parse_path(d) for d in d_strings]
|
||||
if return_tree: # svg2paths3 default behavior
|
||||
return path_list, tree
|
||||
|
||||
elif return_svg_attributes: # svg2paths2 default behavior
|
||||
svg_attributes = getElementsByTagName('svg')[0].attrib
|
||||
return path_list, attribute_dictionary_list, svg_attributes
|
||||
else:
|
||||
doc.unlink()
|
||||
path_list = [parse_path(d) for d in d_strings]
|
||||
|
||||
else: # svg2paths default behavior
|
||||
return path_list, attribute_dictionary_list
|
||||
|
||||
|
||||
def svg2paths2(svg_file_location,
|
||||
return_svg_attributes=True,
|
||||
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):
|
||||
def svg2paths2(svg_file_location, return_svg_attributes=True,
|
||||
conversions=CONVERSIONS, return_tree=False):
|
||||
"""Convenience function; identical to svg2paths() except that
|
||||
return_svg_attributes=True by default. See svg2paths() docstring for more
|
||||
info."""
|
||||
return svg2paths(svg_file_location=svg_file_location,
|
||||
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)
|
||||
conversions=conversions, return_tree=return_tree)
|
||||
|
||||
|
||||
def svg2paths3(svg_file_location, return_svg_attributes=True,
|
||||
conversions=CONVERSIONS, return_tree=True):
|
||||
"""Convenience function; identical to svg2paths() except that
|
||||
return_tree=True. See svg2paths() docstring for more info."""
|
||||
return svg2paths(svg_file_location=svg_file_location,
|
||||
return_svg_attributes=return_svg_attributes,
|
||||
conversions=conversions, return_tree=return_tree)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue