diff options
| author | Ted Gould <ted@gould.cx> | 2010-03-26 04:34:25 +0000 |
|---|---|---|
| committer | Ted Gould <ted@gould.cx> | 2010-03-26 04:34:25 +0000 |
| commit | 9e023a3aa964a0d3fa1e31e46d33657367ba68aa (patch) | |
| tree | 33f1392a340737e4eeefca6fd031f96c29befd2b /share/extensions | |
| parent | Installing the pkgconfig file (diff) | |
| parent | Adding in shape-record.h (diff) | |
| download | inkscape-9e023a3aa964a0d3fa1e31e46d33657367ba68aa.tar.gz inkscape-9e023a3aa964a0d3fa1e31e46d33657367ba68aa.zip | |
Merge from trunk
(bzr r8254.1.53)
Diffstat (limited to 'share/extensions')
27 files changed, 2271 insertions, 146 deletions
diff --git a/share/extensions/Barcode/Code128.py b/share/extensions/Barcode/Code128.py index 42342c400..2f0b327ed 100644 --- a/share/extensions/Barcode/Code128.py +++ b/share/extensions/Barcode/Code128.py @@ -2,6 +2,7 @@ Copyright (C) 2007 Martin Owens Debugged by Ralf Heinecke & Martin Siepmann 09/07/2007 +Debugged by Horst Schottky Feb. 27. 2010 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 @@ -51,7 +52,7 @@ class Object(Barcode): # Split up into sections of numbers, or charicters # This makes sure that all the charicters are encoded # In the best way posible for Code128 - for datum in re.findall(r'(?:(?:\d\d){2,})|.', text): + for datum in re.findall(r'(?:(?:\d\d){2,})|(?:^\d\d)|.', text): if len(datum) == 1: block = block + datum else: @@ -96,7 +97,7 @@ class Object(Barcode): i = pos if pos: - num = num + (math.abs(num - 102) * 2) + num = 204 - num else: i = 1 diff --git a/share/extensions/Makefile.am b/share/extensions/Makefile.am index 1650923e0..cd409d233 100644 --- a/share/extensions/Makefile.am +++ b/share/extensions/Makefile.am @@ -18,6 +18,7 @@ extensions = \ addnodes.py \ bezmisc.py \ chardataeffect.py\ + color_blackandwhite.py\ color_brighter.py\ color_custom.py\ color_darker.py\ @@ -61,6 +62,7 @@ extensions = \ fractalize.py \ funcplot.py \ gears.py\ + generate_voronoi.py \ gimp_xcf.py \ grid_cartesian.py \ grid_polar.py \ @@ -87,6 +89,7 @@ extensions = \ pathmodifier.py\ perfectboundcover.py \ perspective.py \ + pixelsnap.py \ plt_output.py \ polyhedron_3d.py \ printing-marks.py \ @@ -129,6 +132,10 @@ extensions = \ txt2svg.pl \ uniconv-ext.py \ uniconv_output.py \ + voronoi.py \ + webslicer-create-group.py \ + webslicer-create-rect.py \ + webslicer-export.py \ web-set-att.py \ web-transmit-att.py \ whirl.py \ @@ -151,6 +158,7 @@ modules = \ cdt_input.inx \ cgm_input.inx \ cmx_input.inx \ + color_blackandwhite.inx\ color_brighter.inx\ color_custom.inx \ color_darker.inx\ @@ -190,6 +198,7 @@ modules = \ fractalize.inx \ funcplot.inx \ gears.inx\ + generate_voronoi.inx \ gimp_xcf.inx \ grid_cartesian.inx \ grid_polar.inx \ @@ -217,6 +226,7 @@ modules = \ pathscatter.inx\ perfectboundcover.inx \ perspective.inx \ + pixelsnap.inx \ plt_input.inx \ plt_output.inx \ polyhedron_3d.inx \ @@ -249,6 +259,9 @@ modules = \ text_braille.inx \ triangle.inx \ txt2svg.inx \ + webslicer-create-group.inx \ + webslicer-create-rect.inx \ + webslicer-export.inx \ web-set-att.inx \ web-transmit-att.inx \ whirl.inx \ diff --git a/share/extensions/ai_input.inx b/share/extensions/ai_input.inx index adc79dc5c..a48825840 100644 --- a/share/extensions/ai_input.inx +++ b/share/extensions/ai_input.inx @@ -1,18 +1,16 @@ <?xml version="1.0" encoding="UTF-8"?> <inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension"> <_name>AI 8.0 Input</_name> - <id>org.inkscape.input.ai</id> - <dependency type="executable" location="path">perl</dependency> - <dependency type="executable" location="extensions">ill2svg.pl</dependency> + <id>org.inkscape.input.ai8</id> + <dependency type="executable" location="extensions">uniconv-ext.py</dependency> <input> <extension>.ai</extension> <mimetype>image/x-adobe-illustrator</mimetype> <_filetypename>Adobe Illustrator 8.0 and below (*.ai)</_filetypename> <_filetypetooltip>Open files saved with Adobe Illustrator 8.0 or older</_filetypetooltip> - <output_extension>org.inkscape.output.ai</output_extension> </input> <script> - <command reldir="extensions" interpreter="perl">ill2svg.pl</command> + <command reldir="extensions" interpreter="python">uniconv-ext.py</command> </script> </inkscape-extension> diff --git a/share/extensions/color_blackandwhite.inx b/share/extensions/color_blackandwhite.inx new file mode 100644 index 000000000..8432ab2d3 --- /dev/null +++ b/share/extensions/color_blackandwhite.inx @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension"> + <_name>Black and White</_name> + <id>org.inkscape.color.blackandwhite</id> + <dependency type="executable" location="extensions">coloreffect.py</dependency> + <dependency type="executable" location="extensions">color_blackandwhite.py</dependency> + <dependency type="executable" location="extensions">simplestyle.py</dependency> + <effect> + <object-type>all</object-type> + <effects-menu> + <submenu _name="Color"/> + </effects-menu> + </effect> + <script> + <command reldir="extensions" interpreter="python">color_blackandwhite.py</command> + </script> +</inkscape-extension> diff --git a/share/extensions/color_blackandwhite.py b/share/extensions/color_blackandwhite.py new file mode 100644 index 000000000..c11b2a127 --- /dev/null +++ b/share/extensions/color_blackandwhite.py @@ -0,0 +1,17 @@ +import coloreffect,sys + +class C(coloreffect.ColorEffect): + def colmod(self,r,g,b): + #ITU-R Recommendation BT.709 + #l = 0.2125 * r + 0.7154 * g + 0.0721 * b + #NTSC and PAL + l = 0.299 * r + 0.587 * g + 0.114 * b + if l > 127: + ig = 255 + else: + ig = 0 + #coloreffect.debug('gs '+hex(r)+' '+hex(g)+' '+hex(b)+'%02x%02x%02x' % (ig,ig,ig)) + return '%02x%02x%02x' % (ig,ig,ig) + +c = C() +c.affect() diff --git a/share/extensions/dxf_outlines.inx b/share/extensions/dxf_outlines.inx index 169385001..fe8048a8e 100644 --- a/share/extensions/dxf_outlines.inx +++ b/share/extensions/dxf_outlines.inx @@ -7,14 +7,16 @@ <dependency type="executable" location="extensions">inkex.py</dependency> <param name="tab" type="notebook"> <page name="options" _gui-text="Options"> - <param name="ROBO" type="boolean" _gui-text="enable ROBO-Master output">false</param> + <param name="ROBO" type="boolean" _gui-text="use ROBO-Master type of spline output">false</param> + <param name="POLY" type="boolean" _gui-text="use LWPOLYLINE type of line output">true</param> </page> <page name="help" _gui-text="Help"> <_param name="inputhelp" type="description" xml:space="preserve">- AutoCAD Release 13 format. - assume svg drawing is in pixels, at 90 dpi. - assume dxf drawing is in mm. -- only LWPOLYLINE and SPLINE elements are supported. -- ROBO-Master option is a specialized spline readable only by ROBO-Master and AutoDesk viewers, not Inkscape.</_param> +- only line and spline elements are supported. +- ROBO-Master spline output is a specialized spline readable only by ROBO-Master and AutoDesk viewers, not Inkscape. +- LWPOLYLINE output is a multiply-connected polyline, disable it to use a legacy version of the LINE output.</_param> </page> </param> <output> diff --git a/share/extensions/dxf_outlines.py b/share/extensions/dxf_outlines.py index ecdc6ce40..295fc7466 100755 --- a/share/extensions/dxf_outlines.py +++ b/share/extensions/dxf_outlines.py @@ -7,6 +7,7 @@ Copyright (C) 2008 Alvin Penner, penner@vaxxine.com - ROBO-Master output option added Aug 2008 - ROBO-Master multispline output added Sept 2008 - LWPOLYLINE output modification added Dec 2008 +- toggle between LINE/LWPOLYLINE added Jan 2010 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 @@ -53,6 +54,7 @@ class MyEffect(inkex.Effect): def __init__(self): inkex.Effect.__init__(self) self.OptionParser.add_option("-R", "--ROBO", action="store", type="string", dest="ROBO") + self.OptionParser.add_option("-P", "--POLY", action="store", type="string", dest="POLY") self.OptionParser.add_option("--tab", action="store", type="string", dest="tab") self.OptionParser.add_option("--inputhelp", action="store", type="string", dest="inputhelp") self.dxf = [] @@ -65,6 +67,10 @@ class MyEffect(inkex.Effect): def dxf_add(self, str): self.dxf.append(str) def dxf_line(self,csp): + self.handle += 1 + self.dxf_add(" 0\nLINE\n 5\n%x\n100\nAcDbEntity\n 8\n0\n 62\n%d\n100\nAcDbLine\n" % (self.handle, self.color)) + self.dxf_add(" 10\n%f\n 20\n%f\n 30\n0.0\n 11\n%f\n 21\n%f\n 31\n0.0\n" % (csp[0][0],csp[0][1],csp[1][0],csp[1][1])) + def LWPOLY_line(self,csp): if (abs(csp[0][0] - self.poly[-1][0]) > .0001 or abs(csp[0][1] - self.poly[-1][1]) > .0001): self.LWPOLY_output() # terminate current polyline @@ -174,14 +180,18 @@ class MyEffect(inkex.Effect): s = sub[i] e = sub[i+1] if s[1] == s[2] and e[0] == e[1]: - self.dxf_line([s[1],e[1]]) + if (self.options.POLY == 'true'): + self.LWPOLY_line([s[1],e[1]]) + else: + self.dxf_line([s[1],e[1]]) elif (self.options.ROBO == 'true'): self.ROBO_spline([s[1],s[2],e[0],e[1]]) else: self.dxf_spline([s[1],s[2],e[0],e[1]]) if self.options.ROBO == 'true': self.ROBO_output() - self.LWPOLY_output() + if self.options.POLY == 'true': + self.LWPOLY_output() self.dxf_add(dxf_templates.r14_footer) if __name__ == '__main__': diff --git a/share/extensions/generate_voronoi.inx b/share/extensions/generate_voronoi.inx new file mode 100644 index 000000000..4a04ffe1d --- /dev/null +++ b/share/extensions/generate_voronoi.inx @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension"> + <_name>Voronoi Pattern</_name> + <id>com.vaxxine.generate.voronoi</id> + <dependency type="executable" location="extensions">generate_voronoi.py</dependency> + <dependency type="executable" location="extensions">voronoi.py</dependency> + <dependency type="executable" location="extensions">inkex.py</dependency> + <_param name="title1" type="description">Generate a random pattern of Voronoi cells. The pattern will be accessible in the Fill and Stroke dialog. You must select an object or a group.</_param> + <_param name="title2" type="description">If border is zero, the pattern will be discontinuous at the edges. Use a positive border, preferably greater than the cell size, to produce a smooth join of the pattern at the edges. Use a negative border to reduce the size of the pattern and get an empty border.</_param> + <param name="size" type="int" min="2" max="200" _gui-text=" Average size of cell (px) ">10</param> + <param name="border" type="int" min="-200" max="200" _gui-text=" Size of Border (px) ">0</param> + <effect> + <object-type>all</object-type> + <effects-menu> + <submenu _name="Generate from Path"/> + </effects-menu> + </effect> + <script> + <command reldir="extensions" interpreter="python">generate_voronoi.py</command> + </script> +</inkscape-extension> diff --git a/share/extensions/generate_voronoi.py b/share/extensions/generate_voronoi.py new file mode 100644 index 000000000..3359685fc --- /dev/null +++ b/share/extensions/generate_voronoi.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python +""" +Copyright (C) 2010 Alvin Penner, penner@vaxxine.com + +- Voronoi Diagram algorithm and C code by Steven Fortune, 1987, http://ect.bell-labs.com/who/sjf/ +- Python translation to file voronoi.py by Bill Simons, 2005, http://www.oxfish.com/ + +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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +""" +import random, inkex, simplestyle, gettext, voronoi +_ = gettext.gettext + +try: + from subprocess import Popen, PIPE +except: + inkex.errormsg(_("Failed to import the subprocess module. Please report this as a bug at : https://bugs.launchpad.net/inkscape.")) + inkex.errormsg("Python version is : " + str(inkex.sys.version_info)) + exit() + +def clip_line(x1, y1, x2, y2, w, h): + if x1 < 0 and x2 < 0: + return [0, 0, 0, 0] + if x1 > w and x2 > w: + return [0, 0, 0, 0] + if x1 < 0: + y1 = (y1*x2 - y2*x1)/(x2 - x1) + x1 = 0 + if x2 < 0: + y2 = (y1*x2 - y2*x1)/(x2 - x1) + x2 = 0 + if x1 > w: + y1 = y1 + (w - x1)*(y2 - y1)/(x2 - x1) + x1 = w + if x2 > w: + y2 = y1 + (w - x1)*(y2 - y1)/(x2 - x1) + x2 = w + if y1 < 0 and y2 < 0: + return [0, 0, 0, 0] + if y1 > h and y2 > h: + return [0, 0, 0, 0] + if x1 == x2 and y1 == y2: + return [0, 0, 0, 0] + if y1 < 0: + x1 = (x1*y2 - x2*y1)/(y2 - y1) + y1 = 0 + if y2 < 0: + x2 = (x1*y2 - x2*y1)/(y2 - y1) + y2 = 0 + if y1 > h: + x1 = x1 + (h - y1)*(x2 - x1)/(y2 - y1) + y1 = h + if y2 > h: + x2 = x1 + (h - y1)*(x2 - x1)/(y2 - y1) + y2 = h + return [x1, y1, x2, y2] + +class Pattern(inkex.Effect): + def __init__(self): + inkex.Effect.__init__(self) + self.OptionParser.add_option("--size", + action="store", type="int", + dest="size", default=10, + help="Average size of cell (px)") + self.OptionParser.add_option("--border", + action="store", type="int", + dest="border", default=0, + help="Size of Border (px)") + + def effect(self): + if not self.options.ids: + inkex.errormsg(_("Please select an object")) + exit() + q = {'x':0,'y':0,'width':0,'height':0} # query the bounding box of ids[0] + for query in q.keys(): + p = Popen('inkscape --query-%s --query-id=%s "%s"' % (query, self.options.ids[0], self.args[-1]), shell=True, stdout=PIPE, stderr=PIPE) + rc = p.wait() + q[query] = float(p.stdout.read()) + defs = self.xpathSingle('/svg:svg//svg:defs') + pattern = inkex.etree.SubElement(defs ,inkex.addNS('pattern','svg')) + pattern.set('id', 'Voronoi' + str(random.randint(1, 9999))) + pattern.set('width', str(q['width'])) + pattern.set('height', str(q['height'])) + pattern.set('patternTransform', 'translate(%s,%s)' % (q['x'], q['y'])) + pattern.set('patternUnits', 'userSpaceOnUse') + + # generate random pattern of points + c = voronoi.Context() + pts = [] + b = float(self.options.border) # width of border + for i in range(int(q['width']*q['height']/self.options.size/self.options.size)): + x = random.random()*q['width'] + y = random.random()*q['height'] + if b > 0: # duplicate border area + pts.append(voronoi.Site(x, y)) + if x < b: + pts.append(voronoi.Site(x + q['width'], y)) + if y < b: + pts.append(voronoi.Site(x + q['width'], y + q['height'])) + if y > q['height'] - b: + pts.append(voronoi.Site(x + q['width'], y - q['height'])) + if x > q['width'] - b: + pts.append(voronoi.Site(x - q['width'], y)) + if y < b: + pts.append(voronoi.Site(x - q['width'], y + q['height'])) + if y > q['height'] - b: + pts.append(voronoi.Site(x - q['width'], y - q['height'])) + if y < b: + pts.append(voronoi.Site(x, y + q['height'])) + if y > q['height'] - b: + pts.append(voronoi.Site(x, y - q['height'])) + elif x > -b and y > -b and x < q['width'] + b and y < q['height'] + b: + pts.append(voronoi.Site(x, y)) # leave border area blank + # dot = inkex.etree.SubElement(pattern, inkex.addNS('rect','svg')) + # dot.set('x', str(x-1)) + # dot.set('y', str(y-1)) + # dot.set('width', '2') + # dot.set('height', '2') + if len(pts) < 3: + inkex.errormsg("Please choose a larger object, or smaller cell size") + exit() + + # plot Voronoi diagram + sl = voronoi.SiteList(pts) + voronoi.voronoi(sl, c) + for edge in c.edges: + if edge[1] >= 0 and edge[2] >= 0: # two vertices + [x1, y1, x2, y2] = clip_line(c.vertices[edge[1]][0], c.vertices[edge[1]][1], c.vertices[edge[2]][0], c.vertices[edge[2]][1], q['width'], q['height']) + elif edge[1] >= 0: # only one vertex + if c.lines[edge[0]][1] == 0: # vertical line + xtemp = c.lines[edge[0]][2]/c.lines[edge[0]][0] + if c.vertices[edge[1]][1] > q['height']/2: + ytemp = q['height'] + else: + ytemp = 0 + else: + xtemp = q['width'] + ytemp = (c.lines[edge[0]][2] - q['width']*c.lines[edge[0]][0])/c.lines[edge[0]][1] + [x1, y1, x2, y2] = clip_line(c.vertices[edge[1]][0], c.vertices[edge[1]][1], xtemp, ytemp, q['width'], q['height']) + elif edge[2] >= 0: # only one vertex + if c.lines[edge[0]][1] == 0: # vertical line + xtemp = c.lines[edge[0]][2]/c.lines[edge[0]][0] + if c.vertices[edge[2]][1] > q['height']/2: + ytemp = q['height'] + else: + ytemp = 0 + else: + xtemp = 0 + ytemp = c.lines[edge[0]][2]/c.lines[edge[0]][1] + [x1, y1, x2, y2] = clip_line(xtemp, ytemp, c.vertices[edge[2]][0], c.vertices[edge[2]][1], q['width'], q['height']) + if x1 or x2 or y1 or y2: + path = 'M %f,%f %f,%f' % (x1, y1, x2, y2) + attribs = {'d': path, 'style': 'stroke:#000000'} + inkex.etree.SubElement(pattern, inkex.addNS('path', 'svg'), attribs) + + # link selected object to pattern + obj = self.selected[self.options.ids[0]] + style = {} + if obj.attrib.has_key('style'): + style = simplestyle.parseStyle(obj.attrib['style']) + style['fill'] = 'url(#%s)' % pattern.get('id') + obj.attrib['style'] = simplestyle.formatStyle(style) + if obj.tag == inkex.addNS('g', 'svg'): + for node in obj: + style = {} + if node.attrib.has_key('style'): + style = simplestyle.parseStyle(node.attrib['style']) + style['fill'] = 'url(#%s)' % pattern.get('id') + node.attrib['style'] = simplestyle.formatStyle(style) + +if __name__ == '__main__': + e = Pattern() + e.affect() + +# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 encoding=utf-8 textwidth=99 diff --git a/share/extensions/inkex.py b/share/extensions/inkex.py index 1a70c25d6..49de51ef3 100755 --- a/share/extensions/inkex.py +++ b/share/extensions/inkex.py @@ -175,6 +175,13 @@ class Effect: else: return None + def getParentNode(self, node): + for parent in self.document.getiterator(): + if node in parent.getchildren(): + return parent + break + + def getdocids(self): docIdNodes = self.document.xpath('//@id', namespaces=NSS) for m in docIdNodes: diff --git a/share/extensions/pixelsnap.inx b/share/extensions/pixelsnap.inx new file mode 100644 index 000000000..5413148dc --- /dev/null +++ b/share/extensions/pixelsnap.inx @@ -0,0 +1,17 @@ +<inkscape-extension> + <_name>PixelSnap</_name> + <id>bryhoyt.pixelsnap</id> + <dependency type="executable" location="extensions">pixelsnap.py</dependency> + <param name="title" type="description">Snap all paths in selection to pixels. Snaps borders to half-points and fills to full points</param> + <effect> + <effects-menu> + <submenu _name="Modify Path"/> + </effects-menu> + </effect> + <script> + <command reldir="extensions" interpreter="python">pixelsnap.py</command> + </script> +</inkscape-extension> + + + diff --git a/share/extensions/pixelsnap.py b/share/extensions/pixelsnap.py new file mode 100644 index 000000000..95ae7f0dc --- /dev/null +++ b/share/extensions/pixelsnap.py @@ -0,0 +1,509 @@ +#!/usr/bin/env python + +""" +TODO: This only snaps selected elements, and if those elements are part of a + group or layer that has it's own transform, that won't be taken into + account, unless you snap the group or layer as a whole. This can account + for unexpected results in some cases (eg where you've got a non-integer + translation on the layer you're working in, the elements in that layer + won't snap properly). The workaround for now is to snap the whole + group/layer, or remove the transform on the group/layer. + + I could fix it in the code by traversing the parent elements up to the + document root & calculating the cumulative parent_transform. This could + be done at the top of the pixel_snap method if parent_transform==None, + or before calling it for the first time. + +TODO: Transforming points isn't quite perfect, to say the least. In particular, + when translating a point bezier curve, we translate the handles by the same amount. + BUT, some handles that are attached to a particular point are conceptually + handles of the prev/next node. + Best way to fix it would be to keep a list of the fractional_offsets[] of + each point, without transforming anything. Then go thru each point and + transform the appropriate handle according to the relevant fraction_offset + in the list. + + i.e. calculate first, then modify. + + In fact, that might be a simpler algorithm anyway -- it avoids having + to keep track of all the first_xy/next_xy guff. + +TODO: make elem_offset return [x_offset, y_offset] so we can handle non-symetric scaling + +------------ + +Note: This doesn't work very well on paths which have both straight segments + and curved segments. + The biggest three problems are: + a) we don't take handles into account (segments where the nodes are + aligned are always treated as straight segments, even where the + handles make it curve) + b) when we snap a straight segment right before/after a curve, it + doesn't make any attempt to keep the transition from the straight + segment to the curve smooth. + c) no attempt is made to keep equal widths equal. (or nearly-equal + widths nearly-equal). For example, font strokes. + + I guess that amounts to the problyem that font hinting solves for fonts. + I wonder if I could find an automatic font-hinting algorithm and munge + it to my purposes? + + Some good autohinting concepts that may help: + http://freetype.sourceforge.net/autohinting/archive/10Mar2000/hinter.html + +Note: Paths that have curves & arcs on some sides of the bounding box won't + be snapped correctly on that side of the bounding box, and nor will they + be translated/resized correctly before the path is modified. Doesn't affect + most applications of this extension, but it highlights the fact that we + take a geometrically simplistic approach to inspecting & modifying the path. +""" + +from __future__ import division + +import sys +# *** numpy causes issue #4 on Mac OS 10.6.2. I use it for +# matrix inverse -- my linear algebra's a bit rusty, but I could implement my +# own matrix inverse function if necessary, I guess. +from numpy import matrix +import simplestyle, simpletransform, simplepath + +# INKEX MODULE +# If you get the "No module named inkex" error, uncomment the relevant line +# below by removing the '#' at the start of the line. +# +#sys.path += ['/usr/share/inkscape/extensions'] # If you're using a standard Linux installation +#sys.path += ['/usr/local/share/inkscape/extensions'] # If you're using a custom Linux installation +#sys.path += ['C:\\Program Files\\Inkscape\\share\\extensions'] # If you're using a standard Windows installation + +try: + import inkex + from inkex import unittouu +except ImportError: + raise ImportError("No module named inkex.\nPlease edit the file %s and see the section titled 'INKEX MODULE'" % __file__) + +Precision = 5 # number of digits of precision for comparing float numbers + +MaxGradient = 1/200 # lines that are almost-but-not-quite straight will be snapped, too. + +class TransformError(Exception): pass + +def elemtype(elem, matches): + if not isinstance(matches, (list, tuple)): matches = [matches] + for m in matches: + if elem.tag == inkex.addNS(m, 'svg'): return True + return False + +def invert_transform(transform): + transform = transform[:] # duplicate list to avoid modifying it + transform += [[0, 0, 1]] + inverse = matrix(transform).I.tolist() + inverse.pop() + return inverse + +def transform_point(transform, pt, inverse=False): + """ Better than simpletransform.applyTransformToPoint, + a) coz it's a simpler name + b) coz it returns the new xy, rather than modifying the input + """ + if inverse: + transform = invert_transform(transform) + + x = transform[0][0]*pt[0] + transform[0][1]*pt[1] + transform[0][2] + y = transform[1][0]*pt[0] + transform[1][1]*pt[1] + transform[1][2] + return x,y + +def transform_dimensions(transform, width=None, height=None, inverse=False): + """ Dimensions don't get translated. I'm not sure how much diff rotate/skew + makes in this context, but we currently ignore anything besides scale. + """ + if inverse: transform = invert_transform(transform) + + if width is not None: width *= transform[0][0] + if height is not None: height *= transform[1][1] + + if width is not None and height is not None: return width, height + if width is not None: return width + if height is not None: return height + + +def vertical(pt1, pt2): + hlen = abs(pt1[0] - pt2[0]) + vlen = abs(pt1[1] - pt2[1]) + if vlen==0 and hlen==0: + return True + elif vlen==0: + return False + return (hlen / vlen) < MaxGradient + +def horizontal(pt1, pt2): + hlen = round(abs(pt1[0] - pt2[0]), Precision) + vlen = round(abs(pt1[1] - pt2[1]), Precision) + if hlen==0 and vlen==0: + return True + elif hlen==0: + return False + return (vlen / hlen) < MaxGradient + +class PixelSnapEffect(inkex.Effect): + def elem_offset(self, elem, parent_transform=None): + """ Returns a value which is the amount the + bounding-box is offset due to the stroke-width. + Transform is taken into account. + """ + stroke_width = self.stroke_width(elem) + if stroke_width == 0: return 0 # if there's no stroke, no need to worry about the transform + + transform = self.transform(elem, parent_transform=parent_transform) + if abs(abs(transform[0][0]) - abs(transform[1][1])) > (10**-Precision): + raise TransformError("Selection contains non-symetric scaling") # *** wouldn't be hard to get around this by calculating vertical_offset & horizontal_offset separately, maybe 2 functions, or maybe returning a tuple + + stroke_width = transform_dimensions(transform, width=stroke_width) + + return (stroke_width/2) + + def stroke_width(self, elem, setval=None): + """ Return stroke-width in pixels, untransformed + """ + style = simplestyle.parseStyle(elem.attrib.get('style', '')) + stroke = style.get('stroke', None) + if stroke == 'none': stroke = None + + stroke_width = 0 + if stroke and setval is None: + stroke_width = unittouu(style.get('stroke-width', '').strip()) + + if setval: + style['stroke-width'] = str(setval) + elem.attrib['style'] = simplestyle.formatStyle(style) + else: + return stroke_width + + def snap_stroke(self, elem, parent_transform=None): + transform = self.transform(elem, parent_transform=parent_transform) + + stroke_width = self.stroke_width(elem) + if (stroke_width == 0): return # no point raising a TransformError if there's no stroke to snap + + if abs(abs(transform[0][0]) - abs(transform[1][1])) > (10**-Precision): + raise TransformError("Selection contains non-symetric scaling, can't snap stroke width") + + if stroke_width: + stroke_width = transform_dimensions(transform, width=stroke_width) + stroke_width = round(stroke_width) + stroke_width = transform_dimensions(transform, width=stroke_width, inverse=True) + self.stroke_width(elem, stroke_width) + + def transform(self, elem, setval=None, parent_transform=None): + """ Gets this element's transform. Use setval=matrix to + set this element's transform. + You can only specify parent_transform when getting. + """ + transform = elem.attrib.get('transform', '').strip() + + if transform: + transform = simpletransform.parseTransform(transform) + else: + transform = [[1,0,0], [0,1,0], [0,0,1]] + if parent_transform: + transform = simpletransform.composeTransform(parent_transform, transform) + + if setval: + elem.attrib['transform'] = simpletransform.formatTransform(setval) + else: + return transform + + def snap_transform(self, elem): + # Only snaps the x/y translation of the transform, nothing else. + # Scale transforms are handled only in snap_rect() + # Doesn't take any parent_transform into account -- assumes + # that the parent's transform has already been snapped. + transform = self.transform(elem) + if transform[0][1] or transform[1][0]: return # if we've got any skew/rotation, get outta here + + transform[0][2] = round(transform[0][2]) + transform[1][2] = round(transform[1][2]) + + self.transform(elem, transform) + + def transform_path_node(self, transform, path, i): + """ Modifies a segment so that every point is transformed, including handles + """ + segtype = path[i][0].lower() + + if segtype == 'z': return + elif segtype == 'h': + path[i][1][0] = transform_point(transform, [path[i][1][0], 0])[0] + elif segtype == 'v': + path[i][1][0] = transform_point(transform, [0, path[i][1][0]])[1] + else: + first_coordinate = 0 + if (segtype == 'a'): first_coordinate = 5 # for elliptical arcs, skip the radius x/y, rotation, large-arc, and sweep + for j in range(first_coordinate, len(path[i][1]), 2): + x, y = path[i][1][j], path[i][1][j+1] + x, y = transform_point(transform, (x, y)) + path[i][1][j] = x + path[i][1][j+1] = y + + + def pathxy(self, path, i, setval=None): + """ Return the endpoint of the given path segment. + Inspects the segment type to know which elements are the endpoints. + """ + segtype = path[i][0].lower() + x = y = 0 + + if segtype == 'z': i = 0 + + if segtype == 'h': + if setval: path[i][1][0] = setval[0] + else: x = path[i][1][0] + + elif segtype == 'v': + if setval: path[i][1][0] = setval[1] + else: y = path[i][1][0] + else: + if setval and segtype != 'z': + path[i][1][-2] = setval[0] + path[i][1][-1] = setval[1] + else: + x = path[i][1][-2] + y = path[i][1][-1] + + if setval is None: return [x, y] + + def path_bounding_box(self, elem, parent_transform=None): + """ Returns [min_x, min_y], [max_x, max_y] of the transformed + element. (It doesn't make any sense to return the untransformed + bounding box, with the intent of transforming it later, because + the min/max points will be completely different points) + + The returned bounding box includes stroke-width offset. + + This function uses a simplistic algorithm & doesn't take curves + or arcs into account, just node positions. + """ + # If we have a Live Path Effect, modify original-d. If anyone clamours + # for it, we could make an option to ignore paths with Live Path Effects + original_d = '{%s}original-d' % inkex.NSS['inkscape'] + path = simplepath.parsePath(elem.attrib.get(original_d, elem.attrib['d'])) + + transform = self.transform(elem, parent_transform=parent_transform) + offset = self.elem_offset(elem, parent_transform) + + min_x = min_y = max_x = max_y = 0 + for i in range(len(path)): + x, y = self.pathxy(path, i) + x, y = transform_point(transform, (x, y)) + + if i == 0: + min_x = max_x = x + min_y = max_y = y + else: + min_x = min(x, min_x) + min_y = min(y, min_y) + max_x = max(x, max_x) + max_y = max(y, max_y) + + return (min_x-offset, min_y-offset), (max_x+offset, max_y+offset) + + + def snap_path_scale(self, elem, parent_transform=None): + # If we have a Live Path Effect, modify original-d. If anyone clamours + # for it, we could make an option to ignore paths with Live Path Effects + original_d = '{%s}original-d' % inkex.NSS['inkscape'] + path = simplepath.parsePath(elem.attrib.get(original_d, elem.attrib['d'])) + transform = self.transform(elem, parent_transform=parent_transform) + min_xy, max_xy = self.path_bounding_box(elem, parent_transform) + + width = max_xy[0] - min_xy[0] + height = max_xy[1] - min_xy[1] + + # In case somebody tries to snap a 0-high element, + # or a curve/arc with all nodes in a line, and of course + # because we should always check for divide-by-zero! + if (width==0 or height==0): return + + rescale = round(width)/width, round(height)/height + + min_xy = transform_point(transform, min_xy, inverse=True) + max_xy = transform_point(transform, max_xy, inverse=True) + + for i in range(len(path)): + self.transform_path_node([[1, 0, -min_xy[0]], [0, 1, -min_xy[1]]], path, i) # center transform + self.transform_path_node([[rescale[0], 0, 0], + [0, rescale[1], 0]], + path, i) + self.transform_path_node([[1, 0, +min_xy[0]], [0, 1, +min_xy[1]]], path, i) # uncenter transform + + path = simplepath.formatPath(path) + if original_d in elem.attrib: elem.attrib[original_d] = path + else: elem.attrib['d'] = path + + def snap_path_pos(self, elem, parent_transform=None): + # If we have a Live Path Effect, modify original-d. If anyone clamours + # for it, we could make an option to ignore paths with Live Path Effects + original_d = '{%s}original-d' % inkex.NSS['inkscape'] + path = simplepath.parsePath(elem.attrib.get(original_d, elem.attrib['d'])) + transform = self.transform(elem, parent_transform=parent_transform) + min_xy, max_xy = self.path_bounding_box(elem, parent_transform) + + fractional_offset = min_xy[0]-round(min_xy[0]), min_xy[1]-round(min_xy[1])-self.document_offset + fractional_offset = transform_dimensions(transform, fractional_offset[0], fractional_offset[1], inverse=True) + + for i in range(len(path)): + self.transform_path_node([[1, 0, -fractional_offset[0]], + [0, 1, -fractional_offset[1]]], + path, i) + + path = simplepath.formatPath(path) + if original_d in elem.attrib: elem.attrib[original_d] = path + else: elem.attrib['d'] = path + + def snap_path(self, elem, parent_transform=None): + # If we have a Live Path Effect, modify original-d. If anyone clamours + # for it, we could make an option to ignore paths with Live Path Effects + original_d = '{%s}original-d' % inkex.NSS['inkscape'] + path = simplepath.parsePath(elem.attrib.get(original_d, elem.attrib['d'])) + + transform = self.transform(elem, parent_transform=parent_transform) + + if transform[0][1] or transform[1][0]: # if we've got any skew/rotation, get outta here + raise TransformError("Selection contains transformations with skew/rotation") + + offset = self.elem_offset(elem, parent_transform) % 1 + + prev_xy = self.pathxy(path, -1) + first_xy = self.pathxy(path, 0) + for i in range(len(path)): + segtype = path[i][0].lower() + xy = self.pathxy(path, i) + if segtype == 'z': + xy = first_xy + if (i == len(path)-1) or \ + ((i == len(path)-2) and path[-1][0].lower() == 'z'): + next_xy = first_xy + else: + next_xy = self.pathxy(path, i+1) + + if not (xy and prev_xy and next_xy): + prev_xy = xy + continue + + xy_untransformed = tuple(xy) + xy = list(transform_point(transform, xy)) + prev_xy = transform_point(transform, prev_xy) + next_xy = transform_point(transform, next_xy) + + on_vertical = on_horizontal = False + + if horizontal(xy, prev_xy): + if len(path) > 2 or i==0: # on 2-point paths, first.next==first.prev==last and last.next==last.prev==first + xy[1] = prev_xy[1] # make the almost-equal values equal, so they round in the same direction + on_horizontal = True + if horizontal(xy, next_xy): + on_horizontal = True + + if vertical(xy, prev_xy): # as above + if len(path) > 2 or i==0: + xy[0] = prev_xy[0] + on_vertical = True + if vertical(xy, next_xy): + on_vertical = True + + prev_xy = tuple(xy_untransformed) + + fractional_offset = [0,0] + if on_vertical: + fractional_offset[0] = xy[0] - (round(xy[0]-offset) + offset) + if on_horizontal: + fractional_offset[1] = xy[1] - (round(xy[1]-offset) + offset) - self.document_offset + + fractional_offset = transform_dimensions(transform, fractional_offset[0], fractional_offset[1], inverse=True) + self.transform_path_node([[1, 0, -fractional_offset[0]], + [0, 1, -fractional_offset[1]]], + path, i) + + + path = simplepath.formatPath(path) + if original_d in elem.attrib: elem.attrib[original_d] = path + else: elem.attrib['d'] = path + + def snap_rect(self, elem, parent_transform=None): + transform = self.transform(elem, parent_transform=parent_transform) + + if transform[0][1] or transform[1][0]: # if we've got any skew/rotation, get outta here + raise TransformError("Selection contains transformations with skew/rotation") + + offset = self.elem_offset(elem, parent_transform) % 1 + + width = unittouu(elem.attrib['width']) + height = unittouu(elem.attrib['height']) + x = unittouu(elem.attrib['x']) + y = unittouu(elem.attrib['y']) + + width, height = transform_dimensions(transform, width, height) + x, y = transform_point(transform, [x, y]) + + # Snap to the nearest pixel + height = round(height) + width = round(width) + x = round(x - offset) + offset # If there's a stroke of non-even width, it's shifted by half a pixel + y = round(y - offset) + offset + + width, height = transform_dimensions(transform, width, height, inverse=True) + x, y = transform_point(transform, [x, y], inverse=True) + + y += self.document_offset/transform[1][1] + + # Position the elem at the newly calculate values + elem.attrib['width'] = str(width) + elem.attrib['height'] = str(height) + elem.attrib['x'] = str(x) + elem.attrib['y'] = str(y) + + def snap_image(self, elem, parent_transform=None): + self.snap_rect(elem, parent_transform) + + def pixel_snap(self, elem, parent_transform=None): + if elemtype(elem, 'g'): + self.snap_transform(elem) + transform = self.transform(elem, parent_transform=parent_transform) + for e in elem: + try: + self.pixel_snap(e, transform) + except TransformError, e: + print >>sys.stderr, e + return + + if not elemtype(elem, ('path', 'rect', 'image')): + return + + self.snap_transform(elem) + try: + self.snap_stroke(elem, parent_transform) + except TransformError, e: + print >>sys.stderr, e + + if elemtype(elem, 'path'): + self.snap_path_scale(elem, parent_transform) + self.snap_path_pos(elem, parent_transform) + self.snap_path(elem, parent_transform) # would be quite useful to make this an option, as scale/pos alone doesn't mess with the path itself, and works well for sans-serif text + elif elemtype(elem, 'rect'): self.snap_rect(elem, parent_transform) + elif elemtype(elem, 'image'): self.snap_image(elem, parent_transform) + + def effect(self): + svg = self.document.getroot() + + self.document_offset = unittouu(svg.attrib['height']) % 1 # although SVG units are absolute, the elements are positioned relative to the top of the page, rather than zero + + for id, elem in self.selected.iteritems(): + try: + self.pixel_snap(elem) + except TransformError, e: + print >>sys.stderr, e + + +if __name__ == '__main__': + effect = PixelSnapEffect() + effect.affect() + diff --git a/share/extensions/printing-marks.inx b/share/extensions/printing-marks.inx index 40954380f..e652945d8 100644 --- a/share/extensions/printing-marks.inx +++ b/share/extensions/printing-marks.inx @@ -17,6 +17,7 @@ <page name="tab" _gui-text="Positioning"> <param name="where" type="enum" _gui-text="Set crop marks to"> <_item value="canvas">Canvas</_item> + <_item value="selection">Selection</_item> </param> <param name="unit" _gui-text="Unit" type="optiongroup" appearance="minimal"> <option value="px">px</option> @@ -34,7 +35,7 @@ </page> </param> - <effect> + <effect needs-live-preview="false"> <object-type>all</object-type> <effects-menu> <submenu _name="Render"/> diff --git a/share/extensions/printing-marks.py b/share/extensions/printing-marks.py index 6128d7027..775f6b643 100644 --- a/share/extensions/printing-marks.py +++ b/share/extensions/printing-marks.py @@ -25,6 +25,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ''' import inkex, simplestyle, math +from subprocess import Popen, PIPE, STDOUT class Printing_Marks (inkex.Effect): @@ -146,7 +147,7 @@ class Printing_Marks (inkex.Effect): def draw_star_target(self, cx, cy, name, parent): r = (self.mark_size/2) - style = {'fill':'#000', 'fill-opacity':'1', 'stroke':'none'} + style = {'fill':'#000 device-cmyk(1,1,1,1)', 'fill-opacity':'1', 'stroke':'none'} d = ' M 0,0' i = 0 while i < ( 2 * math.pi ): @@ -166,7 +167,7 @@ class Printing_Marks (inkex.Effect): 'id':name, 'transform':'translate('+str(cx)+','+str(cy)+\ ') rotate('+str(rotate)+')' }) - l = min( self.mark_size / 3, max(self.width,self.height) / 45 ) + l = min( self.mark_size / 3, max(self.area_w,self.area_h) / 45 ) for bar in [{'c':'*', 'stroke':'#000', 'x':0, 'y':-(l+1)}, {'c':'r', 'stroke':'#0FF', 'x':0, 'y':0}, {'c':'g', 'stroke':'#F0F', 'x':(l*11)+1, 'y':-(l+1)}, @@ -188,15 +189,61 @@ class Printing_Marks (inkex.Effect): r = inkex.etree.SubElement(g, 'rect', r_att) i += 0.1 + def get_selection_area(self): + sel_area = {} + min_x, min_y, max_x, max_y = False, False, False, False + for id in self.options.ids: + sel_area[id] = {} + for att in [ "x", "y", "width", "height" ]: + args = [ "inkscape", "-I", id, "--query-"+att, self.svg_file ] + sel_area[id][att] = \ + Popen(args, stdout=PIPE, stderr=PIPE).communicate()[0] + current_min_x = float( sel_area[id]["x"] ) + current_min_y = float( sel_area[id]["y"] ) + current_max_x = float( sel_area[id]["x"] ) + \ + float( sel_area[id]["width"] ) + current_max_y = float( sel_area[id]["y"] ) + \ + float( sel_area[id]["height"] ) + if not min_x: min_x = current_min_x + if not min_y: min_y = current_min_y + if not max_x: max_x = current_max_x + if not max_y: max_y = current_max_y + if current_min_x < min_x: min_x = current_min_x + if current_min_y < min_y: min_y = current_min_y + if current_max_x > max_x: max_x = current_max_x + if current_max_y > max_y: max_y = current_max_y + #inkex.errormsg( '>> '+ id + + # ' min_x:'+ str(min_x) + + # ' min_y:'+ str(min_y) + + # ' max_x:'+ str(max_x) + + # ' max_y:'+ str(max_y) ) + self.area_x1 = min_x + self.area_y1 = min_y + self.area_x2 = max_x + self.area_y2 = max_y + self.area_w = max_x - min_x + self.area_h = max_y - min_y + def effect(self): if self.options.where_to_crop == 'selection' : - inkex.errormsg('Sory, the crop to selection is a TODO feature') + self.get_selection_area() + #inkex.errormsg('Sory, the crop to selection is a TODO feature') + #exit(1) + else : + svg = self.document.getroot() + self.area_w = inkex.unittouu(svg.get('width')) + self.area_h = inkex.unittouu(svg.attrib['height']) + self.area_x1 = 0 + self.area_y1 = 0 + self.area_x2 = self.area_w + self.area_y2 = self.area_h # Get SVG document dimensions + # self.width must be replaced by self.area_x2. same to others. svg = self.document.getroot() - self.width = width = inkex.unittouu(svg.get('width')) - self.height = height = inkex.unittouu(svg.attrib['height']) + #self.width = width = inkex.unittouu(svg.get('width')) + #self.height = height = inkex.unittouu(svg.attrib['height']) # Convert parameters to user unit offset = inkex.unittouu(str(self.options.crop_offset) + \ @@ -216,10 +263,14 @@ class Printing_Marks (inkex.Effect): else : bmr = br - offset # Define the new document limits - left = - offset - right = width + offset - top = - offset - bottom = height + offset + offset_left = self.area_x1 - offset + offset_right = self.area_x2 + offset + offset_top = self.area_y1 - offset + offset_bottom = self.area_y2 + offset + + # Get middle positions + middle_vertical = self.area_y1 + ( self.area_h / 2 ) + middle_horizontal = self.area_x1 + ( self.area_w / 2 ) # Test if printing-marks layer existis layer = self.document.xpath( @@ -241,35 +292,35 @@ class Printing_Marks (inkex.Effect): g_crops = inkex.etree.SubElement(layer, 'g', g_attribs) # Top left Mark - self.draw_crop_line(0, top, - 0, top - self.mark_size, + self.draw_crop_line(self.area_x1, offset_top, + self.area_x1, offset_top - self.mark_size, 'cropTL1', g_crops) - self.draw_crop_line(left, 0, - left - self.mark_size, 0, + self.draw_crop_line(offset_left, self.area_y1, + offset_left - self.mark_size, self.area_y1, 'cropTL2', g_crops) # Top right Mark - self.draw_crop_line(width, top, - width , top - self.mark_size, + self.draw_crop_line(self.area_x2, offset_top, + self.area_x2, offset_top - self.mark_size, 'cropTR1', g_crops) - self.draw_crop_line(right, 0, - right + self.mark_size, 0, + self.draw_crop_line(offset_right, self.area_y1, + offset_right + self.mark_size, self.area_y1, 'cropTR2', g_crops) # Bottom left Mark - self.draw_crop_line(0, bottom, - 0, bottom + self.mark_size, + self.draw_crop_line(self.area_x1, offset_bottom, + self.area_x1, offset_bottom + self.mark_size, 'cropBL1', g_crops) - self.draw_crop_line(left, height, - left - self.mark_size, height, + self.draw_crop_line(offset_left, self.area_y2, + offset_left - self.mark_size, self.area_y2, 'cropBL2', g_crops) # Bottom right Mark - self.draw_crop_line(width, bottom, - width, bottom + self.mark_size, + self.draw_crop_line(self.area_x2, offset_bottom, + self.area_x2, offset_bottom + self.mark_size, 'cropBR1', g_crops) - self.draw_crop_line(right, height, - right + self.mark_size, height, + self.draw_crop_line(offset_right, self.area_y2, + offset_right + self.mark_size, self.area_y2, 'cropBR2', g_crops) # Bleed Mark @@ -280,35 +331,35 @@ class Printing_Marks (inkex.Effect): g_bleed = inkex.etree.SubElement(layer, 'g', g_attribs) # Top left Mark - self.draw_bleed_line(-bl, top - bmt, - -bl, top - bmt - self.mark_size, + self.draw_bleed_line(self.area_x1 - bl, offset_top - bmt, + self.area_x1 - bl, offset_top - bmt - self.mark_size, 'bleedTL1', g_bleed) - self.draw_bleed_line(left - bml, -bt, - left - bml - self.mark_size, -bt, + self.draw_bleed_line(offset_left - bml, self.area_y1 - bt, + offset_left - bml - self.mark_size, self.area_y1 - bt, 'bleedTL2', g_bleed) # Top right Mark - self.draw_bleed_line(width + br, top - bmt, - width + br, top - bmt - self.mark_size, + self.draw_bleed_line(self.area_x2 + br, offset_top - bmt, + self.area_x2 + br, offset_top - bmt - self.mark_size, 'bleedTR1', g_bleed) - self.draw_bleed_line(right + bmr, -bt, - right + bmr + self.mark_size, -bt, + self.draw_bleed_line(offset_right + bmr, self.area_y1 - bt, + offset_right + bmr + self.mark_size, self.area_y1 - bt, 'bleedTR2', g_bleed) # Bottom left Mark - self.draw_bleed_line(-bl, bottom + bmb, - -bl, bottom + bmb + self.mark_size, + self.draw_bleed_line(self.area_x1 - bl, offset_bottom + bmb, + self.area_x1 - bl, offset_bottom + bmb + self.mark_size, 'bleedBL1', g_bleed) - self.draw_bleed_line(left - bml, height + bb, - left - bml - self.mark_size, height + bb, - 'bleedBL2', g_bleed) + self.draw_bleed_line(offset_left - bml, self.area_y2 + bb, + offset_left - bml - self.mark_size, self.area_y2 + bb, + 'bleedBL2', g_bleed) # Bottom right Mark - self.draw_bleed_line(width + br, bottom + bmb, - width + br, bottom + bmb + self.mark_size, + self.draw_bleed_line(self.area_x2 + br, offset_bottom + bmb, + self.area_x2 + br, offset_bottom + bmb + self.mark_size, 'bleedBR1', g_bleed) - self.draw_bleed_line(right + bmr, height + bb, - right + bmr + self.mark_size, height + bb, + self.draw_bleed_line(offset_right + bmr, self.area_y2 + bb, + offset_right + bmr + self.mark_size, self.area_y2 + bb, 'bleedBR2', g_bleed) # Registration Mark @@ -320,26 +371,26 @@ class Printing_Marks (inkex.Effect): # Left Mark cx = max( bml + offset, self.min_mark_margin ) - self.draw_reg_marks(-cx - (self.mark_size/2), - (height/2) - self.mark_size*1.5, + self.draw_reg_marks(self.area_x1 - cx - (self.mark_size/2), + middle_vertical - self.mark_size*1.5, '0', 'regMarkL', g_center) # Right Mark cx = max( bmr + offset, self.min_mark_margin ) - self.draw_reg_marks(width + cx + (self.mark_size/2), - (height/2) - self.mark_size*1.5, + self.draw_reg_marks(self.area_x2 + cx + (self.mark_size/2), + middle_vertical - self.mark_size*1.5, '180', 'regMarkR', g_center) # Top Mark cy = max( bmt + offset, self.min_mark_margin ) - self.draw_reg_marks((width/2), - -cy - (self.mark_size/2), + self.draw_reg_marks(middle_horizontal, + self.area_y1 - cy - (self.mark_size/2), '90', 'regMarkT', g_center) # Bottom Mark cy = max( bmb + offset, self.min_mark_margin ) - self.draw_reg_marks((width/2), - height + cy + (self.mark_size/2), + self.draw_reg_marks(middle_horizontal, + self.area_y2 + cy + (self.mark_size/2), '-90', 'regMarkB', g_center) # Star Target @@ -349,27 +400,27 @@ class Printing_Marks (inkex.Effect): 'id':'StarTarget'} g_center = inkex.etree.SubElement(layer, 'g', g_attribs) - if height < width : + if self.area_h < self.area_w : # Left Star cx = max( bml + offset, self.min_mark_margin ) - self.draw_star_target(-cx - (self.mark_size/2), - (height/2), + self.draw_star_target(self.area_x1 - cx - (self.mark_size/2), + middle_vertical, 'starTargetL', g_center) # Right Star cx = max( bmr + offset, self.min_mark_margin ) - self.draw_star_target(width + cx + (self.mark_size/2), - (height/2), + self.draw_star_target(self.area_x2 + cx + (self.mark_size/2), + middle_vertical, 'starTargetR', g_center) else : # Top Star cy = max( bmt + offset, self.min_mark_margin ) - self.draw_star_target((width/2) - self.mark_size*1.5, - -cy - (self.mark_size/2), + self.draw_star_target(middle_horizontal - self.mark_size*1.5, + self.area_y1 - cy - (self.mark_size/2), 'starTargetT', g_center) # Bottom Star cy = max( bmb + offset, self.min_mark_margin ) - self.draw_star_target((width/2) - self.mark_size*1.5, - height + cy + (self.mark_size/2), + self.draw_star_target(middle_horizontal - self.mark_size*1.5, + self.area_y2 + cy + (self.mark_size/2), 'starTargetB', g_center) @@ -380,30 +431,30 @@ class Printing_Marks (inkex.Effect): 'id':'PrintingColourBars'} g_center = inkex.etree.SubElement(layer, 'g', g_attribs) - if height > width : + if self.area_h > self.area_w : # Left Bars cx = max( bml + offset, self.min_mark_margin ) - self.draw_coluor_bars(-cx - (self.mark_size/2), - height/2, + self.draw_coluor_bars(self.area_x1 - cx - (self.mark_size/2), + middle_vertical + self.mark_size, 90, 'PrintingColourBarsL', g_center) # Right Bars cx = max( bmr + offset, self.min_mark_margin ) - self.draw_coluor_bars(width + cx + (self.mark_size/2), - height/2, + self.draw_coluor_bars(self.area_x2 + cx + (self.mark_size/2), + middle_vertical + self.mark_size, 90, 'PrintingColourBarsR', g_center) else : # Top Bars cy = max( bmt + offset, self.min_mark_margin ) - self.draw_coluor_bars(width/2, - -cy - (self.mark_size/2), + self.draw_coluor_bars(middle_horizontal + self.mark_size, + self.area_y1 - cy - (self.mark_size/2), 0, 'PrintingColourBarsT', g_center) # Bottom Bars cy = max( bmb + offset, self.min_mark_margin ) - self.draw_coluor_bars(width/2, - height + cy + (self.mark_size/2), + self.draw_coluor_bars(middle_horizontal + self.mark_size, + self.area_y2 + cy + (self.mark_size/2), 0, 'PrintingColourBarsB', g_center) @@ -415,13 +466,16 @@ class Printing_Marks (inkex.Effect): 'id':'PageInformation'} g_pag_info = inkex.etree.SubElement(layer, 'g', g_attribs) y_margin = max( bmb + offset, self.min_mark_margin ) - txt_attribs = {'style':'font-size:12px;font-style:normal;font-weight:normal;fill:#000000;font-family:Bitstream Vera Sans,sans-serif;text-anchor:middle;text-align:center', - 'x':str(width/2), 'y':str(height+y_margin+self.mark_size+20)} + txt_attribs = { + 'style': 'font-size:12px;font-style:normal;font-weight:normal;fill:#000000;font-family:Bitstream Vera Sans,sans-serif;text-anchor:middle;text-align:center', + 'x': str(middle_horizontal), + 'y': str(self.area_y2+y_margin+self.mark_size+20) + } txt = inkex.etree.SubElement(g_pag_info, 'text', txt_attribs) txt.text = 'Page size: ' +\ - str(round(inkex.uutounit(width,self.options.unit),2)) +\ + str(round(inkex.uutounit(self.area_w,self.options.unit),2)) +\ 'x' +\ - str(round(inkex.uutounit(height,self.options.unit),2)) +\ + str(round(inkex.uutounit(self.area_h,self.options.unit),2)) +\ ' ' + self.options.unit diff --git a/share/extensions/run_command.py b/share/extensions/run_command.py index e1b51b3b0..f688444d6 100644 --- a/share/extensions/run_command.py +++ b/share/extensions/run_command.py @@ -23,8 +23,8 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ -# Run a command that generates an SVG file from an input file. -# On success, outputs the contents of the SVG file to stdout, and exits +# Run a command that generates an SVG (or PDF) file from an input file. +# On success, outputs the contents of the resulting file to stdout, and exits # with a return code of 0. # On failure, outputs an error message to stderr, and exits with a return # code of 1. @@ -32,6 +32,12 @@ def run(command_format, prog_name): svgfile = tempfile.mktemp(".svg") command = command_format % svgfile msg = None + # ps2pdf may attempt to write to the current directory, which may not + # be writeable, so we switch to the temp directory first. + try: + os.chdir(tempfile.gettempdir()) + except Exception: + pass # In order to get a return code from the process, we use subprocess.Popen # if it's available (Python 2.4 onwards) and otherwise use popen2.Popen3 # (Unix only). As the Inkscape package for Windows includes Python 2.5, @@ -59,7 +65,7 @@ def run(command_format, prog_name): except Exception, inst: msg = "Error attempting to run %s: %s" % (prog_name, str(inst)) - # If successful, copy the SVG file to stdout. + # If successful, copy the output file to stdout. if msg is None: if os.name == 'nt': # make stdout work in binary on Windows import msvcrt @@ -70,7 +76,7 @@ def run(command_format, prog_name): sys.stdout.write(data) f.close() except IOError, inst: - msg = "Error reading temporary SVG file: %s" % str(inst) + msg = "Error reading temporary file: %s" % str(inst) # Clean up. try: diff --git a/share/extensions/scour.inkscape.py b/share/extensions/scour.inkscape.py index 9e8775782..f21e223a0 100755 --- a/share/extensions/scour.inkscape.py +++ b/share/extensions/scour.inkscape.py @@ -37,10 +37,13 @@ class ScourInkscape (inkex.Effect): self.OptionParser.add_option("--indent", action="store", type="string", dest="indent_type", default="space", help="indentation of the output: none, space, tab (default: %default)") + self.OptionParser.add_option("--enable-viewboxing", type="inkbool", + action="store", dest="enable_viewboxing", default=False, + help="changes document width/height to 100%/100% and creates viewbox coordinates") def effect(self): - input = file(sys.argv[11], "r") + input = file(sys.argv[12], "r") sys.stdout.write(scourString(input.read(), self.options).encode("UTF-8")) input.close() sys.stdout.close() diff --git a/share/extensions/scour.inx b/share/extensions/scour.inx index d5cddeea5..ee310c503 100644 --- a/share/extensions/scour.inx +++ b/share/extensions/scour.inx @@ -13,6 +13,7 @@ <param name="enable-id-stripping" type="boolean" _gui-text="Enable id stripping">false</param> <param name="embed-rasters" type="boolean" _gui-text="Embed rasters">true</param> <param name="keep-editor-data" type="boolean" _gui-text="Keep editor data">false</param> + <param name="enable-viewboxing" type="boolean" _gui-text="Enable viewboxing">false</param> <param name="strip-xml-prolog" type="boolean" _gui-text="Strip xml prolog">false</param> <param name="set-precision" type="int" _gui-text="Set precision">5</param> <param name="indent" type="enum" _gui-text="Indent"> @@ -29,6 +30,7 @@ * Enable id stripping: remove all un-referenced ID attributes. * Embed rasters: embed rasters as base64-encoded data. * Keep editor data: don't remove Inkscape, Sodipodi or Adobe Illustrator elements and attributes. + * Enable viewboxing: size image to 100%/100% and introduce a viewBox * Strip xml prolog: don't output the xml prolog. * Set precision: set number of significant digits (default: 5). * Indent: indentation of the output: none, space, tab (default: space).</_param> diff --git a/share/extensions/scour.py b/share/extensions/scour.py index c68295c15..b851e48c3 100755 --- a/share/extensions/scour.py +++ b/share/extensions/scour.py @@ -3,7 +3,7 @@ # Scour # -# Copyright 2009 Jeff Schiller +# Copyright 2010 Jeff Schiller # # This file is part of Scour, http://www.codedread.com/scour/ # @@ -34,7 +34,8 @@ # at rounded corners) # Next Up: -# - TODO: fix the removal of comment elements (between <?xml?> and <svg>) +# - Bug 511186: option to keep XML comments between prolog and root element +# - only remove unreferenced elements if they are not children of a referenced element # - add an option to remove ids if they match the Inkscape-style of IDs # - investigate point-reducing algorithms # - parse transform attribute @@ -63,9 +64,16 @@ except ImportError: from fixedpoint import * Decimal = FixedPoint +# Import Psyco if available +try: + import psyco + psyco.full() +except ImportError: + pass + APP = 'scour' -VER = '0.22' -COPYRIGHT = 'Copyright Jeff Schiller, 2009' +VER = '0.24' +COPYRIGHT = 'Copyright Jeff Schiller, 2010' NS = { 'SVG': 'http://www.w3.org/2000/svg', 'XLINK': 'http://www.w3.org/1999/xlink', @@ -499,16 +507,20 @@ def removeUnusedDefs(doc, defElem, elemsToRemove=None): identifiedElements = findElementsWithId(doc.documentElement) referencedIDs = findReferencedElements(doc.documentElement) - + keepTags = ['font', 'style', 'metadata', 'script', 'title', 'desc'] for elem in defElem.childNodes: - if elem.nodeName == 'g' and elem.namespaceURI == NS['SVG']: - elemsToRemove = removeUnusedDefs(doc, elem, elemsToRemove) - continue + # only look at it if an element and not referenced anywhere else if elem.nodeType == 1 and (elem.getAttribute('id') == '' or \ - (not elem.getAttribute('id') in referencedIDs)) and \ - not elem.nodeName in keepTags: - elemsToRemove.append(elem) + (not elem.getAttribute('id') in referencedIDs)): + + # we only inspect the children of a group in a defs if the group + # is not referenced anywhere else + if elem.nodeName == 'g' and elem.namespaceURI == NS['SVG']: + elemsToRemove = removeUnusedDefs(doc, elem, elemsToRemove) + # we only remove if it is not one of our tags we always keep (see above) + elif not elem.nodeName in keepTags: + elemsToRemove.append(elem) return elemsToRemove def removeUnreferencedElements(doc): @@ -2009,21 +2021,17 @@ def remapNamespacePrefix(node, oldprefix, newprefix): remapNamespacePrefix(child, oldprefix, newprefix) def makeWellFormed(str): - newstr = str - - # encode & as & ( must do this first so that < does not become &lt; ) - if str.find('&') != -1: - newstr = str.replace('&', '&') + xml_ents = { '<':'<', '>':'>', '&':'&', "'":''', '"':'"'} + +# starr = [] +# for c in str: +# if c in xml_ents: +# starr.append(xml_ents[c]) +# else: +# starr.append(c) - # encode < as < - if str.find("<") != -1: - newstr = str.replace('<', '<') - - # encode > as > (TODO: is this necessary?) - if str.find('>') != -1: - newstr = str.replace('>', '>') - - return newstr + # this list comprehension is short-form for the above for-loop: + return ''.join([xml_ents[c] if c in xml_ents else c for c in str]) # hand-rolled serialization function that has the following benefits: # - pretty printing @@ -2092,7 +2100,7 @@ def serializeXML(element, options, ind = 0, preserveWhitespace = False): if preserveWhitespace: outString += serializeXML(child, options, 0, preserveWhitespace) else: - outString += '\n' + serializeXML(child, options, indent + 1, preserveWhitespace) + outString += os.linesep + serializeXML(child, options, indent + 1, preserveWhitespace) onNewLine = True # text node elif child.nodeType == 3: @@ -2114,10 +2122,10 @@ def serializeXML(element, options, ind = 0, preserveWhitespace = False): if onNewLine: outString += (I * ind) outString += '</' + element.nodeName + '>' - if indent > 0: outString += '\n' + if indent > 0: outString += os.linesep else: outString += '/>' - if indent > 0: outString += '\n' + if indent > 0: outString += os.linesep return outString @@ -2266,14 +2274,15 @@ def scourString(in_string, options=None): embedRasters(elem, options) # properly size the SVG document (ideally width/height should be 100% with a viewBox) - properlySizeDoc(doc.documentElement) + if options.enable_viewboxing: + properlySizeDoc(doc.documentElement) # output the document as a pretty string with a single space for indent # NOTE: removed pretty printing because of this problem: # http://ronrothman.com/public/leftbraned/xml-dom-minidom-toprettyxml-and-silly-whitespace/ # rolled our own serialize function here to save on space, put id first, customize indentation, etc # out_string = doc.documentElement.toprettyxml(' ') - out_string = serializeXML(doc.documentElement, options) + out_string = serializeXML(doc.documentElement, options) + os.linesep # now strip out empty lines lines = [] @@ -2282,13 +2291,19 @@ def scourString(in_string, options=None): if line.strip(): lines.append(line) - # return the string stripped of empty lines + # return the string with its XML prolog and surrounding comments if options.strip_xml_prolog == False: - xmlprolog = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n' + total_output = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>' + os.linesep else: - xmlprolog = "" + total_output = "" + + for child in doc.childNodes: + if child.nodeType == 1: + total_output += "".join(lines) + else: # doctypes, entities, comments + total_output += child.toxml() + os.linesep - return xmlprolog + "".join(lines) + return total_output # used mostly by unit tests # input is a filename @@ -2340,6 +2355,9 @@ _options_parser.add_option("--keep-editor-data", _options_parser.add_option("--strip-xml-prolog", action="store_true", dest="strip_xml_prolog", default=False, help="won't output the <?xml ?> prolog") +_options_parser.add_option("--enable-viewboxing", + action="store_true", dest="enable_viewboxing", default=False, + help="changes document width/height to 100%/100% and creates viewbox coordinates") # GZ: this is confusing, most people will be thinking in terms of # decimal places, which is not what decimal precision is doing @@ -2385,15 +2403,15 @@ def parse_args(args=None): return options, [infile, outfile] def getReport(): - return ' Number of elements removed: ' + str(numElemsRemoved) + \ - '\n Number of attributes removed: ' + str(numAttrsRemoved) + \ - '\n Number of unreferenced id attributes removed: ' + str(numIDsRemoved) + \ - '\n Number of style properties fixed: ' + str(numStylePropsFixed) + \ - '\n Number of raster images embedded inline: ' + str(numRastersEmbedded) + \ - '\n Number of path segments reduced/removed: ' + str(numPathSegmentsReduced) + \ - '\n Number of bytes saved in path data: ' + str(numBytesSavedInPathData) + \ - '\n Number of bytes saved in colors: ' + str(numBytesSavedInColors) + \ - '\n Number of points removed from polygons: ' + str(numPointsRemovedFromPolygon) + return ' Number of elements removed: ' + str(numElemsRemoved) + os.linesep + \ + ' Number of attributes removed: ' + str(numAttrsRemoved) + os.linesep + \ + ' Number of unreferenced id attributes removed: ' + str(numIDsRemoved) + os.linesep + \ + ' Number of style properties fixed: ' + str(numStylePropsFixed) + os.linesep + \ + ' Number of raster images embedded inline: ' + str(numRastersEmbedded) + os.linesep + \ + ' Number of path segments reduced/removed: ' + str(numPathSegmentsReduced) + os.linesep + \ + ' Number of bytes saved in path data: ' + str(numBytesSavedInPathData) + os.linesep + \ + ' Number of bytes saved in colors: ' + str(numBytesSavedInColors) + os.linesep + \ + ' Number of points removed from polygons: ' + str(numPointsRemovedFromPolygon) if __name__ == '__main__': if sys.platform == "win32": @@ -2423,7 +2441,7 @@ if __name__ == '__main__': # GZ: unless silenced by -q or something? # GZ: not using globals would be good too print >>sys.stderr, ' File:', input.name, \ - '\n Time taken:', str(end-start) + 's\n', \ + os.linesep + ' Time taken:', str(end-start) + 's' + os.linesep, \ getReport() oldsize = len(in_string) diff --git a/share/extensions/svg2xaml.xsl b/share/extensions/svg2xaml.xsl index cf8f78f74..9cc71e8c6 100644 --- a/share/extensions/svg2xaml.xsl +++ b/share/extensions/svg2xaml.xsl @@ -944,7 +944,9 @@ exclude-result-prefixes="rdf xlink msxsl"> <xsl:if test="@width"><xsl:attribute name="Width"><xsl:value-of select="@width" /></xsl:attribute></xsl:if>
<xsl:if test="@height"><xsl:attribute name="Height"><xsl:value-of select="@height" /></xsl:attribute></xsl:if>
<xsl:if test="@rx"><xsl:attribute name="RadiusX"><xsl:value-of select="@rx" /></xsl:attribute></xsl:if>
- <xsl:if test="@ry"><xsl:attribute name="RadiusY"><xsl:value-of select="@ry" /></xsl:attribute></xsl:if>
+ <xsl:if test="@ry"><xsl:attribute name="RadiusY"><xsl:value-of select="@ry" /></xsl:attribute></xsl:if>
+ <xsl:if test="@rx and not(@ry)"><xsl:attribute name="RadiusX"><xsl:value-of select="@rx" /></xsl:attribute><xsl:attribute name="RadiusY"><xsl:value-of select="@rx" /></xsl:attribute></xsl:if>
+ <xsl:if test="@ry and not(@rx)"><xsl:attribute name="RadiusX"><xsl:value-of select="@ry" /></xsl:attribute><xsl:attribute name="RadiusY"><xsl:value-of select="@ry" /></xsl:attribute></xsl:if>
<xsl:apply-templates mode="id" select="." />
<xsl:apply-templates mode="template_fill" select="." />
<xsl:apply-templates mode="template_stroke" select="." />
diff --git a/share/extensions/svg_and_media_zip_output.py b/share/extensions/svg_and_media_zip_output.py index 341de728b..8308d6062 100644 --- a/share/extensions/svg_and_media_zip_output.py +++ b/share/extensions/svg_and_media_zip_output.py @@ -23,11 +23,8 @@ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -Version 0.4 (Nicolas Dufour, nicoduf@yahoo.fr) - fix a coding bug: now use UTF-8 to save filenames in the archive. - fix xlink href parsing (now a real URL in 0.47). - fix Win32 stdout \r\r\n bug (stdout now set to bin mode). - fix a double .svg extension bug (added svg and svgz in the docstripped extensions). +Version 0.5 (Nicolas Dufour, nicoduf@yahoo.fr) + Fix a bug related to special caracters in the path (LP #456248). TODO - fix bug: not saving existing .zip after a Collect for Output is run @@ -35,6 +32,7 @@ TODO the file name is still xxx.zip. after saving again the file xxx.zip is written with a plain .svg which looks like a corrupt zip - maybe add better extention +- consider switching to lzma in order to allow cross platform compression with no encoding problem... """ import inkex @@ -52,6 +50,10 @@ _ = gettext.gettext class SVG_and_Media_ZIP_Output(inkex.Effect): def __init__(self): inkex.Effect.__init__(self) + if os.name == 'nt': + self.encoding = "cp437" + else: + self.encoding = "latin-1" def output(self): out = open(self.zip_file,'rb') @@ -98,7 +100,7 @@ class SVG_and_Media_ZIP_Output(inkex.Effect): stream.close() - z.write(dst_file,docstripped.encode("utf_8")+'.svg') + z.write(dst_file,docstripped.encode(self.encoding)+'.svg') z.close() @@ -107,20 +109,21 @@ class SVG_and_Media_ZIP_Output(inkex.Effect): if (xlink[:4]!='data'): absref=node.get(inkex.addNS('absref',u'sodipodi')) url=urlparse.urlparse(xlink) - href=urllib.unquote(url.path) - if os.name == 'nt' and href[0] == '/': - href = href[1:] + href=urllib.url2pathname(url.path) + if (href != None): absref=os.path.realpath(href) + absref=unicode(absref, "utf-8") + if (os.path.isfile(absref)): shutil.copy(absref, self.tmp_dir) - z.write(absref, os.path.basename(absref).encode("utf_8")) + z.write(absref, os.path.basename(absref).encode(self.encoding)) elif (os.path.isfile(os.path.join(self.tmp_dir, absref))): #TODO: please explain why this clause is necessary shutil.copy(os.path.join(self.tmp_dir, absref), self.tmp_dir) z.write(os.path.join(self.tmp_dir, absref), - os.path.basename(absref).encode("utf_8")) + os.path.basename(absref).encode(self.encoding)) else: inkex.errormsg(_('Could not locate file: %s') % absref) diff --git a/share/extensions/voronoi.py b/share/extensions/voronoi.py new file mode 100644 index 000000000..be15fbe13 --- /dev/null +++ b/share/extensions/voronoi.py @@ -0,0 +1,789 @@ +############################################################################# +# +# Voronoi diagram calculator/ Delaunay triangulator +# Translated to Python by Bill Simons +# September, 2005 +# +# Calculate Delaunay triangulation or the Voronoi polygons for a set of +# 2D input points. +# +# Derived from code bearing the following notice: +# +# The author of this software is Steven Fortune. Copyright (c) 1994 by AT&T +# Bell Laboratories. +# Permission to use, copy, modify, and distribute this software for any +# purpose without fee is hereby granted, provided that this entire notice +# is included in all copies of any software which is or includes a copy +# or modification of this software and in all copies of the supporting +# documentation for such software. +# THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED +# WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY +# REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY +# OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. +# +# Comments were incorporated from Shane O'Sullivan's translation of the +# original code into C++ (http://mapviewer.skynet.ie/voronoi.html) +# +# Steve Fortune's homepage: http://netlib.bell-labs.com/cm/cs/who/sjf/index.html +# +############################################################################# + +def usage(): + print """ +voronoi - compute Voronoi diagram or Delaunay triangulation + +voronoi [-t -p -d] [filename] + +Voronoi reads from filename (or standard input if no filename given) for a set +of points in the plane and writes either the Voronoi diagram or the Delaunay +triangulation to the standard output. Each input line should consist of two +real numbers, separated by white space. + +If option -t is present, the Delaunay triangulation is produced. +Each output line is a triple i j k, which are the indices of the three points +in a Delaunay triangle. Points are numbered starting at 0. + +If option -t is not present, the Voronoi diagram is produced. +There are four output record types. + +s a b indicates that an input point at coordinates a b was seen. +l a b c indicates a line with equation ax + by = c. +v a b indicates a vertex at a b. +e l v1 v2 indicates a Voronoi segment which is a subsegment of line number l + with endpoints numbered v1 and v2. If v1 or v2 is -1, the line + extends to infinity. + +Other options include: + +d Print debugging info + +p Produce output suitable for input to plot (1), rather than the forms + described above. + +On unsorted data uniformly distributed in the unit square, voronoi uses about +20n+140 bytes of storage. + +AUTHOR +Steve J. Fortune (1987) A Sweepline Algorithm for Voronoi Diagrams, +Algorithmica 2, 153-174. +""" + +############################################################################# +# +# For programmatic use two functions are available: +# +# computeVoronoiDiagram(points) +# +# Takes a list of point objects (which must have x and y fields). +# Returns a 3-tuple of: +# +# (1) a list of 2-tuples, which are the x,y coordinates of the +# Voronoi diagram vertices +# (2) a list of 3-tuples (a,b,c) which are the equations of the +# lines in the Voronoi diagram: a*x + b*y = c +# (3) a list of 3-tuples, (l, v1, v2) representing edges of the +# Voronoi diagram. l is the index of the line, v1 and v2 are +# the indices of the vetices at the end of the edge. If +# v1 or v2 is -1, the line extends to infinity. +# +# computeDelaunayTriangulation(points): +# +# Takes a list of point objects (which must have x and y fields). +# Returns a list of 3-tuples: the indices of the points that form a +# Delaunay triangle. +# +############################################################################# +import math +import sys +import getopt +TOLERANCE = 1e-9 +BIG_FLOAT = 1e38 + +#------------------------------------------------------------------ +class Context(object): + def __init__(self): + self.doPrint = 0 + self.debug = 0 + self.plot = 0 + self.triangulate = False + self.vertices = [] # list of vertex 2-tuples: (x,y) + self.lines = [] # equation of line 3-tuple (a b c), for the equation of the line a*x+b*y = c + self.edges = [] # edge 3-tuple: (line index, vertex 1 index, vertex 2 index) if either vertex index is -1, the edge extends to infiinity + self.triangles = [] # 3-tuple of vertex indices + + def circle(self,x,y,rad): + pass + + def clip_line(self,edge): + pass + + def line(self,x0,y0,x1,y1): + pass + + def outSite(self,s): + if(self.debug): + print "site (%d) at %f %f" % (s.sitenum, s.x, s.y) + elif(self.triangulate): + pass + elif(self.plot): + self.circle (s.x, s.y, cradius) + elif(self.doPrint): + print "s %f %f" % (s.x, s.y) + + def outVertex(self,s): + self.vertices.append((s.x,s.y)) + if(self.debug): + print "vertex(%d) at %f %f" % (s.sitenum, s.x, s.y) + elif(self.triangulate): + pass + elif(self.doPrint and not self.plot): + print "v %f %f" % (s.x,s.y) + + def outTriple(self,s1,s2,s3): + self.triangles.append((s1.sitenum, s2.sitenum, s3.sitenum)) + if(self.debug): + print "circle through left=%d right=%d bottom=%d" % (s1.sitenum, s2.sitenum, s3.sitenum) + elif(self.triangulate and self.doPrint and not self.plot): + print "%d %d %d" % (s1.sitenum, s2.sitenum, s3.sitenum) + + def outBisector(self,edge): + self.lines.append((edge.a, edge.b, edge.c)) + if(self.debug): + print "line(%d) %gx+%gy=%g, bisecting %d %d" % (edge.edgenum, edge.a, edge.b, edge.c, edge.reg[0].sitenum, edge.reg[1].sitenum) + elif(self.triangulate): + if(self.plot): + self.line(edge.reg[0].x, edge.reg[0].y, edge.reg[1].x, edge.reg[1].y) + elif(self.doPrint and not self.plot): + print "l %f %f %f" % (edge.a, edge.b, edge.c) + + def outEdge(self,edge): + sitenumL = -1 + if edge.ep[Edge.LE] is not None: + sitenumL = edge.ep[Edge.LE].sitenum + sitenumR = -1 + if edge.ep[Edge.RE] is not None: + sitenumR = edge.ep[Edge.RE].sitenum + self.edges.append((edge.edgenum,sitenumL,sitenumR)) + if(not self.triangulate): + if self.plot: + self.clip_line(edge) + elif(self.doPrint): + print "e %d" % edge.edgenum, + print " %d " % sitenumL, + print "%d" % sitenumR + +#------------------------------------------------------------------ +def voronoi(siteList,context): + edgeList = EdgeList(siteList.xmin,siteList.xmax,len(siteList)) + priorityQ = PriorityQueue(siteList.ymin,siteList.ymax,len(siteList)) + siteIter = siteList.iterator() + + bottomsite = siteIter.next() + context.outSite(bottomsite) + newsite = siteIter.next() + minpt = Site(-BIG_FLOAT,-BIG_FLOAT) + while True: + if not priorityQ.isEmpty(): + minpt = priorityQ.getMinPt() + + if (newsite and (priorityQ.isEmpty() or cmp(newsite,minpt) < 0)): + # newsite is smallest - this is a site event + context.outSite(newsite) + + # get first Halfedge to the LEFT and RIGHT of the new site + lbnd = edgeList.leftbnd(newsite) + rbnd = lbnd.right + + # if this halfedge has no edge, bot = bottom site (whatever that is) + # create a new edge that bisects + bot = lbnd.rightreg(bottomsite) + edge = Edge.bisect(bot,newsite) + context.outBisector(edge) + + # create a new Halfedge, setting its pm field to 0 and insert + # this new bisector edge between the left and right vectors in + # a linked list + bisector = Halfedge(edge,Edge.LE) + edgeList.insert(lbnd,bisector) + + # if the new bisector intersects with the left edge, remove + # the left edge's vertex, and put in the new one + p = lbnd.intersect(bisector) + if p is not None: + priorityQ.delete(lbnd) + priorityQ.insert(lbnd,p,newsite.distance(p)) + + # create a new Halfedge, setting its pm field to 1 + # insert the new Halfedge to the right of the original bisector + lbnd = bisector + bisector = Halfedge(edge,Edge.RE) + edgeList.insert(lbnd,bisector) + + # if this new bisector intersects with the right Halfedge + p = bisector.intersect(rbnd) + if p is not None: + # push the Halfedge into the ordered linked list of vertices + priorityQ.insert(bisector,p,newsite.distance(p)) + + newsite = siteIter.next() + + elif not priorityQ.isEmpty(): + # intersection is smallest - this is a vector (circle) event + + # pop the Halfedge with the lowest vector off the ordered list of + # vectors. Get the Halfedge to the left and right of the above HE + # and also the Halfedge to the right of the right HE + lbnd = priorityQ.popMinHalfedge() + llbnd = lbnd.left + rbnd = lbnd.right + rrbnd = rbnd.right + + # get the Site to the left of the left HE and to the right of + # the right HE which it bisects + bot = lbnd.leftreg(bottomsite) + top = rbnd.rightreg(bottomsite) + + # output the triple of sites, stating that a circle goes through them + mid = lbnd.rightreg(bottomsite) + context.outTriple(bot,top,mid) + + # get the vertex that caused this event and set the vertex number + # couldn't do this earlier since we didn't know when it would be processed + v = lbnd.vertex + siteList.setSiteNumber(v) + context.outVertex(v) + + # set the endpoint of the left and right Halfedge to be this vector + if lbnd.edge.setEndpoint(lbnd.pm,v): + context.outEdge(lbnd.edge) + + if rbnd.edge.setEndpoint(rbnd.pm,v): + context.outEdge(rbnd.edge) + + + # delete the lowest HE, remove all vertex events to do with the + # right HE and delete the right HE + edgeList.delete(lbnd) + priorityQ.delete(rbnd) + edgeList.delete(rbnd) + + + # if the site to the left of the event is higher than the Site + # to the right of it, then swap them and set 'pm' to RIGHT + pm = Edge.LE + if bot.y > top.y: + bot,top = top,bot + pm = Edge.RE + + # Create an Edge (or line) that is between the two Sites. This + # creates the formula of the line, and assigns a line number to it + edge = Edge.bisect(bot, top) + context.outBisector(edge) + + # create a HE from the edge + bisector = Halfedge(edge, pm) + + # insert the new bisector to the right of the left HE + # set one endpoint to the new edge to be the vector point 'v' + # If the site to the left of this bisector is higher than the right + # Site, then this endpoint is put in position 0; otherwise in pos 1 + edgeList.insert(llbnd, bisector) + if edge.setEndpoint(Edge.RE - pm, v): + context.outEdge(edge) + + # if left HE and the new bisector don't intersect, then delete + # the left HE, and reinsert it + p = llbnd.intersect(bisector) + if p is not None: + priorityQ.delete(llbnd); + priorityQ.insert(llbnd, p, bot.distance(p)) + + # if right HE and the new bisector don't intersect, then reinsert it + p = bisector.intersect(rrbnd) + if p is not None: + priorityQ.insert(bisector, p, bot.distance(p)) + else: + break + + he = edgeList.leftend.right + while he is not edgeList.rightend: + context.outEdge(he.edge) + he = he.right + +#------------------------------------------------------------------ +def isEqual(a,b,relativeError=TOLERANCE): + # is nearly equal to within the allowed relative error + norm = max(abs(a),abs(b)) + return (norm < relativeError) or (abs(a - b) < (relativeError * norm)) + +#------------------------------------------------------------------ +class Site(object): + def __init__(self,x=0.0,y=0.0,sitenum=0): + self.x = x + self.y = y + self.sitenum = sitenum + + def dump(self): + print "Site #%d (%g, %g)" % (self.sitenum,self.x,self.y) + + def __cmp__(self,other): + if self.y < other.y: + return -1 + elif self.y > other.y: + return 1 + elif self.x < other.x: + return -1 + elif self.x > other.x: + return 1 + else: + return 0 + + def distance(self,other): + dx = self.x - other.x + dy = self.y - other.y + return math.sqrt(dx*dx + dy*dy) + +#------------------------------------------------------------------ +class Edge(object): + LE = 0 + RE = 1 + EDGE_NUM = 0 + DELETED = {} # marker value + + def __init__(self): + self.a = 0.0 + self.b = 0.0 + self.c = 0.0 + self.ep = [None,None] + self.reg = [None,None] + self.edgenum = 0 + + def dump(self): + print "(#%d a=%g, b=%g, c=%g)" % (self.edgenum,self.a,self.b,self.c) + print "ep",self.ep + print "reg",self.reg + + def setEndpoint(self, lrFlag, site): + self.ep[lrFlag] = site + if self.ep[Edge.RE - lrFlag] is None: + return False + return True + + @staticmethod + def bisect(s1,s2): + newedge = Edge() + newedge.reg[0] = s1 # store the sites that this edge is bisecting + newedge.reg[1] = s2 + + # to begin with, there are no endpoints on the bisector - it goes to infinity + # ep[0] and ep[1] are None + + # get the difference in x dist between the sites + dx = float(s2.x - s1.x) + dy = float(s2.y - s1.y) + adx = abs(dx) # make sure that the difference in positive + ady = abs(dy) + + # get the slope of the line + newedge.c = float(s1.x * dx + s1.y * dy + (dx*dx + dy*dy)*0.5) + if adx > ady : + # set formula of line, with x fixed to 1 + newedge.a = 1.0 + newedge.b = dy/dx + newedge.c /= dx + else: + # set formula of line, with y fixed to 1 + newedge.b = 1.0 + newedge.a = dx/dy + newedge.c /= dy + + newedge.edgenum = Edge.EDGE_NUM + Edge.EDGE_NUM += 1 + return newedge + + +#------------------------------------------------------------------ +class Halfedge(object): + def __init__(self,edge=None,pm=Edge.LE): + self.left = None # left Halfedge in the edge list + self.right = None # right Halfedge in the edge list + self.qnext = None # priority queue linked list pointer + self.edge = edge # edge list Edge + self.pm = pm + self.vertex = None # Site() + self.ystar = BIG_FLOAT + + def dump(self): + print "Halfedge--------------------------" + print "left: ", self.left + print "right: ", self.right + print "edge: ", self.edge + print "pm: ", self.pm + print "vertex: ", + if self.vertex: self.vertex.dump() + else: print "None" + print "ystar: ", self.ystar + + + def __cmp__(self,other): + if self.ystar > other.ystar: + return 1 + elif self.ystar < other.ystar: + return -1 + elif self.vertex.x > other.vertex.x: + return 1 + elif self.vertex.x < other.vertex.x: + return -1 + else: + return 0 + + def leftreg(self,default): + if not self.edge: + return default + elif self.pm == Edge.LE: + return self.edge.reg[Edge.LE] + else: + return self.edge.reg[Edge.RE] + + def rightreg(self,default): + if not self.edge: + return default + elif self.pm == Edge.LE: + return self.edge.reg[Edge.RE] + else: + return self.edge.reg[Edge.LE] + + + # returns True if p is to right of halfedge self + def isPointRightOf(self,pt): + e = self.edge + topsite = e.reg[1] + right_of_site = pt.x > topsite.x + + if(right_of_site and self.pm == Edge.LE): + return True + + if(not right_of_site and self.pm == Edge.RE): + return False + + if(e.a == 1.0): + dyp = pt.y - topsite.y + dxp = pt.x - topsite.x + fast = 0; + if ((not right_of_site and e.b < 0.0) or (right_of_site and e.b >= 0.0)): + above = dyp >= e.b * dxp + fast = above + else: + above = pt.x + pt.y * e.b > e.c + if(e.b < 0.0): + above = not above + if (not above): + fast = 1 + if (not fast): + dxs = topsite.x - (e.reg[0]).x + above = e.b * (dxp*dxp - dyp*dyp) < dxs*dyp*(1.0+2.0*dxp/dxs + e.b*e.b) + if(e.b < 0.0): + above = not above + else: # e.b == 1.0 + yl = e.c - e.a * pt.x + t1 = pt.y - yl + t2 = pt.x - topsite.x + t3 = yl - topsite.y + above = t1*t1 > t2*t2 + t3*t3 + + if(self.pm==Edge.LE): + return above + else: + return not above + + #-------------------------- + # create a new site where the Halfedges el1 and el2 intersect + def intersect(self,other): + e1 = self.edge + e2 = other.edge + if (e1 is None) or (e2 is None): + return None + + # if the two edges bisect the same parent return None + if e1.reg[1] is e2.reg[1]: + return None + + d = e1.a * e2.b - e1.b * e2.a + if isEqual(d,0.0): + return None + + xint = (e1.c*e2.b - e2.c*e1.b) / d + yint = (e2.c*e1.a - e1.c*e2.a) / d + if(cmp(e1.reg[1],e2.reg[1]) < 0): + he = self + e = e1 + else: + he = other + e = e2 + + rightOfSite = xint >= e.reg[1].x + if((rightOfSite and he.pm == Edge.LE) or + (not rightOfSite and he.pm == Edge.RE)): + return None + + # create a new site at the point of intersection - this is a new + # vector event waiting to happen + return Site(xint,yint) + + + +#------------------------------------------------------------------ +class EdgeList(object): + def __init__(self,xmin,xmax,nsites): + if xmin > xmax: xmin,xmax = xmax,xmin + self.hashsize = int(2*math.sqrt(nsites+4)) + + self.xmin = xmin + self.deltax = float(xmax - xmin) + self.hash = [None]*self.hashsize + + self.leftend = Halfedge() + self.rightend = Halfedge() + self.leftend.right = self.rightend + self.rightend.left = self.leftend + self.hash[0] = self.leftend + self.hash[-1] = self.rightend + + def insert(self,left,he): + he.left = left + he.right = left.right + left.right.left = he + left.right = he + + def delete(self,he): + he.left.right = he.right + he.right.left = he.left + he.edge = Edge.DELETED + + # Get entry from hash table, pruning any deleted nodes + def gethash(self,b): + if(b < 0 or b >= self.hashsize): + return None + he = self.hash[b] + if he is None or he.edge is not Edge.DELETED: + return he + + # Hash table points to deleted half edge. Patch as necessary. + self.hash[b] = None + return None + + def leftbnd(self,pt): + # Use hash table to get close to desired halfedge + bucket = int(((pt.x - self.xmin)/self.deltax * self.hashsize)) + + if(bucket < 0): + bucket =0; + + if(bucket >=self.hashsize): + bucket = self.hashsize-1 + + he = self.gethash(bucket) + if(he is None): + i = 1 + while True: + he = self.gethash(bucket-i) + if (he is not None): break; + he = self.gethash(bucket+i) + if (he is not None): break; + i += 1 + + # Now search linear list of halfedges for the corect one + if (he is self.leftend) or (he is not self.rightend and he.isPointRightOf(pt)): + he = he.right + while he is not self.rightend and he.isPointRightOf(pt): + he = he.right + he = he.left; + else: + he = he.left + while (he is not self.leftend and not he.isPointRightOf(pt)): + he = he.left + + # Update hash table and reference counts + if(bucket > 0 and bucket < self.hashsize-1): + self.hash[bucket] = he + return he + + +#------------------------------------------------------------------ +class PriorityQueue(object): + def __init__(self,ymin,ymax,nsites): + self.ymin = ymin + self.deltay = ymax - ymin + self.hashsize = int(4 * math.sqrt(nsites)) + self.count = 0 + self.minidx = 0 + self.hash = [] + for i in range(self.hashsize): + self.hash.append(Halfedge()) + + def __len__(self): + return self.count + + def isEmpty(self): + return self.count == 0 + + def insert(self,he,site,offset): + he.vertex = site + he.ystar = site.y + offset + last = self.hash[self.getBucket(he)] + next = last.qnext + while((next is not None) and cmp(he,next) > 0): + last = next + next = last.qnext + he.qnext = last.qnext + last.qnext = he + self.count += 1 + + def delete(self,he): + if (he.vertex is not None): + last = self.hash[self.getBucket(he)] + while last.qnext is not he: + last = last.qnext + last.qnext = he.qnext + self.count -= 1 + he.vertex = None + + def getBucket(self,he): + bucket = int(((he.ystar - self.ymin) / self.deltay) * self.hashsize) + if bucket < 0: bucket = 0 + if bucket >= self.hashsize: bucket = self.hashsize-1 + if bucket < self.minidx: self.minidx = bucket + return bucket + + def getMinPt(self): + while(self.hash[self.minidx].qnext is None): + self.minidx += 1 + he = self.hash[self.minidx].qnext + x = he.vertex.x + y = he.ystar + return Site(x,y) + + def popMinHalfedge(self): + curr = self.hash[self.minidx].qnext + self.hash[self.minidx].qnext = curr.qnext + self.count -= 1 + return curr + + +#------------------------------------------------------------------ +class SiteList(object): + def __init__(self,pointList): + self.__sites = [] + self.__sitenum = 0 + + self.__xmin = pointList[0].x + self.__ymin = pointList[0].y + self.__xmax = pointList[0].x + self.__ymax = pointList[0].y + for i,pt in enumerate(pointList): + self.__sites.append(Site(pt.x,pt.y,i)) + if pt.x < self.__xmin: self.__xmin = pt.x + if pt.y < self.__ymin: self.__ymin = pt.y + if pt.x > self.__xmax: self.__xmax = pt.x + if pt.y > self.__ymax: self.__ymax = pt.y + self.__sites.sort() + + def setSiteNumber(self,site): + site.sitenum = self.__sitenum + self.__sitenum += 1 + + class Iterator(object): + def __init__(this,lst): this.generator = (s for s in lst) + def __iter__(this): return this + def next(this): + try: + return this.generator.next() + except StopIteration: + return None + + def iterator(self): + return SiteList.Iterator(self.__sites) + + def __iter__(self): + return SiteList.Iterator(self.__sites) + + def __len__(self): + return len(self.__sites) + + def _getxmin(self): return self.__xmin + def _getymin(self): return self.__ymin + def _getxmax(self): return self.__xmax + def _getymax(self): return self.__ymax + xmin = property(_getxmin) + ymin = property(_getymin) + xmax = property(_getxmax) + ymax = property(_getymax) + + +#------------------------------------------------------------------ +def computeVoronoiDiagram(points): + """ Takes a list of point objects (which must have x and y fields). + Returns a 3-tuple of: + + (1) a list of 2-tuples, which are the x,y coordinates of the + Voronoi diagram vertices + (2) a list of 3-tuples (a,b,c) which are the equations of the + lines in the Voronoi diagram: a*x + b*y = c + (3) a list of 3-tuples, (l, v1, v2) representing edges of the + Voronoi diagram. l is the index of the line, v1 and v2 are + the indices of the vetices at the end of the edge. If + v1 or v2 is -1, the line extends to infinity. + """ + siteList = SiteList(points) + context = Context() + voronoi(siteList,context) + return (context.vertices,context.lines,context.edges) + +#------------------------------------------------------------------ +def computeDelaunayTriangulation(points): + """ Takes a list of point objects (which must have x and y fields). + Returns a list of 3-tuples: the indices of the points that form a + Delaunay triangle. + """ + siteList = SiteList(points) + context = Context() + context.triangulate = true + voronoi(siteList,context) + return context.triangles + +#----------------------------------------------------------------------------- +if __name__=="__main__": + try: + optlist,args = getopt.getopt(sys.argv[1:],"thdp") + except getopt.GetoptError: + usage() + sys.exit(2) + + doHelp = 0 + c = Context() + c.doPrint = 1 + for opt in optlist: + if opt[0] == "-d": c.debug = 1 + if opt[0] == "-p": c.plot = 1 + if opt[0] == "-t": c.triangulate = 1 + if opt[0] == "-h": doHelp = 1 + + if not doHelp: + pts = [] + fp = sys.stdin + if len(args) > 0: + fp = open(args[0],'r') + for line in fp: + fld = line.split() + x = float(fld[0]) + y = float(fld[1]) + pts.append(Site(x,y)) + if len(args) > 0: fp.close() + + if doHelp or len(pts) == 0: + usage() + sys.exit(2) + + sl = SiteList(pts) + voronoi(sl,c) + diff --git a/share/extensions/webslicer-create-group.inx b/share/extensions/webslicer-create-group.inx new file mode 100644 index 000000000..b5c5b48ed --- /dev/null +++ b/share/extensions/webslicer-create-group.inx @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension"> + <_name>Set a layout group</_name> + <id>org.inkscape.web.slicer.create-group</id> + <dependency type="executable" location="extensions">webslicer-create-group.py</dependency> + <dependency type="executable" location="extensions">inkex.py</dependency> + <_param name="about" type="description">Layout Group is only about to help a better code generation (if you need it). To use this, first you must to select some "Slicer rectangles".</_param> + <param name="html-id" type="string" _gui-text="HTML id atribute"></param> + <param name="html-class" type="string" _gui-text="HTML class atribute"></param> + <param name="width-unity" type="enum" _gui-text="Width Unity"> + <_item value="px">Pixel (fixed)</_item> + <_item value="percent">Percent (relative to parent size)</_item> + <_item value="undefined">Undefined (relative to non-floating content size)</_item> + </param> + <param name="height-unity" type="enum" _gui-text="Height Unity"> + <_item value="px">Pixel (fixed)</_item> + <_item value="percent">Percent (relative to parent size)</_item> + <_item value="undefined">Undefined (relative to non-floating content size)</_item> + </param> + <effect needs-live-preview="false"> + <object-type>all</object-type> + <effects-menu> + <submenu _name="Web"> + <submenu name="Slicer"/> + </submenu> + </effects-menu> + </effect> + <script> + <command reldir="extensions" interpreter="python">webslicer-create-group.py</command> + </script> +</inkscape-extension> diff --git a/share/extensions/webslicer-create-group.py b/share/extensions/webslicer-create-group.py new file mode 100755 index 000000000..aadfded38 --- /dev/null +++ b/share/extensions/webslicer-create-group.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python +''' +Copyright (C) 2010 Aurelio A. Heckert, aurium (a) gmail dot com + +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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +''' + +import inkex +import gettext + +_ = gettext.gettext + +def is_empty(val): + if val is None: + return True + else: + return len(str(val)) == 0 + +class WebSlicer_CreateGroup(inkex.Effect): + + def __init__(self): + inkex.Effect.__init__(self) + self.OptionParser.add_option("--html-id", + action="store", type="string", + dest="html_id", + help="") + self.OptionParser.add_option("--html-class", + action="store", type="string", + dest="html_class", + help="") + self.OptionParser.add_option("--width-unity", + action="store", type="string", + dest="width_unity", + help="") + self.OptionParser.add_option("--height-unity", + action="store", type="string", + dest="height_unity", + help="") + + + def get_base_elements(self): + layerArr = self.document.xpath( + '//*[@id="webslicer-layer" and @inkscape:groupmode="layer"]', + namespaces=inkex.NSS) + if len(layerArr) > 0: + self.layer = layerArr[0] + else: + inkex.errormsg(_('You must to create and select some "Slicer rectangles" before try to group.')) + exit(3) + self.layer_descendants = self.get_descendants_in_array(self.layer) + + + def get_descendants_in_array(self, el): + descendants = el.getchildren() + for e in descendants: + descendants.extend( self.get_descendants_in_array(e) ) + return descendants + + + def effect(self): + self.get_base_elements() + if len(self.selected) == 0: + inkex.errormsg(_('You must to select some "Slicer rectangles" or other "Layout groups".')) + exit(1) + for id,node in self.selected.iteritems(): + if node not in self.layer_descendants: + inkex.errormsg(_('Opss... The element "%s" is not in the Web Slicer layer') % id) + exit(2) + g_parent = self.getParentNode(node) + group = inkex.etree.SubElement(g_parent, 'g') + desc = inkex.etree.SubElement(group, 'desc') + conf_txt = '' + if not is_empty(self.options.html_id): + conf_txt += 'html-id:' + self.options.html_id +'\n' + if not is_empty(self.options.html_class): + conf_txt += 'html-class:' + self.options.html_class +'\n' + conf_txt += 'width-unity:' + self.options.width_unity +'\n' + conf_txt += 'height-unity:' + self.options.height_unity + desc.text = conf_txt + for id,node in self.selected.iteritems(): + group.insert( 1, node ) + + +if __name__ == '__main__': + e = WebSlicer_CreateGroup() + e.affect() diff --git a/share/extensions/webslicer-create-rect.inx b/share/extensions/webslicer-create-rect.inx new file mode 100644 index 000000000..7ac681e69 --- /dev/null +++ b/share/extensions/webslicer-create-rect.inx @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="UTF-8"?> +<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension"> + <_name>Create a slicer rectangle</_name> + <id>org.inkscape.web.slicer.create-rect</id> + <dependency type="executable" location="extensions">webslicer-create-rect.py</dependency> + <dependency type="executable" location="extensions">inkex.py</dependency> + <param name="name" type="string" _gui-text="Name"></param> + <param name="format" type="enum" _gui-text="Format"> + <item value="png">PNG</item> + <item value="jpg">JPG</item> + <item value="gif">GIF</item> + </param> + <param name="dpi" type="float" min="1" max="9999" _gui-text="DPI">90</param> + <param name="dimension" type="string" _gui-text="Force Dimension"></param> + <_param name="help-dimension1" type="description">Force Dimension must be set as "<width>x<height>"</_param> + <_param name="help-dimension2" type="description">If had set, this will replace DPI.</_param> + <param name="bg-color" type="string" _gui-text="Background color"></param> + <param name="tab" type="notebook"> + <page name="tabJPG" gui-text="JPG"> + <_param name="help-jpg" type="description">JPG specific options</_param> + <param name="quality" type="int" min="0" max="100" _gui-text="Quality">85</param> + <_param name="help-quality" type="description">0 is the lowest image quality and highest compression, and 100 is the best quality but least effective compression</_param> + </page> + <page name="tabGIF" gui-text="GIF"> + <_param name="help-gif" type="description">GIF specific options</_param> + <param name="gif-type" type="enum" _gui-text="Type"> + <_item value="grayscale">Grayscale</_item> + <_item value="palette">Palette</_item> + </param> + <param name="palette-size" type="int" min="2" max="256" _gui-text="Palette size">256</param> + </page> + <page name="tabHTML" gui-text="HTML"> + <param name="html-id" type="string" _gui-text="HTML id atribute"></param> + <param name="html-class" type="string" _gui-text="HTML class atribute"></param> + <_param name="help-gif" type="description">Options for HTML export</_param> + <param name="layout-disposition" type="enum" _gui-text="Layout disposition"> + <_item value="bg-parent-repeat">Tiled Background (on parent group)</_item> + <_item value="bg-parent-repeat-x">Background — repeat horizontally (on parent group)</_item> + <_item value="bg-parent-repeat-y">Background — repeat vertically (on parent group)</_item> + <_item value="bg-parent-norepeat">Background — no repeat (on parent group)</_item> + <_item value="bg-div-norepeat">Positioned <div> width the image as Background</_item> + <_item value="img-pos">Positioned Image</_item> + <_item value="img-nonpos">Non Positioned Image</_item> + <_item value="img-float-left">Left Floated Image</_item> + <_item value="img-float-right">Right Floated Image</_item> + </param> + <param name="layout-position-anchor" type="enum" _gui-text="Position anchor"> + <_item value="tl">Top and Left</_item> + <_item value="tr">Top and right</_item> + <_item value="bl">Bottom and Left</_item> + <_item value="br">Bottom and Right</_item> + </param> + </page> + </param> + <effect needs-live-preview="false"> + <object-type>all</object-type> + <effects-menu> + <submenu _name="Web"> + <submenu name="Slicer"/> + </submenu> + </effects-menu> + </effect> + <script> + <command reldir="extensions" interpreter="python">webslicer-create-rect.py</command> + </script> +</inkscape-extension> diff --git a/share/extensions/webslicer-create-rect.py b/share/extensions/webslicer-create-rect.py new file mode 100755 index 000000000..957d6a8b2 --- /dev/null +++ b/share/extensions/webslicer-create-rect.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python +''' +Copyright (C) 2010 Aurelio A. Heckert, aurium (a) gmail dot com + +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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +''' + +import inkex +import gettext + +_ = gettext.gettext + +def is_empty(val): + if val is None: + return True + else: + return len(str(val)) == 0 + +class WebSlicer_CreateRect(inkex.Effect): + + def __init__(self): + inkex.Effect.__init__(self) + self.OptionParser.add_option("--name", + action="store", type="string", + dest="name", + help="") + self.OptionParser.add_option("--format", + action="store", type="string", + dest="format", + help="") + self.OptionParser.add_option("--dpi", + action="store", type="int", + dest="dpi", + help="") + self.OptionParser.add_option("--dimension", + action="store", type="string", + dest="dimension", + help="") + self.OptionParser.add_option("--bg-color", + action="store", type="string", + dest="bg_color", + help="") + self.OptionParser.add_option("--quality", + action="store", type="int", + dest="quality", + help="") + self.OptionParser.add_option("--gif-type", + action="store", type="string", + dest="gif_type", + help="") + self.OptionParser.add_option("--palette-size", + action="store", type="int", + dest="palette_size", + help="") + self.OptionParser.add_option("--html-id", + action="store", type="string", + dest="html_id", + help="") + self.OptionParser.add_option("--html-class", + action="store", type="string", + dest="html_class", + help="") + self.OptionParser.add_option("--layout-disposition", + action="store", type="string", + dest="layout_disposition", + help="") + self.OptionParser.add_option("--layout-position-anchor", + action="store", type="string", + dest="layout_position_anchor", + help="") + # inkscape param workarround + self.OptionParser.add_option("--tab") + + + def unique_slice_name(self): + name = self.options.name + el = self.document.xpath( '//*[@id="'+name+'"]', namespaces=inkex.NSS ) + if len(el) > 0: + if name[-3:] == '-00': name = name[:-3] + num = 0 + num_s = '00' + while len(el) > 0: + num += 1 + num_s = str(num) + if len(num_s)==1 : num_s = '0'+num_s + el = self.document.xpath( '//*[@id="'+name+'-'+num_s+'"]', + namespaces=inkex.NSS ) + self.options.name = name+'-'+num_s + + + def validate_options(self): + self.options.format = self.options.ensure_value('format', 'png').lower() + if not is_empty( self.options.dimension ): + self.options.dimension + + def effect(self): + self.validate_options() + layer = self.get_slicer_layer() + #TODO: get selected elements to define location and size + rect = inkex.etree.SubElement(layer, 'rect') + if is_empty(self.options.name): + self.options.name = 'slice-00' + self.unique_slice_name() + rect.set('id', self.options.name) + rect.set('fill', 'red') + rect.set('opacity', '0.5') + rect.set('x', '-100') + rect.set('y', '-100') + rect.set('width', '200') + rect.set('height', '200') + desc = inkex.etree.SubElement(rect, 'desc') + conf_txt = "format:"+ self.options.format +"\n" + if not is_empty(self.options.dpi): + conf_txt += "dpi:" + str(self.options.dpi) +"\n" + if not is_empty(self.options.html_id): + conf_txt += "html-id:" + self.options.html_id + desc.text = "\n".join( self.get_full_conf_list() ) + + + + def get_conf_from_list(self, conf_atts): + conf_list = [] + for att in conf_atts: + if not is_empty(getattr(self.options, att)): + conf_list.append( att +':'+ str(getattr(self.options, att)) ) + return conf_list + + + def get_full_conf_list(self): + conf_list = [ 'format:'+self.options.format ] + if self.options.format == 'gif': + conf_list.extend( get_conf_from_list([ 'gif_type', 'palette_size' ]) ) + if self.options.format == 'jpg': + conf_list.extend( get_conf_from_list([ 'quality' ]) ) + conf_general_atts = [ + 'dpi', 'dimension', + 'bg_color', 'html_id', 'html_class', + 'layout_disposition', 'layout_position_anchor' + ] + conf_list.extend( get_conf_from_list(conf_general_atts) ) + return conf_list + + + def get_slicer_layer(self): + # Test if webslicer-layer layer existis + layer = self.document.xpath( + '//*[@id="webslicer-layer" and @inkscape:groupmode="layer"]', + namespaces=inkex.NSS) + if len(layer) is 0: + # Create a new layer + layer = inkex.etree.SubElement(self.document.getroot(), 'g') + layer.set('id', 'webslicer-layer') + layer.set(inkex.addNS('label', 'inkscape'), 'Web Slicer') + layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer') + else: + layer = layer[0] + return layer + + +if __name__ == '__main__': + e = WebSlicer_CreateRect() + e.affect() diff --git a/share/extensions/webslicer-export.inx b/share/extensions/webslicer-export.inx new file mode 100644 index 000000000..9f7aac323 --- /dev/null +++ b/share/extensions/webslicer-export.inx @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension"> + <_name>Export layout pieces and HTML+CSS code</_name> + <id>org.inkscape.web.slicer.export</id> + <dependency type="executable" location="extensions">webslicer-export.py</dependency> + <dependency type="executable" location="extensions">inkex.py</dependency> + <_param name="about" type="description">All sliced images, and optionaly code, will be generated as you had configured and saved to one directory.</_param> + <param name="dir" type="string" _gui-text="Directory path to export"></param> + <param name="with-code" type="boolean" _gui-text="With HTML and CSS">true</param> + <effect needs-live-preview="false"> + <object-type>all</object-type> + <effects-menu> + <submenu _name="Web"> + <submenu name="Slicer"/> + </submenu> + </effects-menu> + </effect> + <script> + <command reldir="extensions" interpreter="python">webslicer-export.py</command> + </script> +</inkscape-extension> diff --git a/share/extensions/webslicer-export.py b/share/extensions/webslicer-export.py new file mode 100755 index 000000000..c8e4cbb0d --- /dev/null +++ b/share/extensions/webslicer-export.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python +''' +Copyright (C) 2010 Aurelio A. Heckert, aurium (a) gmail dot com + +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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +''' + +import inkex +import gettext +import os.path +import commands + +_ = gettext.gettext + +def is_empty(val): + if val is None: + return True + else: + return len(str(val)) == 0 + +class WebSlicer_Export(inkex.Effect): + + def __init__(self): + inkex.Effect.__init__(self) + self.OptionParser.add_option("--with-code", + action="store", type="string", + dest="with_code", + help="") + self.OptionParser.add_option("--dir", + action="store", type="string", + dest="dir", + help="") + + def effect(self): + if is_empty( self.options.dir ): + inkex.errormsg(_('You must to give a directory to export the slices.')) + return + if not os.path.exists( self.options.dir ): + inkex.errormsg(_('The directory "%s" does not exists.') % self.options.dir) + return + (status, output) = commands.getstatusoutput("inkscape -e ...") + + +if __name__ == '__main__': + e = WebSlicer_Export() + e.affect() |
