inkex.py revision 1d7baac718ad43ee1a8621a7c871024879e6f8e0
fa9e4066f08beec538e775443c5be79dd423fcabahrens#!/usr/bin/env python
fa9e4066f08beec538e775443c5be79dd423fcabahrens# -*- coding: utf-8 -*-
fa9e4066f08beec538e775443c5be79dd423fcabahrens"""
fa9e4066f08beec538e775443c5be79dd423fcabahrensinkex.py
ea8dc4b6d2251b437950c0056bc626b311c73c27eschrockA helper module for creating Inkscape extensions
ea8dc4b6d2251b437950c0056bc626b311c73c27eschrock
fa9e4066f08beec538e775443c5be79dd423fcabahrensCopyright (C) 2005,2010 Aaron Spike <aaron@ekips.org> and contributors
fa9e4066f08beec538e775443c5be79dd423fcabahrens
fa9e4066f08beec538e775443c5be79dd423fcabahrensContributors:
fa9e4066f08beec538e775443c5be79dd423fcabahrens Aurélio A. Heckert <aurium(a)gmail.com>
fa9e4066f08beec538e775443c5be79dd423fcabahrens Bulia Byak <buliabyak@users.sf.net>
fa9e4066f08beec538e775443c5be79dd423fcabahrens Nicolas Dufour, nicoduf@yahoo.fr
fa9e4066f08beec538e775443c5be79dd423fcabahrens Peter J. R. Moulder <pjrm@users.sourceforge.net>
fa9e4066f08beec538e775443c5be79dd423fcabahrens
fa9e4066f08beec538e775443c5be79dd423fcabahrensThis program is free software; you can redistribute it and/or modify
fa9e4066f08beec538e775443c5be79dd423fcabahrensit under the terms of the GNU General Public License as published by
fa9e4066f08beec538e775443c5be79dd423fcabahrensthe Free Software Foundation; either version 2 of the License, or
fa9e4066f08beec538e775443c5be79dd423fcabahrens(at your option) any later version.
fa9e4066f08beec538e775443c5be79dd423fcabahrens
fa9e4066f08beec538e775443c5be79dd423fcabahrensThis program is distributed in the hope that it will be useful,
fa9e4066f08beec538e775443c5be79dd423fcabahrensbut WITHOUT ANY WARRANTY; without even the implied warranty of
e6032be1b8a5a1d03081e0d62b624db95c4cf8b7marksMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
fa9e4066f08beec538e775443c5be79dd423fcabahrensGNU General Public License for more details.
fa9e4066f08beec538e775443c5be79dd423fcabahrens
fa9e4066f08beec538e775443c5be79dd423fcabahrensYou should have received a copy of the GNU General Public License
fa9e4066f08beec538e775443c5be79dd423fcabahrensalong with this program; if not, write to the Free Software
fa9e4066f08beec538e775443c5be79dd423fcabahrensFoundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
fa9e4066f08beec538e775443c5be79dd423fcabahrens"""
fa9e4066f08beec538e775443c5be79dd423fcabahrensimport copy
fa9e4066f08beec538e775443c5be79dd423fcabahrensimport gettext
fa9e4066f08beec538e775443c5be79dd423fcabahrensimport optparse
fa9e4066f08beec538e775443c5be79dd423fcabahrensimport os
fa9e4066f08beec538e775443c5be79dd423fcabahrensimport random
fa9e4066f08beec538e775443c5be79dd423fcabahrensimport re
fa9e4066f08beec538e775443c5be79dd423fcabahrensimport sys
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amwfrom math import *
fa9e4066f08beec538e775443c5be79dd423fcabahrens
fa9e4066f08beec538e775443c5be79dd423fcabahrens#a dictionary of all of the xmlns prefixes in a standard inkscape doc
fa9e4066f08beec538e775443c5be79dd423fcabahrensNSS = {
fa9e4066f08beec538e775443c5be79dd423fcabahrensu'sodipodi' :u'http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd',
fa9e4066f08beec538e775443c5be79dd423fcabahrensu'cc' :u'http://creativecommons.org/ns#',
fa9e4066f08beec538e775443c5be79dd423fcabahrensu'ccOLD' :u'http://web.resource.org/cc/',
169cdae232f15e542d6af0a9ce30c3f84222bc0fmarksu'svg' :u'http://www.w3.org/2000/svg',
fa9e4066f08beec538e775443c5be79dd423fcabahrensu'dc' :u'http://purl.org/dc/elements/1.1/',
fa9e4066f08beec538e775443c5be79dd423fcabahrensu'rdf' :u'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
fa9e4066f08beec538e775443c5be79dd423fcabahrensu'inkscape' :u'http://www.inkscape.org/namespaces/inkscape',
fa9e4066f08beec538e775443c5be79dd423fcabahrensu'xlink' :u'http://www.w3.org/1999/xlink',
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amwu'xml' :u'http://www.w3.org/XML/1998/namespace'
fa9e4066f08beec538e775443c5be79dd423fcabahrens}
fa9e4066f08beec538e775443c5be79dd423fcabahrens
fa9e4066f08beec538e775443c5be79dd423fcabahrensdef localize():
fa9e4066f08beec538e775443c5be79dd423fcabahrens domain = 'inkscape'
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw if sys.platform.startswith('win'):
fa9e4066f08beec538e775443c5be79dd423fcabahrens import locale
fa9e4066f08beec538e775443c5be79dd423fcabahrens current_locale, encoding = locale.getdefaultlocale()
fa9e4066f08beec538e775443c5be79dd423fcabahrens os.environ['LANG'] = current_locale
fa9e4066f08beec538e775443c5be79dd423fcabahrens try:
fa9e4066f08beec538e775443c5be79dd423fcabahrens localdir = os.environ['INKSCAPE_LOCALEDIR'];
fa9e4066f08beec538e775443c5be79dd423fcabahrens trans = gettext.translation(domain, localdir, [current_locale], fallback=True)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw except KeyError:
fa9e4066f08beec538e775443c5be79dd423fcabahrens trans = gettext.translation(domain, fallback=True)
fa9e4066f08beec538e775443c5be79dd423fcabahrens elif sys.platform.startswith('darwin'):
fa9e4066f08beec538e775443c5be79dd423fcabahrens try:
fa9e4066f08beec538e775443c5be79dd423fcabahrens localdir = os.environ['INKSCAPE_LOCALEDIR'];
fa9e4066f08beec538e775443c5be79dd423fcabahrens trans = gettext.translation(domain, localdir, fallback=True)
fa9e4066f08beec538e775443c5be79dd423fcabahrens except KeyError:
fa9e4066f08beec538e775443c5be79dd423fcabahrens try:
fa9e4066f08beec538e775443c5be79dd423fcabahrens localdir = os.environ['PACKAGE_LOCALE_DIR'];
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw trans = gettext.translation(domain, localdir, fallback=True)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw except KeyError:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw trans = gettext.translation(domain, fallback=True)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw else:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw try:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw localdir = os.environ['PACKAGE_LOCALE_DIR'];
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw trans = gettext.translation(domain, localdir, fallback=True)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw except KeyError:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw trans = gettext.translation(domain, fallback=True)
fa9e4066f08beec538e775443c5be79dd423fcabahrens #sys.stderr.write(str(localdir) + "\n")
fa9e4066f08beec538e775443c5be79dd423fcabahrens trans.install()
fa9e4066f08beec538e775443c5be79dd423fcabahrens
fa9e4066f08beec538e775443c5be79dd423fcabahrensdef debug(what):
fa9e4066f08beec538e775443c5be79dd423fcabahrens sys.stderr.write(str(what) + "\n")
fa9e4066f08beec538e775443c5be79dd423fcabahrens return what
fa9e4066f08beec538e775443c5be79dd423fcabahrens
fa9e4066f08beec538e775443c5be79dd423fcabahrensdef errormsg(msg):
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw """Intended for end-user-visible error messages.
fa9e4066f08beec538e775443c5be79dd423fcabahrens
169cdae232f15e542d6af0a9ce30c3f84222bc0fmarks (Currently just writes to stderr with an appended newline, but could do
fa9e4066f08beec538e775443c5be79dd423fcabahrens something better in future: e.g. could add markup to distinguish error
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw messages from status messages or debugging output.)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw Note that this should always be combined with translation:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw import inkex
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw inkex.localize()
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw ...
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw inkex.errormsg(_("This extension requires two selected paths."))
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw """
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw if isinstance(msg, unicode):
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw sys.stderr.write(msg.encode("UTF-8") + "\n")
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw else:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw sys.stderr.write((unicode(msg, "utf-8", errors='replace') + "\n").encode("UTF-8"))
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amwdef are_near_relative(a, b, eps):
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw if (a-b <= a*eps) and (a-b >= -a*eps):
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw return True
fa9e4066f08beec538e775443c5be79dd423fcabahrens else:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw return False
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw# third party library
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amwtry:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw from lxml import etree
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amwexcept Exception, e:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw localize()
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw errormsg(_("The fantastic lxml wrapper for libxml2 is required by inkex.py and therefore this extension. Please download and install the latest version from http://cheeseshop.python.org/pypi/lxml/, or install it through your package manager by a command like: sudo apt-get install python-lxml\n\nTechnical details:\n%s" % (e,)))
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw sys.exit()
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amwdef check_inkbool(option, opt, value):
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw if str(value).capitalize() == 'True':
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw return True
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw elif str(value).capitalize() == 'False':
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw return False
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw else:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw raise optparse.OptionValueError("option %s: invalid inkbool value: %s" % (opt, value))
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amwdef addNS(tag, ns=None):
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw val = tag
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw if ns!=None and len(ns)>0 and NSS.has_key(ns) and len(tag)>0 and tag[0]!='{':
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw val = "{%s}%s" % (NSS[ns], tag)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw return val
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amwclass InkOption(optparse.Option):
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw TYPES = optparse.Option.TYPES + ("inkbool",)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw TYPE_CHECKER = copy.copy(optparse.Option.TYPE_CHECKER)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw TYPE_CHECKER["inkbool"] = check_inkbool
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amwclass Effect:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw """A class for creating Inkscape SVG Effects"""
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw def __init__(self, *args, **kwargs):
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw self.document=None
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw self.original_document=None
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw self.ctx=None
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw self.selected={}
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw self.doc_ids={}
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw self.options=None
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw self.args=None
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw self.OptionParser = optparse.OptionParser(usage="usage: %prog [options] SVGfile",option_class=InkOption)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw self.OptionParser.add_option("--id",
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw action="append", type="string", dest="ids", default=[],
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw help="id attribute of object to manipulate")
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw def effect(self):
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw pass
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw def getoptions(self,args=sys.argv[1:]):
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw """Collect command line arguments"""
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw self.options, self.args = self.OptionParser.parse_args(args)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw def parse(self, filename=None):
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw """Parse document in specified file or on stdin"""
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw # First try to open the file from the function argument
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw if filename != None:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw try:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw stream = open(filename, 'r')
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw except Exception:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw errormsg(_("Unable to open specified file: %s") % filename)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw sys.exit()
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw # If it wasn't specified, try to open the file specified as
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw # an object member
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw elif self.svg_file != None:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw try:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw stream = open(self.svg_file, 'r')
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw except Exception:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw errormsg(_("Unable to open object member file: %s") % self.svg_file)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw sys.exit()
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw # Finally, if the filename was not specified anywhere, use
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw # standard input stream
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw else:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw stream = sys.stdin
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw p = etree.XMLParser(huge_tree=True)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw self.document = etree.parse(stream, parser=p)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw self.original_document = copy.deepcopy(self.document)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw stream.close()
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw # defines view_center in terms of document units
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw def getposinlayer(self):
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw #defaults
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw self.current_layer = self.document.getroot()
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw self.view_center = (0.0,0.0)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw layerattr = self.document.xpath('//sodipodi:namedview/@inkscape:current-layer', namespaces=NSS)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw if layerattr:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw layername = layerattr[0]
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw layer = self.document.xpath('//svg:g[@id="%s"]' % layername, namespaces=NSS)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw if layer:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw self.current_layer = layer[0]
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw xattr = self.document.xpath('//sodipodi:namedview/@inkscape:cx', namespaces=NSS)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw yattr = self.document.xpath('//sodipodi:namedview/@inkscape:cy', namespaces=NSS)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw if xattr and yattr:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw x = self.unittouu( xattr[0] + 'px' )
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw y = self.unittouu( yattr[0] + 'px')
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw doc_height = self.unittouu(self.getDocumentHeight())
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw if x and y and doc_height is not None:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw self.view_center = (float(x), doc_height - float(y)) # FIXME: y-coordinate flip, eliminate it when it's gone in Inkscape
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw def getselected(self):
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw """Collect selected nodes"""
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw for i in self.options.ids:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw path = '//*[@id="%s"]' % i
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw for node in self.document.xpath(path, namespaces=NSS):
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw self.selected[i] = node
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw def getElementById(self, id):
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw path = '//*[@id="%s"]' % id
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw el_list = self.document.xpath(path, namespaces=NSS)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw if el_list:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw return el_list[0]
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw else:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw return None
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw def getParentNode(self, node):
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw for parent in self.document.getiterator():
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw if node in parent.getchildren():
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw return parent
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw break
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw def getdocids(self):
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw docIdNodes = self.document.xpath('//@id', namespaces=NSS)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw for m in docIdNodes:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw self.doc_ids[m] = 1
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw def getNamedView(self):
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw return self.document.xpath('//sodipodi:namedview', namespaces=NSS)[0]
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw def createGuide(self, posX, posY, angle):
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw atts = {
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw 'position': str(posX)+','+str(posY),
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw 'orientation': str(sin(radians(angle)))+','+str(-cos(radians(angle)))
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw }
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw guide = etree.SubElement(
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw self.getNamedView(),
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw addNS('guide','sodipodi'), atts )
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw return guide
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw def output(self):
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw """Serialize document into XML on stdout"""
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw original = etree.tostring(self.original_document)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw result = etree.tostring(self.document)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw if original != result:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw self.document.write(sys.stdout)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw def affect(self, args=sys.argv[1:], output=True):
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw """Affect an SVG document with a callback effect"""
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw self.svg_file = args[-1]
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw self.getoptions(args)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw self.parse()
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw self.getposinlayer()
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw self.getselected()
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw self.getdocids()
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw self.effect()
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw if output: self.output()
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw def uniqueId(self, old_id, make_new_id = True):
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw new_id = old_id
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw if make_new_id:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw while new_id in self.doc_ids:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw new_id += random.choice('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw self.doc_ids[new_id] = 1
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw return new_id
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw def xpathSingle(self, path):
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw try:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw retval = self.document.xpath(path, namespaces=NSS)[0]
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw except:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw errormsg(_("No matching node for expression: %s") % path)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw retval = None
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw return retval
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw #a dictionary of unit to user unit conversion factors
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw __uuconv = {'in':96.0, 'pt':1.33333333333, 'px':1.0, 'mm':3.77952755913, 'cm':37.7952755913,
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw 'm':3779.52755913, 'km':3779527.55913, 'pc':16.0, 'yd':3456.0 , 'ft':1152.0}
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw # Fault tolerance for lazily defined SVG
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw def getDocumentWidth(self):
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw width = self.document.getroot().get('width')
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw if width:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw return width
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw else:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw viewbox = self.document.getroot().get('viewBox')
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw if viewbox:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw return viewbox.split()[2]
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw else:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw return '0'
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw # Fault tolerance for lazily defined SVG
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw def getDocumentHeight(self):
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw height = self.document.getroot().get('height')
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw if height:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw return height
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw else:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw viewbox = self.document.getroot().get('viewBox')
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw if viewbox:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw return viewbox.split()[3]
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw else:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw return '0'
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw # Function returns the unit used for the values in SVG.
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw # For lack of an attribute in SVG that explicitly defines what units are used for SVG coordinates,
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw # try to calculate the unit from the SVG width and SVG viewbox.
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw # Defaults to 'px' units.
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw def getDocumentUnit(self):
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw svgunit = 'px' #default to pixels
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw svgwidth = self.getDocumentWidth()
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw viewboxstr = self.document.getroot().get('viewBox')
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw if viewboxstr and svgwidth is not None:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw unitmatch = re.compile('(%s)$' % '|'.join(self.__uuconv.keys()))
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw param = re.compile(r'(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)')
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw p = param.match(svgwidth)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw u = unitmatch.search(svgwidth)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw width = 100 #default
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw viewboxwidth = 100 #default
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw svgwidthunit = 'px' #default assume 'px' unit
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw if p:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw width = float(p.string[p.start():p.end()])
fa9e4066f08beec538e775443c5be79dd423fcabahrens else:
fa9e4066f08beec538e775443c5be79dd423fcabahrens errormsg(_("SVG Width not set correctly! Assuming width = 100"))
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw if u:
fa9e4066f08beec538e775443c5be79dd423fcabahrens svgwidthunit = u.string[u.start():u.end()]
fa9e4066f08beec538e775443c5be79dd423fcabahrens
fa9e4066f08beec538e775443c5be79dd423fcabahrens viewboxnumbers = []
fa9e4066f08beec538e775443c5be79dd423fcabahrens for t in viewboxstr.split():
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw try:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw viewboxnumbers.append(float(t))
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw except ValueError:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw pass
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw if len(viewboxnumbers) == 4: #check for correct number of numbers
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw viewboxwidth = viewboxnumbers[2]
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
fa9e4066f08beec538e775443c5be79dd423fcabahrens svgunitfactor = self.__uuconv[svgwidthunit] * width / viewboxwidth
fa9e4066f08beec538e775443c5be79dd423fcabahrens
fa9e4066f08beec538e775443c5be79dd423fcabahrens # try to find the svgunitfactor in the list of units known. If we don't find something, ...
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw eps = 0.01 #allow 1% error in factor
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw for key in self.__uuconv:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw if are_near_relative(self.__uuconv[key], svgunitfactor, eps):
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw #found match!
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw svgunit = key;
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw return svgunit
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw def unittouu(self, string):
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw '''Returns userunits given a string representation of units in another system'''
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw unit = re.compile('(%s)$' % '|'.join(self.__uuconv.keys()))
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw param = re.compile(r'(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)')
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw if string is not None:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw p = param.match(string)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw u = unit.search(string)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw if p:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw retval = float(p.string[p.start():p.end()])
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw else:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw retval = 0.0
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw if u:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw try:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw return retval * (self.__uuconv[u.string[u.start():u.end()]] / self.__uuconv[self.getDocumentUnit()])
2459a9eaca6b6525c76289d22ffe4c96be1956d6marks except KeyError:
2459a9eaca6b6525c76289d22ffe4c96be1956d6marks pass
fa9e4066f08beec538e775443c5be79dd423fcabahrens else: # default assume 'px' unit
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw return retval / self.__uuconv[self.getDocumentUnit()]
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw else:
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw retval = None
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw return retval
fa9e4066f08beec538e775443c5be79dd423fcabahrens
2459a9eaca6b6525c76289d22ffe4c96be1956d6marks def uutounit(self, val, unit):
2459a9eaca6b6525c76289d22ffe4c96be1956d6marks return val / (self.__uuconv[unit] / self.__uuconv[self.getDocumentUnit()])
2459a9eaca6b6525c76289d22ffe4c96be1956d6marks
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw def addDocumentUnit(self, value):
2459a9eaca6b6525c76289d22ffe4c96be1956d6marks ''' Add document unit when no unit is specified in the string '''
2459a9eaca6b6525c76289d22ffe4c96be1956d6marks try:
2459a9eaca6b6525c76289d22ffe4c96be1956d6marks float(value)
2459a9eaca6b6525c76289d22ffe4c96be1956d6marks return value + self.getDocumentUnit()
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw except ValueError:
fa9e4066f08beec538e775443c5be79dd423fcabahrens return value
fa9e4066f08beec538e775443c5be79dd423fcabahrens
fa9e4066f08beec538e775443c5be79dd423fcabahrens# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw