summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/build.yml2
-rw-r--r--Makefile2
-rw-r--r--inkstitch.py7
-rw-r--r--lib/commands.py122
-rw-r--r--lib/elements/element.py37
-rw-r--r--lib/extensions/base.py19
-rw-r--r--lib/extensions/convert_to_satin.py20
-rw-r--r--lib/extensions/input.py2
-rw-r--r--lib/extensions/layer_commands.py22
-rw-r--r--lib/extensions/lettering.py12
-rw-r--r--lib/extensions/troubleshoot.py117
-rw-r--r--lib/lettering/font.py11
-rw-r--r--lib/lettering/font_variant.py16
-rw-r--r--lib/lettering/glyph.py5
-rw-r--r--lib/stitches/auto_satin.py15
-rw-r--r--lib/svg/path.py3
-rw-r--r--lib/svg/rendering.py38
-rw-r--r--requirements.txt7
-rw-r--r--tests/style_cascade_and_inheritance.svg100
19 files changed, 279 insertions, 278 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index f985eddc..7aa7452d 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -152,7 +152,7 @@ jobs:
pip install git+https://github.com/gtaylor/python-colormath
pip install -r requirements.txt
- pip install pyinstaller
+ pip install pyinstaller==4.3
echo "${{ env.pythonLocation }}/bin" >> $GITHUB_PATH
- shell: bash
diff --git a/Makefile b/Makefile
index 277b47f2..e7fcf2df 100644
--- a/Makefile
+++ b/Makefile
@@ -55,4 +55,4 @@ version:
.PHONY: style
style:
- flake8 . --count --max-complexity=10 --max-line-length=150 --statistics --exclude=pyembroidery,__init__.py,electron,build
+ flake8 . --count --max-complexity=10 --max-line-length=150 --statistics --exclude=pyembroidery,__init__.py,electron,build,src
diff --git a/inkstitch.py b/inkstitch.py
index 4c9d2789..864c3a98 100644
--- a/inkstitch.py
+++ b/inkstitch.py
@@ -10,6 +10,12 @@ import traceback
from argparse import ArgumentParser
from io import StringIO
+if getattr(sys, 'frozen', None) is None:
+ # When running in development mode, we want to use the inkex installed by
+ # pip install, not the one bundled with Inkscape which is not new enough.
+ sys.path.remove('/usr/share/inkscape/extensions')
+ sys.path.append('/usr/share/inkscape/extensions')
+
from inkex import errormsg
from lxml.etree import XMLSyntaxError
@@ -27,7 +33,6 @@ formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
logger.addHandler(ch)
-
parser = ArgumentParser()
parser.add_argument("--extension")
my_args, remaining_args = parser.parse_known_args()
diff --git a/lib/commands.py b/lib/commands.py
index f2ab8c3e..7435f753 100644
--- a/lib/commands.py
+++ b/lib/commands.py
@@ -9,15 +9,13 @@ from copy import deepcopy
from random import random
import inkex
-from lxml import etree
from shapely import geometry as shgeo
from .i18n import N_, _
from .svg import (apply_transforms, generate_unique_id,
get_correction_transform, get_document, get_node_transform)
from .svg.tags import (CONNECTION_END, CONNECTION_START, CONNECTOR_TYPE,
- INKSCAPE_LABEL, INKSTITCH_ATTRIBS, SVG_DEFS_TAG,
- SVG_GROUP_TAG, SVG_PATH_TAG, SVG_SYMBOL_TAG,
+ INKSCAPE_LABEL, INKSTITCH_ATTRIBS, SVG_SYMBOL_TAG,
SVG_USE_TAG, XLINK_HREF)
from .utils import Point, cache, get_bundled_dir
@@ -259,42 +257,36 @@ def symbols_path():
@cache
def symbols_svg():
with open(symbols_path()) as symbols_file:
- return etree.parse(symbols_file)
+ return inkex.load_svg(symbols_file).getroot()
@cache
def symbol_defs():
- return get_defs(symbols_svg())
+ return symbols_svg().defs
@cache
-def get_defs(document):
- defs = document.find(SVG_DEFS_TAG)
-
- if defs is None:
- defs = etree.SubElement(document, SVG_DEFS_TAG)
-
- return defs
-
-
-def ensure_symbol(document, command):
+def ensure_symbol(svg, command):
"""Make sure the command's symbol definition exists in the <svg:defs> tag."""
+ # using @cache really just makes sure that we don't bother ensuring the
+ # same symbol is there twice, which would be wasted work
+
path = "./*[@id='inkstitch_%s']" % command
- defs = get_defs(document)
+ defs = svg.defs
if defs.find(path) is None:
defs.append(deepcopy(symbol_defs().find(path)))
def add_group(document, node, command):
- group = etree.Element(
- SVG_GROUP_TAG,
- {
- "id": generate_unique_id(document, "command_group"),
- INKSCAPE_LABEL: _("Ink/Stitch Command") + ": %s" % get_command_description(command),
- "transform": get_correction_transform(node)
- })
- node.getparent().insert(node.getparent().index(node) + 1, group)
+ parent = node.getparent()
+ group = inkex.Group(attrib={
+ "id": generate_unique_id(document, "command_group"),
+ INKSCAPE_LABEL: _("Ink/Stitch Command") + ": %s" % get_command_description(command),
+ "transform": get_correction_transform(node)
+ })
+ parent.insert(parent.index(node) + 1, group)
+
return group
@@ -308,35 +300,36 @@ def add_connector(document, symbol, element):
if element.node.get('id') is None:
element.node.set('id', generate_unique_id(document, "object"))
- path = etree.Element(SVG_PATH_TAG,
- {
- "id": generate_unique_id(document, "command_connector"),
- "d": "M %s,%s %s,%s" % (start_pos[0], start_pos[1], end_pos.x, end_pos.y),
- "style": "stroke:#000000;stroke-width:1px;stroke-opacity:0.5;fill:none;",
- CONNECTION_START: "#%s" % symbol.get('id'),
- CONNECTION_END: "#%s" % element.node.get('id'),
- CONNECTOR_TYPE: "polyline",
+ path = inkex.PathElement(attrib={
+ "id": generate_unique_id(document, "command_connector"),
+ "d": "M %s,%s %s,%s" % (start_pos[0], start_pos[1], end_pos.x, end_pos.y),
+ "style": "stroke:#000000;stroke-width:1px;stroke-opacity:0.5;fill:none;",
+ CONNECTION_START: "#%s" % symbol.get('id'),
+ CONNECTION_END: "#%s" % element.node.get('id'),
+ CONNECTOR_TYPE: "polyline",
- # l10n: the name of the line that connects a command to the object it applies to
- INKSCAPE_LABEL: _("connector")
- })
+ # l10n: the name of the line that connects a command to the object it applies to
+ INKSCAPE_LABEL: _("connector")
+ })
symbol.getparent().insert(0, path)
def add_symbol(document, group, command, pos):
- return etree.SubElement(group, SVG_USE_TAG,
- {
- "id": generate_unique_id(document, "command_use"),
- XLINK_HREF: "#inkstitch_%s" % command,
- "height": "100%",
- "width": "100%",
- "x": str(pos.x),
- "y": str(pos.y),
+ symbol = inkex.Use(attrib={
+ "id": generate_unique_id(document, "command_use"),
+ XLINK_HREF: "#inkstitch_%s" % command,
+ "height": "100%",
+ "width": "100%",
+ "x": str(pos.x),
+ "y": str(pos.y),
+
+ # l10n: the name of a command symbol (example: scissors icon for trim command)
+ INKSCAPE_LABEL: _("command marker"),
+ })
+ group.append(symbol)
- # l10n: the name of a command symbol (example: scissors icon for trim command)
- INKSCAPE_LABEL: _("command marker"),
- })
+ return symbol
def get_command_pos(element, index, total):
@@ -383,32 +376,31 @@ def remove_legacy_param(element, command):
def add_commands(element, commands):
- document = get_document(element.node)
+ svg = get_document(element.node)
for i, command in enumerate(commands):
- ensure_symbol(document, command)
+ ensure_symbol(svg, command)
remove_legacy_param(element, command)
- group = add_group(document, element.node, command)
+ group = add_group(svg, element.node, command)
pos = get_command_pos(element, i, len(commands))
- symbol = add_symbol(document, group, command, pos)
- add_connector(document, symbol, element)
+ symbol = add_symbol(svg, group, command, pos)
+ add_connector(svg, symbol, element)
def add_layer_commands(layer, commands):
- document = get_document(layer)
+ svg = layer.root()
correction_transform = get_correction_transform(layer)
- for command in commands:
- ensure_symbol(document, command)
- etree.SubElement(layer, SVG_USE_TAG,
- {
- "id": generate_unique_id(document, "use"),
- INKSCAPE_LABEL: _("Ink/Stitch Command") + ": %s" % get_command_description(command),
- XLINK_HREF: "#inkstitch_%s" % command,
- "height": "100%",
- "width": "100%",
- "x": "0",
- "y": "-10",
- "transform": correction_transform
- })
+ for i, command in enumerate(commands):
+ ensure_symbol(svg, command)
+ layer.append(inkex.Use(attrib={
+ "id": generate_unique_id(svg, "use"),
+ INKSCAPE_LABEL: _("Ink/Stitch Command") + ": %s" % get_command_description(command),
+ XLINK_HREF: "#inkstitch_%s" % command,
+ "height": "100%",
+ "width": "100%",
+ "x": str(i * 20),
+ "y": "-10",
+ "transform": correction_transform
+ }))
diff --git a/lib/elements/element.py b/lib/elements/element.py
index 9b894d89..670b05d8 100644
--- a/lib/elements/element.py
+++ b/lib/elements/element.py
@@ -7,15 +7,13 @@ import sys
from copy import deepcopy
import inkex
-import tinycss2
from inkex import bezier
from ..commands import find_commands
from ..i18n import _
from ..svg import (PIXELS_PER_MM, apply_transforms, convert_length,
get_node_transform)
-from ..svg.tags import (EMBROIDERABLE_TAGS, INKSCAPE_LABEL, INKSTITCH_ATTRIBS,
- SVG_GROUP_TAG, SVG_LINK_TAG, SVG_USE_TAG)
+from ..svg.tags import INKSCAPE_LABEL, INKSTITCH_ATTRIBS
from ..utils import Point, cache
@@ -165,37 +163,12 @@ class EmbroideryElement(object):
self.node.set(param, str(value))
@cache
- def parse_style(self, node=None):
- if node is None:
- node = self.node
- element_style = node.get("style", "")
- if element_style is None:
- return None
- declarations = tinycss2.parse_declaration_list(node.get("style", ""))
- style = {declaration.lower_name: declaration.value[0].serialize() for declaration in declarations}
- return style
-
- @cache
- def _get_style_raw(self, style_name):
- if self.node is None:
- return None
- if self.node.tag not in [SVG_GROUP_TAG, SVG_LINK_TAG, SVG_USE_TAG] and self.node.tag not in EMBROIDERABLE_TAGS:
- return None
-
- style = self.parse_style()
- if style:
- style = style.get(style_name) or self.node.get(style_name)
- parent = self.node.getparent()
- # style not found, get inherited style elements
- while not style and parent is not None:
- style = self.parse_style(parent)
- if style:
- style = style.get(style_name) or parent.get(style_name)
- parent = parent.getparent()
- return style
+ def _get_specified_style(self):
+ # We want to cache this, because it's quite expensive to generate.
+ return self.node.specified_style()
def get_style(self, style_name, default=None):
- style = self._get_style_raw(style_name) or default
+ style = self._get_specified_style().get(style_name, default)
if style == 'none':
style = None
return style
diff --git a/lib/extensions/base.py b/lib/extensions/base.py
index 8d5cccad..dc71d922 100644
--- a/lib/extensions/base.py
+++ b/lib/extensions/base.py
@@ -49,19 +49,7 @@ class InkStitchMetadata(MutableMapping):
def __init__(self, document):
self.document = document
- self.metadata = self._get_or_create_metadata()
-
- def _get_or_create_metadata(self):
- metadata = self.document.find(SVG_METADATA_TAG)
-
- if metadata is None:
- metadata = etree.SubElement(self.document.getroot(), SVG_METADATA_TAG)
-
- # move it so that it goes right after the first element, sodipodi:namedview
- self.document.getroot().remove(metadata)
- self.document.getroot().insert(1, metadata)
-
- return metadata
+ self.metadata = document.metadata
# Because this class inherints from MutableMapping, all we have to do is
# implement these five methods and we get a full dict-like interface.
@@ -149,7 +137,8 @@ class InkstitchExtension(inkex.Effect):
if len(list(layer_commands(node, "ignore_layer"))):
return []
- if element.has_style('display') and element.get_style('display') is None:
+ if (node.tag in [EMBROIDERABLE_TAGS, SVG_GROUP_TAG] and
+ element.get_style('display', 'inline') is None):
return []
if node.tag == SVG_DEFS_TAG:
@@ -211,7 +200,7 @@ class InkstitchExtension(inkex.Effect):
return patches
def get_inkstitch_metadata(self):
- return InkStitchMetadata(self.document)
+ return InkStitchMetadata(self.svg)
def get_base_file_name(self):
svg_filename = self.document.getroot().get(inkex.addNS('docname', 'sodipodi'), "embroidery.svg")
diff --git a/lib/extensions/convert_to_satin.py b/lib/extensions/convert_to_satin.py
index 393ffd9e..f3b43366 100644
--- a/lib/extensions/convert_to_satin.py
+++ b/lib/extensions/convert_to_satin.py
@@ -9,16 +9,15 @@ from itertools import chain, groupby
import inkex
import numpy
-from lxml import etree
from numpy import diff, setdiff1d, sign
from shapely import geometry as shgeo
+from .base import InkstitchExtension
from ..elements import Stroke
from ..i18n import _
from ..svg import PIXELS_PER_MM, get_correction_transform
-from ..svg.tags import INKSTITCH_ATTRIBS, SVG_PATH_TAG
+from ..svg.tags import INKSTITCH_ATTRIBS
from ..utils import Point
-from .base import InkstitchExtension
class SelfIntersectionError(Exception):
@@ -317,11 +316,10 @@ class ConvertToSatin(InkstitchExtension):
d += "%s,%s " % (x, y)
d += " "
- return etree.Element(SVG_PATH_TAG,
- {
- "id": self.uniqueId("path"),
- "style": path_style,
- "transform": correction_transform,
- "d": d,
- INKSTITCH_ATTRIBS['satin_column']: "true",
- })
+ return inkex.PathElement(attrib={
+ "id": self.uniqueId("path"),
+ "style": path_style,
+ "transform": correction_transform,
+ "d": d,
+ INKSTITCH_ATTRIBS['satin_column']: "true",
+ })
diff --git a/lib/extensions/input.py b/lib/extensions/input.py
index a0861bbc..a8b8bee3 100644
--- a/lib/extensions/input.py
+++ b/lib/extensions/input.py
@@ -47,7 +47,7 @@ class Input(object):
del stitch_plan.last_color_block[-1]
extents = stitch_plan.extents
- svg = etree.Element("svg", nsmap=inkex.NSS, attrib={
+ svg = inkex.SvgDocumentElement("svg", nsmap=inkex.NSS, attrib={
"width": str(extents[0] * 2),
"height": str(extents[1] * 2),
"viewBox": "0 0 %s %s" % (extents[0] * 2, extents[1] * 2),
diff --git a/lib/extensions/layer_commands.py b/lib/extensions/layer_commands.py
index 2494e820..26f01fad 100644
--- a/lib/extensions/layer_commands.py
+++ b/lib/extensions/layer_commands.py
@@ -4,12 +4,9 @@
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
import inkex
-from lxml import etree
-from ..commands import LAYER_COMMANDS, ensure_symbol, get_command_description
+from ..commands import LAYER_COMMANDS, add_layer_commands
from ..i18n import _
-from ..svg import get_correction_transform
-from ..svg.tags import INKSCAPE_LABEL, SVG_USE_TAG, XLINK_HREF
from .commands import CommandsExtension
@@ -23,19 +20,4 @@ class LayerCommands(CommandsExtension):
inkex.errormsg(_("Please choose one or more commands to add."))
return
- correction_transform = get_correction_transform(self.svg.get_current_layer(), child=True)
-
- for i, command in enumerate(commands):
- ensure_symbol(self.document, command)
-
- etree.SubElement(self.svg.get_current_layer(), SVG_USE_TAG,
- {
- "id": self.uniqueId("use"),
- INKSCAPE_LABEL: _("Ink/Stitch Command") + ": %s" % get_command_description(command),
- XLINK_HREF: "#inkstitch_%s" % command,
- "height": "100%",
- "width": "100%",
- "x": str(i * 20),
- "y": "-10",
- "transform": correction_transform
- })
+ add_layer_commands(self.svg.get_current_layer(), commands)
diff --git a/lib/extensions/lettering.py b/lib/extensions/lettering.py
index 7ee162ea..75a18b24 100644
--- a/lib/extensions/lettering.py
+++ b/lib/extensions/lettering.py
@@ -12,8 +12,9 @@ import appdirs
import inkex
import wx
import wx.adv
-from lxml import etree
+from .commands import CommandsExtension
+from .lettering_custom_font_dir import get_custom_font_dir
from ..elements import nodes_to_elements
from ..gui import PresetsPanel, SimulatorPreview, info_dialog
from ..i18n import _
@@ -22,8 +23,6 @@ from ..svg import get_correction_transform
from ..svg.tags import (INKSCAPE_LABEL, INKSTITCH_LETTERING, SVG_GROUP_TAG,
SVG_PATH_TAG)
from ..utils import DotDict, cache, get_bundled_dir
-from .commands import CommandsExtension
-from .lettering_custom_font_dir import get_custom_font_dir
class LetteringFrame(wx.Frame):
@@ -258,12 +257,13 @@ class LetteringFrame(wx.Frame):
if self.settings.scale == 100:
destination_group = self.group
else:
- destination_group = etree.SubElement(self.group, SVG_GROUP_TAG, {
+ destination_group = inkex.Group(attrib={
# L10N The user has chosen to scale the text by some percentage
# (50%, 200%, etc). If you need to use the percentage symbol,
# make sure to double it (%%).
INKSCAPE_LABEL: _("Text scale %s%%") % self.settings.scale
})
+ self.group.append(destination_group)
font = self.fonts.get(self.font_chooser.GetValue(), self.default_font)
try:
@@ -414,10 +414,12 @@ class Lettering(CommandsExtension):
else:
return list(groups)[0]
else:
- return etree.SubElement(self.get_current_layer(), SVG_GROUP_TAG, {
+ group = inkex.Group(attrib={
INKSCAPE_LABEL: _("Ink/Stitch Lettering"),
"transform": get_correction_transform(self.get_current_layer(), child=True)
})
+ self.get_current_layer().append(group)
+ return group
def effect(self):
app = wx.App()
diff --git a/lib/extensions/troubleshoot.py b/lib/extensions/troubleshoot.py
index 8e37acc3..bf7faf76 100644
--- a/lib/extensions/troubleshoot.py
+++ b/lib/extensions/troubleshoot.py
@@ -5,18 +5,15 @@
import textwrap
-from inkex import errormsg
-from lxml import etree
+import inkex
+from .base import InkstitchExtension
from ..commands import add_layer_commands
from ..elements.validation import (ObjectTypeWarning, ValidationError,
ValidationWarning)
from ..i18n import _
from ..svg.path import get_correction_transform
-from ..svg.tags import (INKSCAPE_GROUPMODE, INKSCAPE_LABEL, SODIPODI_ROLE,
- SVG_GROUP_TAG, SVG_PATH_TAG, SVG_TEXT_TAG,
- SVG_TSPAN_TAG)
-from .base import InkstitchExtension
+from ..svg.tags import (INKSCAPE_GROUPMODE, INKSCAPE_LABEL, SODIPODI_ROLE)
class Troubleshoot(InkstitchExtension):
@@ -49,7 +46,7 @@ class Troubleshoot(InkstitchExtension):
message += "\n\n"
message += _("If you are still having trouble with a shape not being embroidered, "
"check if it is in a layer with an ignore command.")
- errormsg(message)
+ inkex.errormsg(message)
def insert_pointer(self, problem):
correction_transform = get_correction_transform(self.troubleshoot_layer)
@@ -67,31 +64,25 @@ class Troubleshoot(InkstitchExtension):
pointer_style = "stroke:#000000;stroke-width:0.2;fill:%s;" % (fill_color)
text_style = "fill:%s;stroke:#000000;stroke-width:0.2;font-size:8px;text-align:center;text-anchor:middle" % (fill_color)
- path = etree.Element(
- SVG_PATH_TAG,
- {
- "id": self.uniqueId("inkstitch__invalid_pointer__"),
- "d": "m %s,%s 4,20 h -8 l 4,-20" % (problem.position.x, problem.position.y),
- "style": pointer_style,
- INKSCAPE_LABEL: _('Invalid Pointer'),
- "transform": correction_transform
- }
- )
+ path = inkex.PathElement(attrib={
+ "id": self.uniqueId("inkstitch__invalid_pointer__"),
+ "d": "m %s,%s 4,20 h -8 l 4,-20" % (problem.position.x, problem.position.y),
+ "style": pointer_style,
+ INKSCAPE_LABEL: _('Invalid Pointer'),
+ "transform": correction_transform
+ })
layer.insert(0, path)
- text = etree.Element(
- SVG_TEXT_TAG,
- {
- INKSCAPE_LABEL: _('Description'),
- "x": str(problem.position.x),
- "y": str(float(problem.position.y) + 30),
- "transform": correction_transform,
- "style": text_style
- }
- )
+ text = inkex.TextElement(attrib={
+ INKSCAPE_LABEL: _('Description'),
+ "x": str(problem.position.x),
+ "y": str(float(problem.position.y) + 30),
+ "transform": correction_transform,
+ "style": text_style
+ })
layer.append(text)
- tspan = etree.Element(SVG_TSPAN_TAG)
+ tspan = inkex.Tspan()
tspan.text = problem.name
if problem.label:
tspan.text += " (%s)" % problem.label
@@ -102,46 +93,34 @@ class Troubleshoot(InkstitchExtension):
layer = svg.find(".//*[@id='__validation_layer__']")
if layer is None:
- layer = etree.Element(
- SVG_GROUP_TAG,
- {
- 'id': '__validation_layer__',
- INKSCAPE_LABEL: _('Troubleshoot'),
- INKSCAPE_GROUPMODE: 'layer',
- })
+ layer = inkex.Group(attrib={
+ 'id': '__validation_layer__',
+ INKSCAPE_LABEL: _('Troubleshoot'),
+ INKSCAPE_GROUPMODE: 'layer',
+ })
svg.append(layer)
-
else:
# Clear out everything from the last run
del layer[:]
add_layer_commands(layer, ["ignore_layer"])
- error_group = etree.SubElement(
- layer,
- SVG_GROUP_TAG,
- {
- "id": '__validation_errors__',
- INKSCAPE_LABEL: _("Errors"),
- })
+ error_group = inkex.Group(attrib={
+ "id": '__validation_errors__',
+ INKSCAPE_LABEL: _("Errors"),
+ })
layer.append(error_group)
- warning_group = etree.SubElement(
- layer,
- SVG_GROUP_TAG,
- {
- "id": '__validation_warnings__',
- INKSCAPE_LABEL: _("Warnings"),
- })
+ warning_group = inkex.Group(attrib={
+ "id": '__validation_warnings__',
+ INKSCAPE_LABEL: _("Warnings"),
+ })
layer.append(warning_group)
- type_warning_group = etree.SubElement(
- layer,
- SVG_GROUP_TAG,
- {
- "id": '__validation_ignored__',
- INKSCAPE_LABEL: _("Type Warnings"),
- })
+ type_warning_group = inkex.Group(attrib={
+ "id": '__validation_ignored__',
+ INKSCAPE_LABEL: _("Type Warnings"),
+ })
layer.append(type_warning_group)
self.troubleshoot_layer = layer
@@ -153,14 +132,11 @@ class Troubleshoot(InkstitchExtension):
svg = self.document.getroot()
text_x = str(float(svg.get('viewBox', '0 0 800 0').split(' ')[2]) + 5.0)
- text_container = etree.Element(
- SVG_TEXT_TAG,
- {
- "x": text_x,
- "y": str(5),
- "style": "fill:#000000;font-size:5px;line-height:1;"
- }
- )
+ text_container = inkex.TextElement(attrib={
+ "x": text_x,
+ "y": str(5),
+ "style": "fill:#000000;font-size:5px;line-height:1;"
+ })
self.troubleshoot_layer.append(text_container)
text = [
@@ -209,13 +185,10 @@ class Troubleshoot(InkstitchExtension):
text = self.split_text(text)
for text_line in text:
- tspan = etree.Element(
- SVG_TSPAN_TAG,
- {
- SODIPODI_ROLE: "line",
- "style": text_line[1]
- }
- )
+ tspan = inkex.Tspan(attrib={
+ SODIPODI_ROLE: "line",
+ "style": text_line[1]
+ })
tspan.text = text_line[0]
text_container.append(tspan)
diff --git a/lib/lettering/font.py b/lib/lettering/font.py
index d241bf05..cbd8f257 100644
--- a/lib/lettering/font.py
+++ b/lib/lettering/font.py
@@ -7,16 +7,15 @@ import json
import os
from copy import deepcopy
-from inkex import styles
-from lxml import etree
+import inkex
+from .font_variant import FontVariant
from ..elements import nodes_to_elements
from ..exceptions import InkstitchException
from ..i18n import _, get_languages
from ..stitches.auto_satin import auto_satin
-from ..svg.tags import INKSCAPE_LABEL, SVG_GROUP_TAG, SVG_PATH_TAG
+from ..svg.tags import INKSCAPE_LABEL, SVG_PATH_TAG
from ..utils import Point
-from .font_variant import FontVariant
class FontError(InkstitchException):
@@ -190,7 +189,7 @@ class Font(object):
for element in destination_group.iterdescendants(SVG_PATH_TAG):
dash_array = ""
stroke_width = ""
- style = styles.Style(element.get('style'))
+ style = inkex.styles.Style(element.get('style'))
if style.get('fill') == 'none':
stroke_width = ";stroke-width:1px"
@@ -224,7 +223,7 @@ class Font(object):
An svg:g element containing the rendered text.
"""
- group = etree.Element(SVG_GROUP_TAG, {
+ group = inkex.Group(attrib={
INKSCAPE_LABEL: line
})
diff --git a/lib/lettering/font_variant.py b/lib/lettering/font_variant.py
index b1e38368..d9d8ed44 100644
--- a/lib/lettering/font_variant.py
+++ b/lib/lettering/font_variant.py
@@ -6,7 +6,6 @@
import os
import inkex
-from lxml import etree
from ..svg.tags import INKSCAPE_GROUPMODE, INKSCAPE_LABEL
from .glyph import Glyph
@@ -61,8 +60,7 @@ class FontVariant(object):
def _load_glyphs(self):
svg_path = os.path.join(self.path, "%s.svg" % self.variant)
- with open(svg_path, encoding="utf-8") as svg_file:
- svg = etree.parse(svg_file)
+ svg = inkex.load_svg(svg_path)
glyph_layers = svg.xpath(".//svg:g[starts-with(@inkscape:label, 'GlyphLayer-')]", namespaces=inkex.NSS)
for layer in glyph_layers:
@@ -76,14 +74,10 @@ class FontVariant(object):
# glyph.
del group.attrib[INKSCAPE_GROUPMODE]
- style_text = group.get('style')
-
- if style_text:
- # The layer may be marked invisible, so we'll clear the 'display'
- # style.
- style = dict(inkex.Style.parse_str(group.get('style')))
- style.pop('display')
- group.set('style', str(inkex.Style(style)))
+ # The layer may be marked invisible, so we'll clear the 'display'
+ # style and presentation attribute.
+ group.style.pop('display', None)
+ group.attrib.pop('display', None)
def __getitem__(self, character):
if character in self.glyphs:
diff --git a/lib/lettering/glyph.py b/lib/lettering/glyph.py
index 3bedd7ed..047c12cf 100644
--- a/lib/lettering/glyph.py
+++ b/lib/lettering/glyph.py
@@ -8,7 +8,6 @@ from copy import copy
from inkex import paths, transforms
from ..svg import get_guides
-from ..svg.path import get_correction_transform
from ..svg.tags import SVG_GROUP_TAG, SVG_PATH_TAG
@@ -53,9 +52,7 @@ class Glyph(object):
node_copy = copy(node)
if "d" in node.attrib:
- transform = -transforms.Transform(get_correction_transform(node, True))
- path = paths.Path(node.get("d")).transform(transform).to_absolute()
- node_copy.set("d", str(path))
+ node_copy.path = node.path.transform(node.composed_transform()).to_absolute()
# Delete transforms from paths and groups, since we applied
# them to the paths already.
diff --git a/lib/stitches/auto_satin.py b/lib/stitches/auto_satin.py
index a047ea74..2b7f0906 100644
--- a/lib/stitches/auto_satin.py
+++ b/lib/stitches/auto_satin.py
@@ -8,7 +8,6 @@ from itertools import chain
import inkex
import networkx as nx
-from lxml import etree
from shapely import geometry as shgeo
from shapely.geometry import Point as ShapelyPoint
@@ -17,8 +16,7 @@ from ..elements import SatinColumn, Stroke
from ..i18n import _
from ..svg import (PIXELS_PER_MM, generate_unique_id, get_correction_transform,
line_strings_to_csp)
-from ..svg.tags import (INKSCAPE_LABEL, INKSTITCH_ATTRIBS, SVG_GROUP_TAG,
- SVG_PATH_TAG)
+from ..svg.tags import (INKSCAPE_LABEL, INKSTITCH_ATTRIBS)
from ..utils import Point as InkstitchPoint
from ..utils import cache, cut
@@ -219,14 +217,13 @@ class RunningStitch(object):
original_element.node.get(INKSTITCH_ATTRIBS['contour_underlay_stitch_length_mm'], '')
def to_element(self):
- node = etree.Element(SVG_PATH_TAG)
+ node = inkex.PathElement()
d = str(inkex.paths.CubicSuperPath(line_strings_to_csp([self.path])))
node.set("d", d)
- style = self.original_element.parse_style()
- style['stroke-dasharray'] = "0.5,0.5"
- style = str(inkex.Style(style))
- node.set("style", style)
+ dasharray = inkex.Style("stroke-dasharray:0.5,0.5;")
+ style = inkex.Style(self.original_element.node.get('style', '')) + dasharray
+ node.set("style", str(style))
node.set(INKSTITCH_ATTRIBS['running_stitch_length_mm'], self.running_stitch_length)
stroke = Stroke(node)
@@ -658,7 +655,7 @@ def preserve_original_groups(elements, original_parent_nodes):
def create_new_group(parent, insert_index):
- group = etree.Element(SVG_GROUP_TAG, {
+ group = inkex.Group(attrib={
INKSCAPE_LABEL: _("Auto-Satin"),
"transform": get_correction_transform(parent, child=True)
})
diff --git a/lib/svg/path.py b/lib/svg/path.py
index b503cc75..53cf80f2 100644
--- a/lib/svg/path.py
+++ b/lib/svg/path.py
@@ -4,7 +4,6 @@
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
import inkex
-from lxml import etree
from .tags import SVG_GROUP_TAG, SVG_LINK_TAG
from .units import get_viewbox_transform
@@ -96,6 +95,6 @@ def point_lists_to_csp(point_lists):
def line_strings_to_path(line_strings):
csp = line_strings_to_csp(line_strings)
- return etree.Element("path", {
+ return inkex.PathElement(attrib={
"d": str(inkex.paths.CubicSuperPath(csp))
})
diff --git a/lib/svg/rendering.py b/lib/svg/rendering.py
index c28c7003..7be1e8b0 100644
--- a/lib/svg/rendering.py
+++ b/lib/svg/rendering.py
@@ -4,16 +4,14 @@
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
import math
+from math import pi
import inkex
-from lxml import etree
-from math import pi
+from .tags import (INKSCAPE_GROUPMODE, INKSCAPE_LABEL, INKSTITCH_ATTRIBS)
+from .units import PIXELS_PER_MM, get_viewbox_transform
from ..i18n import _
from ..utils import Point, cache
-from .tags import (INKSCAPE_GROUPMODE, INKSCAPE_LABEL, INKSTITCH_ATTRIBS,
- SVG_DEFS_TAG, SVG_GROUP_TAG, SVG_PATH_TAG)
-from .units import PIXELS_PER_MM, get_viewbox_transform
# The stitch vector path looks like this:
# _______
@@ -174,7 +172,7 @@ def color_block_to_realistic_stitches(color_block, svg, destination):
color = color_block.color.visible_on_white.darker.to_hex_str()
start = point_list[0]
for point in point_list[1:]:
- destination.append(etree.Element(SVG_PATH_TAG, {
+ destination.append(inkex.PathElement(attrib={
'style': "fill: %s; stroke: none; filter: url(#realistic-stitch-filter);" % color,
'd': realistic_stitch(start, point),
'transform': get_correction_transform(svg)
@@ -200,7 +198,7 @@ def color_block_to_paths(color_block, svg, destination, visual_commands):
color = color_block.color.visible_on_white.to_hex_str()
- path = etree.Element(SVG_PATH_TAG, {
+ path = inkex.PathElement(attrib={
'style': "stroke: %s; stroke-width: 0.4; fill: none;" % color,
'd': "M" + " ".join(" ".join(str(coord) for coord in point) for point in point_list),
'transform': get_correction_transform(svg),
@@ -219,10 +217,11 @@ def color_block_to_paths(color_block, svg, destination, visual_commands):
def render_stitch_plan(svg, stitch_plan, realistic=False, visual_commands=True):
layer = svg.find(".//*[@id='__inkstitch_stitch_plan__']")
if layer is None:
- layer = etree.Element(SVG_GROUP_TAG,
- {'id': '__inkstitch_stitch_plan__',
- INKSCAPE_LABEL: _('Stitch Plan'),
- INKSCAPE_GROUPMODE: 'layer'})
+ layer = inkex.Group(attrib={
+ 'id': '__inkstitch_stitch_plan__',
+ INKSCAPE_LABEL: _('Stitch Plan'),
+ INKSCAPE_GROUPMODE: 'layer'
+ })
else:
# delete old stitch plan
del layer[:]
@@ -233,19 +232,16 @@ def render_stitch_plan(svg, stitch_plan, realistic=False, visual_commands=True):
svg.append(layer)
for i, color_block in enumerate(stitch_plan):
- group = etree.SubElement(layer,
- SVG_GROUP_TAG,
- {'id': '__color_block_%d__' % i,
- INKSCAPE_LABEL: "color block %d" % (i + 1)})
+ group = inkex.Group(attrib={
+ 'id': '__color_block_%d__' % i,
+ INKSCAPE_LABEL: "color block %d" % (i + 1)
+ })
+ layer.append(group)
if realistic:
color_block_to_realistic_stitches(color_block, svg, group)
else:
color_block_to_paths(color_block, svg, group, visual_commands)
if realistic:
- defs = svg.find(SVG_DEFS_TAG)
-
- if defs is None:
- defs = etree.SubElement(svg, SVG_DEFS_TAG)
-
- defs.append(etree.fromstring(realistic_filter))
+ filter_document = inkex.load_svg(realistic_filter)
+ svg.defs.append(filter_document.getroot())
diff --git a/requirements.txt b/requirements.txt
index 84ba47d0..698ea35d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,10 @@
./pyembroidery
-inkex
+
+# This installs inkex, the Inkscape python extension library.
+# We need the new style handling that was added after the inkex version bundled
+# with Inkscape 1.1. That's why we're installing from Git.
+-e git+https://gitlab.com/inkscape/extensions.git@139d71470e7d6bbe9fcd869f385fc73e3a8a8bea#egg=inkscape-core-extensions
+
backports.functools_lru_cache
wxPython
networkx
diff --git a/tests/style_cascade_and_inheritance.svg b/tests/style_cascade_and_inheritance.svg
new file mode 100644
index 00000000..89210040
--- /dev/null
+++ b/tests/style_cascade_and_inheritance.svg
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ width="100mm"
+ height="100mm"
+ viewBox="0 0 377.95276 377.95276"
+ id="svg8375"
+ version="1.1"
+ inkscape:version="1.1 (1:1.1+202105261517+ce6663b3b7)"
+ sodipodi:docname="style_cascade_and_inheritance.svg"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <style
+ id="style2">
+ .greenstroke {
+ stroke: green;
+ stroke-width: 10px;
+ fill: none;
+ }
+ .bluefill {
+ fill: blue;
+ }
+ </style>
+ <defs
+ id="defs8377" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1.979899"
+ inkscape:cx="303.55084"
+ inkscape:cy="244.96199"
+ inkscape:document-units="mm"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ units="mm"
+ inkscape:window-width="1366"
+ inkscape:window-height="705"
+ inkscape:window-x="-4"
+ inkscape:window-y="-4"
+ inkscape:window-maximized="1"
+ inkscape:pagecheckerboard="0"
+ inkscape:lockguides="true" />
+ <metadata
+ id="metadata8380">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <g
+ fill="none"
+ style="stroke:#ff0000"
+ id="g858">
+ <path
+ d="m 48.538989,130.3934 c 3.326181,-8.42241 9.209428,-14.93056 15.256084,-21.44505 67.367287,-72.579537 58.649627,29.08478 99.857957,35.46681 49.83745,7.71844 71.22003,-40.00458 105.12825,-62.410584"
+ id="path855" />
+ </g>
+ <path
+ d="m 48.538989,96.942485 c 3.326181,8.422415 9.209428,14.930565 15.256084,21.445055 67.367287,72.57953 58.649627,-29.084785 99.857957,-35.466815 49.83745,-7.71844 71.22003,40.004585 105.12825,62.410585"
+ id="path855-6"
+ transform="translate(0,80)"
+ class="greenstroke" />
+ <g
+ style="stroke:#ffff00"
+ id="g98473">
+ <path
+ d="m 91.07929,256.94248 c 2.38168,8.42242 6.59431,14.93057 10.92395,21.44506 48.23758,72.57953 41.9954,-29.08479 71.50215,-35.46682 35.68554,-7.71843 50.99629,40.00459 75.27589,62.41059"
+ id="path855-6-3"
+ class="greenstroke"
+ sodipodi:nodetypes="cssc" />
+ </g>
+ <g
+ class="bluefill"
+ id="g12">
+ <path
+ d="m 271.07929,316.94248 c 6.30625,17.64804 17.75622,33.53816 32.08917,45.49885 10.71785,6.11302 19.63778,-5.0749 22.62041,-14.31971 6.7262,-14.65667 9.98208,-31.76158 22.29605,-43.1292 7.42851,0.83153 13.85966,-9.52025 2.61436,-9.26942 -3.7666,-37.59816 -44.38889,-14.02914 -72.24439,-4.7814 -11.41463,2.62143 -1.42076,18.19844 -7.3756,26.00088 z"
+ id="path855-6-3-6"
+ style="stroke:#800080;stroke-width:5px"
+ sodipodi:nodetypes="csscccc" />
+ </g>
+ </g>
+</svg>