summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRichard White <rwhite8282@gmail.com>2016-02-27 01:18:49 +0000
committerRichard White <rwhite8282@gmail.com>2016-02-27 01:18:49 +0000
commit5cf5ebf8ef5cef77e0b491f52ac37e317b06279d (patch)
tree38b0b7445a4b16ef7b4ce4decb8b2adce40195ac
parentinkview: Drop use of obsolete getopt (diff)
downloadinkscape-5cf5ebf8ef5cef77e0b491f52ac37e317b06279d.tar.gz
inkscape-5cf5ebf8ef5cef77e0b491f52ac37e317b06279d.zip
Added frame extension.
(bzr r14668.1.1)
-rw-r--r--share/extensions/frame.inx32
-rw-r--r--share/extensions/frame.py175
-rwxr-xr-xshare/extensions/test/frame_test.py109
-rw-r--r--share/extensions/test/svg/single_box.svg62
4 files changed, 378 insertions, 0 deletions
diff --git a/share/extensions/frame.inx b/share/extensions/frame.inx
new file mode 100644
index 000000000..a2b011430
--- /dev/null
+++ b/share/extensions/frame.inx
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
+ <_name>Frame</_name>
+ <id>frame</id>
+ <dependency type="executable" location="extensions">frame.py</dependency>
+ <dependency type="executable" location="extensions">inkex.py</dependency>
+ <param name="tab" type="notebook">
+ <page name="stroke" gui-text="Stroke">
+ <param name="stroke_color" type="color" gui-text="Stroke Color:">000000FF</param>
+ </page>
+ <page name="fill" gui-text="Fill">
+ <param name="fill_color" type="color" gui-text="Fill Color:">00000000</param>
+ </page>
+ </param>
+ <param name="position" type="optiongroup" appearance="minimal" gui-text="Position">
+ <option value="outside">Outside</option>
+ <option value="inside">Inside</option>
+ </param>
+ <param name="clip" type="boolean" gui-text="Clip"></param>
+ <param name="group" type="boolean" gui-text="Group"></param>
+ <param name="width" type="float" min="0" max="100" gui-text="Width(px)">2</param>
+ <param name="corner_radius" type="int" min="0" max="1000" gui-text="Corner Radius">0</param>
+ <effect>
+ <object-type>all</object-type>
+ <effects-menu>
+ <submenu _name="Render"/>
+ </effects-menu>
+ </effect>
+ <script>
+ <command reldir="extensions" interpreter="python">frame.py</command>
+ </script>
+</inkscape-extension> \ No newline at end of file
diff --git a/share/extensions/frame.py b/share/extensions/frame.py
new file mode 100644
index 000000000..c63be52e5
--- /dev/null
+++ b/share/extensions/frame.py
@@ -0,0 +1,175 @@
+#!/usr/bin/env python
+"""
+An Inkscape extension that creates a frame around a selected object.
+
+Copyright (C) 2016 Richard White, rwhite8282@gmail.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
+"""
+
+# These two lines are only needed if you don't put the script directly into
+# the installation directory
+import sys
+sys.path.append('/usr/share/inkscape/extensions')
+
+import inkex
+import simplestyle
+from simpletransform import *
+from simplestyle import *
+
+
+def get_picker_data(value):
+ """ Returns color data in style string format.
+ value -- The value returned from the color picker.
+ Returns an object with color and opacity properties.
+ """
+ v = hex(value & 0xFFFFFFFF)[2:-1].rjust(8, '0').upper()
+ color = '#' + v[0:-2].rjust(6, '0')
+ opacity = '%1.2f' % (float(int(v[6:].rjust(2, '0'), 16))/255)
+ return type('', (object,), {'color':color, 'opacity':opacity})()
+
+
+def size_box(box, delta):
+ """ Returns a box with an altered size.
+ delta -- The amount the box should grow.
+ Returns a box with an altered size.
+ """
+ return ((box[0]-delta), (box[1]+delta), (box[2]-delta), (box[3]+delta))
+
+
+# Frame maker Inkscape effect extension
+class Frame(inkex.Effect):
+ """ An Inkscape extension that creates a frame around a selected object.
+ """
+ def __init__(self):
+ inkex.Effect.__init__(self)
+ self.defs = None
+
+ # Parse the options.
+ self.OptionParser.add_option('--clip',
+ action='store', type='inkbool',
+ dest='clip', default=False)
+ self.OptionParser.add_option('--corner_radius',
+ action='store', type='int',
+ dest='corner_radius', default=0)
+ self.OptionParser.add_option('--fill_color',
+ action='store', type='int',
+ dest='fill_color', default='00000000')
+ self.OptionParser.add_option('--group',
+ action='store', type='inkbool',
+ dest='group', default=False)
+ self.OptionParser.add_option('--position',
+ action='store', type='string',
+ dest='position', default='outside')
+ self.OptionParser.add_option('--stroke_color',
+ action='store', type='int',
+ dest='stroke_color', default='00000000')
+ self.OptionParser.add_option('--tab',
+ action='store', type='string',
+ dest='tab', default='object')
+ self.OptionParser.add_option('--width',
+ action='store', type='float',
+ dest='width', default=2)
+
+
+ def add_clip(self, node, clip_path):
+ """ Adds a new clip path node to the defs and sets
+ the clip-path on the node.
+ node -- The node that will be clipped.
+ clip_path -- The clip path object.
+ """
+ if self.defs is None:
+ defs_nodes = self.document.getroot().xpath('//svg:defs', namespaces=inkex.NSS)
+ if defs_nodes:
+ self.defs = defs_nodes[0]
+ else:
+ inkex.errormsg('Could not locate defs node for clip.')
+ return
+ clip = inkex.etree.SubElement(self.defs, inkex.addNS('clipPath','svg'))
+ clip.append(copy.deepcopy(clip_path))
+ clip_id = self.uniqueId('clipPath')
+ clip.set('id', clip_id)
+ node.set('clip-path', 'url(#%s)' % str(clip_id))
+
+
+ def add_frame(self, parent, name, box, style, radius=0):
+ """ Adds a new frame to the parent object.
+ parent -- The parent that the frame will be added to.
+ name -- The name of the new frame object.
+ box -- The boundary box of the node.
+ style -- The style used to draw the path.
+ radius -- The corner radius of the frame.
+ returns a new frame node.
+ """
+ r = min([radius, (abs(box[1]-box[0])/2), (abs(box[3]-box[2])/2)])
+ if (radius > 0):
+ d = ' '.join(str(x) for x in
+ ['M', box[0], (box[2]+r)
+ ,'A', r, r, '0 0 1', (box[0]+r), box[2]
+ ,'L', (box[1]-r), box[2]
+ ,'A', r, r, '0 0 1', box[1], (box[2]+r)
+ ,'L', box[1], (box[3]-r)
+ ,'A', r, r, '0 0 1', (box[1]-r), box[3]
+ ,'L', (box[0]+r), box[3]
+ ,'A', r, r, '0 0 1', box[0], (box[3]-r), 'Z'])
+ else:
+ d = ' '.join(str(x) for x in
+ ['M', box[0], box[2]
+ ,'L', box[1], box[2]
+ ,'L', box[1], box[3]
+ ,'L', box[0], box[3], 'Z'])
+
+ attributes = {'style':style, inkex.addNS('label','inkscape'):name, 'd':d}
+ return inkex.etree.SubElement(parent, inkex.addNS('path','svg'), attributes )
+
+
+ def effect(self):
+ """ Performs the effect.
+ """
+ # Get the style values.
+ corner_radius = self.options.corner_radius
+ stroke_data = get_picker_data(self.options.stroke_color)
+ fill_data = get_picker_data(self.options.fill_color)
+
+ # Determine common properties.
+ parent = self.current_layer
+ position = self.options.position
+ width = self.options.width
+ style = simplestyle.formatStyle({'stroke':stroke_data.color
+ , 'stroke-opacity':stroke_data.opacity
+ , 'stroke-width':str(width)
+ , 'fill':(fill_data.color if (fill_data.opacity > 0) else 'none')
+ , 'fill-opacity':fill_data.opacity})
+
+ for id, node in self.selected.iteritems():
+ box = computeBBox([node])
+ if 'outside' == position:
+ box = size_box(box, (3.5 + (width/2)))
+ else:
+ box = size_box(box, (3.5 - (width/2)))
+ name = 'Frame'
+ frame = self.add_frame(parent, name, box, style, corner_radius)
+ if self.options.clip:
+ self.add_clip(node, frame)
+ if self.options.group:
+ group = inkex.etree.SubElement(node.getparent(),inkex.addNS('g','svg'))
+ group.append(node)
+ group.append(frame)
+
+
+if __name__ == '__main__': #pragma: no cover
+ # Create effect instance and apply it.
+ effect = Frame()
+ effect.affect() \ No newline at end of file
diff --git a/share/extensions/test/frame_test.py b/share/extensions/test/frame_test.py
new file mode 100755
index 000000000..72c5b66e8
--- /dev/null
+++ b/share/extensions/test/frame_test.py
@@ -0,0 +1,109 @@
+#!/usr/bin/env python
+
+"""
+An Inkscape frame extension test class.
+
+Copyright (C) 2016 Richard White, rwhite8282@gmail.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 sys
+sys.path.append('/usr/share/inkscape/extensions')
+sys.path.append('..') # this line allows to import the extension code
+
+import unittest
+import inkex
+from frame import *
+
+
+class Frame_Test(unittest.TestCase):
+
+
+ def get_frame(self, document):
+ return document.xpath('//svg:g[@id="layer1"]//svg:path[@inkscape:label="Frame"]'
+ , namespaces=inkex.NSS)[0]
+
+
+ def test_empty_no_parameters(self):
+ args = [ 'svg/empty-SVG.svg' ]
+ uut = Frame()
+ uut.affect( args, False )
+
+
+ def test_single_frame(self):
+ args = [
+ '--corner_radius=20'
+ , '--fill_color=-16777124'
+ , '--id=rect3006'
+ , '--position=inside'
+ , '--stroke_color=255'
+ , '--tab="stroke"'
+ , '--width=10'
+ , 'svg/single_box.svg']
+ uut = Frame()
+ uut.affect( args, False )
+ new_frame = self.get_frame(uut.document)
+ self.assertIsNotNone(new_frame)
+ self.assertEqual('{http://www.w3.org/2000/svg}path', new_frame.tag)
+
+
+ def test_single_frame_grouped(self):
+ args = [
+ '--corner_radius=20'
+ , '--fill_color=-16777124'
+ , '--group=True'
+ , '--id=rect3006'
+ , '--position=inside'
+ , '--stroke_color=255'
+ , '--tab="stroke"'
+ , '--width=10'
+ , 'svg/single_box.svg']
+ uut = Frame()
+ uut.affect( args, False )
+ new_frame = self.get_frame(uut.document)
+ self.assertIsNotNone(new_frame)
+ self.assertEqual('{http://www.w3.org/2000/svg}path', new_frame.tag)
+ group = new_frame.getparent()
+ self.assertEqual('{http://www.w3.org/2000/svg}g', group.tag)
+ self.assertEqual('{http://www.w3.org/2000/svg}rect', group[0].tag)
+ self.assertEqual('{http://www.w3.org/2000/svg}path', group[1].tag)
+ self.assertEqual("Frame", group[1].xpath('@inkscape:label', namespaces=inkex.NSS)[0])
+
+
+ def test_single_frame_clipped(self):
+ args = [
+ '--clip=True'
+ , '--corner_radius=20'
+ , '--fill_color=-16777124'
+ , '--id=rect3006'
+ , '--position=inside'
+ , '--stroke_color=255'
+ , '--tab="stroke"'
+ , '--width=10'
+ , 'svg/single_box.svg']
+ uut = Frame()
+ uut.affect( args, False )
+ new_frame = self.get_frame(uut.document)
+ self.assertIsNotNone(new_frame)
+ self.assertEqual('{http://www.w3.org/2000/svg}path', new_frame.tag)
+ group = new_frame.getparent()
+ self.assertEqual('url(#clipPath)', group[0].get('clip-path'))
+ clip_path = uut.document.xpath('//svg:defs/svg:clipPath', namespaces=inkex.NSS)[0]
+ self.assertEqual('{http://www.w3.org/2000/svg}clipPath', clip_path.tag)
+
+
+if __name__ == '__main__':
+ unittest.main() \ No newline at end of file
diff --git a/share/extensions/test/svg/single_box.svg b/share/extensions/test/svg/single_box.svg
new file mode 100644
index 000000000..094233d15
--- /dev/null
+++ b/share/extensions/test/svg/single_box.svg
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="744.09448819"
+ height="1052.3622047"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.3.1 r9886"
+ sodipodi:docname="New document 1">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.35"
+ inkscape:cx="375"
+ inkscape:cy="514.28571"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:window-width="479"
+ inkscape:window-height="379"
+ inkscape:window-x="1319"
+ inkscape:window-y="75"
+ inkscape:window-maximized="0" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <rect
+ style="opacity:0.5;fill:#6900ff;fill-opacity:0.36000001;stroke:#cae8ef;stroke-width:7.19999981;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="rect3006"
+ width="237.14285"
+ height="305.71429"
+ x="285.71429"
+ y="406.64789" />
+ </g>
+</svg>