diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/extensions/__init__.py | 2 | ||||
| -rw-r--r-- | lib/extensions/lettering_generate_json.py | 12 | ||||
| -rw-r--r-- | lib/extensions/lettering_set_color_sort_index.py | 28 | ||||
| -rw-r--r-- | lib/gui/lettering/main_panel.py | 22 | ||||
| -rw-r--r-- | lib/gui/lettering/option_panel.py | 28 | ||||
| -rw-r--r-- | lib/gui/lettering_font_sample.py | 18 | ||||
| -rw-r--r-- | lib/lettering/font.py | 92 | ||||
| -rw-r--r-- | lib/marker.py | 7 |
8 files changed, 178 insertions, 31 deletions
diff --git a/lib/extensions/__init__.py b/lib/extensions/__init__.py index 8bf0b021..d5cfc8f8 100644 --- a/lib/extensions/__init__.py +++ b/lib/extensions/__init__.py @@ -37,6 +37,7 @@ from .lettering_font_sample import LetteringFontSample from .lettering_force_lock_stitches import LetteringForceLockStitches from .lettering_generate_json import LetteringGenerateJson from .lettering_remove_kerning import LetteringRemoveKerning +from .lettering_set_color_sort_index import LetteringSetColorSortIndex from .lettering_update_json_glyphlist import LetteringUpdateJsonGlyphlist from .letters_to_font import LettersToFont from .object_commands import ObjectCommands @@ -105,6 +106,7 @@ __all__ = extensions = [About, LetteringForceLockStitches, LetteringGenerateJson, LetteringRemoveKerning, + LetteringSetColorSortIndex, LetteringUpdateJsonGlyphlist, LettersToFont, ObjectCommands, diff --git a/lib/extensions/lettering_generate_json.py b/lib/extensions/lettering_generate_json.py index 5ff64c41..e6f997c7 100644 --- a/lib/extensions/lettering_generate_json.py +++ b/lib/extensions/lettering_generate_json.py @@ -5,9 +5,8 @@ import json import os -import sys -from inkex import Boolean +from inkex import Boolean, errormsg from ..i18n import _ from ..lettering.categories import FONT_CATEGORIES @@ -27,6 +26,8 @@ class LetteringGenerateJson(InkstitchExtension): self.arg_parser.add_argument("-d", "--font-description", type=str, default="Description", dest="font_description") self.arg_parser.add_argument("-s", "--auto-satin", type=Boolean, default="true", dest="auto_satin") self.arg_parser.add_argument("-r", "--reversible", type=Boolean, default="true", dest="reversible") + self.arg_parser.add_argument("-o", "--combine-at-sort-indices", type=str, default="", dest="combine_at_sort_indices") + self.arg_parser.add_argument("-t", "--sortable", type=Boolean, default="false", dest="sortable") self.arg_parser.add_argument("-u", "--letter-case", type=str, default="", dest="letter_case") self.arg_parser.add_argument("-g", "--default-glyph", type=str, default="", dest="default_glyph") self.arg_parser.add_argument("-z", "--size", type=float, default=15, dest="size") @@ -45,7 +46,7 @@ class LetteringGenerateJson(InkstitchExtension): # file paths path = self.options.path if not os.path.isfile(path): - print(_("Please specify a font file."), file=sys.stderr) + errormsg(_("Please specify a font file.")) return output_path = os.path.join(os.path.dirname(path), 'font.json') @@ -77,6 +78,9 @@ class LetteringGenerateJson(InkstitchExtension): if getattr(self.options, category.id): keywords.append(category.id) + combine_at_sort_indices = self.options.combine_at_sort_indices.split(',') + combine_at_sort_indices = set([index.strip() for index in combine_at_sort_indices if index.strip()]) + # collect data data = {'name': self.options.font_name, 'description': self.options.font_description, @@ -84,6 +88,8 @@ class LetteringGenerateJson(InkstitchExtension): 'leading': leading, 'auto_satin': self.options.auto_satin, 'reversible': self.options.reversible, + 'sortable': self.options.sortable, + 'combine_at_sort_indices': list(combine_at_sort_indices), 'letter_case': self.options.letter_case, 'default_glyph': self.options.default_glyph, 'size': self.options.size, diff --git a/lib/extensions/lettering_set_color_sort_index.py b/lib/extensions/lettering_set_color_sort_index.py new file mode 100644 index 00000000..3fb33094 --- /dev/null +++ b/lib/extensions/lettering_set_color_sort_index.py @@ -0,0 +1,28 @@ +# Authors: see git history +# +# Copyright (c) 2024 Authors +# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. + +from .base import InkstitchExtension + + +class LetteringSetColorSortIndex(InkstitchExtension): + ''' + This extension sets a color sort index to selected elements. + It enables font authors to define the order of elements in multicolor fonts when color sorted. + ''' + def __init__(self, *args, **kwargs): + InkstitchExtension.__init__(self, *args, **kwargs) + self.arg_parser.add_argument("--notebook") + self.arg_parser.add_argument("-i", "--color-sort-index", type=int, default=0, dest="color_sort_index") + + def effect(self): + selection = self.svg.selection + self.set_index(selection) + + def set_index(self, element_list): + for element in element_list: + if element.TAG == "path": + element.set('inkstitch:color_sort_index', self.options.color_sort_index) + elif element.TAG == "g": + self.set_index(element.getchildren()) diff --git a/lib/gui/lettering/main_panel.py b/lib/gui/lettering/main_panel.py index 5393fcf3..06388dd0 100644 --- a/lib/gui/lettering/main_panel.py +++ b/lib/gui/lettering/main_panel.py @@ -81,7 +81,8 @@ class LetteringPanel(wx.Panel): "font": None, "scale": 100, "trim_option": 0, - "use_trim_symbols": False + "use_trim_symbols": False, + "color_sort": False }) if INKSTITCH_LETTERING in self.group.attrib: @@ -98,6 +99,7 @@ class LetteringPanel(wx.Panel): def apply_settings(self): """Make the settings in self.settings visible in the UI.""" + self.options_panel.color_sort_checkbox.SetValue(bool(self.settings.color_sort)) self.options_panel.back_and_forth_checkbox.SetValue(bool(self.settings.back_and_forth)) self.options_panel.trim_option_choice.SetSelection(self.settings.trim_option) self.options_panel.use_trim_symbols.SetValue(bool(self.settings.use_trim_symbols)) @@ -230,6 +232,14 @@ class LetteringPanel(wx.Panel): self.options_panel.back_and_forth_checkbox.Disable() self.options_panel.back_and_forth_checkbox.SetValue(False) + if font.sortable: + # The creator of the font allowed color sorting: "sortable": false + self.options_panel.color_sort_checkbox.Enable() + self.options_panel.color_sort_checkbox.SetValue(bool(self.settings.color_sort)) + else: + self.options_panel.color_sort_checkbox.Disable() + self.options_panel.color_sort_checkbox.SetValue(False) + self.options_panel.Layout() self.update_preview() @@ -279,15 +289,17 @@ class LetteringPanel(wx.Panel): # 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 + INKSCAPE_LABEL: _("Text scale") + f' {self.settings.scale}%' }) self.group.append(destination_group) font = self.fonts.get(self.options_panel.font_chooser.GetValue(), self.default_font) try: - font.render_text(self.settings.text, destination_group, back_and_forth=self.settings.back_and_forth, - trim_option=self.settings.trim_option, use_trim_symbols=self.settings.use_trim_symbols) - + font.render_text( + self.settings.text, destination_group, back_and_forth=self.settings.back_and_forth, + trim_option=self.settings.trim_option, use_trim_symbols=self.settings.use_trim_symbols, + color_sort=self.settings.color_sort + ) except FontError as e: if raise_error: inkex.errormsg(_("Error: Text cannot be applied to the document.\n%s") % e) diff --git a/lib/gui/lettering/option_panel.py b/lib/gui/lettering/option_panel.py index e0c4388b..aea281ba 100644 --- a/lib/gui/lettering/option_panel.py +++ b/lib/gui/lettering/option_panel.py @@ -57,14 +57,16 @@ class LetteringOptionsPanel(wx.Panel): outer_sizer.Add(filter_sizer, 0, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT, 10) # options - self.options_box = wx.StaticBox(self, wx.ID_ANY, label=_("Options")) - self.scale_spinner = wx.SpinCtrl(self, wx.ID_ANY, min=0, max=1000, initial=100) self.scale_spinner.Bind(wx.EVT_SPINCTRL, lambda event: self.panel.on_change("scale", event)) self.back_and_forth_checkbox = wx.CheckBox(self, label=_("Stitch lines of text back and forth")) self.back_and_forth_checkbox.Bind(wx.EVT_CHECKBOX, lambda event: self.panel.on_change("back_and_forth", event)) + self.color_sort_checkbox = wx.CheckBox(self, label=_("Color sort")) + self.color_sort_checkbox.Bind(wx.EVT_CHECKBOX, lambda event: self.panel.on_change("color_sort", event)) + self.color_sort_checkbox.SetToolTip(_("Sort multicolor fonts. Unifies tartan patterns.")) + self.trim_option_choice = wx.Choice(self, choices=[_("Never"), _("after each line"), _("after each word"), _("after each letter")], name=_("Add trim command")) self.trim_option_choice.Bind(wx.EVT_CHOICE, lambda event: self.panel.on_trim_option_change(event)) @@ -74,22 +76,26 @@ class LetteringOptionsPanel(wx.Panel): self.use_trim_symbols.SetToolTip(_('Uses command symbols if enabled. When disabled inserts trim commands as params.')) left_option_sizer = wx.BoxSizer(wx.VERTICAL) - left_option_sizer.Add(self.back_and_forth_checkbox, 1, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT, 5) - - trim_option_sizer = wx.BoxSizer(wx.HORIZONTAL) - trim_option_sizer.Add(wx.StaticText(self, wx.ID_ANY, _("Add trims")), 0, wx.LEFT | wx.ALIGN_TOP, 5) - trim_option_sizer.Add(self.trim_option_choice, 1, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT | wx.BOTTOM, 5) - trim_option_sizer.Add(self.use_trim_symbols, 1, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT | wx.BOTTOM, 5) - left_option_sizer.Add(trim_option_sizer, 0, wx.ALIGN_LEFT, 5) font_scale_sizer = wx.BoxSizer(wx.HORIZONTAL) font_scale_sizer.Add(wx.StaticText(self, wx.ID_ANY, _("Scale")), 0, wx.LEFT | wx.ALIGN_CENTRE_VERTICAL, 0) font_scale_sizer.Add(self.scale_spinner, 0, wx.LEFT, 10) font_scale_sizer.Add(wx.StaticText(self, wx.ID_ANY, "%"), 0, wx.LEFT | wx.ALIGN_CENTRE_VERTICAL, 3) + left_option_sizer.Add(font_scale_sizer, 0, wx.ALIGN_LEFT, 5) + left_option_sizer.Add(self.back_and_forth_checkbox, 1, wx.LEFT | wx.TOP | wx.RIGHT, 5) + left_option_sizer.Add(self.color_sort_checkbox, 1, wx.LEFT | wx.TOP | wx.RIGHT, 5) + + right_option_sizer = wx.BoxSizer(wx.VERTICAL) + + right_option_sizer.Add(wx.StaticText(self, wx.ID_ANY, _("Add trims")), 0, wx.LEFT | wx.ALIGN_TOP, 5) + right_option_sizer.Add(self.trim_option_choice, 1, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT | wx.BOTTOM, 5) + right_option_sizer.Add(self.use_trim_symbols, 1, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT | wx.BOTTOM, 5) + + self.options_box = wx.StaticBox(self, wx.ID_ANY, label=_("Options")) options_sizer = wx.StaticBoxSizer(self.options_box, wx.HORIZONTAL) - options_sizer.Add(left_option_sizer, 1, wx.EXPAND, 10) - options_sizer.Add(font_scale_sizer, 0, wx.RIGHT, 10) + options_sizer.Add(left_option_sizer, 1, wx.LEFT | wx.RIGHT, 10) + options_sizer.Add(right_option_sizer, 0, wx.RIGHT, 10) outer_sizer.Add(options_sizer, 0, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT, 10) # text input diff --git a/lib/gui/lettering_font_sample.py b/lib/gui/lettering_font_sample.py index e5312c7e..984f6356 100644 --- a/lib/gui/lettering_font_sample.py +++ b/lib/gui/lettering_font_sample.py @@ -36,7 +36,7 @@ class FontSampleFrame(wx.Frame): self.font_chooser = wx.adv.BitmapComboBox(self.settings, wx.ID_ANY, style=wx.CB_READONLY | wx.CB_SORT, size=((800, 20))) self.font_chooser.Bind(wx.EVT_COMBOBOX, self.on_font_changed) - grid_settings_sizer = wx.FlexGridSizer(6, 2, 5, 5) + grid_settings_sizer = wx.FlexGridSizer(7, 2, 5, 5) grid_settings_sizer.AddGrowableCol(1) direction_label = wx.StaticText(self.settings, label=_("Stitch direction")) @@ -45,6 +45,7 @@ class FontSampleFrame(wx.Frame): self.scale_spinner = wx.SpinCtrl(self.settings, wx.ID_ANY, min=0, max=1000, initial=100) max_line_width_label = wx.StaticText(self.settings, label=_("Max. line width")) self.max_line_width = wx.SpinCtrl(self.settings, wx.ID_ANY, min=0, max=5000, initial=180) + self.color_sort_checkbox = wx.CheckBox(self.settings, label=_("Color sort")) grid_settings_sizer.Add(direction_label, 0, wx.ALIGN_LEFT, 0) grid_settings_sizer.Add(self.direction, 0, wx.EXPAND, 0) @@ -52,6 +53,8 @@ class FontSampleFrame(wx.Frame): grid_settings_sizer.Add(self.scale_spinner, 0, wx.EXPAND, 0) grid_settings_sizer.Add(max_line_width_label, 0, wx.ALIGN_LEFT, 0) grid_settings_sizer.Add(self.max_line_width, 0, wx.EXPAND, 0) + grid_settings_sizer.Add(wx.StaticText(), 0, wx.ALIGN_LEFT, 0) + grid_settings_sizer.Add(self.color_sort_checkbox, 0, wx.EXPAND, 0) apply_sizer = wx.BoxSizer(wx.HORIZONTAL) self.cancel_button = wx.Button(self.settings, label=_("Cancel")) @@ -127,6 +130,10 @@ class FontSampleFrame(wx.Frame): for variant in font.has_variants(): self.direction.Append(variant) self.direction.SetSelection(0) + if font.sortable: + self.color_sort_checkbox.Enable() + else: + self.color_sort_checkbox.Disable() def apply(self, event): # apply scale to layer and extract for later use @@ -142,6 +149,7 @@ class FontSampleFrame(wx.Frame): # parameters line_width = self.max_line_width.GetValue() direction = self.direction.GetValue() + color_sort = self.sortable(font) font._load_variants() font_variant = font.variants[direction] @@ -192,9 +200,15 @@ class FontSampleFrame(wx.Frame): width += width_to_add # render text and close - font.render_text(text, self.layer, variant=direction, back_and_forth=False) + font.render_text(text, self.layer, variant=direction, back_and_forth=False, color_sort=color_sort) self.GetTopLevelParent().Close() + def sortable(self, font): + color_sort = self.color_sort_checkbox.GetValue() + if color_sort and not font.sortable: + color_sort = False + return color_sort + def duplicate_warning(self, font): # warn about duplicated glyphs if len(set(font.available_glyphs)) != len(font.available_glyphs): diff --git a/lib/lettering/font.py b/lib/lettering/font.py index 431dc8d4..e1f0962b 100644 --- a/lib/lettering/font.py +++ b/lib/lettering/font.py @@ -15,13 +15,14 @@ from ..elements import SatinColumn, Stroke, nodes_to_elements from ..exceptions import InkstitchException from ..extensions.lettering_custom_font_dir import get_custom_font_dir from ..i18n import _, get_languages -from ..marker import MARKER, ensure_marker, has_marker +from ..marker import MARKER, ensure_marker, has_marker, is_grouped_with_marker from ..stitches.auto_satin import auto_satin from ..svg.tags import (CONNECTION_END, CONNECTION_START, EMBROIDERABLE_TAGS, INKSCAPE_LABEL, INKSTITCH_ATTRIBS, SVG_GROUP_TAG, SVG_PATH_TAG, SVG_USE_TAG, XLINK_HREF) from ..utils import Point from .font_variant import FontVariant +from collections import defaultdict class FontError(InkstitchException): @@ -147,6 +148,8 @@ class Font(object): word_spacing = font_metadata('horiz_adv_x_space', 20) reversible = font_metadata('reversible', True) + sortable = font_metadata('sortable', False) + combine_at_sort_indices = font_metadata('combine_at_sort_indices', []) @property def id(self): @@ -201,7 +204,7 @@ class Font(object): return False return custom_dir in self.path - def render_text(self, text, destination_group, variant=None, back_and_forth=True, trim_option=0, use_trim_symbols=False): + def render_text(self, text, destination_group, variant=None, back_and_forth=True, trim_option=0, use_trim_symbols=False, color_sort=False): """Render text into an SVG group element.""" self._load_variants() @@ -230,6 +233,20 @@ class Font(object): if self.auto_satin and len(destination_group) > 0: self._apply_auto_satin(destination_group) + self._set_style(destination_group) + + # add trims + self._add_trims(destination_group, text, trim_option, use_trim_symbols, back_and_forth) + # make sure necessary marker and command symbols are in the defs section + self._ensure_command_symbols(destination_group) + self._ensure_marker_symbols(destination_group) + + if color_sort and self.sortable: + self.do_color_sort(destination_group) + + return destination_group + + def _set_style(self, destination_group): # make sure font stroke styles have always a similar look for element in destination_group.iterdescendants(SVG_PATH_TAG): style = inkex.Style(element.get('style')) @@ -242,14 +259,6 @@ class Font(object): style += inkex.Style("stroke-width:0.5px") element.set('style', '%s' % style.to_str()) - # add trims - self._add_trims(destination_group, text, trim_option, use_trim_symbols, back_and_forth) - # make sure necessary marker and command symbols are in the defs section - self._ensure_command_symbols(destination_group) - self._ensure_marker_symbols(destination_group) - - return destination_group - def get_variant(self, variant): return self.variants.get(variant, self.variants[self.default_variant]) @@ -441,3 +450,66 @@ class Font(object): if elements: auto_satin(elements, preserve_order=True, trim=False) + + def do_color_sort(self, group): + """Sort elements by their color sort index as defined by font author""" + elements_by_color = self._get_color_sorted_elements(group) + + # there are no sort indexes defined, abort color sorting and return to normal + if not elements_by_color: + return + + group.remove_all() + for index, grouped_elements in sorted(elements_by_color.items()): + color_group = inkex.Group(attrib={ + INKSCAPE_LABEL: _("Color Group") + f' {index}' + }) + + # combined indices + if index in self.combine_at_sort_indices: + path = "" + for element_list in grouped_elements: + for element in element_list: + path += element.get("d", "") + grouped_elements[0][0].set("d", path) + color_group.append(grouped_elements[0][0]) + group.append(color_group) + continue + + # everything else, create marker groups if applicable + for element_list in grouped_elements: + if len(element_list) == 1: + color_group.append(element_list[0]) + continue + elements_group = inkex.Group() + for element in element_list: + elements_group.append(element) + color_group.append(elements_group) + + group.append(color_group) + + def _get_color_sorted_elements(self, group): + elements_by_color = defaultdict(list) + last_parent = None + for element in group.iterdescendants(SVG_PATH_TAG): + sort_index = element.get('inkstitch:color_sort_index', None) + + # get glyph group to calculate transform + for ancestor in element.ancestors(group): + if ancestor.get_id().startswith("glyph"): + glyph_group = ancestor + break + element.transform = element.composed_transform(glyph_group.getparent()) + element.apply_transform() + + if not sort_index: + elements_by_color[404].append([element]) + continue + + parent = element.getparent() + if last_parent != parent or int(sort_index) not in elements_by_color or not is_grouped_with_marker(element): + elements_by_color[int(sort_index)].append([element]) + else: + elements_by_color[int(sort_index)][-1].append(element) + last_parent = element.getparent() + return elements_by_color diff --git a/lib/marker.py b/lib/marker.py index 91a5862f..6a5f71e6 100644 --- a/lib/marker.py +++ b/lib/marker.py @@ -93,3 +93,10 @@ def has_marker(node, marker=list()): if "marker-start:url(#inkstitch-%s-marker" % m in style: return True return False + + +def is_grouped_with_marker(node): + for element in node.getparent().iterchildren(): + if has_marker(element): + return True + return False |
