summaryrefslogtreecommitdiffstats
path: root/share/extensions/inkex/elements/_svg.py
diff options
context:
space:
mode:
Diffstat (limited to 'share/extensions/inkex/elements/_svg.py')
-rw-r--r--share/extensions/inkex/elements/_svg.py211
1 files changed, 211 insertions, 0 deletions
diff --git a/share/extensions/inkex/elements/_svg.py b/share/extensions/inkex/elements/_svg.py
new file mode 100644
index 0000000..3e1af09
--- /dev/null
+++ b/share/extensions/inkex/elements/_svg.py
@@ -0,0 +1,211 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2020 Martin Owens <doctormo@gmail.com>
+# Thomas Holder <thomas.holder@schrodinger.com>
+# Sergei Izmailov <sergei.a.izmailov@gmail.com>
+# Windell Oskay <windell@oskay.net>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# pylint: disable=attribute-defined-outside-init
+#
+"""
+Provide a way to load lxml attributes with an svg API on top.
+"""
+
+import random
+from lxml import etree
+
+from ..deprecated import DeprecatedSvgMixin
+from ..units import discover_unit, convert_unit, render_unit
+from ._selected import ElementList
+from ..transforms import BoundingBox
+from ..styles import StyleSheets
+
+from ._base import BaseElement
+from ._meta import NamedView, Defs, StyleElement, Metadata
+
+if False: # pylint: disable=using-constant-test
+ import typing # pylint: disable=unused-import
+
+
+class SvgDocumentElement(DeprecatedSvgMixin, BaseElement):
+ """Provide access to the document level svg functionality"""
+ tag_name = 'svg'
+
+ def _init(self):
+ self.current_layer = None
+ self.view_center = (0.0, 0.0)
+ self.selection = ElementList(self)
+ self.ids = {}
+
+ def tostring(self):
+ """Convert document to string"""
+ return etree.tostring(etree.ElementTree(self))
+
+ def get_ids(self):
+ """Returns a set of unique document ids"""
+ if not self.ids:
+ self.ids = set(self.xpath('//@id'))
+ return self.ids
+
+ def get_unique_id(self, prefix, size=4):
+ """Generate a new id from an existing old_id"""
+ ids = self.get_ids()
+ new_id = None
+ _from = 10 ** size - 1
+ _to = 10 ** size
+ while new_id is None or new_id in ids:
+ # Do not use randint because py2/3 incompatibility
+ new_id = prefix + str(int(random.random() * _from - _to) + _to)
+ self.ids.add(new_id)
+ return new_id
+
+ def get_page_bbox(self):
+ """Gets the page dimensions as a bbox"""
+ return BoundingBox((0, float(self.width)), (0, float(self.height)))
+
+ def get_current_layer(self):
+ """Returns the currently selected layer"""
+ layer = self.getElementById(self.namedview.current_layer, 'svg:g')
+ if layer is None:
+ return self
+ return layer
+
+ def getElement(self, xpath): # pylint: disable=invalid-name
+ """Gets a single element from the given xpath or returns None"""
+ return self.findone(xpath)
+
+ def getElementById(self, eid, elm='*'): # pylint: disable=invalid-name
+ """Get an element in this svg document by it's ID attribute"""
+ if eid is not None:
+ eid = eid.strip()[4:-1] if eid.startswith('url(') else eid
+ eid = eid.lstrip('#')
+ return self.getElement('//{elm}[@id="{eid}"]'.format(elm=elm, eid=eid))
+
+ def getElementByName(self, name, elm='*'): # pylint: disable=invalid-name
+ """Get an element by it's inkscape:label (aka name)"""
+ return self.getElement('//{elm}[@inkscape:label="{name}"]'.format(elm=elm, name=name))
+
+ def getElementsByClass(self, class_name): # pylint: disable=invalid-name
+ """Get elements by it's class name"""
+ from inkex.styles import ConditionalRule
+ return self.xpath(ConditionalRule(".{}".format(class_name)).to_xpath())
+
+ def getElementsByHref(self, eid): # pylint: disable=invalid-name
+ """Get elements by their href xlink attribute"""
+ return self.xpath('//*[@xlink:href="#{}"]'.format(eid))
+
+ def getElementsByStyleUrl(self, eid, style=None): # pylint: disable=invalid-name
+ """Get elements by a style attribute url"""
+ url = "url(#{})".format(eid)
+ if style is not None:
+ url = style + ":" + url
+ return self.xpath('//*[contains(@style,"{}")]'.format(url))
+
+ @property
+ def name(self):
+ """Returns the Document Name"""
+ return self.get('sodipodi:docname', '')
+
+ @property
+ def namedview(self):
+ """Return the sp namedview meta information element"""
+ return self.get_or_create('//sodipodi:namedview', NamedView, True)
+
+ @property
+ def metadata(self):
+ """Return the svg metadata meta element container"""
+ return self.get_or_create('//svg:metadata', Metadata, True)
+
+ @property
+ def defs(self):
+ """Return the svg defs meta element container"""
+ return self.get_or_create('//svg:defs', Defs, True)
+
+ def get_viewbox(self):
+ """Parse and return the document's viewBox attribute"""
+ try:
+ ret = [float(unit) for unit in self.get('viewBox', '0').split()]
+ except ValueError:
+ ret = ''
+ if len(ret) != 4:
+ return [0, 0, 0, 0]
+ return ret
+
+ @property
+ def width(self): # getDocumentWidth(self):
+ """Fault tolerance for lazily defined SVG"""
+ return self.unittouu(self.get('width')) or self.get_viewbox()[2]
+
+ @property
+ def height(self): # getDocumentHeight(self):
+ """Returns a string corresponding to the height of the document, as
+ defined in the SVG file. If it is not defined, returns the height
+ as defined by the viewBox attribute. If viewBox is not defined,
+ returns the string '0'."""
+ return self.unittouu(self.get('height')) or self.get_viewbox()[3]
+
+ @property
+ def scale(self):
+ """Return the ratio between the page width and the viewBox width"""
+ try:
+ scale_x = float(self.width) / float(self.get_viewbox()[2])
+ scale_y = float(self.height) / float(self.get_viewbox()[3])
+ return max([scale_x, scale_y])
+ except (ValueError, ZeroDivisionError):
+ return 1.0
+
+ @property
+ def unit(self):
+ """Returns the unit used for in the SVG document.
+ In the case the SVG document lacks an attribute that explicitly
+ defines what units are used for SVG coordinates, it tries to calculate
+ the unit from the SVG width and viewBox attributes.
+ Defaults to 'px' units."""
+ viewbox = self.get_viewbox()
+ if viewbox and set(viewbox) != {0}:
+ return discover_unit(self.get('width'), viewbox[2], default='px')
+ return 'px' # Default is px
+
+ def unittouu(self, value):
+ """Convert a unit value into the document's units"""
+ return convert_unit(value, self.unit)
+
+ def uutounit(self, value, to_unit):
+ """Convert from the document's units to the given unit"""
+ return convert_unit(render_unit(value, self.unit), to_unit)
+
+ def add_unit(self, value):
+ """Add document unit when no unit is specified in the string """
+ return render_unit(value, self.unit)
+
+ @property
+ def stylesheets(self):
+ """Get all the stylesheets, bound together to one, (for reading)"""
+ sheets = StyleSheets(self)
+ for node in self.xpath('//svg:style'):
+ sheets.append(node.stylesheet())
+ return sheets
+
+ @property
+ def stylesheet(self):
+ """Return the first stylesheet or create one if needed (for writing)"""
+ for sheet in self.stylesheets:
+ return sheet
+
+ style_node = StyleElement()
+ self.defs.append(style_node)
+ return style_node.stylesheet()