summaryrefslogtreecommitdiffstats
path: root/share/extensions/frame.py
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:24:48 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:24:48 +0000
commitcca66b9ec4e494c1d919bff0f71a820d8afab1fa (patch)
tree146f39ded1c938019e1ed42d30923c2ac9e86789 /share/extensions/frame.py
parentInitial commit. (diff)
downloadinkscape-cca66b9ec4e494c1d919bff0f71a820d8afab1fa.tar.xz
inkscape-cca66b9ec4e494c1d919bff0f71a820d8afab1fa.zip
Adding upstream version 1.2.2.upstream/1.2.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'share/extensions/frame.py')
-rwxr-xr-xshare/extensions/frame.py183
1 files changed, 183 insertions, 0 deletions
diff --git a/share/extensions/frame.py b/share/extensions/frame.py
new file mode 100755
index 0000000..39ad789
--- /dev/null
+++ b/share/extensions/frame.py
@@ -0,0 +1,183 @@
+#!/usr/bin/env python
+# coding=utf-8
+#
+# 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
+#
+"""
+An Inkscape extension that creates a frame around a selected object.
+"""
+
+from typing import List
+
+import inkex
+from inkex import Group, PathElement, ClipPath
+from inkex.localization import inkex_gettext as _
+
+
+class Frame(inkex.EffectExtension):
+ """
+ An Inkscape extension that creates a frame around a selected object.
+ """
+
+ def add_arguments(self, pars):
+ # Parse the options.
+ pars.add_argument("--tab", default="stroke")
+ pars.add_argument("--clip", type=inkex.Boolean, default=False)
+ pars.add_argument("--type", default="rect", choices=["rect", "ellipse"])
+ pars.add_argument("--corner_radius", type=int, default=0)
+ pars.add_argument("--fill_color", type=inkex.Color, default=inkex.Color(0))
+ pars.add_argument("--group", type=inkex.Boolean, default=False)
+ pars.add_argument("--stroke_color", type=inkex.Color, default=inkex.Color(0))
+ pars.add_argument("--width", type=float, default=2.0)
+ pars.add_argument(
+ "--offset_absolute",
+ type=float,
+ default=0,
+ help="Offset in user units, positive = outside",
+ )
+ pars.add_argument(
+ "--offset_relative",
+ type=float,
+ default=0,
+ help="Relative offset in percentage of the bounding box size",
+ )
+ pars.add_argument(
+ "--z_position",
+ type=str,
+ default="bottom",
+ choices=["top", "bottom", "split"],
+ )
+ pars.add_argument("--asgroup", type=inkex.Boolean, default=False)
+
+ def add_clip(self, node: inkex.BaseElement, clip_path: inkex.PathElement):
+ """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.
+ """
+ clip = ClipPath()
+ # apply the reverse transform to the clip path. no composed_transform here,
+ # since the frame will be appended to the object' parent (or an untransformed
+ # group within).
+ clip.append(PathElement.new(path=clip_path.path, transform=-node.transform))
+ self.svg.defs.add(clip)
+ node.set("clip-path", clip.get_id(as_url=2))
+
+ @staticmethod
+ def generate_frame(name, box: inkex.BoundingBox, rectangle=True, radius=0):
+ """
+ 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.
+ """
+ if rectangle:
+ r = min([radius, abs(box.x.size / 2), abs(box.y.size / 2)])
+ elem = inkex.Rectangle.new(
+ left=box.x.minimum,
+ top=box.y.minimum,
+ width=box.x.size,
+ height=box.y.size,
+ rx=r,
+ )
+ else:
+ elem = inkex.Ellipse.new(center=box.center, radius=box.size / 2)
+ elem.label = name
+ return elem
+
+ def create_frame(self, containedelements: List[inkex.BaseElement]):
+ """generate the frame for an element or a group of elements"""
+ width = self.options.width
+ style = inkex.Style({"stroke-width": width})
+ style.set_color(self.options.stroke_color, "stroke")
+ elem_top = None
+ elem_bottom = None
+
+ box = inkex.BoundingBox()
+ for node in containedelements:
+ if isinstance(node, (inkex.TextElement, inkex.Tspan, inkex.FlowRoot)):
+ try:
+ box += node.get_inkscape_bbox()
+ except ValueError:
+ continue
+ else:
+ box += node.bounding_box()
+ box = box.resize(
+ box.x.size * (self.options.offset_relative / 100),
+ box.y.size * (self.options.offset_relative / 100),
+ )
+ box = box.resize(self.options.offset_absolute)
+
+ frame = self.generate_frame(
+ "Frame", box, self.options.type == "rect", self.options.corner_radius
+ )
+ if self.options.z_position != "split":
+ style.set_color(self.options.fill_color, "fill")
+ frame.style = style
+ if self.options.z_position == "bottom":
+ elem_bottom = frame
+ else:
+ elem_top = frame
+ else:
+ fill = frame.copy()
+ elem_top = frame
+ elem_top.style = style
+ elem_bottom = fill
+ elem_bottom.style.set_color(self.options.fill_color, "fill")
+ elem_top.style["fill"] = None
+ return elem_bottom, elem_top
+
+ def process_elements(self, containedelements: List[inkex.BaseElement]):
+ """Create and append the frame for an object or a set of objects."""
+ elem_bottom, elem_top = self.create_frame(containedelements)
+ if self.options.clip:
+ for node in containedelements:
+ element = elem_top if elem_top is not None else elem_bottom
+ self.add_clip(node, element)
+ if self.options.group:
+ group = containedelements[0].getparent().add(Group())
+ if elem_bottom is not None:
+ group.append(elem_bottom)
+ for node in containedelements:
+ group.append(node)
+ if elem_top is not None:
+ group.append(elem_top)
+ else:
+ if elem_bottom is not None:
+ containedelements[0].addprevious(elem_bottom)
+ if elem_top is not None:
+ containedelements[-1].addnext(elem_top)
+
+ def effect(self):
+ """Performs the effect."""
+ # Determine common properties.
+ if not self.svg.selection:
+ raise inkex.AbortExtension(_("Select at least one object."))
+ style = inkex.Style({"stroke-width": self.options.width})
+ style.set_color(self.options.stroke_color, "stroke")
+
+ if not self.options.asgroup:
+
+ for node in self.svg.selection:
+ self.process_elements([node])
+ else:
+ self.process_elements(self.svg.selection.rendering_order())
+
+
+if __name__ == "__main__":
+ Frame().run()