200 lines
7.0 KiB
Python
200 lines
7.0 KiB
Python
"""(Experimental) replacement for import/export functionality SAX
|
|
|
|
"""
|
|
|
|
# External dependencies
|
|
from __future__ import division, absolute_import, print_function
|
|
import os
|
|
from xml.etree.cElementTree import Element, ElementTree, SubElement
|
|
from defusedxml.cElementTree import iterparse
|
|
|
|
# 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)
|
|
elif 'polygon' == name:
|
|
values["d"] = polygon2pathd(values)
|
|
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)
|