diff options
| author | Richard White <rwhite8282@gmail.com> | 2016-02-27 01:18:49 +0000 |
|---|---|---|
| committer | Richard White <rwhite8282@gmail.com> | 2016-02-27 01:18:49 +0000 |
| commit | 5cf5ebf8ef5cef77e0b491f52ac37e317b06279d (patch) | |
| tree | 38b0b7445a4b16ef7b4ce4decb8b2adce40195ac | |
| parent | inkview: Drop use of obsolete getopt (diff) | |
| download | inkscape-5cf5ebf8ef5cef77e0b491f52ac37e317b06279d.tar.gz inkscape-5cf5ebf8ef5cef77e0b491f52ac37e317b06279d.zip | |
Added frame extension.
(bzr r14668.1.1)
| -rw-r--r-- | share/extensions/frame.inx | 32 | ||||
| -rw-r--r-- | share/extensions/frame.py | 175 | ||||
| -rwxr-xr-x | share/extensions/test/frame_test.py | 109 | ||||
| -rw-r--r-- | share/extensions/test/svg/single_box.svg | 62 |
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> |
