From 43a31ba80ee844699a52341ff7d6ef6b881e6276 Mon Sep 17 00:00:00 2001 From: Claudine Peyrat <88194877+claudinepeyrat06@users.noreply.github.com> Date: Tue, 22 Nov 2022 10:46:44 +0100 Subject: [PATCH] Claudine/lettering with trims (#1866) * add trim after each letter allow to add a trim after each letter * add trim after letter, word or line rewriting everythng * style correction correcting all that is signaled by make style * corrections i don't understand why i add to modify get_command_pos in commands. If I don't i can not add a command at the end of a glyph that ends with a polyline (eg B from Fold) * replace checkbox with dropdown * rename variables in English * use same trim methods for auto_satin and non auto_satin * check if trim option is never strip lines of text Co-authored-by: Claudine Co-authored-by: Kaalleen Co-authored-by: Kaalleen <36401965+kaalleen@users.noreply.github.com> --- fonts/infinipicto/→.svg | 2 +- lib/commands.py | 8 +++- lib/extensions/lettering.py | 31 ++++++++------ lib/lettering/font.py | 80 ++++++++++++++++++++++++++++++++----- 4 files changed, 95 insertions(+), 26 deletions(-) diff --git a/fonts/infinipicto/→.svg b/fonts/infinipicto/→.svg index 79b53ae14..c213490e9 100644 --- a/fonts/infinipicto/→.svg +++ b/fonts/infinipicto/→.svg @@ -24,7 +24,7 @@ showgrid="true" inkscape:zoom="2.8284271" inkscape:cx="122.8598" - inkscape:cy="90.1561 + inkscape:cy="90.1561" inkscape:window-width="1440" inkscape:window-height="796" inkscape:window-x="0" diff --git a/lib/commands.py b/lib/commands.py index a7affb6d8..6280fc0ca 100644 --- a/lib/commands.py +++ b/lib/commands.py @@ -347,7 +347,13 @@ def get_command_pos(element, index, total): # Put command symbols 30 pixels out from the shape, spaced evenly around it. # get a line running 30 pixels out from the shape - outline = element.shape.buffer(30).exterior + + if not isinstance(element.shape.buffer(30), shgeo.MultiPolygon): + outline = element.shape.buffer(30).exterior + else: + polygons = element.shape.buffer(30).geoms + polygon = polygons[len(polygons)-1] + outline = polygon.exterior # find the top center point on the outline and start there top_center = shgeo.Point(outline.centroid.x, outline.bounds[1]) diff --git a/lib/extensions/lettering.py b/lib/extensions/lettering.py index c870a764d..40fd48af6 100644 --- a/lib/extensions/lettering.py +++ b/lib/extensions/lettering.py @@ -71,8 +71,9 @@ class LetteringFrame(wx.Frame): 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.on_change("back_and_forth", event)) - self.trim_checkbox = wx.CheckBox(self, label=_("Add trims")) - self.trim_checkbox.Bind(wx.EVT_CHECKBOX, lambda event: self.on_change("trim", event)) + self.trim_option_choice = wx.Choice(self, choices=["Never", "after each line", "after each word", "after each letter"], + name=_("Add trim after")) + self.trim_option_choice.Bind(wx.EVT_CHOICE, lambda event: self.on_trim_option_change(event)) # text editor self.text_input_box = wx.StaticBox(self, wx.ID_ANY, label=_("Text")) @@ -105,7 +106,8 @@ class LetteringFrame(wx.Frame): "text": "", "back_and_forth": False, "font": None, - "scale": 100 + "scale": 100, + "trim_option": 0 }) if INKSTITCH_LETTERING in self.group.attrib: @@ -123,7 +125,7 @@ class LetteringFrame(wx.Frame): def apply_settings(self): """Make the settings in self.settings visible in the UI.""" self.back_and_forth_checkbox.SetValue(bool(self.settings.back_and_forth)) - self.trim_checkbox.SetValue(bool(self.settings.trim)) + self.trim_option_choice.SetSelection(self.settings.trim_option) self.set_initial_font(self.settings.font) self.text_editor.SetValue(self.settings.text) self.scale_spinner.SetValue(self.settings.scale) @@ -219,6 +221,10 @@ class LetteringFrame(wx.Frame): self.settings[attribute] = event.GetEventObject().GetValue() self.preview.update() + def on_trim_option_change(self, event=None): + self.settings.trim_option = self.trim_option_choice.GetCurrentSelection() + self.preview.update() + def on_font_changed(self, event=None): font = self.fonts.get(self.font_chooser.GetValue(), self.default_font) self.settings.font = font.marked_custom_font_id @@ -253,13 +259,6 @@ class LetteringFrame(wx.Frame): self.back_and_forth_checkbox.Disable() self.back_and_forth_checkbox.SetValue(False) - if font.auto_satin: - self.trim_checkbox.Enable() - self.trim_checkbox.SetValue(bool(self.settings.trim)) - else: - self.trim_checkbox.Disable() - self.trim_checkbox.SetValue(False) - self.update_preview() self.Layout() @@ -314,7 +313,9 @@ class LetteringFrame(wx.Frame): font = self.fonts.get(self.font_chooser.GetValue(), self.default_font) try: - font.render_text(self.settings.text, destination_group, back_and_forth=self.settings.back_and_forth, trim=self.settings.trim) + font.render_text(self.settings.text, destination_group, back_and_forth=self.settings.back_and_forth, + trim_option=self.settings.trim_option) + except FontError as e: if raise_error: inkex.errormsg(_("Error: Text cannot be applied to the document.\n%s") % e) @@ -400,7 +401,11 @@ class LetteringFrame(wx.Frame): # options 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) - left_option_sizer.Add(self.trim_checkbox, 1, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT | wx.BOTTOM, 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_CENTRE_VERTICAL, 5) + trim_option_sizer.Add(self.trim_option_choice, 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) diff --git a/lib/lettering/font.py b/lib/lettering/font.py index 328d2ba46..5a617da18 100644 --- a/lib/lettering/font.py +++ b/lib/lettering/font.py @@ -10,15 +10,16 @@ from random import randint import inkex -from ..commands import ensure_symbol -from ..elements import nodes_to_elements +from ..commands import add_commands, ensure_symbol +from ..elements import FillStitch, 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 +from ..marker import MARKER, ensure_marker, has_marker from ..stitches.auto_satin import auto_satin -from ..svg.tags import (CONNECTION_END, CONNECTION_START, INKSCAPE_LABEL, - SVG_PATH_TAG, SVG_USE_TAG, XLINK_HREF) +from ..svg.tags import (CONNECTION_END, CONNECTION_START, EMBROIDERABLE_TAGS, + INKSCAPE_LABEL, SVG_GROUP_TAG, SVG_PATH_TAG, + SVG_USE_TAG, XLINK_HREF) from ..utils import Point from .font_variant import FontVariant @@ -180,7 +181,8 @@ class Font(object): def is_custom_font(self): return get_custom_font_dir() in self.path - def render_text(self, text, destination_group, variant=None, back_and_forth=True, trim=False): + def render_text(self, text, destination_group, variant=None, back_and_forth=True, trim_option=0): + """Render text into an SVG group element.""" self._load_variants() @@ -206,7 +208,7 @@ class Font(object): position.y += self.leading if self.auto_satin and len(destination_group) > 0: - self._apply_auto_satin(destination_group, trim) + self._apply_auto_satin(destination_group) # make sure font stroke styles have always a similar look for element in destination_group.iterdescendants(SVG_PATH_TAG): @@ -220,6 +222,8 @@ 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, 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) @@ -309,6 +313,10 @@ class Font(object): self._update_commands(node, glyph) + # this is used to recognize a glyph layer later in the process + # because this is not unique it will be overwritten by inkscape when inserted into the document + node.set("id", "glyph") + return node def _update_commands(self, node, glyph): @@ -329,6 +337,58 @@ class Font(object): c.set(CONNECTION_END, "#%s" % new_element_id) c.set(CONNECTION_START, "#%s" % new_symbol_id) + def _add_trims(self, destination_group, text, trim_option, back_and_forth): + """ + trim_option == 0 --> no trims + trim_option == 1 --> trim at the end of each line + trim_option == 2 --> trim after each word + trim_option == 3 --> trim after each letter + """ + if trim_option == 0: + return + + # reverse every second line of text if back and forth is true and strip spaces + text = text.splitlines() + text = [t[::-1].strip() if i % 2 != 0 and back_and_forth else t.strip() for i, t in enumerate(text)] + text = "\n".join(text) + + i = -1 + space_indices = [i for i, t in enumerate(text) if t == " "] + line_break_indices = [i for i, t in enumerate(text) if t == "\n"] + for group in destination_group.iterdescendants(SVG_GROUP_TAG): + # make sure we are only looking at glyph groups + if group.get("id") != "glyph": + continue + + i += 1 + while i in space_indices + line_break_indices: + i += 1 + + # letter + if trim_option == 3: + self._process_trim(group) + # word + elif trim_option == 2 and i+1 in space_indices + line_break_indices: + self._process_trim(group) + # line + elif trim_option == 1 and i+1 in line_break_indices: + self._process_trim(group) + + def _process_trim(self, group): + # find the last path that does not carry a marker and add a trim there + for path_child in group.iterdescendants(EMBROIDERABLE_TAGS): + if not has_marker(path_child): + path = path_child + if path.get('style') and "fill" in path.get('style'): + element = FillStitch(path) + else: + element = Stroke(path) + + if element.shape: + element_id = "%s_%s" % (element.node.get('id'), randint(0, 9999)) + element.node.set("id", element_id) + add_commands(element, ['trim']) + def _ensure_command_symbols(self, group): # collect commands commands = set() @@ -349,16 +409,14 @@ class Font(object): for element in marked_elements: element.style['marker-start'] = "url(#inkstitch-%s-marker)" % marker - def _apply_auto_satin(self, group, trim): + def _apply_auto_satin(self, group): """Apply Auto-Satin to an SVG XML node tree with an svg:g at its root. The group's contents will be replaced with the results of the auto- satin operation. Any nested svg:g elements will be removed. """ - # TODO: trim option for non-auto-route - elements = nodes_to_elements(group.iterdescendants(SVG_PATH_TAG)) if elements: - auto_satin(elements, preserve_order=True, trim=trim) + auto_satin(elements, preserve_order=True, trim=False)