From 2f35a4a192eb6aa3b68b715da6c1ba984084e0e5 Mon Sep 17 00:00:00 2001 From: Kaalleen <36401965+kaalleen@users.noreply.github.com> Date: Sun, 25 Jul 2021 07:24:34 +0200 Subject: [PATCH] Fix Style Issues (#1154) Co-authored-by: Lex Neva --- .github/workflows/build.yml | 2 +- Makefile | 2 +- inkstitch.py | 7 +- lib/commands.py | 122 +++++++++++------------- lib/elements/element.py | 37 +------ lib/extensions/base.py | 19 +--- lib/extensions/convert_to_satin.py | 20 ++-- lib/extensions/input.py | 2 +- lib/extensions/layer_commands.py | 22 +---- lib/extensions/lettering.py | 12 ++- lib/extensions/troubleshoot.py | 117 +++++++++-------------- lib/lettering/font.py | 11 +-- lib/lettering/font_variant.py | 16 +--- lib/lettering/glyph.py | 5 +- lib/stitches/auto_satin.py | 15 ++- lib/svg/path.py | 3 +- lib/svg/rendering.py | 40 ++++---- requirements.txt | 7 +- tests/style_cascade_and_inheritance.svg | 100 +++++++++++++++++++ 19 files changed, 280 insertions(+), 279 deletions(-) create mode 100644 tests/style_cascade_and_inheritance.svg diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f985eddc8..7aa7452d7 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 277b47f23..e7fcf2dfe 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 4c9d2789d..864c3a986 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 f2ab8c3ee..7435f7536 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 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"), - }) + # l10n: the name of a command symbol (example: scissors icon for trim command) + INKSCAPE_LABEL: _("command marker"), + }) + group.append(symbol) + + 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 9b894d893..670b05d86 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 8d5cccad9..dc71d9226 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 393ffd9ef..f3b433661 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 a0861bbcb..a8b8bee3c 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 2494e8204..26f01fad1 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 7ee162eab..75a18b245 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 8e37acc36..bf7faf766 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 d241bf05b..cbd8f2577 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 b1e383682..d9d8ed445 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 3bedd7edc..047c12cff 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 a047ea748..2b7f0906b 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 b503cc759..53cf80f2c 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 c28c70035..7be1e8b0e 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 - -import inkex -from lxml import etree from math import pi +import inkex + +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 84ba47d0b..698ea35d5 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 000000000..89210040c --- /dev/null +++ b/tests/style_cascade_and_inheritance.svg @@ -0,0 +1,100 @@ + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + +