diff --git a/lib/extensions/__init__.py b/lib/extensions/__init__.py index c0ddf0cef..7674fba52 100644 --- a/lib/extensions/__init__.py +++ b/lib/extensions/__init__.py @@ -31,6 +31,7 @@ from .layer_commands import LayerCommands from .lettering import Lettering from .lettering_along_path import LetteringAlongPath from .lettering_custom_font_dir import LetteringCustomFontDir +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 @@ -90,6 +91,7 @@ __all__ = extensions = [ApplyPalette, Lettering, LetteringAlongPath, LetteringCustomFontDir, + LetteringFontSample, LetteringForceLockStitches, LetteringGenerateJson, LetteringRemoveKerning, diff --git a/lib/extensions/lettering_font_sample.py b/lib/extensions/lettering_font_sample.py new file mode 100644 index 000000000..2da7d44e3 --- /dev/null +++ b/lib/extensions/lettering_font_sample.py @@ -0,0 +1,27 @@ +# Authors: see git history +# +# Copyright (c) 2010 Authors +# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. + +from inkex import Layer + +from ..gui.lettering_font_sample import LetteringFontSampleApp +from ..i18n import _ +from .base import InkstitchExtension +from ..svg import get_correction_transform + + +class LetteringFontSample(InkstitchExtension): + ''' + This extension helps font creators to generate an output of every glyph from a selected font + ''' + def effect(self): + layer = Layer() + self.svg.add(layer) + layer.label = _("Font Sample") + transform = get_correction_transform(layer, child=True) + layer.transform = transform + app = LetteringFontSampleApp(layer=layer) + app.MainLoop() + if len(layer) == 0: + self.svg.remove(layer) diff --git a/lib/gui/lettering.py b/lib/gui/lettering.py index 0d006f9e1..edbebb208 100644 --- a/lib/gui/lettering.py +++ b/lib/gui/lettering.py @@ -4,23 +4,20 @@ # Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. import json -import os from base64 import b64decode -import appdirs import inkex import wx import wx.adv import wx.lib.agw.floatspin as fs from ..elements import nodes_to_elements -from ..extensions.lettering_custom_font_dir import get_custom_font_dir from ..i18n import _ -from ..lettering import Font, FontError +from ..lettering import FontError, get_font_list from ..lettering.categories import FONT_CATEGORIES, FontCategory from ..stitch_plan import stitch_groups_to_stitch_plan from ..svg.tags import INKSCAPE_LABEL, INKSTITCH_LETTERING, SVG_PATH_TAG -from ..utils import DotDict, cache, get_bundled_dir +from ..utils import DotDict, cache from ..utils.threading import ExitThread, check_stop_flag from . import PresetsPanel, PreviewRenderer, info_dialog @@ -153,26 +150,7 @@ class LetteringPanel(wx.Panel): @property @cache def font_list(self): - fonts = [] - font_paths = { - get_bundled_dir("fonts"), - os.path.expanduser("~/.inkstitch/fonts"), - os.path.join(appdirs.user_config_dir('inkstitch'), 'fonts'), - get_custom_font_dir() - } - - for font_path in font_paths: - try: - font_dirs = os.listdir(font_path) - except OSError: - continue - - for font_dir in font_dirs: - font = Font(os.path.join(font_path, font_dir)) - if font.marked_custom_font_name == "" or font.marked_custom_font_id == "": - continue - fonts.append(font) - return fonts + return get_font_list() def update_font_list(self): self.fonts = {} diff --git a/lib/gui/lettering_font_sample.py b/lib/gui/lettering_font_sample.py new file mode 100644 index 000000000..cb0df222e --- /dev/null +++ b/lib/gui/lettering_font_sample.py @@ -0,0 +1,187 @@ +# 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 wx +import wx.adv + +from ..i18n import _ +from ..lettering import get_font_list + + +class FontSampleFrame(wx.Frame): + + def __init__(self, *args, **kwargs): + self.layer = kwargs.pop("layer") + wx.Frame.__init__(self, None, wx.ID_ANY, _("Font Sampling"), *args, **kwargs) + + self.SetWindowStyle(wx.FRAME_FLOAT_ON_PARENT | wx.DEFAULT_FRAME_STYLE) + + self.fonts = None + + self.main_panel = wx.Panel(self, wx.ID_ANY) + + notebook_sizer = wx.BoxSizer(wx.VERTICAL) + self.notebook = wx.Notebook(self.main_panel, wx.ID_ANY) + notebook_sizer.Add(self.notebook, 1, wx.EXPAND, 0) + + self.settings = wx.Panel(self.notebook, wx.ID_ANY) + self.notebook.AddPage(self.settings, _("Settings")) + + # settings + settings_sizer = wx.BoxSizer(wx.VERTICAL) + + 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.AddGrowableCol(1) + + direction_label = wx.StaticText(self.settings, label=_("Stitch direction")) + self.direction = wx.ComboBox(self.settings, choices=[], style=wx.CB_READONLY) + scale_spinner_label = wx.StaticText(self.settings, label=_("Scale (%)")) + 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=500) + + grid_settings_sizer.Add(direction_label, 0, wx.ALIGN_LEFT, 0) + grid_settings_sizer.Add(self.direction, 0, wx.EXPAND, 0) + grid_settings_sizer.Add(scale_spinner_label, 0, wx.ALIGN_LEFT, 0) + 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) + + apply_sizer = wx.BoxSizer(wx.HORIZONTAL) + self.cancel_button = wx.Button(self.settings, label=_("Cancel")) + self.cancel_button.Bind(wx.EVT_BUTTON, self.cancel) + self.apply_button = wx.Button(self.settings, label=_("Apply")) + self.apply_button.Bind(wx.EVT_BUTTON, self.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, 1, wx.ALL | wx.EXPAND, 10) + settings_sizer.Add(grid_settings_sizer, 1, wx.ALL | wx.EXPAND, 10) + settings_sizer.Add(apply_sizer, 1, wx.ALIGN_RIGHT | wx.ALL, 10) + + # help + self.help = wx.Panel(self.notebook, wx.ID_ANY) + self.notebook.AddPage(self.help, _("Help")) + + help_sizer = wx.BoxSizer(wx.VERTICAL) + + help_text = wx.StaticText( + self.help, + wx.ID_ANY, + _(" This extension helps font creators to generate an output of every glyph from a selected font."), + style=wx.ALIGN_LEFT + ) + help_text.Wrap(500) + help_sizer.Add(help_text, 0, wx.ALL, 8) + + help_sizer.Add((20, 20), 0, 0, 0) + + website_info = wx.StaticText(self.help, wx.ID_ANY, _("More information on our website:")) + help_sizer.Add(website_info, 0, wx.ALL, 8) + + self.website_link = wx.adv.HyperlinkCtrl( + self.help, + wx.ID_ANY, + _("https://inkstitch.org/docs/font-tools/#font-sampling"), + _("https://inkstitch.org/docs/font-tools/#font-sampling") + ) + help_sizer.Add(self.website_link, 0, wx.ALL, 8) + + self.help.SetSizer(help_sizer) + self.settings.SetSizer(settings_sizer) + self.main_panel.SetSizer(notebook_sizer) + + self.set_font_list() + self.font_chooser.SetValue(list(self.fonts.values())[0].marked_custom_font_name) + self.on_font_changed() + + self.SetSizeHints(notebook_sizer.CalcMin()) + + self.Layout() + + 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.font_chooser.Append(font.marked_custom_font_name, wx.Bitmap(image)) + else: + 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)) + # font._load_variants() + self.direction.Clear() + for variant in font.has_variants(): + self.direction.Append(variant) + self.direction.SetSelection(0) + + def apply(self, event): + # apply scale to layer and extract for later use + 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: + self.GetTopLevelParent().Close() + return + + # parameters + line_width = self.max_line_width.GetValue() + font._load_variants() + font_variant = font.variants[self.direction.GetValue()] + + # setup lines of text + text = '' + width = 0 + last_glyph = None + for glyph in font.available_glyphs: + glyph_obj = font_variant[glyph] + if last_glyph is not None: + width_to_add = (glyph_obj.min_x - font.kerning_pairs.get(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 + except TypeError: + width += glyph_obj.width + + if width + width_to_add > line_width: + text += '\n' + width = 0 + last_glyph = None + else: + last_glyph = glyph + text += glyph + width += width_to_add + + # render text and close + font.render_text(text, self.layer) + self.GetTopLevelParent().Close() + + def cancel(self, event): + self.GetTopLevelParent().Close() + + +class LetteringFontSampleApp(wx.App): + def __init__(self, layer): + self.layer = layer + super().__init__() + + def OnInit(self): + self.frame = FontSampleFrame(layer=self.layer) + self.SetTopWindow(self.frame) + self.frame.Show() + return True diff --git a/lib/lettering/__init__.py b/lib/lettering/__init__.py index a733aa9f6..dcfccaba8 100644 --- a/lib/lettering/__init__.py +++ b/lib/lettering/__init__.py @@ -4,3 +4,4 @@ # Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. from .font import Font, FontError +from .utils import get_font_list diff --git a/lib/lettering/utils.py b/lib/lettering/utils.py new file mode 100644 index 000000000..f4e2b0a70 --- /dev/null +++ b/lib/lettering/utils.py @@ -0,0 +1,35 @@ +# 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 os + +import appdirs + +from ..extensions.lettering_custom_font_dir import get_custom_font_dir +from ..lettering import Font +from ..utils import get_bundled_dir + + +def get_font_list(): + fonts = [] + font_paths = { + get_bundled_dir("fonts"), + os.path.expanduser("~/.inkstitch/fonts"), + os.path.join(appdirs.user_config_dir('inkstitch'), 'fonts'), + get_custom_font_dir() + } + + for font_path in font_paths: + try: + font_dirs = os.listdir(font_path) + except OSError: + continue + + for font_dir in font_dirs: + font = Font(os.path.join(font_path, font_dir)) + if font.marked_custom_font_name == "" or font.marked_custom_font_id == "": + continue + fonts.append(font) + return fonts diff --git a/templates/lettering_font_sample.xml b/templates/lettering_font_sample.xml new file mode 100644 index 000000000..54205125f --- /dev/null +++ b/templates/lettering_font_sample.xml @@ -0,0 +1,17 @@ + + + Font Sampling + org.{{ id_inkstitch }}.lettering_font_sample + lettering_font_sample + + all + + + + + + + +