diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:24:48 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:24:48 +0000 |
commit | cca66b9ec4e494c1d919bff0f71a820d8afab1fa (patch) | |
tree | 146f39ded1c938019e1ed42d30923c2ac9e86789 /share/extensions/ink2canvas_lib | |
parent | Initial commit. (diff) | |
download | inkscape-upstream.tar.xz inkscape-upstream.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/ink2canvas_lib')
-rw-r--r-- | share/extensions/ink2canvas_lib/__init__.py | 0 | ||||
-rw-r--r-- | share/extensions/ink2canvas_lib/canvas.py | 200 | ||||
-rw-r--r-- | share/extensions/ink2canvas_lib/svg.py | 326 |
3 files changed, 526 insertions, 0 deletions
diff --git a/share/extensions/ink2canvas_lib/__init__.py b/share/extensions/ink2canvas_lib/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/share/extensions/ink2canvas_lib/__init__.py diff --git a/share/extensions/ink2canvas_lib/canvas.py b/share/extensions/ink2canvas_lib/canvas.py new file mode 100644 index 0000000..9429420 --- /dev/null +++ b/share/extensions/ink2canvas_lib/canvas.py @@ -0,0 +1,200 @@ +# coding=utf-8 +# +# Copyright (C) 2011 Karlisson Bezerra <contact@hacktoon.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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Canvas module for ink2canvas extension +""" + +from textwrap import dedent + +from inkex import Color, Style + +# pylint: disable=too-many-public-methods, disable=missing-function-docstring + + +class Canvas: + """Canvas API helper class""" + + def __init__(self, parent, width, height, context="ctx"): + self.obj = context + self.code = [] # stores the code + self.style = Style() + self.style_cache = Style() # stores the previous style applied + self.parent = parent + self.width = width + self.height = height + + def write(self, text): + self.code.append("\t" + text.replace("ctx", self.obj) + "\n") + + def output(self): + + html = """ + <!DOCTYPE html> + <html> + <head> + <title>Inkscape Output</title> + </head> + <body> + <canvas id='canvas' width='%d' height='%d'></canvas> + <script> + var %s = document.getElementById("canvas").getContext("2d"); + %s + </script> + </body> + </html> + """ + return dedent(html) % (self.width, self.height, self.obj, "".join(self.code)) + + def equal_style(self, style, key): + """Checks if the last style used is the same or there's no style yet""" + if key in self.style_cache: + return True + if key not in style: + return True + return style[key] == self.style_cache[key] + + def begin_path(self): + self.write("ctx.beginPath();") + + def create_linear_gradient(self, href, x1, y1, x2, y2): + data = (href, x1, y1, x2, y2) + self.write( + "var %s = \ + ctx.createLinearGradient(%f,%f,%f,%f);" + % data + ) + + def create_radial_gradient(self, href, cx1, cy1, rx, cx2, cy2, ry): + data = (href, cx1, cy1, rx, cx2, cy2, ry) + self.write( + "var %s = ctx.createRadialGradient\ + (%f,%f,%f,%f,%f,%f);" + % data + ) + + def add_color_stop(self, href, pos, color): + self.write("%s.addColorStop(%f, %s);" % (href, pos, color)) + + @staticmethod + def get_color(rgb, alpha): + return "'{}'".format(str(Color(rgb).to_rgba(alpha))) + + def set_gradient(self, href): + """ + for stop in gstops: + style = simplestyle.parseStyle(stop.get("style")) + stop_color = style["stop-color"] + opacity = style["stop-opacity"] + color = self.get_color(stop_color, opacity) + pos = float(stop.get("offset")) + self.add_color_stop(href, pos, color) + """ + return None # href + + def set_opacity(self, value): + self.write("ctx.globalAlpha = %.1f;" % float(value)) + + def set_fill(self, value): + alpha = self.style("fill-opacity") + if not value.startswith("url("): + fill = self.get_color(value, alpha) + self.write("ctx.fillStyle = %s;" % fill) + + def set_stroke(self, value): + alpha = self.style("stroke-opacity") + self.write("ctx.strokeStyle = %s;" % self.get_color(value, alpha)) + + def set_stroke_width(self, value): + self.write("ctx.lineWidth = %f;" % self.parent.svg.unittouu(value)) + + def set_stroke_linecap(self, value): + self.write("ctx.lineCap = '%s';" % value) + + def set_stroke_linejoin(self, value): + self.write("ctx.lineJoin = '%s';" % value) + + def set_stroke_miterlimit(self, value): + self.write("ctx.miterLimit = %s;" % value) + + def set_font(self, value): + self.write('ctx.font = "%s";' % value) + + def move_to(self, x, y): + self.write("ctx.moveTo(%f, %f);" % (x, y)) + + def line_to(self, x, y): + self.write("ctx.lineTo(%f, %f);" % (x, y)) + + def close_path(self): + self.write("ctx.closePath();") + + def quadratic_curve_to(self, cpx, cpy, x, y): + data = (cpx, cpy, x, y) + self.write("ctx.quadraticCurveTo(%f, %f, %f, %f);" % data) + + def bezier_curve_to(self, x1, y1, x2, y2, x, y): + data = (x1, y1, x2, y2, x, y) + self.write("ctx.bezierCurveTo(%f, %f, %f, %f, %f, %f);" % data) + + def rect(self, x, y, width, height, rx=0, ry=0): + if rx or ry: + # rounded rectangle, starts top-left anticlockwise + self.move_to(x, y + ry) + self.line_to(x, y + height - ry) + self.quadratic_curve_to(x, y + height, x + rx, y + height) + self.line_to(x + width - rx, y + height) + self.quadratic_curve_to(x + width, y + height, x + width, y + height - ry) + self.line_to(x + width, y + ry) + self.quadratic_curve_to(x + width, y, x + width - rx, y) + self.line_to(x + rx, y) + self.quadratic_curve_to(x, y, x, y + ry) + else: + self.write("ctx.rect(%f, %f, %f, %f);" % (x, y, width, height)) + + def arc(self, x, y, r, a1, a2, flag): + data = (x, y, r, a1, a2, flag) + self.write("ctx.arc(%f, %f, %f, %f, %.8f, %d);" % data) + + def fill_text(self, text, x, y): + self.write('ctx.fillText("%s", %f, %f);' % (text, x, y)) + + def translate(self, cx, cy): + self.write("ctx.translate(%f, %f);" % (cx, cy)) + + def rotate(self, angle): + self.write("ctx.rotate(%f);" % angle) + + def scale(self, rx, ry): + self.write("ctx.scale(%f, %f);" % (rx, ry)) + + def transform(self, m11, m12, m21, m22, dx, dy): + data = (m11, m12, m21, m22, dx, dy) + self.write("ctx.transform(%f, %f, %f, %f, %f, %f);" % data) + + def save(self): + self.write("ctx.save();") + + def restore(self): + self.write("ctx.restore();") + + def finish_path(self): + if self.style("fill") is not None: + self.write("ctx.fill();") + if self.style("stroke") is not None: + self.write("ctx.stroke();") diff --git a/share/extensions/ink2canvas_lib/svg.py b/share/extensions/ink2canvas_lib/svg.py new file mode 100644 index 0000000..3d3fafb --- /dev/null +++ b/share/extensions/ink2canvas_lib/svg.py @@ -0,0 +1,326 @@ +# coding=utf-8 +# +# Copyright (C) 2011 Karlisson Bezerra <contact@hacktoon.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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Element parsing and context for ink2canvas extensions +""" + +from __future__ import unicode_literals +import math + +import inkex +from inkex.localization import inkex_gettext as _ + + +# pylint: disable=missing-function-docstring, missing-class-docstring +# pylint: disable=too-few-public-methods + + +class Element: + """Base Element""" + + def __init__(self, node): + self.node = node + + def attr(self, val): + """Get attribute""" + try: + attr = float(self.node.get(val)) + except (ValueError, TypeError, AttributeError): + attr = self.node.get(val) + return attr + + +class GradientDef(Element): + def __init__(self, node, stops): + super().__init__(node) + self.stops = stops + + +class LinearGradientDef(GradientDef): + def get_data(self): + # pylint: disable=unused-variable + x1 = self.attr("x1") + y1 = self.attr("y1") + x2 = self.attr("x2") + y2 = self.attr("y2") + # self.create_linear_gradient(href, x1, y1, x2, y2) + + def draw(self): + pass + + +class RadialGradientDef(GradientDef): + def get_data(self): + # pylint: disable=unused-variable + cx = self.attr("cx") + cy = self.attr("cy") + r = self.attr("r") + # self.create_radial_gradient(href, cx, cy, r, cx, cy, r) + + def draw(self): + pass + + +class AbstractShape(Element): + def __init__(self, command, node, ctx): + super().__init__(node) + self.command = command + self.ctx = ctx + self.gradient = None + + def get_data(self): # pylint: disable=no-self-use + return None + + def get_style(self): + return self.node.specified_style() + + def set_style(self, style): + """Translates style properties names into method calls""" + self.ctx.style = style + for key in style: + method = "set_" + "_".join(key.split("-")) + if hasattr(self.ctx, method) and style[key] != "none": + getattr(self.ctx, method)(style[key]) + # saves style to compare in next iteration + if ( + hasattr(self.ctx, "style_cache") + and "opacity" not in style + and self.ctx.style_cache("opacity") != style("opacity") + ): + self.ctx.set_opacity( + style("opacity") + ) # opacity is kept in memory, need to reset + self.ctx.style_cache = style + + def has_transform(self): + return bool(self.attr("transform")) + + def get_transform(self): + return self.node.transform.to_hexad() + + def has_gradient(self): + style = self.get_style() + fill = style("fill") + return fill is not None and isinstance(fill, inkex.Gradient) + + def get_gradient_href(self): + style = self.get_style() + return style("fill").get_id() + + def has_clip(self): + return bool(self.attr("clip-path")) + + def start(self, gradient): + self.gradient = gradient + self.ctx.write("\n// #%s" % self.attr("id")) + if self.has_transform() or self.has_clip(): + self.ctx.save() + + def draw(self): + data = self.get_data() # pylint: disable=assignment-from-none + style = self.get_style() + self.ctx.begin_path() + if self.has_transform(): + trans_matrix = self.get_transform() + self.ctx.transform(*trans_matrix) # unpacks argument list + if self.has_gradient(): + self.gradient.draw() + self.set_style(style) + # unpacks "data" in parameters to given method + getattr(self.ctx, self.command)(*data) + self.ctx.finish_path() + + def end(self): + if self.has_transform() or self.has_clip(): + self.ctx.restore() + + +class G(AbstractShape): # pylint: disable=invalid-name + def draw(self): + # get layer label, if exists + if self.has_transform(): + trans_matrix = self.get_transform() + self.ctx.transform(*trans_matrix) + + +class Rect(AbstractShape): + def get_data(self): + x = self.attr("x") + y = self.attr("y") + width = self.attr("width") + height = self.attr("height") + rx = self.attr("rx") or 0 + ry = self.attr("ry") or 0 + return x, y, width, height, rx, ry + + +class Circle(AbstractShape): + def __init__(self, command, node, ctx): + AbstractShape.__init__(self, command, node, ctx) + self.command = "arc" + + def get_data(self): + + cx = self.attr("cx") + cy = self.attr("cy") + r = self.attr("r") + return cx, cy, r, 0, math.pi * 2, True + + +class Ellipse(AbstractShape): + def get_data(self): + cx = self.attr("cx") + cy = self.attr("cy") + rx = self.attr("rx") + ry = self.attr("ry") + return cx, cy, rx, ry + + def draw(self): + cx, cy, rx, ry = self.get_data() + style = self.get_style() + self.ctx.begin_path() + if self.has_transform(): + trans_matrix = self.get_transform() + self.ctx.transform(*trans_matrix) # unpacks argument list + self.set_style(style) + + kappa = 4 * ((math.sqrt(2) - 1) / 3) + self.ctx.move_to(cx, cy - ry) + self.ctx.bezier_curve_to( + cx + (kappa * rx), cy - ry, cx + rx, cy - (kappa * ry), cx + rx, cy + ) + self.ctx.bezier_curve_to( + cx + rx, cy + (kappa * ry), cx + (kappa * rx), cy + ry, cx, cy + ry + ) + self.ctx.bezier_curve_to( + cx - (kappa * rx), cy + ry, cx - rx, cy + (kappa * ry), cx - rx, cy + ) + self.ctx.bezier_curve_to( + cx - rx, cy - (kappa * ry), cx - (kappa * rx), cy - ry, cx, cy - ry + ) + self.ctx.finish_path() + + +class Path(AbstractShape): + def __init__(self, command, node, ctx): + AbstractShape.__init__(self, command, node, ctx) + self.current_position = 0, 0 + + def path_move_to(self, data): + self.ctx.move_to(data[0], data[1]) + self.current_position = data[0], data[1] + + def path_line_to(self, data): + self.ctx.line_to(data[0], data[1]) + self.current_position = data[0], data[1] + + def path_curve_to(self, data): + x1, y1, x2, y2 = data[0], data[1], data[2], data[3] + x, y = data[4], data[5] + self.ctx.bezier_curve_to(x1, y1, x2, y2, x, y) + self.current_position = x, y + + def path_close(self, data): # pylint: disable=unused-argument + self.ctx.close_path() + + def draw(self): + """Gets the node type and calls the given method""" + style = self.get_style() + self.ctx.begin_path() + if self.has_transform(): + trans_matrix = self.get_transform() + self.ctx.transform(*trans_matrix) # unpacks argument list + self.set_style(style) + + # Draws path commands + path_command = { + "M": self.path_move_to, + "L": self.path_line_to, + "C": self.path_curve_to, + "Z": self.path_close, + } + # Make sure we only have Lines and curves (no arcs etc) + for comm, data in self.node.path.to_superpath().to_path().to_arrays(): + if comm in path_command: + path_command[comm](data) + + self.ctx.finish_path() + + +class Line(Path): + def get_data(self): + x1 = self.attr("x1") + y1 = self.attr("y1") + x2 = self.attr("x2") + y2 = self.attr("y2") + return ("M", (x1, y1)), ("L", (x2, y2)) + + +class Polygon(Path): + def get_data(self): + points = self.attr("points").strip().split(" ") + points = map(lambda x: x.split(","), points) + comm = [] + for point in points: # creating path command similar + point = list(map(float, point)) + comm.append(["L", point]) + comm[0][0] = "M" # first command must be a 'M' => moveTo + return comm + + +class Polyline(Polygon): + pass + + +class Text(AbstractShape): + def text_helper(self, tspan): + if tspan is not None: + return tspan.text + for ts_cur in tspan: + return ts_cur.text + self.text_helper(ts_cur) + ts_cur.tail + + def set_text_style(self, style): + keys = ("font-style", "font-weight", "font-size", "font-family") + text = [] + for key in keys: + if key in style: + text.append(style[key]) + self.ctx.set_font(" ".join(text)) + + def get_data(self): + x = self.attr("x") + y = self.attr("y") + return x, y + + def draw(self): + for tspan in self.node: + if isinstance(tspan, inkex.TextPath): + raise ValueError(_("TextPath elements are not supported")) + style = self.get_style() + if self.has_transform(): + trans_matrix = self.get_transform() + self.ctx.transform(*trans_matrix) # unpacks argument list + self.set_style(style) + self.set_text_style(style) + + for tspan in self.node: + text = self.text_helper(tspan) + cur_x = float(tspan.get("x").split()[0]) + cur_y = float(tspan.get("y").split()[0]) + self.ctx.fill_text(text, cur_x, cur_y) |