summaryrefslogtreecommitdiff
path: root/lib/gui
diff options
context:
space:
mode:
Diffstat (limited to 'lib/gui')
-rw-r--r--lib/gui/edit_json/__init__.py8
-rw-r--r--lib/gui/edit_json/editable_list.py39
-rw-r--r--lib/gui/edit_json/help_panel.py42
-rw-r--r--lib/gui/edit_json/main_panel.py383
-rw-r--r--lib/gui/edit_json/settings_panel.py276
-rw-r--r--lib/gui/lettering_kerning.py21
6 files changed, 757 insertions, 12 deletions
diff --git a/lib/gui/edit_json/__init__.py b/lib/gui/edit_json/__init__.py
new file mode 100644
index 00000000..3616c0f5
--- /dev/null
+++ b/lib/gui/edit_json/__init__.py
@@ -0,0 +1,8 @@
+# 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 .help_panel import HelpPanel
+from .settings_panel import SettingsPanel
+from .main_panel import LetteringEditJsonPanel
diff --git a/lib/gui/edit_json/editable_list.py b/lib/gui/edit_json/editable_list.py
new file mode 100644
index 00000000..51026a0c
--- /dev/null
+++ b/lib/gui/edit_json/editable_list.py
@@ -0,0 +1,39 @@
+import wx
+from wx.lib.mixins.listctrl import TextEditMixin
+
+
+class EditableListCtrl(wx.ListCtrl, TextEditMixin):
+
+ def __init__(self, parent, ID=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0):
+ wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
+ TextEditMixin.__init__(self)
+
+ def OpenEditor(self, column, row):
+ self.original_data = self.GetItemText(row, column)
+ if column == 2:
+ TextEditMixin.OpenEditor(self, column, row)
+ self.editor.Bind(wx.EVT_KEY_DOWN, self.on_escape)
+
+ def on_escape(self, event=None):
+ keycode = event.GetKeyCode()
+ if keycode == wx.WXK_ESCAPE:
+ self.CloseEditor(event=None, swap=True)
+ event.Skip()
+
+ def CloseEditor(self, event=None, swap=False):
+ text = self.editor.GetValue()
+ if swap:
+ self.editor.Hide()
+ TextEditMixin.CloseEditor(self, event)
+ return
+
+ if text:
+ try:
+ float(text)
+ except ValueError:
+ swap = True
+
+ if swap:
+ self.editor.SetValue(self.original_data)
+
+ TextEditMixin.CloseEditor(self, event)
diff --git a/lib/gui/edit_json/help_panel.py b/lib/gui/edit_json/help_panel.py
new file mode 100644
index 00000000..739830a6
--- /dev/null
+++ b/lib/gui/edit_json/help_panel.py
@@ -0,0 +1,42 @@
+# Authors: see git history
+#
+# Copyright (c) 2024 Authors
+# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
+
+import wx
+
+from ...i18n import _
+
+
+class HelpPanel(wx.Panel):
+ def __init__(self, parent):
+ wx.Panel.__init__(self, parent)
+ help_sizer = wx.BoxSizer(wx.VERTICAL)
+
+ help_text = wx.StaticText(
+ self,
+ wx.ID_ANY,
+ _("Feature to verify or update font information for an existing font."),
+ style=wx.ALIGN_LEFT
+ )
+ help_text.Wrap(500)
+ help_sizer.Add(help_text, 0, wx.ALL, 20)
+
+ help_sizer.Add((20, 20), 0, 0, 0)
+
+ website_info = wx.StaticText(self, wx.ID_ANY, _("More information on our website:"))
+ help_sizer.Add(website_info, 0, wx.ALL, 8)
+
+ self.website_link = wx.adv.HyperlinkCtrl(
+ self,
+ wx.ID_ANY,
+ _("https://inkstitch.org/docs/font-tools/#edit-json"),
+ _("https://inkstitch.org/docs/font-tools/#edit-json")
+ )
+ self.website_link.Bind(wx.adv.EVT_HYPERLINK, self.on_link_clicked)
+ help_sizer.Add(self.website_link, 0, wx.ALL, 8)
+
+ self.SetSizer(help_sizer)
+
+ def on_link_clicked(self, event):
+ event.Skip()
diff --git a/lib/gui/edit_json/main_panel.py b/lib/gui/edit_json/main_panel.py
new file mode 100644
index 00000000..29e0e9d0
--- /dev/null
+++ b/lib/gui/edit_json/main_panel.py
@@ -0,0 +1,383 @@
+# Authors: see git history
+#
+# Copyright (c) 2023 Authors
+# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
+
+import json
+from collections import defaultdict
+from copy import deepcopy
+from itertools import combinations_with_replacement
+from os import path
+
+import wx
+import wx.adv
+from inkex import errormsg
+
+from ...elements import nodes_to_elements
+from ...i18n import _
+from ...lettering import get_font_list
+from ...lettering.categories import FONT_CATEGORIES
+from ...stitch_plan import stitch_groups_to_stitch_plan
+from ...svg.tags import SVG_PATH_TAG
+from ...utils.threading import ExitThread, check_stop_flag
+from .. import PreviewRenderer
+from . import HelpPanel, SettingsPanel
+
+LETTER_CASE = {0: '', 1: 'upper', 2: 'lower'}
+
+
+class LetteringEditJsonPanel(wx.Panel):
+
+ def __init__(self, parent, simulator, layer, metadata=None, background_color='white'):
+ self.parent = parent
+ self.simulator = simulator
+ self.layer = layer
+ self.metadata = metadata or dict()
+ self.background_color = background_color
+
+ self.fonts = None
+ self.font = None
+ self.default_variant = None
+ self.font_meta = defaultdict(list)
+ self.glyphs = None
+ self.kerning_pairs = None
+ self.kerning_combinations = []
+ self.horiz_adv_x = {}
+
+ self.text_before = ''
+ self.text_after = ''
+
+ super().__init__(parent, wx.ID_ANY)
+
+ self.SetWindowStyle(wx.FRAME_FLOAT_ON_PARENT | wx.DEFAULT_FRAME_STYLE)
+
+ # preview
+ self.preview_renderer = PreviewRenderer(self.render_stitch_plan, self.on_stitch_plan_rendered)
+
+ notebook_sizer = wx.BoxSizer(wx.VERTICAL)
+ self.notebook = wx.Notebook(self, wx.ID_ANY)
+ notebook_sizer.Add(self.notebook, 1, wx.EXPAND, 0)
+
+ self.settings = wx.Panel(self.notebook, wx.ID_ANY)
+ self.settings_panel = SettingsPanel(self.notebook)
+ self.notebook.AddPage(self.settings_panel, _("Settings"))
+ self.notebook.AddPage(HelpPanel(self.notebook), _("Help"))
+
+ self.SetSizer(notebook_sizer)
+
+ self.set_font_list()
+ self.settings_panel.font_chooser.SetValue(list(self.fonts.values())[0].marked_custom_font_name)
+ self.on_font_changed()
+
+ self.SetSizeHints(notebook_sizer.CalcMin())
+ self.Layout()
+
+ def on_text_before_changed(self, event):
+ self.text_before = event.GetEventObject().GetValue()
+ self.update_preview()
+
+ def on_text_after_changed(self, event):
+ self.text_after = event.GetEventObject().GetValue()
+ self.update_preview()
+
+ def on_kerning_update(self, event=None):
+ self.update_preview()
+
+ def on_kerning_list_select(self, event=None):
+ self.update_preview()
+ event.Skip()
+
+ def on_font_meta_value_changed(self, name, needs_update, event=None):
+ self.font_meta[name] = event.GetEventObject().GetValue()
+ import sys
+ print(self.font_meta[name], file=sys.stderr)
+ if needs_update:
+ self.update_preview()
+
+ def on_keyword_changed(self, event=None):
+ keywords = []
+ selections = self.settings_panel.font_info.keywords.GetSelections()
+ for selection in selections:
+ cat_name = self.settings_panel.font_info.keywords.GetString(selection)
+ for category in FONT_CATEGORIES:
+ if cat_name == category.name:
+ keywords.append(category.id)
+ self.font_meta['keywords'] = keywords
+
+ def on_combine_indices_changed(self, event=None):
+ indices = self.settings_panel.font_settings.combine_at_sort_indices.GetValue()
+ if not indices:
+ self.font_meta['combine_at_sort_indices'] = ''
+ return
+ indices = indices.split(',')
+ try:
+ indices = [int(i) for i in indices]
+ except ValueError:
+ self.settings_panel.font_settings.combine_at_sort_indices.SetForegroundColour('red')
+ return
+ self.settings_panel.font_settings.combine_at_sort_indices.SetForegroundColour(wx.NullColour)
+ self.font_meta['combine_at_sort_indices'] = indices
+
+ def on_letter_case_change(self, event=None):
+ selection = self.settings_panel.font_settings.letter_case.GetSelection()
+ value = ''
+ if selection == 1:
+ value = 'upper'
+ elif selection == 2:
+ value = 'lower'
+ self.font_meta['letter_case'] = value
+
+ def set_font_list(self):
+ self.fonts = {}
+ font_list = get_font_list()
+ for font in font_list:
+ self.fonts[font.marked_custom_font_name] = font
+ image = font.preview_image
+ if image is not None:
+ image = wx.Image(image)
+ # Windows requires all images to have the exact same size
+ image.Rescale(300, 20, quality=wx.IMAGE_QUALITY_HIGH)
+ self.settings_panel.font_chooser.Append(font.marked_custom_font_name, wx.Bitmap(image))
+ else:
+ self.settings_panel.font_chooser.Append(font.marked_custom_font_name)
+
+ def get_active_kerning_pair(self):
+ kerning_list = self.settings_panel.kerning_list
+ selection = kerning_list.GetFirstSelected()
+ if selection == -1:
+ return ''
+ kerning_pair = kerning_list.GetItem(selection, 0).Text
+ kerning = float(kerning_list.GetItem(selection, 1).Text)
+ if kerning_list.GetItem(selection, 2).Text:
+ try:
+ kerning = float(kerning_list.GetItem(selection, 2).Text)
+ self.kerning_pairs[kerning_pair] = float(kerning)
+ except (ValueError, IndexError):
+ pass
+ return kerning_pair
+
+ def get_active_glyph(self):
+ glyph_list = self.settings_panel.glyph_list
+ selection = glyph_list.GetFirstSelected()
+ if selection == -1:
+ return ''
+ glyph = glyph_list.GetItem(selection, 0).Text
+ horiz_adv_x = float(glyph_list.GetItem(selection, 1).Text)
+ if glyph_list.GetItem(selection, 2).Text:
+ try:
+ horiz_adv_x = float(glyph_list.GetItem(selection, 2).Text)
+ self.horiz_adv_x[glyph] = float(horiz_adv_x)
+ except (ValueError, IndexError):
+ pass
+ return glyph
+
+ def on_font_changed(self, event=None):
+ self.font = self.fonts.get(self.settings_panel.font_chooser.GetValue(), list(self.fonts.values())[0].marked_custom_font_name)
+ self.kerning_pairs = self.font.kerning_pairs
+ self.font._load_variants()
+ self.default_variant = self.font.variants[self.font.default_variant]
+ self.glyphs = list(self.default_variant.glyphs.keys())
+ 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 = list(set(self.kerning_combinations))
+ self.kerning_combinations.sort()
+
+ self.update_settings()
+ self.update_kerning_list()
+ self.update_glyph_list()
+ self.update_preview()
+
+ def update_settings(self):
+ # reset font_meta
+ self.font_meta = defaultdict(list)
+ self.font_meta['name'] = self.font.name
+ self.font_meta['description'] = self.font.metadata['description'] # untranslated description
+ self.font_meta['keywords'] = self.font.keywords
+ self.font_meta['default_glyph'] = self.font.default_glyph
+ self.font_meta['auto_satin'] = self.font.auto_satin
+ self.font_meta['letter_case'] = self.font.letter_case
+ self.font_meta['reversible'] = self.font.reversible
+ self.font_meta['sortable'] = self.font.sortable
+ self.font_meta['combine_at_sort_indices'] = self.font.combine_at_sort_indices
+ self.font_meta['leading'] = self.font.leading
+ self.font_meta['size'] = self.font.size
+ self.font_meta['max_scale'] = self.font.max_scale
+ self.font_meta['min_scale'] = self.font.min_scale
+ self.font_meta['horiz_adv_x_default'] = self.font.horiz_adv_x_default
+ self.font_meta['horiz_adv_x_space'] = self.font.word_spacing
+
+ # update ctrl
+ self.settings_panel.font_info.name.ChangeValue(self.font.name)
+ self.settings_panel.font_info.description.ChangeValue(self.font.metadata['description'])
+ self.settings_panel.font_info.keywords.SetSelection(-1)
+ for category in FONT_CATEGORIES:
+ if category.id in self.font.keywords:
+ self.settings_panel.font_info.keywords.SetStringSelection(category.name)
+ self.settings_panel.font_settings.default_glyph.ChangeValue(self.font.default_glyph)
+ self.settings_panel.font_settings.auto_satin.SetValue(self.font.auto_satin)
+ selection = list(LETTER_CASE.keys())[list(LETTER_CASE.values()).index(self.font.letter_case)]
+ self.settings_panel.font_settings.letter_case.SetSelection(selection)
+ self.settings_panel.font_settings.reversible.SetValue(self.font.reversible)
+ self.settings_panel.font_settings.sortable.SetValue(self.font.sortable)
+ self.settings_panel.font_settings.combine_at_sort_indices.ChangeValue(
+ ', '.join([str(i) for i in self.font.combine_at_sort_indices])
+ )
+ self.settings_panel.font_kerning.leading.SetValue(self.font.leading)
+ self.settings_panel.font_kerning.size.SetValue(self.font.size)
+ self.settings_panel.font_kerning.max_scale.SetValue(self.font.max_scale)
+ self.settings_panel.font_kerning.min_scale.SetValue(self.font.min_scale)
+ self.settings_panel.font_kerning.horiz_adv_x_default.SetValue(self.font.horiz_adv_x_default)
+ self.settings_panel.font_kerning.horiz_adv_x_space.SetValue(self.font.word_spacing)
+
+ def update_kerning_list(self):
+ kerning_list = self.settings_panel.kerning_list
+ # Add the rows
+ kerning_list.ClearAll()
+ # Add some columns
+ kerning_list.InsertColumn(0, "Kerning pair")
+ kerning_list.InsertColumn(1, "Current kerning")
+ kerning_list.InsertColumn(2, "New kerning")
+ # Set the width of the columns
+ kerning_list.SetColumnWidth(0, 120)
+ kerning_list.SetColumnWidth(1, 120)
+ kerning_list.SetColumnWidth(2, 120)
+ for kerning_pair in self.kerning_combinations:
+ 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)))
+ if kerning_list.GetItemCount() != 0:
+ kerning_list.Select(0)
+ kerning_list.Focus(0)
+
+ def update_glyph_list(self):
+ glyph_list = self.settings_panel.glyph_list
+ # Add the rows
+ glyph_list.ClearAll()
+ # Add some columns
+ glyph_list.InsertColumn(0, "Glyph")
+ glyph_list.InsertColumn(1, "Current horizontal advance")
+ glyph_list.InsertColumn(2, "New horizontal advance")
+ # Set the width of the columns
+ glyph_list.SetColumnWidth(0, 120)
+ glyph_list.SetColumnWidth(1, 120)
+ glyph_list.SetColumnWidth(2, 120)
+ horiz_adv_x_default = self.font.horiz_adv_x_default
+ for glyph in self.glyphs:
+ index = glyph_list.InsertItem(glyph_list.GetItemCount(), glyph)
+ glyph_list.SetItem(index, 0, glyph)
+ glyph_list.SetItem(index, 1, str(self.font.horiz_adv_x.get(glyph, horiz_adv_x_default)))
+ if glyph_list.GetItemCount() != 0:
+ glyph_list.Select(0)
+ glyph_list.Focus(0)
+
+ def apply(self, event):
+ json_file = path.join(self.font.path, 'font.json')
+
+ if not path.isfile(json_file) or not path.isfile(json_file):
+ errormsg(_("Could not read json file."))
+ return
+
+ with open(json_file, 'r') as font_data:
+ data = json.load(font_data)
+
+ for key, val in self.font_meta.items():
+ data[key] = val
+ horiz_adv_x = {key: val for key, val in self.horiz_adv_x.items() if val != self.font.horiz_adv_x_default}
+ kerning_pairs = {key: val for key, val in self.kerning_pairs.items() if val != 0}
+ data['horiz_adv_x'] = horiz_adv_x
+ data['kerning_pairs'] = kerning_pairs
+ data['glyphs'] = self.glyphs
+
+ # write data to font.json into the same directory as the font file
+ with open(json_file, 'w', encoding="utf8") as font_data:
+ json.dump(data, font_data, indent=4, ensure_ascii=False)
+
+ self.GetTopLevelParent().Close()
+
+ def cancel(self, event):
+ self.GetTopLevelParent().Close()
+
+ def update_preview(self, event=None):
+ self.preview_renderer.update()
+
+ def update_lettering(self):
+ del self.layer[:]
+
+ if self.settings_panel.notebook.GetSelection() == 3:
+ text = self.get_active_glyph()
+ else:
+ text = self.get_active_kerning_pair()
+ if not text:
+ return
+
+ text = self.text_before + text + self.text_after
+
+ 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:
+ if glyph is None:
+ glyph = self.default_variant[self.font_meta['default_glyph']]
+
+ if glyph is not None:
+ node = deepcopy(glyph.node)
+ if last_character is not None:
+ position_x += glyph.min_x - self.kerning_pairs.get(last_character + character, 0)
+
+ transform = f"translate({position_x}, 0)"
+ node.set('transform', transform)
+
+ horiz_adv_x_default = self.font_meta['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(self.layer, 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")
+ self.layer.add(node)
+ last_character = character
+
+ def render_stitch_plan(self):
+ stitch_groups = []
+ try:
+ self.update_lettering()
+ elements = nodes_to_elements(self.layer.iterdescendants(SVG_PATH_TAG))
+ last_stitch_group = None
+ for element in elements:
+ check_stop_flag()
+ stitch_groups.extend(element.embroider(last_stitch_group))
+ if stitch_groups:
+ last_stitch_group = stitch_groups[-1]
+
+ if stitch_groups:
+ return stitch_groups_to_stitch_plan(
+ stitch_groups,
+ collapse_len=self.metadata['collapse_len_mm'],
+ min_stitch_len=self.metadata['min_stitch_len_mm']
+ )
+ except SystemExit:
+ raise
+ except ExitThread:
+ raise
+ except Exception:
+ raise
+ # Ignore errors. This can be things like incorrect paths for
+ # satins or division by zero caused by incorrect param values.
+ pass
+
+ def on_stitch_plan_rendered(self, stitch_plan):
+ self.simulator.stop()
+ self.simulator.load(stitch_plan)
+ self.simulator.go()
diff --git a/lib/gui/edit_json/settings_panel.py b/lib/gui/edit_json/settings_panel.py
new file mode 100644
index 00000000..d600153b
--- /dev/null
+++ b/lib/gui/edit_json/settings_panel.py
@@ -0,0 +1,276 @@
+# Authors: see git history
+#
+# Copyright (c) 2024 Authors
+# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
+
+import wx
+
+from ...i18n import _
+from ...lettering.categories import FONT_CATEGORIES
+from .editable_list import EditableListCtrl
+
+
+class SettingsPanel(wx.Panel):
+ def __init__(self, parent):
+ self.parent = parent.GetParent()
+ wx.Panel.__init__(self, parent)
+ # settings
+ settings_sizer = wx.BoxSizer(wx.VERTICAL)
+
+ self.font_chooser = wx.adv.BitmapComboBox(self, wx.ID_ANY, style=wx.CB_READONLY | wx.CB_SORT, size=(600, 40))
+ self.font_chooser.Bind(wx.EVT_COMBOBOX, self.parent.on_font_changed)
+
+ text_before_label = wx.StaticText(self, label=_("Text before"))
+ text_before = wx.TextCtrl(self)
+ text_before.Bind(wx.EVT_TEXT, self.parent.on_text_before_changed)
+ text_after_label = wx.StaticText(self, label=_("Text after"))
+ text_after = wx.TextCtrl(self)
+ text_after.Bind(wx.EVT_TEXT, self.parent.on_text_after_changed)
+ grid_text_sizer = wx.FlexGridSizer(2, 2, 10, 10)
+ grid_text_sizer.AddGrowableCol(1)
+ grid_text_sizer.AddMany([
+ (text_before_label, 1, wx.ALL, 0),
+ (text_before, 1, wx.EXPAND, 0),
+ (text_after_label, 1, wx.ALL, 0),
+ (text_after, 1, wx.EXPAND, 0)
+ ])
+
+ notebook_sizer = wx.BoxSizer(wx.VERTICAL)
+ self.notebook = wx.Notebook(self, wx.ID_ANY)
+ notebook_sizer.Add(self.notebook, 1, wx.EXPAND, 0)
+ self.notebook.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.parent.update_preview)
+
+ self.font_info = FontInfo(self.notebook)
+ self.notebook.AddPage(self.font_info, _("Font Info"))
+
+ self.font_settings = FontSettings(self.notebook)
+ self.notebook.AddPage(self.font_settings, _("Font Settings"))
+
+ self.font_kerning = GeneralKerning(self.notebook)
+ self.notebook.AddPage(self.font_kerning, _("General Kerning"))
+
+ glyph_list = KerningPairs(self.notebook)
+ self.notebook.AddPage(glyph_list, _("Horizontal advance"))
+ self.glyph_list = glyph_list.kerning_list
+
+ kerning_pairs = KerningPairs(self.notebook)
+ self.notebook.AddPage(kerning_pairs, _("Kerning pairs"))
+ self.kerning_list = kerning_pairs.kerning_list
+
+ apply_sizer = wx.BoxSizer(wx.HORIZONTAL)
+ self.cancel_button = wx.Button(self, label=_("Cancel"))
+ self.cancel_button.Bind(wx.EVT_BUTTON, self.parent.cancel)
+ self.apply_button = wx.Button(self, label=_("Apply"))
+ self.apply_button.Bind(wx.EVT_BUTTON, self.parent.apply)
+ apply_sizer.Add(self.cancel_button, 0, wx.RIGHT | wx.BOTTOM, 5)
+ apply_sizer.Add(self.apply_button, 0, wx.RIGHT | wx.BOTTOM, 10)
+
+ settings_sizer.Add(self.font_chooser, 0, wx.ALL | wx.EXPAND, 10)
+ settings_sizer.Add(grid_text_sizer, 0, wx.ALL | wx.EXPAND, 10)
+ settings_sizer.Add(notebook_sizer, 2, wx.ALL | wx.EXPAND, 10)
+ settings_sizer.Add(apply_sizer, 0, wx.ALIGN_RIGHT | wx.ALL, 10)
+
+ self.SetSizer(settings_sizer)
+
+
+class FontInfo(wx.Panel):
+ def __init__(self, parent):
+ self.parent = parent.GetParent().parent
+ wx.Panel.__init__(self, parent)
+
+ sizer = wx.BoxSizer(wx.VERTICAL)
+ grid_sizer = wx.FlexGridSizer(20, 2, 10, 10)
+ grid_sizer.AddGrowableCol(1)
+
+ name_label = wx.StaticText(self, label=_("Name"))
+ self.name = wx.TextCtrl(self)
+ self.name.Bind(
+ wx.EVT_TEXT,
+ lambda event: self.parent.on_font_meta_value_changed("name", False, event)
+ )
+
+ description_label = wx.StaticText(self, label=_("Description"))
+ self.description = wx.TextCtrl(self, size=wx.Size(10, 100), style=wx.TE_MULTILINE)
+ self.description.Bind(
+ wx.EVT_TEXT,
+ lambda event: self.parent.on_font_meta_value_changed("description", False, event)
+ )
+
+ keywords_label = wx.StaticText(self, label=_("Keywords"))
+ self.keywords = wx.ListBox(
+ self,
+ size=wx.Size(10, 400),
+ choices=[cat.name for cat in FONT_CATEGORIES],
+ style=wx.CB_SORT | wx.LB_EXTENDED
+ )
+ self.keywords.Bind(wx.EVT_LISTBOX, self.parent.on_keyword_changed)
+
+ grid_sizer.AddMany([
+ (name_label, 0, wx.ALL, 0),
+ (self.name, 0, wx.ALL | wx.EXPAND, 0),
+ (description_label, 0, wx.ALL, 0),
+ (self.description, 1, wx.ALL | wx.EXPAND, 0),
+ (keywords_label, 0, wx.ALL, 0),
+ (self.keywords, 1, wx.ALL | wx.EXPAND, 0)
+ ])
+
+ sizer.Add(grid_sizer, 1, wx.EXPAND | wx.ALL, 10)
+ self.SetSizer(sizer)
+
+
+class FontSettings(wx.Panel):
+ def __init__(self, parent):
+ self.parent = parent.GetParent().parent
+ wx.Panel.__init__(self, parent)
+
+ sizer = wx.BoxSizer(wx.VERTICAL)
+ grid_sizer = wx.FlexGridSizer(20, 2, 10, 10)
+ grid_sizer.AddGrowableCol(1)
+
+ default_glyph_label = wx.StaticText(self, label=_("Default glyph"))
+ self.default_glyph = wx.TextCtrl(self)
+ self.default_glyph.Bind(
+ wx.EVT_TEXT,
+ lambda event: self.parent.on_font_meta_value_changed("default_glyph", True, event)
+ )
+
+ auto_satin_label = wx.StaticText(self, label=_("AutoSatin"))
+ self.auto_satin = wx.CheckBox(self)
+ self.auto_satin.Bind(
+ wx.EVT_CHECKBOX,
+ lambda event: self.parent.on_font_meta_value_changed("auto_satin", False, event)
+ )
+
+ letter_case_label = wx.StaticText(self, label=_("Letter case"))
+ self.letter_case = wx.Choice(self, choices=[_("None"), _("Upper"), _("Lower")])
+ self.letter_case.Bind(wx.EVT_CHOICE, self.parent.on_letter_case_change)
+
+ reversible_label = wx.StaticText(self, label=_("Reversible"))
+ self.reversible = wx.CheckBox(self)
+ self.reversible.Bind(
+ wx.EVT_CHECKBOX,
+ lambda event: self.parent.on_font_meta_value_changed("reversible", False, event)
+ )
+
+ sortable_label = wx.StaticText(self, label=_("Sortable"))
+ self.sortable = wx.CheckBox(self)
+ self.sortable.Bind(
+ wx.EVT_CHECKBOX,
+ lambda event: self.parent.on_font_meta_value_changed("sortable", False, event)
+ )
+
+ combine_indices_label = wx.StaticText(self, label=_("Combine Indices"))
+ self.combine_at_sort_indices = wx.TextCtrl(self)
+ self.combine_at_sort_indices.Bind(wx.EVT_TEXT, self.parent.on_combine_indices_changed)
+
+ grid_sizer.AddMany([
+ (default_glyph_label, 0, wx.ALL, 0),
+ (self.default_glyph, 1, wx.ALL | wx.EXPAND, 0),
+ (auto_satin_label, 0, wx.ALL, 0),
+ (self.auto_satin, 1, wx.ALL | wx.EXPAND, 0),
+ (letter_case_label, 0, wx.ALL, 0),
+ (self.letter_case, 1, wx.ALL | wx.EXPAND, 0),
+ (reversible_label, 0, wx.ALL, 0),
+ (self.reversible, 1, wx.ALL | wx.EXPAND, 0),
+ (sortable_label, 0, wx.ALL, 0),
+ (self.sortable, 1, wx.ALL | wx.EXPAND, 0),
+ (combine_indices_label, 0, wx.ALL, 0),
+ (self.combine_at_sort_indices, 1, wx.ALL | wx.EXPAND, 0)
+ ])
+ sizer.Add(grid_sizer, 1, wx.EXPAND | wx.ALL, 10)
+ self.SetSizer(sizer)
+
+
+class GeneralKerning(wx.Panel):
+ def __init__(self, parent):
+ self.parent = parent.GetParent().parent
+ wx.Panel.__init__(self, parent)
+
+ sizer = wx.BoxSizer(wx.VERTICAL)
+ grid_sizer = wx.FlexGridSizer(20, 2, 10, 10)
+ grid_sizer.AddGrowableCol(1)
+
+ size_label = wx.StaticText(self, label=_("Size"))
+ self.size = wx.SpinCtrlDouble(self, min=0, max=10000, inc=0.1, initial=50, style=wx.SP_WRAP)
+ self.size.Bind(
+ wx.EVT_SPINCTRLDOUBLE,
+ lambda event: self.parent.on_font_meta_value_changed("size", True, event)
+ )
+ min_scale_label = wx.StaticText(self, label=_("Min Scale"))
+ self.min_scale = wx.SpinCtrlDouble(self, min=0, max=100, inc=0.1, initial=1, style=wx.SP_WRAP)
+ self.min_scale.Bind(
+ wx.EVT_SPINCTRLDOUBLE,
+ lambda event: self.parent.on_font_meta_value_changed("min_scale", True, event)
+ )
+ max_scale_label = wx.StaticText(self, label=_("Max Scale"))
+ self.max_scale = wx.SpinCtrlDouble(self, min=0, max=100, inc=0.1, initial=1, style=wx.SP_WRAP)
+ self.max_scale.Bind(
+ wx.EVT_SPINCTRLDOUBLE,
+ lambda event: self.parent.on_font_meta_value_changed("max_scale", True, event)
+ )
+ leading_label = wx.StaticText(self, label=_("Leading"))
+ self.leading = wx.SpinCtrlDouble(self, min=0, max=10000, inc=1, initial=0, style=wx.SP_WRAP)
+ self.leading.Bind(
+ wx.EVT_SPINCTRLDOUBLE,
+ lambda event: self.parent.on_font_meta_value_changed("leading", False, event)
+ )
+ horiz_adv_x_default_label = wx.StaticText(self, label=_("Horizontal advance x"))
+ self.horiz_adv_x_default = wx.SpinCtrlDouble(self, min=0, max=10000, inc=0.1, initial=50, style=wx.SP_WRAP)
+ self.horiz_adv_x_default.Bind(
+ wx.EVT_SPINCTRLDOUBLE,
+ lambda event: self.parent.on_font_meta_value_changed("horiz_adv_x_default", True, event)
+ )
+ horiz_adv_x_space_label = wx.StaticText(self, label=_("Horizontal advance x space"))
+ self.horiz_adv_x_space = wx.SpinCtrlDouble(self, min=0, max=10000, inc=0.1, initial=50, style=wx.SP_WRAP)
+ self.horiz_adv_x_space.Bind(
+ wx.EVT_SPINCTRLDOUBLE,
+ lambda event: self.parent.on_font_meta_value_changed("horiz_adv_x_space", True, event)
+ )
+
+ grid_sizer.AddMany([
+ (size_label, 0, wx.ALL, 0),
+ (self.size, 1, wx.ALL | wx.EXPAND, 0),
+ (min_scale_label, 0, wx.ALL, 0),
+ (self.min_scale, 1, wx.ALL | wx.EXPAND, 0),
+ (max_scale_label, 0, wx.ALL, 0),
+ (self.max_scale, 1, wx.ALL | wx.EXPAND, 0),
+ (leading_label, 0, wx.ALL, 0),
+ (self.leading, 1, wx.ALL | wx.EXPAND, 0),
+ (horiz_adv_x_default_label, 0, wx.ALL, 0),
+ (self.horiz_adv_x_default, 1, wx.ALL | wx.EXPAND, 0),
+ (horiz_adv_x_space_label, 0, wx.ALL, 0),
+ (self.horiz_adv_x_space, 1, wx.ALL | wx.EXPAND, 0)
+ ])
+
+ sizer.Add(grid_sizer, 1, wx.EXPAND | wx.ALL, 10)
+ self.SetSizer(sizer)
+
+
+class GlyphList(wx.Panel):
+ def __init__(self, parent):
+ self.parent = parent.GetParent().parent
+ wx.Panel.__init__(self, parent)
+
+ sizer = wx.BoxSizer(wx.VERTICAL)
+
+ self.glyph_list = EditableListCtrl(self, style=wx.LC_REPORT | wx.SUNKEN_BORDER)
+ self.glyph_list.Bind(wx.EVT_LIST_ITEM_SELECTED, self.parent.on_kerning_list_select)
+ self.glyph_list.Bind(wx.EVT_LIST_END_LABEL_EDIT, self.parent.on_kerning_update)
+
+ sizer.Add(self.glyph_list, 1, wx.EXPAND, 0)
+ self.SetSizer(sizer)
+
+
+class KerningPairs(wx.Panel):
+ def __init__(self, parent):
+ self.parent = parent.GetParent().parent
+ wx.Panel.__init__(self, parent)
+
+ sizer = wx.BoxSizer(wx.VERTICAL)
+
+ self.kerning_list = EditableListCtrl(self, style=wx.LC_REPORT | wx.SUNKEN_BORDER)
+ self.kerning_list.Bind(wx.EVT_LIST_ITEM_SELECTED, self.parent.on_kerning_list_select)
+ self.kerning_list.Bind(wx.EVT_LIST_END_LABEL_EDIT, self.parent.on_kerning_update)
+
+ sizer.Add(self.kerning_list, 1, wx.EXPAND, 0)
+ self.SetSizer(sizer)
diff --git a/lib/gui/lettering_kerning.py b/lib/gui/lettering_kerning.py
index 11ec4b6e..73e40865 100644
--- a/lib/gui/lettering_kerning.py
+++ b/lib/gui/lettering_kerning.py
@@ -37,8 +37,11 @@ class LetteringKerningPanel(wx.Panel):
self.fonts = None
self.font = None
+ self.default_variant = None
+ self.glyphs = None
self.kerning_pairs = None
self.kerning_combinations = []
+
self.text_before = ''
self.text_after = ''
@@ -176,8 +179,10 @@ class LetteringKerningPanel(wx.Panel):
self.font = self.fonts.get(self.font_chooser.GetValue(), list(self.fonts.values())[0].marked_custom_font_name)
self.kerning_pairs = self.font.kerning_pairs
self.font._load_variants()
+ self.default_variant = self.font.variants[self.font.default_variant]
+ self.glyphs = list(self.default_variant.glyphs.keys())
- kerning_combinations = combinations_with_replacement(self.font.available_glyphs, 2)
+ 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 = list(set(self.kerning_combinations))
@@ -215,6 +220,7 @@ class LetteringKerningPanel(wx.Panel):
kerning_pairs = {key: val for key, val in self.kerning_pairs.items() if val != 0}
data['kerning_pairs'] = kerning_pairs
+ data['glyphs'] = self.glyphs
# write data to font.json into the same directory as the font file
with open(json_file, 'w', encoding="utf8") as font_data:
@@ -222,14 +228,6 @@ class LetteringKerningPanel(wx.Panel):
self.GetTopLevelParent().Close()
- def duplicate_warning(self):
- # warn about duplicated glyphs
- if len(set(self.font.available_glyphs)) != len(self.font.available_glyphs):
- duplicated_glyphs = " ".join(
- [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 cancel(self, event):
self.GetTopLevelParent().Close()
@@ -239,7 +237,6 @@ class LetteringKerningPanel(wx.Panel):
def update_lettering(self):
del self.layer[:]
- variant = self.font.variants[self.font.default_variant]
text = self.get_active_kerning_pair()
if not text:
return
@@ -249,13 +246,13 @@ class LetteringKerningPanel(wx.Panel):
last_character = None
position_x = 0
for character in text:
- glyph = variant[character]
+ glyph = self.default_variant[character]
if character == " " or (glyph is None and self.font.default_glyph == " "):
position_x += self.font.word_spacing
last_character = None
else:
if glyph is None:
- glyph = variant[self.font.default_glyph]
+ glyph = self.default_variant[self.font.default_glyph]
if glyph is not None:
node = deepcopy(glyph.node)