summaryrefslogtreecommitdiff
path: root/lib/gui
diff options
context:
space:
mode:
authorKaalleen <36401965+kaalleen@users.noreply.github.com>2025-02-05 18:50:31 +0100
committerGitHub <noreply@github.com>2025-02-05 18:50:31 +0100
commitaf6cdc442bbcc1323ac1d13adaa49982318c9cfe (patch)
treef71d2de5c2a89167cdb4dc9975b5f6e6d97f978e /lib/gui
parent8f1f68a1db65b150a6429f334059bcae34a9b883 (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.py78
-rw-r--r--lib/gui/edit_json/settings_panel.py2
-rw-r--r--lib/gui/lettering/main_panel.py10
-rw-r--r--lib/gui/lettering_font_sample.py118
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()