summaryrefslogtreecommitdiffstats
path: root/share/extensions
diff options
context:
space:
mode:
authorTed Gould <ted@gould.cx>2010-03-26 04:34:25 +0000
committerTed Gould <ted@gould.cx>2010-03-26 04:34:25 +0000
commit9e023a3aa964a0d3fa1e31e46d33657367ba68aa (patch)
tree33f1392a340737e4eeefca6fd031f96c29befd2b /share/extensions
parentInstalling the pkgconfig file (diff)
parentAdding in shape-record.h (diff)
downloadinkscape-9e023a3aa964a0d3fa1e31e46d33657367ba68aa.tar.gz
inkscape-9e023a3aa964a0d3fa1e31e46d33657367ba68aa.zip
Merge from trunk
(bzr r8254.1.53)
Diffstat (limited to 'share/extensions')
-rw-r--r--share/extensions/Barcode/Code128.py5
-rw-r--r--share/extensions/Makefile.am13
-rw-r--r--share/extensions/ai_input.inx8
-rw-r--r--share/extensions/color_blackandwhite.inx17
-rw-r--r--share/extensions/color_blackandwhite.py17
-rw-r--r--share/extensions/dxf_outlines.inx8
-rwxr-xr-xshare/extensions/dxf_outlines.py14
-rw-r--r--share/extensions/generate_voronoi.inx21
-rw-r--r--share/extensions/generate_voronoi.py187
-rwxr-xr-xshare/extensions/inkex.py7
-rw-r--r--share/extensions/pixelsnap.inx17
-rw-r--r--share/extensions/pixelsnap.py509
-rw-r--r--share/extensions/printing-marks.inx3
-rw-r--r--share/extensions/printing-marks.py198
-rw-r--r--share/extensions/run_command.py14
-rwxr-xr-xshare/extensions/scour.inkscape.py5
-rw-r--r--share/extensions/scour.inx2
-rwxr-xr-xshare/extensions/scour.py106
-rw-r--r--share/extensions/svg2xaml.xsl4
-rw-r--r--share/extensions/svg_and_media_zip_output.py25
-rw-r--r--share/extensions/voronoi.py789
-rw-r--r--share/extensions/webslicer-create-group.inx31
-rwxr-xr-xshare/extensions/webslicer-create-group.py98
-rw-r--r--share/extensions/webslicer-create-rect.inx66
-rwxr-xr-xshare/extensions/webslicer-create-rect.py174
-rw-r--r--share/extensions/webslicer-export.inx21
-rwxr-xr-xshare/extensions/webslicer-export.py58
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 &amp; ( must do this first so that &lt; does not become &amp;lt; )
- if str.find('&') != -1:
- newstr = str.replace('&', '&amp;')
+ xml_ents = { '<':'&lt;', '>':'&gt;', '&':'&amp;', "'":'&apos;', '"':'&quot;'}
+
+# starr = []
+# for c in str:
+# if c in xml_ents:
+# starr.append(xml_ents[c])
+# else:
+# starr.append(c)
- # encode < as &lt;
- if str.find("<") != -1:
- newstr = str.replace('<', '&lt;')
-
- # encode > as &gt; (TODO: is this necessary?)
- if str.find('>') != -1:
- newstr = str.replace('>', '&gt;')
-
- 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 "&lt;width&gt;x&lt;height&gt;"</_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 &lt;div&gt; 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()