1
0
Fork 0
inkscape/share/extensions/ink2canvas_lib/svg.py
Daniel Baumann 02d935e272
Adding upstream version 1.4.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 23:40:13 +02:00

325 lines
9.7 KiB
Python

# 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.node.get_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)