diff options
| author | LaureFR38 <63351363+LaureFR38@users.noreply.github.com> | 2021-02-04 16:40:02 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-02-04 16:40:02 +0100 |
| commit | 1cb501986f9c1135c861472366737563ebe79681 (patch) | |
| tree | 00f62bc52928e48100b9b5dab2384d09aedf4034 /lib | |
| parent | dac312f01fe83f6ade1b0b1845c92106587ff89e (diff) | |
Update font.py (#848)
* Add new fonts
* Update old fonts
* Update lettering gui
Co-authored-by: Lex Neva
Co-authored-by: kalleen
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/extensions/lettering.py | 138 | ||||
| -rw-r--r-- | lib/gui/__init__.py | 9 | ||||
| -rw-r--r-- | lib/gui/subtitle_combo_box.py | 85 | ||||
| -rw-r--r-- | lib/lettering/font.py | 73 | ||||
| -rw-r--r-- | lib/lettering/font_variant.py | 5 | ||||
| -rw-r--r-- | lib/lettering/glyph.py | 10 |
6 files changed, 178 insertions, 142 deletions
diff --git a/lib/extensions/lettering.py b/lib/extensions/lettering.py index a2a729b5..d988778d 100644 --- a/lib/extensions/lettering.py +++ b/lib/extensions/lettering.py @@ -1,21 +1,23 @@ # -*- coding: UTF-8 -*- -from base64 import b64encode, b64decode import json import os import sys +from base64 import b64decode, b64encode import appdirs import inkex import wx +import wx.adv from ..elements import nodes_to_elements -from ..gui import PresetsPanel, SimulatorPreview, info_dialog, SubtitleComboBox +from ..gui import PresetsPanel, SimulatorPreview, info_dialog from ..i18n import _ from ..lettering import Font, FontError from ..svg import get_correction_transform -from ..svg.tags import SVG_PATH_TAG, SVG_GROUP_TAG, INKSCAPE_LABEL, INKSTITCH_LETTERING -from ..utils import get_bundled_dir, DotDict, cache +from ..svg.tags import (INKSCAPE_LABEL, INKSTITCH_LETTERING, SVG_GROUP_TAG, + SVG_PATH_TAG) +from ..utils import DotDict, cache, get_bundled_dir from .commands import CommandsExtension @@ -33,9 +35,23 @@ class LetteringFrame(wx.Frame): self.preview = SimulatorPreview(self, target_duration=1) self.presets_panel = PresetsPanel(self) + # font + self.font_selector_box = wx.StaticBox(self, wx.ID_ANY, label=_("Font")) + self.update_font_list() + + self.font_chooser = wx.adv.BitmapComboBox(self, wx.ID_ANY, style=wx.CB_READONLY | wx.CB_SORT) + self.set_font_list() + self.font_chooser.Bind(wx.EVT_COMBOBOX, self.on_font_changed) + + # font details + self.font_description = wx.StaticText(self, wx.ID_ANY) + # options self.options_box = wx.StaticBox(self, wx.ID_ANY, label=_("Options")) + self.scale_spinner = wx.SpinCtrl(self, wx.ID_ANY, min=100, max=100, initial=100) + self.scale_spinner.Bind(wx.EVT_SPINCTRL, lambda event: self.on_change("scale", event)) + 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)) @@ -43,15 +59,7 @@ class LetteringFrame(wx.Frame): self.trim_checkbox.Bind(wx.EVT_CHECKBOX, lambda event: self.on_change("trim", event)) # text editor - self.text_editor_box = wx.StaticBox(self, wx.ID_ANY, label=_("Text")) - - self.update_font_list() - self.font_chooser = SubtitleComboBox(self, wx.ID_ANY, choices=self.get_font_names(), - subtitles=self.get_font_descriptions(), style=wx.CB_READONLY) - self.font_chooser.Bind(wx.EVT_COMBOBOX, self.on_font_changed) - - self.scale_spinner = wx.SpinCtrl(self, wx.ID_ANY, min=100, max=100, initial=100) - self.scale_spinner.Bind(wx.EVT_SPINCTRL, lambda event: self.on_change("scale", event)) + self.text_input_box = wx.StaticBox(self, wx.ID_ANY, label=_("Text")) self.text_editor = wx.TextCtrl(self, style=wx.TE_MULTILINE | wx.TE_DONTWRAP) self.text_editor.Bind(wx.EVT_TEXT, lambda event: self.on_change("text", event)) @@ -73,7 +81,7 @@ class LetteringFrame(wx.Frame): self.settings = DotDict({ "text": u"", - "back_and_forth": True, + "back_and_forth": False, "font": None, "scale": 100 }) @@ -134,6 +142,28 @@ class LetteringFrame(wx.Frame): info_dialog(self, _("Unable to find any fonts! Please try reinstalling Ink/Stitch.")) self.cancel() + def set_font_list(self): + for font in self.fonts.values(): + image = font.preview_image + if image is not None: + image = wx.Image(font.preview_image) + """ + # I would like to do this but Windows requires all images to be the exact same size + # It might work with an updated wxpython version - so let's keep it here + + # Scale to max 20 height + img_height = 20 + width, height = image.GetSize() + scale_factor = height / img_height + width = int(width / scale_factor) + image.Rescale(width, img_height, quality=wx.IMAGE_QUALITY_HIGH) + """ + # Windows requires all images to have the exact same size + image.Rescale(300, 20, quality=wx.IMAGE_QUALITY_HIGH) + self.font_chooser.Append(font.name, wx.Bitmap(image)) + else: + self.font_chooser.Append(font.name) + def get_font_names(self): font_names = [font.name for font in self.fonts.itervalues()] font_names.sort() @@ -149,19 +179,17 @@ class LetteringFrame(wx.Frame): message = '''This text was created using the font "%s", but Ink/Stitch can't find that font. ''' \ '''A default font will be substituted.''' info_dialog(self, _(message) % font_id) - try: - self.font_chooser.SetValueByUser(self.fonts_by_id[font_id].name) + self.font_chooser.SetValue(self.fonts_by_id[font_id].name) except KeyError: - self.font_chooser.SetValueByUser(self.default_font.name) - + self.font_chooser.SetValue(self.default_font.name) self.on_font_changed() @property @cache def default_font(self): try: - return self.fonts[self.DEFAULT_FONT] + return self.fonts_by_id[self.DEFAULT_FONT] except KeyError: return self.fonts.values()[0] @@ -173,7 +201,36 @@ class LetteringFrame(wx.Frame): font = self.fonts.get(self.font_chooser.GetValue(), self.default_font) self.settings.font = font.id self.scale_spinner.SetRange(int(font.min_scale * 100), int(font.max_scale * 100)) + + font_variants = font.has_variants() + + # Update font description + color = (0, 0, 0) + description = font.description + if len(font_variants) == 0: + color = (255, 0, 0) + description = _('This font has no available font variant. Please update or remove the font.') + self.font_description.SetLabel(description) + self.font_description.SetForegroundColour(color) + self.font_description.Wrap(self.GetSize().width - 20) + + if font.reversible: + self.back_and_forth_checkbox.Enable() + self.back_and_forth_checkbox.SetValue(bool(self.settings.back_and_forth)) + else: + # The creator of the font banned the possibility of writing in reverse with json file: "reversible": false + 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.GetSizer().Layout() def update_preview(self, event=None): self.preview.update() @@ -255,27 +312,38 @@ class LetteringFrame(wx.Frame): def __do_layout(self): outer_sizer = wx.BoxSizer(wx.VERTICAL) - options_sizer = wx.StaticBoxSizer(self.options_box, wx.VERTICAL) - options_sizer.Add(self.back_and_forth_checkbox, 1, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT, 5) - options_sizer.Add(self.trim_checkbox, 1, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT | wx.BOTTOM, 5) - outer_sizer.Add(options_sizer, 0, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT, 10) + # font selection + font_selector_sizer = wx.StaticBoxSizer(self.font_selector_box, wx.VERTICAL) + font_selector_sizer.Add(self.font_chooser, 0, wx.EXPAND | wx.ALL, 10) + font_selector_sizer.Add(self.font_description, 1, wx.EXPAND | wx.ALL, 10) + outer_sizer.Add(font_selector_sizer, 0, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT, 10) - font_sizer = wx.BoxSizer(wx.HORIZONTAL) - font_sizer.Add(self.font_chooser, 1, wx.EXPAND, 0) - font_sizer.Add(wx.StaticText(self, wx.ID_ANY, "Scale"), 0, wx.LEFT | wx.ALIGN_CENTRE_VERTICAL, 20) - font_sizer.Add(self.scale_spinner, 0, wx.LEFT, 10) - font_sizer.Add(wx.StaticText(self, wx.ID_ANY, "%"), 0, wx.LEFT | wx.ALIGN_CENTRE_VERTICAL, 3) + # 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) - text_editor_sizer = wx.StaticBoxSizer(self.text_editor_box, wx.VERTICAL) - text_editor_sizer.Add(font_sizer, 0, wx.ALL | wx.EXPAND, 10) - text_editor_sizer.Add(self.text_editor, 1, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10) - outer_sizer.Add(text_editor_sizer, 1, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT, 10) + 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) + font_scale_sizer.Add(self.scale_spinner, 0, wx.LEFT, 10) + font_scale_sizer.Add(wx.StaticText(self, wx.ID_ANY, "%"), 0, wx.LEFT | wx.ALIGN_CENTRE_VERTICAL, 3) - outer_sizer.Add(self.presets_panel, 0, wx.EXPAND | wx.EXPAND | wx.ALL, 10) + options_sizer = wx.StaticBoxSizer(self.options_box, wx.HORIZONTAL) + options_sizer.Add(left_option_sizer, 1, wx.EXPAND, 10) + options_sizer.Add(font_scale_sizer, 0, wx.RIGHT, 10) + outer_sizer.Add(options_sizer, 0, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT, 10) + + # text input + text_input_sizer = wx.StaticBoxSizer(self.text_input_box, wx.VERTICAL) + text_input_sizer.Add(self.text_editor, 1, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10) + outer_sizer.Add(text_input_sizer, 2, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT, 10) + + # presets + outer_sizer.Add(self.presets_panel, 0, wx.EXPAND | wx.EXPAND | wx.ALL, 10) buttons_sizer = wx.BoxSizer(wx.HORIZONTAL) - buttons_sizer.Add(self.cancel_button, 0, wx.ALIGN_RIGHT | wx.RIGHT, 10) - buttons_sizer.Add(self.apply_button, 0, wx.ALIGN_RIGHT | wx.RIGHT | wx.BOTTOM, 10) + buttons_sizer.Add(self.cancel_button, 0, wx.RIGHT, 10) + buttons_sizer.Add(self.apply_button, 0, wx.RIGHT | wx.BOTTOM, 10) outer_sizer.Add(buttons_sizer, 0, wx.ALIGN_RIGHT, 10) self.SetSizerAndFit(outer_sizer) diff --git a/lib/gui/__init__.py b/lib/gui/__init__.py index 2214db5d..8869fb6d 100644 --- a/lib/gui/__init__.py +++ b/lib/gui/__init__.py @@ -1,5 +1,4 @@ -from dialogs import info_dialog, confirm_dialog -from electron import open_url -from presets import PresetsPanel -from simulator import EmbroiderySimulator, SimulatorPreview, show_simulator -from subtitle_combo_box import SubtitleComboBox +from .dialogs import confirm_dialog, info_dialog +from .electron import open_url +from .presets import PresetsPanel +from .simulator import EmbroiderySimulator, SimulatorPreview, show_simulator diff --git a/lib/gui/subtitle_combo_box.py b/lib/gui/subtitle_combo_box.py deleted file mode 100644 index 64c42153..00000000 --- a/lib/gui/subtitle_combo_box.py +++ /dev/null @@ -1,85 +0,0 @@ -import wx -import wx.adv -from wx.lib.wordwrap import wordwrap - - -class SubtitleComboBox(wx.adv.OwnerDrawnComboBox): - TITLE_FONT_SIZE = 12 - SUBTITLE_FONT_SIZE = 10 - - # I'd love to make this 12 too, but if I do it seems to get drawn as 10 - # initially no matter what I do. - CONTROL_FONT_SIZE = 12 - - MARGIN = 5 - - def __init__(self, *args, **kwargs): - self.titles = kwargs.get('choices', []) - subtitles = kwargs.pop('subtitles', {}) - self.subtitles = [subtitles.get(title, '') for title in self.titles] - wx.adv.OwnerDrawnComboBox.__init__(self, *args, **kwargs) - - self.control_font = wx.Font(pointSize=self.CONTROL_FONT_SIZE, family=wx.DEFAULT, style=wx.NORMAL, weight=wx.NORMAL) - self.title_font = wx.Font(pointSize=self.TITLE_FONT_SIZE, family=wx.DEFAULT, style=wx.NORMAL, weight=wx.NORMAL) - self.subtitle_font = wx.Font(pointSize=self.SUBTITLE_FONT_SIZE, family=wx.DEFAULT, style=wx.NORMAL, weight=wx.NORMAL) - - def OnMeasureItemWidth(self, item): - # This _should_ allow us to set the width of the combobox to match the - # width of the widest title. In reality, this method is never called - # and I can't figure out why. We just use self.GetSize().GetWidth() - # instead and rely on the parent window to size us appropriately. Ugh. - - title = self.titles[item] - - # technique from https://stackoverflow.com/a/23529463/4249120 - dc = wx.ScreenDC() - dc.SetFont(self.title_font) - - return dc.GetTextExtent(title).GetWidth() + 2 * self.MARGIN - - def OnMeasureItem(self, item): - title = self.titles[item] - subtitle = self.subtitles[item] - - dc = wx.ScreenDC() - dc.SetFont(self.subtitle_font) - wrapped = wordwrap(subtitle, self.GetSize().GetWidth(), dc) - subtitle_height = dc.GetTextExtent(wrapped).GetHeight() - - dc = wx.ScreenDC() - dc.SetFont(self.title_font) - title_height = dc.GetTextExtent(title).GetHeight() - - return subtitle_height + title_height + 3 * self.MARGIN - - def OnDrawBackground(self, dc, rect, item, flags): - if flags & wx.adv.ODCB_PAINTING_SELECTED: - # let the parent class draw the selected item so we don't - # hae to figure out the highlight color - wx.adv.OwnerDrawnComboBox.OnDrawBackground(self, dc, rect, item, flags) - else: - # alternate white and grey for the dropdown items, and draw the - # combo box itself as white - if flags & wx.adv.ODCB_PAINTING_CONTROL or item % 2 == 0: - background_color = wx.Colour(255, 255, 255) - else: - background_color = wx.Colour(240, 240, 240) - - dc.SetBrush(wx.Brush(background_color)) - dc.SetPen(wx.Pen(background_color)) - dc.DrawRectangle(rect) - - def OnDrawItem(self, dc, rect, item, flags): - if flags & wx.adv.ODCB_PAINTING_CONTROL: - # painting the selected item in the box - dc.SetFont(self.control_font) - dc.DrawText(self.titles[item], rect.x + self.MARGIN, rect.y + self.MARGIN) - else: - # painting the items in the popup - dc.SetFont(self.title_font) - title_height = dc.GetCharHeight() - dc.DrawText(self.titles[item], rect.x + self.MARGIN, rect.y + self.MARGIN) - - dc.SetFont(self.subtitle_font) - subtitle = wordwrap(self.subtitles[item], self.GetSize().GetWidth(), dc) - dc.DrawText(subtitle, rect.x + self.MARGIN, rect.y + title_height + self.MARGIN * 2) diff --git a/lib/lettering/font.py b/lib/lettering/font.py index e8c81019..0974a1cf 100644 --- a/lib/lettering/font.py +++ b/lib/lettering/font.py @@ -1,8 +1,8 @@ # -*- coding: UTF-8 -*- -from copy import deepcopy import json import os +from copy import deepcopy import inkex @@ -11,7 +11,7 @@ from ..exceptions import InkstitchException from ..i18n import _, get_languages from ..stitches.auto_satin import auto_satin from ..svg import PIXELS_PER_MM -from ..svg.tags import SVG_GROUP_TAG, SVG_PATH_TAG, INKSCAPE_LABEL +from ..svg.tags import INKSCAPE_LABEL, SVG_GROUP_TAG, SVG_PATH_TAG from ..utils import Point from .font_variant import FontVariant @@ -44,7 +44,11 @@ def localized_font_metadata(name, default=None): # This may be a font packaged with Ink/Stitch, in which case the # text will have been sent to CrowdIn for community translation. # Try to fetch the translated version. - return _(self.metadata.get(name)) + original_metadata = self.metadata.get(name) + localized_metadata = "" + if original_metadata != "": + localized_metadata = _(original_metadata) + return localized_metadata else: return default @@ -103,20 +107,54 @@ class Font(object): name = localized_font_metadata('name', '') description = localized_font_metadata('description', '') - default_variant = font_metadata('default_variant', FontVariant.LEFT_TO_RIGHT) - default_glyph = font_metadata('defalt_glyph', u"�") - letter_spacing = font_metadata('letter_spacing', 1.5, multiplier=PIXELS_PER_MM) + default_glyph = font_metadata('default_glyph', u"�") leading = font_metadata('leading', 5, multiplier=PIXELS_PER_MM) - word_spacing = font_metadata('word_spacing', 3, multiplier=PIXELS_PER_MM) kerning_pairs = font_metadata('kerning_pairs', {}) auto_satin = font_metadata('auto_satin', True) min_scale = font_metadata('min_scale', 1.0) max_scale = font_metadata('max_scale', 1.0) + # use values from SVG Font, exemple: + # <font horiz-adv-x="45" ... <glyph .... horiz-adv-x="49" glyph-name="A" /> ... <hkern ... k="3"g1="A" g2="B" /> .... /> + + # Example font.json : "horiz_adv_x": {"A":49}, + horiz_adv_x = font_metadata('horiz_adv_x', {}) + + # Example font.json : "horiz_adv_x_default" : 45, + horiz_adv_x_default = font_metadata('horiz_adv_x_default') + + # Define by <glyph glyph-name="space" unicode=" " horiz-adv-x="22" />, Example font.json : "horiz_adv_x_space":22, + word_spacing = font_metadata('horiz_adv_x_space', 0) + + reversible = font_metadata('reversible', True) + @property def id(self): return os.path.basename(self.path) + @property + def default_variant(self): + # Set default variant to any existing variant if default font file is missing + default_variant = font_metadata('default_variant', FontVariant.LEFT_TO_RIGHT) + font_variants = self.has_variants() + if default_variant not in font_variants and len(font_variants) > 0: + default_variant = font_variants[0] + return default_variant + + @property + def preview_image(self): + preview_image_path = os.path.join(self.path, "preview.png") + if os.path.isfile(preview_image_path): + return preview_image_path + return None + + def has_variants(self): + font_variants = [] + for variant in FontVariant.VARIANT_TYPES: + if os.path.isfile(os.path.join(self.path, "%s.svg" % variant)): + font_variants.append(variant) + return font_variants + def render_text(self, text, destination_group, variant=None, back_and_forth=True, trim=False): """Render text into an SVG group element.""" self._load_variants() @@ -124,7 +162,7 @@ class Font(object): if variant is None: variant = self.default_variant - if back_and_forth: + if back_and_forth and self.reversible: glyph_sets = [self.get_variant(variant), self.get_variant(FontVariant.reversed_variant(variant))] else: glyph_sets = [self.get_variant(variant)] * 2 @@ -135,7 +173,7 @@ class Font(object): line = line.strip() letter_group = self._render_line(line, position, glyph_set) - if glyph_set.variant == FontVariant.RIGHT_TO_LEFT: + if back_and_forth and self.reversible and i % 2 == 1: letter_group[:] = reversed(letter_group) destination_group.append(letter_group) @@ -205,14 +243,25 @@ class Font(object): we're at the start of the line or a word. """ - node = deepcopy(glyph.node) + # Concerning min_x: I add it before moving the letter because it is to + # take into account the margin in the drawing of the letter. With respect + # to point 0 the letter can start at 5 or -5. The letters have a defined + # place in the drawing that's important. + # Then to calculate the position of x for the next letter I have to remove + # the min_x margin because the horizontal adv is calculated from point 0 of the drawing. + node = deepcopy(glyph.node) if last_character is not None: - position.x += self.letter_spacing + self.kerning_pairs.get(last_character + character, 0) * PIXELS_PER_MM + position.x += glyph.min_x - self.kerning_pairs.get(last_character + character, 0) transform = "translate(%s, %s)" % position.as_tuple() node.set('transform', transform) - position.x += glyph.width + + horiz_adv_x_default = self.horiz_adv_x_default + if horiz_adv_x_default is None: + horiz_adv_x_default = glyph.width + glyph.min_x + + position.x += self.horiz_adv_x.get(character, horiz_adv_x_default) - glyph.min_x return node diff --git a/lib/lettering/font_variant.py b/lib/lettering/font_variant.py index d826dca6..7c9fa1c0 100644 --- a/lib/lettering/font_variant.py +++ b/lib/lettering/font_variant.py @@ -1,6 +1,7 @@ # -*- coding: UTF-8 -*- import os + import inkex import simplestyle @@ -45,6 +46,10 @@ class FontVariant(object): return None def __init__(self, font_path, variant, default_glyph=None): + # If the font variant file does not exist, this constructor will + # raise an exception. The caller should catch it and decide + # what to do. + self.path = font_path self.variant = variant self.default_glyph = default_glyph diff --git a/lib/lettering/glyph.py b/lib/lettering/glyph.py index bb1a971c..061a930c 100644 --- a/lib/lettering/glyph.py +++ b/lib/lettering/glyph.py @@ -64,21 +64,21 @@ class Glyph(object): def _process_baseline(self, svg): for guide in get_guides(svg): if guide.label == "baseline": - self._baseline = guide.position.y + self.baseline = guide.position.y break else: # no baseline guide found, assume 0 for lack of anything better to use... - self._baseline = 0 + self.baseline = 0 def _process_bbox(self): left, right, top, bottom = simpletransform.computeBBox(self.node.iterdescendants()) self.width = right - left - self._min_x = left + self.min_x = left def _move_to_origin(self): - translate_x = -self._min_x - translate_y = -self._baseline + translate_x = -self.min_x + translate_y = -self.baseline transform = "translate(%s, %s)" % (translate_x, translate_y) for node in self.node.iter(SVG_PATH_TAG): |
