diff options
| author | Kaalleen <36401965+kaalleen@users.noreply.github.com> | 2025-02-05 18:50:31 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-02-05 18:50:31 +0100 |
| commit | af6cdc442bbcc1323ac1d13adaa49982318c9cfe (patch) | |
| tree | f71d2de5c2a89167cdb4dc9975b5f6e6d97f978e /lib/gui | |
| parent | 8f1f68a1db65b150a6429f334059bcae34a9b883 (diff) | |
Lettering typographic features (#3466)
* add svg font to layers extension which saves glyph annotations into the glyph name
---------
Co-authored-by: Claudine
Diffstat (limited to 'lib/gui')
| -rw-r--r-- | lib/gui/edit_json/main_panel.py | 78 | ||||
| -rw-r--r-- | lib/gui/edit_json/settings_panel.py | 2 | ||||
| -rw-r--r-- | lib/gui/lettering/main_panel.py | 10 | ||||
| -rw-r--r-- | lib/gui/lettering_font_sample.py | 118 |
4 files changed, 147 insertions, 61 deletions
diff --git a/lib/gui/edit_json/main_panel.py b/lib/gui/edit_json/main_panel.py index 26eb5793..b175f892 100644 --- a/lib/gui/edit_json/main_panel.py +++ b/lib/gui/edit_json/main_panel.py @@ -237,16 +237,29 @@ class LetteringEditJsonPanel(wx.Panel): self.horiz_adv_x = self.font.horiz_adv_x kerning_combinations = combinations_with_replacement(self.glyphs, 2) - self.kerning_combinations = [''.join(combination) for combination in kerning_combinations] - self.kerning_combinations.extend([combination[1] + combination[0] for combination in self.kerning_combinations]) + self.kerning_combinations = [' '.join(combination) for combination in kerning_combinations] + self.kerning_combinations.extend([f'{combination[1]} {combination[0]}' for combination in kerning_combinations]) self.kerning_combinations = list(set(self.kerning_combinations)) self.kerning_combinations.sort() + self.update_legacy_kerning_pairs() self.update_settings() self.update_kerning_list() self.update_glyph_list() self.update_preview() + def update_legacy_kerning_pairs(self): + new_list = defaultdict(list) + for kerning_pair, value in self.kerning_pairs.items(): + if " " in kerning_pair: + # legacy kerning pairs do not use a space + return + if len(kerning_pair) < 2: + continue + pair = f'{kerning_pair[0]} {kerning_pair[1]}' + new_list[pair] = value + self.kerning_pairs = new_list + def update_settings(self): # reset font_meta self.font_meta = defaultdict(list) @@ -305,7 +318,8 @@ class LetteringEditJsonPanel(wx.Panel): kerning_list.AppendColumn("New kerning", width=wx.LIST_AUTOSIZE_USEHEADER) for kerning_pair in self.kerning_combinations: if self.font_meta['text_direction'] == 'rtl': - kerning_pair = kerning_pair[::-1] + pair = kerning_pair.split() + kerning_pair = ' '.join(pair[::-1]) index = kerning_list.InsertItem(kerning_list.GetItemCount(), kerning_pair) # kerning_list.SetItem(index, 0, kerning_pair) kerning_list.SetItem(index, 1, str(self.kerning_pairs.get(kerning_pair, 0.0))) @@ -373,43 +387,59 @@ class LetteringEditJsonPanel(wx.Panel): if self.last_notebook_selection == 3: text = self.get_active_glyph() else: - text = self.get_active_kerning_pair() + kerning = self.get_active_kerning_pair() + kerning = kerning.split() + text = ''.join(kerning) + if self.font_meta['text_direction'] == 'rtl': + text = ''.join(kerning[::-1]) if not text: return - text = self.text_before + text + self.text_after - if self.font_meta['text_direction'] == 'rtl': - text = text[::-1] - - self._render_text(text) + position_x = self._render_text(self.text_before, 0, True) + position_x = self._render_text(text, position_x, False) + self._render_text(self.text_after, position_x, True) if self.default_variant.variant == FontVariant.RIGHT_TO_LEFT: self.layer[:] = reversed(self.layer) for group in self.layer: group[:] = reversed(group) - def _render_text(self, text): - last_character = None - position_x = 0 - for character in text: - glyph = self.default_variant[character] - if character == " " or (glyph is None and self.font_meta['default_glyph'] == " "): - position_x += self.font_meta['horiz_adv_x_space'] - last_character = None - else: + def _render_text(self, text, position_x, use_character_position): + words = text.split() + for i, word in enumerate(words): + glyphs = [] + skip = [] + previous_is_binding = False + for i, character in enumerate(word): + if i in skip: + continue + if use_character_position: + glyph, glyph_len, previous_is_binding = self.default_variant.get_next_glyph(word, i, previous_is_binding) + else: + glyph, glyph_len = self.default_variant.get_glyph(character, word[i:]) + glyphs.append(glyph) + skip = list(range(i, i+glyph_len)) + + last_character = None + for glyph in glyphs: if glyph is None: - glyph = self.default_variant[self.font_meta['default_glyph']] + position_x += self.font_meta['horiz_adv_x_space'] + last_character = None + continue - if glyph is not None: - position_x, last_character = self._render_glyph(glyph, position_x, character, last_character) + position_x = self._render_glyph(glyph, position_x, glyph.name, last_character) + last_character = glyph.name + position_x += self.font_meta['horiz_adv_x_space'] + position_x -= self.font_meta['horiz_adv_x_space'] + return position_x def _render_glyph(self, glyph, position_x, character, last_character): node = deepcopy(glyph.node) if last_character is not None: if self.font_meta['text_direction'] != 'rtl': - position_x += glyph.min_x - self.kerning_pairs.get(last_character + character, 0) + position_x += glyph.min_x - self.kerning_pairs.get(f'{last_character} {character}', 0) else: - position_x += glyph.min_x - self.kerning_pairs.get(character + last_character, 0) + position_x += glyph.min_x - self.kerning_pairs.get(f'{character} {last_character}', 0) transform = f"translate({position_x}, 0)" node.set('transform', transform) @@ -427,7 +457,7 @@ class LetteringEditJsonPanel(wx.Panel): # because this is not unique it will be overwritten by inkscape when inserted into the document node.set("id", "glyph") self.layer.add(node) - return position_x, character + return position_x def render_stitch_plan(self): stitch_groups = [] diff --git a/lib/gui/edit_json/settings_panel.py b/lib/gui/edit_json/settings_panel.py index 57ba5fdc..8c51a3d0 100644 --- a/lib/gui/edit_json/settings_panel.py +++ b/lib/gui/edit_json/settings_panel.py @@ -97,7 +97,7 @@ class FontInfo(wx.Panel): ) default_variant_label = wx.StaticText(self, label=_("Default Variant")) - self.default_variant = wx.Choice(self, choices=[_("→"), _("←"), _("↓"), ("↑")]) + self.default_variant = wx.Choice(self, choices=["→", "←", "↓", "↑"]) self.default_variant.Bind(wx.EVT_CHOICE, self.parent.on_default_variant_change) text_direction_label = wx.StaticText(self, label=_("Text direction")) diff --git a/lib/gui/lettering/main_panel.py b/lib/gui/lettering/main_panel.py index 630595f1..6bb7219e 100644 --- a/lib/gui/lettering/main_panel.py +++ b/lib/gui/lettering/main_panel.py @@ -15,7 +15,7 @@ from ...i18n import _ from ...lettering import FontError, get_font_list from ...lettering.categories import FONT_CATEGORIES from ...stitch_plan import stitch_groups_to_stitch_plan -from ...svg.tags import INKSCAPE_LABEL, INKSTITCH_LETTERING +from ...svg.tags import INKSTITCH_LETTERING from ...utils import DotDict, cache from ...utils.threading import ExitThread, check_stop_flag from .. import PresetsPanel, PreviewRenderer, info_dialog @@ -292,15 +292,11 @@ class LetteringPanel(wx.Panel): del self.group[:] - 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") + f' {self.settings.scale}%' - }) + destination_group = inkex.Group() self.group.append(destination_group) font = self.fonts.get(self.options_panel.font_chooser.GetValue(), self.default_font) + destination_group.label = f"{font.name} {_('scale')} {self.settings.scale}%" try: font.render_text( self.settings.text, destination_group, back_and_forth=self.settings.back_and_forth, diff --git a/lib/gui/lettering_font_sample.py b/lib/gui/lettering_font_sample.py index 1049747c..e5ce312d 100644 --- a/lib/gui/lettering_font_sample.py +++ b/lib/gui/lettering_font_sample.py @@ -3,9 +3,11 @@ # Copyright (c) 2023 Authors # Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. +from copy import deepcopy + import wx import wx.adv -from inkex import errormsg +from inkex import Group, errormsg from ..i18n import _ from ..lettering import get_font_list @@ -20,6 +22,8 @@ class FontSampleFrame(wx.Frame): self.SetWindowStyle(wx.FRAME_FLOAT_ON_PARENT | wx.DEFAULT_FRAME_STYLE) self.fonts = None + self.font = None + self.font_variant = None self.main_panel = wx.Panel(self, wx.ID_ANY) @@ -124,14 +128,14 @@ class FontSampleFrame(wx.Frame): self.font_chooser.Append(font.marked_custom_font_name) def on_font_changed(self, event=None): - font = self.fonts.get(self.font_chooser.GetValue(), list(self.fonts.values())[0].marked_custom_font_name) - self.scale_spinner.SetRange(int(font.min_scale * 100), int(font.max_scale * 100)) + self.font = self.fonts.get(self.font_chooser.GetValue(), list(self.fonts.values())[0].marked_custom_font_name) + self.scale_spinner.SetRange(int(self.font.min_scale * 100), int(self.font.max_scale * 100)) # font._load_variants() self.direction.Clear() - for variant in font.has_variants(): + for variant in self.font.has_variants(): self.direction.Append(variant) self.direction.SetSelection(0) - if font.sortable: + if self.font.sortable: self.color_sort_label.Enable() self.color_sort_checkbox.Enable() else: @@ -143,19 +147,16 @@ class FontSampleFrame(wx.Frame): self.layer.transform.add_scale(self.scale_spinner.GetValue() / 100) scale = self.layer.transform.a - # set font - font = self.fonts.get(self.font_chooser.GetValue()) - if font is None: + if self.font is None: self.GetTopLevelParent().Close() return # 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] + self.font._load_variants() + self.font_variant = self.font.variants[direction] # setup lines of text text = '' @@ -164,33 +165,32 @@ class FontSampleFrame(wx.Frame): printed_warning = False update_glyphlist_warning = _( "The glyphlist for this font seems to be outdated.\n\n" - "Please update the glyph list for %s:\n" - "open Extensions > Ink/Stitch > Font Management > Edit JSON " - "select this font and apply. No other changes necessary." - % font.marked_custom_font_name - ) + "Please update the glyph list for {font_name}:\n" + "* Open Extensions > Ink/Stitch > Font Management > Edit JSON\n" + "* Select this font and apply." + ).format(font_name=self.font.marked_custom_font_name) - self.duplicate_warning(font) + self.duplicate_warning() # font variant glyph list length falls short if a single quote sign is available # let's add it in the length comparison - if len(set(font.available_glyphs)) != len(font_variant.glyphs): + if len(set(self.font.available_glyphs)) != len(self.font_variant.glyphs): errormsg(update_glyphlist_warning) printed_warning = True - for glyph in font.available_glyphs: - glyph_obj = font_variant[glyph] + for glyph in self.font.available_glyphs: + glyph_obj = self.font_variant[glyph] if glyph_obj is None: if not printed_warning: errormsg(update_glyphlist_warning) printed_warning = True continue if last_glyph is not None: - width_to_add = (glyph_obj.min_x - font.kerning_pairs.get(last_glyph + glyph, 0)) * scale + width_to_add = (glyph_obj.min_x - self.font.kerning_pairs.get(f'{last_glyph} {glyph}', 0)) * scale width += width_to_add try: - width_to_add = (font.horiz_adv_x.get(glyph, font.horiz_adv_x_default) - glyph_obj.min_x) * scale + width_to_add = (self.font.horiz_adv_x.get(glyph, self.font.horiz_adv_x_default) - glyph_obj.min_x) * scale except TypeError: width_to_add = glyph_obj.width @@ -203,24 +203,84 @@ class FontSampleFrame(wx.Frame): text += glyph width += width_to_add - # render text and close - font.render_text(text, self.layer, variant=direction, back_and_forth=False, color_sort=color_sort) + self._render_text(text) + self.GetTopLevelParent().Close() - def sortable(self, font): + def sortable(self): color_sort = self.color_sort_checkbox.GetValue() - if color_sort and not font.sortable: + if color_sort and not self.font.sortable: color_sort = False return color_sort - def duplicate_warning(self, font): + def duplicate_warning(self): # warn about duplicated glyphs - if len(set(font.available_glyphs)) != len(font.available_glyphs): + if len(set(self.font.available_glyphs)) != len(self.font.available_glyphs): duplicated_glyphs = " ".join( - [glyph for glyph in set(font.available_glyphs) if font.available_glyphs.count(glyph) > 1] + [glyph for glyph in set(self.font.available_glyphs) if self.font.available_glyphs.count(glyph) > 1] ) errormsg(_("Found duplicated glyphs in font file: {duplicated_glyphs}").format(duplicated_glyphs=duplicated_glyphs)) + def _render_text(self, text): + lines = text.splitlines() + position = {'x': 0, 'y': 0} + for line in lines: + group = Group() + group.label = line + group.set("inkstitch:letter-group", "line") + glyphs = [] + skip = [] + for i, character in enumerate(line): + if i in skip: + continue + default_variant = self.font.variants[self.font.json_default_variant] + glyph, glyph_len = default_variant.get_glyph(character, line[i:]) + glyphs.append(glyph) + skip = list(range(i, i+glyph_len)) + + last_character = None + for glyph in glyphs: + if glyph is None: + position['x'] += self.font.horiz_adv_x_space + last_character = None + continue + + position = self._render_glyph(group, glyph, position, glyph.name, last_character) + last_character = glyph.name + self.layer.add(group) + position['x'] = 0 + position['y'] += self.font.leading + + if self.sortable(): + self.font.do_color_sort(self.layer, 1) + + def _render_glyph(self, group, glyph, position, character, last_character): + node = deepcopy(glyph.node) + if last_character is not None: + if self.font.text_direction != 'rtl': + position['x'] += glyph.min_x - self.font.kerning_pairs.get(f'{last_character} {character}', 0) + else: + position['x'] += glyph.min_x - self.font.kerning_pairs.get(f'{character} {last_character}', 0) + + transform = f"translate({position['x']}, {position['y']})" + node.set('transform', transform) + + horiz_adv_x_default = self.font.horiz_adv_x_default + if horiz_adv_x_default is None: + horiz_adv_x_default = glyph.width + glyph.min_x + + position['x'] += self.font.horiz_adv_x.get(character, horiz_adv_x_default) - glyph.min_x + + self.font._update_commands(node, glyph) + self.font._update_clips(group, 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") + node.set("inkstitch:letter-group", "glyph") + group.add(node) + return position + def cancel(self, event): self.GetTopLevelParent().Close() |
