Basic Svg_Io_Sax (#66)
* Basic Svg_Io_Sax * Update to binary write * Fixed SaxSvg Load, tweaked test. * Couple tweaks. * Couple tweaks to fit the dom parser api better * Switch to iterparse for speed * Used None matrix for identity in default case. * Test Update for None Matrixpull/65/head^2
parent
2feb3c92b5
commit
e91a35c3da
|
@ -13,6 +13,7 @@ from .polytools import polyroots, polyroots01, rational_limit, real, imag
|
||||||
from .misctools import hex2rgb, rgb2hex
|
from .misctools import hex2rgb, rgb2hex
|
||||||
from .smoothing import smoothed_path, smoothed_joint, is_differentiable, kinks
|
from .smoothing import smoothed_path, smoothed_joint, is_differentiable, kinks
|
||||||
from .document import Document, CONVERSIONS, CONVERT_ONLY_PATHS, SVG_GROUP_TAG, SVG_NAMESPACE
|
from .document import Document, CONVERSIONS, CONVERT_ONLY_PATHS, SVG_GROUP_TAG, SVG_NAMESPACE
|
||||||
|
from .svg_io_sax import SaxDocument
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from .svg_to_paths import svg2paths, svg2paths2
|
from .svg_to_paths import svg2paths, svg2paths2
|
||||||
|
|
|
@ -0,0 +1,198 @@
|
||||||
|
"""(Experimental) replacement for import/export functionality SAX
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# External dependencies
|
||||||
|
from __future__ import division, absolute_import, print_function
|
||||||
|
import os
|
||||||
|
from xml.etree.ElementTree import iterparse, Element, ElementTree, SubElement
|
||||||
|
|
||||||
|
# Internal dependencies
|
||||||
|
from .parser import parse_path
|
||||||
|
from .parser import parse_transform
|
||||||
|
from .svg_to_paths import (path2pathd, ellipse2pathd, line2pathd,
|
||||||
|
polyline2pathd, polygon2pathd, rect2pathd)
|
||||||
|
from .misctools import open_in_browser
|
||||||
|
from .path import *
|
||||||
|
|
||||||
|
# To maintain forward/backward compatibility
|
||||||
|
try:
|
||||||
|
str = basestring
|
||||||
|
except NameError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
NAME_SVG = "svg"
|
||||||
|
ATTR_VERSION = "version"
|
||||||
|
VALUE_SVG_VERSION = "1.1"
|
||||||
|
ATTR_XMLNS = "xmlns"
|
||||||
|
VALUE_XMLNS = "http://www.w3.org/2000/svg"
|
||||||
|
ATTR_XMLNS_LINK = "xmlns:xlink"
|
||||||
|
VALUE_XLINK = "http://www.w3.org/1999/xlink"
|
||||||
|
ATTR_XMLNS_EV = "xmlns:ev"
|
||||||
|
VALUE_XMLNS_EV = "http://www.w3.org/2001/xml-events"
|
||||||
|
ATTR_WIDTH = "width"
|
||||||
|
ATTR_HEIGHT = "height"
|
||||||
|
ATTR_VIEWBOX = "viewBox"
|
||||||
|
NAME_PATH = "path"
|
||||||
|
ATTR_DATA = "d"
|
||||||
|
ATTR_FILL = "fill"
|
||||||
|
ATTR_STROKE = "stroke"
|
||||||
|
ATTR_STROKE_WIDTH = "stroke-width"
|
||||||
|
ATTR_TRANSFORM = "transform"
|
||||||
|
VALUE_NONE = "none"
|
||||||
|
|
||||||
|
|
||||||
|
class SaxDocument:
|
||||||
|
def __init__(self, filename):
|
||||||
|
"""A container for a SAX SVG light tree objects document.
|
||||||
|
|
||||||
|
This class provides functions for extracting SVG data into Path objects.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filename (str): The filename of the SVG file
|
||||||
|
"""
|
||||||
|
self.root_values = {}
|
||||||
|
self.tree = []
|
||||||
|
# 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
|
||||||
|
|
||||||
|
if filename is not None:
|
||||||
|
self.sax_parse(filename)
|
||||||
|
|
||||||
|
def sax_parse(self, filename):
|
||||||
|
self.root_values = {}
|
||||||
|
self.tree = []
|
||||||
|
stack = []
|
||||||
|
values = {}
|
||||||
|
matrix = None
|
||||||
|
for event, elem in iterparse(filename, events=('start', 'end')):
|
||||||
|
if event == 'start':
|
||||||
|
stack.append((values, matrix))
|
||||||
|
if matrix is not None:
|
||||||
|
matrix = matrix.copy() # copy of matrix
|
||||||
|
current_values = values
|
||||||
|
values = {}
|
||||||
|
values.update(current_values) # copy of dictionary
|
||||||
|
attrs = elem.attrib
|
||||||
|
values.update(attrs)
|
||||||
|
name = elem.tag[28:]
|
||||||
|
if "style" in attrs:
|
||||||
|
for equate in attrs["style"].split(";"):
|
||||||
|
equal_item = equate.split(":")
|
||||||
|
values[equal_item[0]] = equal_item[1]
|
||||||
|
if "transform" in attrs:
|
||||||
|
transform_matrix = parse_transform(attrs["transform"])
|
||||||
|
if matrix is None:
|
||||||
|
matrix = np.identity(3)
|
||||||
|
matrix = transform_matrix.dot(matrix)
|
||||||
|
if "svg" == name:
|
||||||
|
current_values = values
|
||||||
|
values = {}
|
||||||
|
values.update(current_values)
|
||||||
|
self.root_values = current_values
|
||||||
|
continue
|
||||||
|
elif "g" == name:
|
||||||
|
continue
|
||||||
|
elif 'path' == name:
|
||||||
|
values['d'] = path2pathd(values)
|
||||||
|
elif 'circle' == name:
|
||||||
|
values["d"] = ellipse2pathd(values)
|
||||||
|
elif 'ellipse' == name:
|
||||||
|
values["d"] = ellipse2pathd(values)
|
||||||
|
elif 'line' == name:
|
||||||
|
values["d"] = line2pathd(values)
|
||||||
|
elif 'polyline' == name:
|
||||||
|
values["d"] = polyline2pathd(values['points'])
|
||||||
|
elif 'polygon' == name:
|
||||||
|
values["d"] = polygon2pathd(values['points'])
|
||||||
|
elif 'rect' == name:
|
||||||
|
values["d"] = rect2pathd(values)
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
values["matrix"] = matrix
|
||||||
|
values["name"] = name
|
||||||
|
self.tree.append(values)
|
||||||
|
else:
|
||||||
|
v = stack.pop()
|
||||||
|
values = v[0]
|
||||||
|
matrix = v[1]
|
||||||
|
|
||||||
|
def flatten_all_paths(self):
|
||||||
|
flat = []
|
||||||
|
for values in self.tree:
|
||||||
|
pathd = values['d']
|
||||||
|
matrix = values['matrix']
|
||||||
|
parsed_path = parse_path(pathd)
|
||||||
|
if matrix is not None:
|
||||||
|
transform(parsed_path, matrix)
|
||||||
|
flat.append(parsed_path)
|
||||||
|
return flat
|
||||||
|
|
||||||
|
def get_pathd_and_matrix(self):
|
||||||
|
flat = []
|
||||||
|
for values in self.tree:
|
||||||
|
pathd = values['d']
|
||||||
|
matrix = values['matrix']
|
||||||
|
flat.append((pathd, matrix))
|
||||||
|
return flat
|
||||||
|
|
||||||
|
def generate_dom(self):
|
||||||
|
root = Element(NAME_SVG)
|
||||||
|
root.set(ATTR_VERSION, VALUE_SVG_VERSION)
|
||||||
|
root.set(ATTR_XMLNS, VALUE_XMLNS)
|
||||||
|
root.set(ATTR_XMLNS_LINK, VALUE_XLINK)
|
||||||
|
root.set(ATTR_XMLNS_EV, VALUE_XMLNS_EV)
|
||||||
|
width = self.root_values.get(ATTR_WIDTH, None)
|
||||||
|
height = self.root_values.get(ATTR_HEIGHT, None)
|
||||||
|
if width is not None:
|
||||||
|
root.set(ATTR_WIDTH, width)
|
||||||
|
if height is not None:
|
||||||
|
root.set(ATTR_HEIGHT, height)
|
||||||
|
viewbox = self.root_values.get(ATTR_VIEWBOX, None)
|
||||||
|
if viewbox is not None:
|
||||||
|
root.set(ATTR_VIEWBOX, viewbox)
|
||||||
|
identity = np.identity(3)
|
||||||
|
for values in self.tree:
|
||||||
|
pathd = values.get('d', '')
|
||||||
|
matrix = values.get('matrix', None)
|
||||||
|
# path_value = parse_path(pathd)
|
||||||
|
|
||||||
|
path = SubElement(root, NAME_PATH)
|
||||||
|
if matrix is not None and not np.all(np.equal(matrix, identity)):
|
||||||
|
matrix_string = "matrix("
|
||||||
|
matrix_string += " "
|
||||||
|
matrix_string += str(matrix[0][0])
|
||||||
|
matrix_string += " "
|
||||||
|
matrix_string += str(matrix[1][0])
|
||||||
|
matrix_string += " "
|
||||||
|
matrix_string += str(matrix[0][1])
|
||||||
|
matrix_string += " "
|
||||||
|
matrix_string += str(matrix[1][1])
|
||||||
|
matrix_string += " "
|
||||||
|
matrix_string += str(matrix[0][2])
|
||||||
|
matrix_string += " "
|
||||||
|
matrix_string += str(matrix[1][2])
|
||||||
|
matrix_string += ")"
|
||||||
|
path.set(ATTR_TRANSFORM, matrix_string)
|
||||||
|
if ATTR_DATA in values:
|
||||||
|
path.set(ATTR_DATA, values[ATTR_DATA])
|
||||||
|
if ATTR_FILL in values:
|
||||||
|
path.set(ATTR_FILL, values[ATTR_FILL])
|
||||||
|
if ATTR_STROKE in values:
|
||||||
|
path.set(ATTR_STROKE, values[ATTR_STROKE])
|
||||||
|
return ElementTree(root)
|
||||||
|
|
||||||
|
def save(self, filename):
|
||||||
|
with open(filename, 'wb') as output_svg:
|
||||||
|
dom_tree = self.generate_dom()
|
||||||
|
dom_tree.write(output_svg)
|
||||||
|
|
||||||
|
def display(self, filename=None):
|
||||||
|
"""Displays/opens the doc using the OS's default application."""
|
||||||
|
if filename is None:
|
||||||
|
filename = 'display_temp.svg'
|
||||||
|
self.save(filename)
|
||||||
|
open_in_browser(filename)
|
|
@ -0,0 +1 @@
|
||||||
|
<svg height="100%" version="1.1" viewBox="0 0 365 365" 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"><path d="M10.0,50.0a40.0,40.0 0 1,0 80.0,0a40.0,40.0 0 1,0 -80.0,0" fill="red" stroke="black" transform="matrix( 1.5 0.0 0.0 0.5 -40.0 20.0)" /><path d="M 150,200 l -50,25" fill="black" stroke="black" transform="matrix( 1.5 0.0 0.0 0.5 -40.0 20.0)" /><path d="M 100 350 l 150 -300" fill="none" stroke="red" /><path d="M 250 50 l 150 300" fill="none" stroke="red" /><path d="M 175 200 l 150 0" fill="none" stroke="green" /><path d="M 100 350 q 150 -300 300 0" fill="none" stroke="blue" /><path d="M97.0,350.0a3.0,3.0 0 1,0 6.0,0a3.0,3.0 0 1,0 -6.0,0" fill="black" stroke="black" /><path d="M247.0,50.0a3.0,3.0 0 1,0 6.0,0a3.0,3.0 0 1,0 -6.0,0" fill="black" stroke="black" /><path d="M397.0,350.0a3.0,3.0 0 1,0 6.0,0a3.0,3.0 0 1,0 -6.0,0" fill="black" stroke="black" /><path d="M200 10L250 190L160 210z" fill="lime" stroke="purple" transform="matrix( 0.1 0.0 0.0 0.1 0.0 0.0)" /></svg>
|
After Width: | Height: | Size: 1.0 KiB |
|
@ -70,7 +70,8 @@
|
||||||
|
|
||||||
<g
|
<g
|
||||||
id="nested group - translate xy"
|
id="nested group - translate xy"
|
||||||
transform="translate(20, 30)">
|
transform="
|
||||||
|
translate(20, 30)">
|
||||||
|
|
||||||
<path
|
<path
|
||||||
d="M 150,200 l -50,25"
|
d="M 150,200 l -50,25"
|
||||||
|
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
@ -0,0 +1,30 @@
|
||||||
|
from __future__ import division, absolute_import, print_function
|
||||||
|
import unittest
|
||||||
|
from svgpathtools import *
|
||||||
|
from os.path import join, dirname
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
class TestSaxGroups(unittest.TestCase):
|
||||||
|
|
||||||
|
def check_values(self, v, z):
|
||||||
|
# Check that the components of 2D vector v match the components
|
||||||
|
# of complex number z
|
||||||
|
self.assertAlmostEqual(v[0], z.real)
|
||||||
|
self.assertAlmostEqual(v[1], z.imag)
|
||||||
|
|
||||||
|
def test_parse_display(self):
|
||||||
|
doc = SaxDocument(join(dirname(__file__), 'transforms.svg'))
|
||||||
|
# doc.display()
|
||||||
|
for i, node in enumerate(doc.tree):
|
||||||
|
values = node
|
||||||
|
path_value = values['d']
|
||||||
|
matrix = values['matrix']
|
||||||
|
self.assertTrue(values is not None)
|
||||||
|
self.assertTrue(path_value is not None)
|
||||||
|
if i == 0:
|
||||||
|
self.assertEqual(values['fill'], 'red')
|
||||||
|
if i == 8 or i == 7:
|
||||||
|
self.assertEqual(matrix, None)
|
||||||
|
if i == 9:
|
||||||
|
self.assertEqual(values['fill'], 'lime')
|
|
@ -0,0 +1,49 @@
|
||||||
|
<?xml version="1.0" ?>
|
||||||
|
<svg
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 365 365"
|
||||||
|
height="100%"
|
||||||
|
width="100%"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g
|
||||||
|
id="matrix group"
|
||||||
|
transform="matrix(1.5 0.0 0.0 0.5 -40.0 20.0)" stroke="black" style="fill:red">
|
||||||
|
<circle cx="50" cy="50" r="40" stroke-width="3" />
|
||||||
|
<g id="scale group" transform="scale(1.25)"></g>
|
||||||
|
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
d="M 150,200 l -50,25"
|
||||||
|
fill="black"
|
||||||
|
stroke="black"
|
||||||
|
stroke-width="3"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<path id="lineAB" d="M 100 350 l 150 -300" stroke="red"
|
||||||
|
stroke-width="3" fill="none" />
|
||||||
|
<path id="lineBC" d="M 250 50 l 150 300" stroke="red"
|
||||||
|
stroke-width="3" fill="none" />
|
||||||
|
<path d="M 175 200 l 150 0" stroke="green" stroke-width="3"
|
||||||
|
fill="none" />
|
||||||
|
<path d="M 100 350 q 150 -300 300 0" stroke="blue"
|
||||||
|
stroke-width="5" fill="none" />
|
||||||
|
<!-- Mark relevant points -->
|
||||||
|
<g stroke="black" stroke-width="3" fill="black">
|
||||||
|
<circle id="pointA" cx="100" cy="350" r="3" />
|
||||||
|
<circle id="pointB" cx="250" cy="50" r="3" />
|
||||||
|
<circle id="pointC" cx="400" cy="350" r="3" />
|
||||||
|
</g>
|
||||||
|
<!-- Label the points -->
|
||||||
|
<g font-size="30" font-family="sans-serif" fill="black" stroke="none"
|
||||||
|
text-anchor="middle">
|
||||||
|
<text x="100" y="350" dx="-30">A</text>
|
||||||
|
<text x="250" y="50" dy="-10">B</text>
|
||||||
|
<text x="400" y="350" dx="30">C</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g transform="scale(0.1)">
|
||||||
|
<polygon points="200,10 250,190 160,210" style="fill:lime;stroke:purple;stroke-width:1" />
|
||||||
|
</g>
|
||||||
|
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
Loading…
Reference in New Issue