From e65788cea72531f922fe4bccda4ec0c06047df10 Mon Sep 17 00:00:00 2001 From: Kaalleen <36401965+kaalleen@users.noreply.github.com> Date: Wed, 12 Jul 2023 18:28:07 +0200 Subject: Lettering: add glyph filter (#2400) Co-authored-by: claudinepeyrat06 --- lib/extensions/__init__.py | 2 + lib/extensions/lettering.py | 88 ++++++++++---- lib/extensions/lettering_generate_json.py | 36 ++++-- lib/extensions/lettering_update_json_glyphlist.py | 41 +++++++ lib/inx/extensions.py | 2 + lib/lettering/categories.py | 30 +++++ lib/lettering/font.py | 2 + lib/lettering/font_info.py | 137 ++++++++++++++++++++++ lib/lettering/kerning.py | 125 -------------------- 9 files changed, 307 insertions(+), 156 deletions(-) create mode 100644 lib/extensions/lettering_update_json_glyphlist.py create mode 100644 lib/lettering/categories.py create mode 100644 lib/lettering/font_info.py delete mode 100644 lib/lettering/kerning.py (limited to 'lib') diff --git a/lib/extensions/__init__.py b/lib/extensions/__init__.py index 25d3214c..d0900f2d 100644 --- a/lib/extensions/__init__.py +++ b/lib/extensions/__init__.py @@ -32,6 +32,7 @@ from .lettering_custom_font_dir import LetteringCustomFontDir from .lettering_force_lock_stitches import LetteringForceLockStitches from .lettering_generate_json import LetteringGenerateJson from .lettering_remove_kerning import LetteringRemoveKerning +from .lettering_update_json_glyphlist import LetteringUpdateJsonGlyphlist from .letters_to_font import LettersToFont from .object_commands import ObjectCommands from .object_commands_toggle_visibility import ObjectCommandsToggleVisibility @@ -84,6 +85,7 @@ __all__ = extensions = [StitchPlanPreview, AutoRun, Lettering, LetteringGenerateJson, + LetteringUpdateJsonGlyphlist, LetteringRemoveKerning, LetteringCustomFontDir, LetteringForceLockStitches, diff --git a/lib/extensions/lettering.py b/lib/extensions/lettering.py index fec48100..0879c30f 100644 --- a/lib/extensions/lettering.py +++ b/lib/extensions/lettering.py @@ -18,13 +18,14 @@ from ..elements import nodes_to_elements from ..gui import PresetsPanel, SimulatorPreview, info_dialog from ..i18n import _ from ..lettering import Font, FontError +from ..lettering.categories import FONT_CATEGORIES, FontCategory from ..svg import get_correction_transform from ..svg.tags import (INKSCAPE_LABEL, INKSTITCH_LETTERING, SVG_GROUP_TAG, SVG_PATH_TAG) from ..utils import DotDict, cache, get_bundled_dir, get_resource_dir +from ..utils.threading import ExitThread from .commands import CommandsExtension from .lettering_custom_font_dir import get_custom_font_dir -from ..utils.threading import ExitThread class LetteringFrame(wx.Frame): @@ -52,19 +53,32 @@ class LetteringFrame(wx.Frame): self.font_chooser = wx.adv.BitmapComboBox(self, wx.ID_ANY, style=wx.CB_READONLY | wx.CB_SORT) self.font_chooser.Bind(wx.EVT_COMBOBOX, self.on_font_changed) - self.font_filter = fs.FloatSpin(self, min_val=0, max_val=None, increment=1, value="0") - self.font_filter.SetFormat("%f") - self.font_filter.SetDigits(2) - self.font_filter.Bind(fs.EVT_FLOATSPIN, self.on_filter_changed) - self.font_filter.SetToolTip(_("Font size filter (mm). 0 for all sizes.")) - - self.update_font_list() - self.set_font_list() + self.font_size_filter = fs.FloatSpin(self, min_val=0, max_val=None, increment=1, value="0") + self.font_size_filter.SetFormat("%f") + self.font_size_filter.SetDigits(2) + self.font_size_filter.Bind(fs.EVT_FLOATSPIN, self.on_filter_changed) + self.font_size_filter.SetToolTip(_("Font size filter (mm). 0 for all sizes.")) + + self.font_glyph_filter = wx.CheckBox(self, label=_("Glyphs")) + self.font_glyph_filter.Bind(wx.EVT_CHECKBOX, self.on_filter_changed) + self.font_glyph_filter.SetToolTip(_("Filter fonts by available glyphs.")) + + self.font_category_filter = wx.ComboBox(self, wx.ID_ANY, choices=[], style=wx.CB_DROPDOWN | wx.CB_READONLY) + unfiltered = FontCategory('unfiltered', "---") + self.font_category_filter.Append(unfiltered.name, unfiltered) + for category in FONT_CATEGORIES: + self.font_category_filter.Append(category.name, category) + self.font_category_filter.SetToolTip(_("Filter fonts by category.")) + self.font_category_filter.SetSelection(0) + self.font_category_filter.Bind(wx.EVT_COMBOBOX, self.on_filter_changed) # font details self.font_description = wx.StaticText(self, wx.ID_ANY) self.Bind(wx.EVT_SIZE, self.resize) + # font filter + self.filter_box = wx.StaticBox(self, wx.ID_ANY, label=_("Font Filter")) + # options self.options_box = wx.StaticBox(self, wx.ID_ANY, label=_("Options")) @@ -95,6 +109,10 @@ class LetteringFrame(wx.Frame): self.apply_button = wx.Button(self, wx.ID_ANY, _("Apply and Quit")) self.apply_button.Bind(wx.EVT_BUTTON, self.apply) + # set font list + self.update_font_list() + self.set_font_list() + self.__do_layout() self.load_settings() @@ -165,10 +183,26 @@ class LetteringFrame(wx.Frame): self.fonts = {} self.fonts_by_id = {} - filter_size = self.font_filter.GetValue() + # font size filter value + filter_size = self.font_size_filter.GetValue() + filter_glyph = self.font_glyph_filter.GetValue() + filter_category = self.font_category_filter.GetSelection() - 1 + + # glyph filter string without spaces + glyphs = [*self.text_editor.GetValue().replace(" ", "").replace("\n", "")] + for font in self.font_list: + if filter_glyph and glyphs and not set(glyphs).issubset(font.available_glyphs): + continue + + if filter_category != -1: + category = FONT_CATEGORIES[filter_category].id + if category not in font.keywords: + continue + if filter_size != 0 and (filter_size < font.size * font.min_scale or filter_size > font.size * font.max_scale): continue + self.fonts[font.marked_custom_font_name] = font self.fonts_by_id[font.marked_custom_font_id] = font @@ -208,7 +242,7 @@ class LetteringFrame(wx.Frame): try: font = self.fonts_by_id[font_id].marked_custom_font_name except KeyError: - font = self.default_font.name + font = self.default_font.marked_custom_font_name self.font_chooser.SetValue(font) self.on_font_changed() @@ -222,6 +256,8 @@ class LetteringFrame(wx.Frame): def on_change(self, attribute, event): self.settings[attribute] = event.GetEventObject().GetValue() + if attribute == "text" and self.font_glyph_filter.GetValue() is True: + self.on_filter_changed() self.preview.update() def on_trim_option_change(self, event=None): @@ -232,7 +268,7 @@ class LetteringFrame(wx.Frame): font = self.fonts.get(self.font_chooser.GetValue(), self.default_font) self.settings.font = font.marked_custom_font_id - filter_size = self.font_filter.GetValue() + filter_size = self.font_size_filter.GetValue() self.scale_spinner.SetRange(int(font.min_scale * 100), int(font.max_scale * 100)) if filter_size != 0: self.scale_spinner.SetValue(int(filter_size / font.size * 100)) @@ -245,7 +281,7 @@ class LetteringFrame(wx.Frame): pass # Update font description - color = (0, 0, 0) + color = wx.NullColour description = font.description if len(font_variants) == 0: color = (255, 0, 0) @@ -271,17 +307,17 @@ class LetteringFrame(wx.Frame): if not self.fonts: # No fonts for filtered size self.font_chooser.Clear() - self.filter_label.SetForegroundColour("red") + self.filter_box.SetForegroundColour("red") return else: - self.filter_label.SetForegroundColour("black") + self.filter_box.SetForegroundColour(wx.NullColour) - filter_size = self.font_filter.GetValue() + filter_size = self.font_size_filter.GetValue() previous_font = self.font_chooser.GetValue() self.set_font_list() font = self.fonts.get(previous_font, self.default_font) - self.font_chooser.SetValue(font.name) - if font.name != previous_font: + self.font_chooser.SetValue(font.marked_custom_font_name) + if font.marked_custom_font_name != previous_font: self.on_font_changed() elif filter_size != 0: self.scale_spinner.SetValue(int(filter_size / font.size * 100)) @@ -396,19 +432,27 @@ class LetteringFrame(wx.Frame): font_selector_sizer = wx.StaticBoxSizer(self.font_selector_box, wx.VERTICAL) font_selector_box = wx.BoxSizer(wx.HORIZONTAL) font_selector_box.Add(self.font_chooser, 4, wx.EXPAND | wx.TOP | wx.BOTTOM | wx.RIGHT, 10) - self.filter_label = wx.StaticText(self, wx.ID_ANY, _("Filter")) - font_selector_box.Add(self.filter_label, 0, wx.LEFT | wx.ALIGN_CENTRE_VERTICAL, 0) - font_selector_box.Add(self.font_filter, 1, wx.LEFT | wx.ALIGN_CENTRE_VERTICAL, 5) font_selector_sizer.Add(font_selector_box, 0, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT, 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) + # filter fon list + filter_sizer = wx.StaticBoxSizer(self.filter_box, wx.HORIZONTAL) + filter_size_label = wx.StaticText(self, wx.ID_ANY, _("Size")) + filter_sizer.Add(filter_size_label, 0, wx.LEFT | wx.TOP | wx.BOTTOM, 10) + filter_sizer.AddSpacer(5) + filter_sizer.Add(self.font_size_filter, 1, wx.RIGHT | wx.TOP | wx.BOTTOM, 10) + filter_sizer.AddSpacer(5) + filter_sizer.Add(self.font_glyph_filter, 1, wx.RIGHT | wx.TOP | wx.BOTTOM, 10) + filter_sizer.Add(self.font_category_filter, 1, wx.RIGHT | wx.TOP | wx.BOTTOM, 10) + outer_sizer.Add(filter_sizer, 0, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT, 10) + # 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) trim_option_sizer = wx.BoxSizer(wx.HORIZONTAL) - trim_option_sizer.Add(wx.StaticText(self, wx.ID_ANY, _("Add trims")), 0, wx.LEFT | wx.ALIGN_CENTRE_VERTICAL, 5) + trim_option_sizer.Add(wx.StaticText(self, wx.ID_ANY, _("Add trims")), 0, wx.LEFT | wx.ALIGN_TOP, 5) trim_option_sizer.Add(self.trim_option_choice, 1, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT | wx.BOTTOM, 5) trim_option_sizer.Add(self.use_trim_symbols, 1, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT | wx.BOTTOM, 5) left_option_sizer.Add(trim_option_sizer, 0, wx.ALIGN_LEFT, 5) diff --git a/lib/extensions/lettering_generate_json.py b/lib/extensions/lettering_generate_json.py index 85c918b2..a884ccac 100644 --- a/lib/extensions/lettering_generate_json.py +++ b/lib/extensions/lettering_generate_json.py @@ -10,7 +10,8 @@ import sys from inkex import Boolean from ..i18n import _ -from ..lettering.kerning import FontKerning +from ..lettering.categories import FONT_CATEGORIES +from ..lettering.font_info import FontFileInfo from .base import InkstitchExtension @@ -20,6 +21,11 @@ class LetteringGenerateJson(InkstitchExtension): ''' def __init__(self, *args, **kwargs): InkstitchExtension.__init__(self, *args, **kwargs) + self.arg_parser.add_argument("--options") + self.arg_parser.add_argument("--general") + self.arg_parser.add_argument("--settings") + self.arg_parser.add_argument("--kerning") + self.arg_parser.add_argument("-n", "--font-name", type=str, default="Font", dest="font_name") self.arg_parser.add_argument("-d", "--font-description", type=str, default="Description", dest="font_description") self.arg_parser.add_argument("-s", "--auto-satin", type=Boolean, default="true", dest="auto_satin") @@ -35,6 +41,9 @@ class LetteringGenerateJson(InkstitchExtension): self.arg_parser.add_argument("-w", "--word-spacing", type=int, default=26, dest="word_spacing") self.arg_parser.add_argument("-p", "--font-file", type=str, default="", dest="path") + for category in FONT_CATEGORIES: + self.arg_parser.add_argument(f"--{category.id}", type=Boolean, default="false", dest=category.id) + def effect(self): # file paths path = self.options.path @@ -43,20 +52,20 @@ class LetteringGenerateJson(InkstitchExtension): return output_path = os.path.join(os.path.dirname(path), 'font.json') - # kerning - kerning = FontKerning(path) + # font info (kerning, glyphs) + font_info = FontFileInfo(path) - horiz_adv_x = kerning.horiz_adv_x() - hkern = kerning.hkern() + horiz_adv_x = font_info.horiz_adv_x() + hkern = font_info.hkern() custom_leading = self.options.use_custom_leading custom_spacing = self.options.use_custom_spacing - word_spacing = kerning.word_spacing() + word_spacing = font_info.word_spacing() # use user input in case that the default word spacing is not defined # in the svg file or the user forces custom values if custom_spacing or not word_spacing: word_spacing = self.options.word_spacing - letter_spacing = kerning.letter_spacing() - units_per_em = kerning.units_per_em() or self.options.leading + letter_spacing = font_info.letter_spacing() + units_per_em = font_info.units_per_em() or self.options.leading # use units_per_em for leading (line height) if defined in the font file, # unless the user wishes to overwrite the value if units_per_em and not custom_leading: @@ -64,9 +73,17 @@ class LetteringGenerateJson(InkstitchExtension): else: leading = self.options.leading + glyphs = font_info.glyph_list() + + keywords = [] + for category in FONT_CATEGORIES: + if getattr(self.options, category.id): + keywords.append(category.id) + # collect data data = {'name': self.options.font_name, 'description': self.options.font_description, + 'keywords': keywords, 'leading': leading, 'auto_satin': self.options.auto_satin, 'reversible': self.options.reversible, @@ -79,7 +96,8 @@ class LetteringGenerateJson(InkstitchExtension): 'horiz_adv_x_space': word_spacing, 'units_per_em': units_per_em, 'horiz_adv_x': horiz_adv_x, - 'kerning_pairs': hkern + 'kerning_pairs': hkern, + 'glyphs': glyphs } # write data to font.json into the same directory as the font file diff --git a/lib/extensions/lettering_update_json_glyphlist.py b/lib/extensions/lettering_update_json_glyphlist.py new file mode 100644 index 00000000..4dea6d51 --- /dev/null +++ b/lib/extensions/lettering_update_json_glyphlist.py @@ -0,0 +1,41 @@ +# Authors: see git history +# +# Copyright (c) 2010 Authors +# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. + +import json +import os +import sys + +from ..i18n import _ +from ..lettering.font_info import FontFileInfo +from .base import InkstitchExtension + + +class LetteringUpdateJsonGlyphlist(InkstitchExtension): + ''' + This extension helps font creators to generate the json file for the lettering tool + ''' + def __init__(self, *args, **kwargs): + InkstitchExtension.__init__(self, *args, **kwargs) + self.arg_parser.add_argument("-f", "--font-file", type=str, default="", dest="font_file") + self.arg_parser.add_argument("-j", "--json-file", type=str, default="", dest="json_file") + + def effect(self): + # file paths + font_file = self.options.font_file + json_file = self.options.json_file + if not os.path.isfile(font_file) or not os.path.isfile(json_file): + print(_("Please verify file locations."), file=sys.stderr) + return + + glyphs = FontFileInfo(font_file).glyph_list() + + with open(json_file, 'r') as font_data: + data = json.load(font_data) + + data['glyphs'] = 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) diff --git a/lib/inx/extensions.py b/lib/inx/extensions.py index 0ff3e889..30fca4da 100755 --- a/lib/inx/extensions.py +++ b/lib/inx/extensions.py @@ -8,6 +8,7 @@ import pyembroidery from ..commands import (COMMANDS, GLOBAL_COMMANDS, LAYER_COMMANDS, OBJECT_COMMANDS) from ..extensions import Input, Output, extensions +from ..lettering.categories import FONT_CATEGORIES from ..threads import ThreadCatalog from .outputs import pyembroidery_output_formats from .utils import build_environment, write_inx_file @@ -51,6 +52,7 @@ def generate_extension_inx_files(): write_inx_file(name, template.render(formats=pyembroidery_output_formats(), debug_formats=pyembroidery_debug_formats(), threadcatalog=threadcatalog(), + font_categories=FONT_CATEGORIES, layer_commands=layer_commands(), object_commands=object_commands(), global_commands=global_commands())) diff --git a/lib/lettering/categories.py b/lib/lettering/categories.py new file mode 100644 index 00000000..40b41529 --- /dev/null +++ b/lib/lettering/categories.py @@ -0,0 +1,30 @@ +# Authors: see git history +# +# Copyright (c) 2023 Authors +# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. + +from ..i18n import _ + + +class FontCategory: + def __init__(self, cat_id=None, name=None): + self.id: str = cat_id + self.name: str = name + + def __repr__(self): + return "FontCategory(%s, %s)" % (self.id, self.name) + + +FONT_CATEGORIES = [ + FontCategory('applique', _("Applique")), + FontCategory('crossstitch', _("Crossstitch")), + FontCategory('display', _('Display')), + FontCategory('handwriting', _("Handwriting")), + FontCategory('italic', _("Italic")), + FontCategory('monogram', _("Monogram")), + FontCategory('multicolor', _('Multicolor')), + FontCategory('running_stitch', _('Running Stitch')), + FontCategory('sans_serif', _("Sans Serif")), + FontCategory('serif', _("Serif")), + FontCategory('tiny', _("Tiny")) +] diff --git a/lib/lettering/font.py b/lib/lettering/font.py index 77f17e7f..fb17f760 100644 --- a/lib/lettering/font.py +++ b/lib/lettering/font.py @@ -111,6 +111,7 @@ class Font(object): name = localized_font_metadata('name', '') description = localized_font_metadata('description', '') + keywords = font_metadata('keywords', '') letter_case = font_metadata('letter_case', '') default_glyph = font_metadata('default_glyph', "�") leading = font_metadata('leading', 100) @@ -119,6 +120,7 @@ class Font(object): min_scale = font_metadata('min_scale', 1.0) max_scale = font_metadata('max_scale', 1.0) size = font_metadata('size', 0) + available_glyphs = font_metadata('glyphs', []) # use values from SVG Font, example: # ... .... /> diff --git a/lib/lettering/font_info.py b/lib/lettering/font_info.py new file mode 100644 index 00000000..398786a4 --- /dev/null +++ b/lib/lettering/font_info.py @@ -0,0 +1,137 @@ +# 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 NSS +from lxml import etree +from ..svg.tags import INKSCAPE_LABEL + + +class FontFileInfo(object): + """ + This class reads kerning information from an SVG file + """ + def __init__(self, path): + with open(path, 'r', encoding="utf-8") as svg: + self.svg = etree.parse(svg) + + # horiz_adv_x defines the wdith of specific letters (distance to next letter) + def horiz_adv_x(self): + # In XPath 2.0 we could use ".//svg:glyph/(@unicode|@horiz-adv-x)" + xpath = ".//svg:glyph[@unicode and @horiz-adv-x]/@*[name()='unicode' or name()='horiz-adv-x']" + hax = self.svg.xpath(xpath, namespaces=NSS) + if len(hax) == 0: + return {} + return dict(zip(hax[0::2], [int(x) for x in hax[1::2]])) + + # kerning (specific distances of two specified letters) + def hkern(self): + xpath = ".//svg:hkern[(@u1 or @g1) and (@u1 or @g1) and @k]/@*[contains(name(), '1') or contains(name(), '2') or name()='k']" + hkern = self.svg.xpath(xpath, namespaces=NSS) + + # the kerning list now contains the kerning values as a list where every first value contains the first letter(s), + # every second value contains the second letter(s) and every third value contains the kerning + u_first = [k for k in hkern[0::3]] + u_second = [k for k in hkern[1::3]] + k = [int(x) for x in hkern[2::3]] + + # sometimes a font file contains conflicting kerning value for a letter pair + # in this case the value which is specified as a single pair overrules the one specified in a list of letters + # therefore we want to sort our list by length of the letter values + kern_list = list(zip(u_first, u_second, k)) + kern_list.sort(key=lambda x: len(x[0] + x[1]), reverse=True) + + for index, kerning in enumerate(kern_list): + first, second, key = kerning + # fontTools.agl will import fontTools.misc.py23 which will output a deprecation warning + # ignore the warning for now - until the library fixed it + if index == 0: + import warnings + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + global toUnicode + from fontTools.agl import toUnicode + first = self.split_glyph_list(first) + second = self.split_glyph_list(second) + kern_list[index] = (first, second, key) + + hkern = {} + for first, second, key in kern_list: + for f in first: + for s in second: + hkern[f+s] = key + return hkern + + def split_glyph_list(self, glyph): + glyphs = [] + if len(glyph) > 1: + # glyph names need to be converted to unicode + # we need to take into account, that there can be more than one first/second letter in the very same hkern element + # in this case they will be commas separated and each first letter needs to be combined with each next letter + # e.g. + glyph_names = glyph.split(",") + for glyph_name in glyph_names: + # each glyph can have additional special markers, e.g. o.cmp + # toUnicode will not respect those and convert them to a simple letter + # this behaviour will generate a wrong spacing for this letter. + # Let's make sure to also transfer the separators and extensions to our json file + separators = [".", "_"] + used_separator = False + for separator in separators: + glyph_with_separator = glyph_name.split(separator) + if len(glyph_with_separator) == 2: + glyphs.append("%s%s%s" % (toUnicode(glyph_with_separator[0]), separator, glyph_with_separator[1])) + used_separator = True + continue + # there is no extra separator + if not used_separator: + glyphs.append(toUnicode(glyph_name)) + else: + glyphs.append(glyph) + return glyphs + + # the space character + def word_spacing(self): + xpath = "string(.//svg:glyph[@glyph-name='space'][1]/@*[name()='horiz-adv-x'])" + word_spacing = self.svg.xpath(xpath, namespaces=NSS) + try: + return int(word_spacing) + except ValueError: + return None + + # default letter spacing + def letter_spacing(self): + xpath = "string(.//svg:font[@horiz-adv-x][1]/@*[name()='horiz-adv-x'])" + letter_spacing = self.svg.xpath(xpath, namespaces=NSS) + try: + return int(letter_spacing) + except ValueError: + return None + + # this value will be saved into the json file to preserve it for later font edits + # additionally it serves to automatically define the line height (leading) + def units_per_em(self): + xpath = "string(.//svg:font-face[@units-per-em][1]/@*[name()='units-per-em'])" + units_per_em = self.svg.xpath(xpath, namespaces=NSS) + try: + return int(units_per_em) + except ValueError: + return None + + """ + def missing_glyph_spacing(self): + xpath = "string(.//svg:missing-glyph/@*[name()='horiz-adv-x'])" + return float(self.svg.xpath(xpath, namespaces=NSS)) + """ + + def glyph_list(self): + """ + Returns a list of available glyphs in the font file + """ + glyphs = [] + glyph_layers = self.svg.xpath(".//svg:g[starts-with(@inkscape:label, 'GlyphLayer-')]", namespaces=NSS) + for layer in glyph_layers: + glyph_name = layer.attrib[INKSCAPE_LABEL].replace("GlyphLayer-", "", 1) + glyphs.append(glyph_name) + return glyphs diff --git a/lib/lettering/kerning.py b/lib/lettering/kerning.py deleted file mode 100644 index 5596ce8a..00000000 --- a/lib/lettering/kerning.py +++ /dev/null @@ -1,125 +0,0 @@ -# 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 NSS -from lxml import etree - - -class FontKerning(object): - """ - This class reads kerning information from an SVG file - """ - def __init__(self, path): - with open(path, 'r', encoding="utf-8") as svg: - self.svg = etree.parse(svg) - - # horiz_adv_x defines the wdith of specific letters (distance to next letter) - def horiz_adv_x(self): - # In XPath 2.0 we could use ".//svg:glyph/(@unicode|@horiz-adv-x)" - xpath = ".//svg:glyph[@unicode and @horiz-adv-x]/@*[name()='unicode' or name()='horiz-adv-x']" - hax = self.svg.xpath(xpath, namespaces=NSS) - if len(hax) == 0: - return {} - return dict(zip(hax[0::2], [int(x) for x in hax[1::2]])) - - # kerning (specific distances of two specified letters) - def hkern(self): - xpath = ".//svg:hkern[(@u1 or @g1) and (@u1 or @g1) and @k]/@*[contains(name(), '1') or contains(name(), '2') or name()='k']" - hkern = self.svg.xpath(xpath, namespaces=NSS) - - # the kerning list now contains the kerning values as a list where every first value contains the first letter(s), - # every second value contains the second letter(s) and every third value contains the kerning - u_first = [k for k in hkern[0::3]] - u_second = [k for k in hkern[1::3]] - k = [int(x) for x in hkern[2::3]] - - # sometimes a font file contains conflicting kerning value for a letter pair - # in this case the value which is specified as a single pair overrules the one specified in a list of letters - # therefore we want to sort our list by length of the letter values - kern_list = list(zip(u_first, u_second, k)) - kern_list.sort(key=lambda x: len(x[0] + x[1]), reverse=True) - - for index, kerning in enumerate(kern_list): - first, second, key = kerning - # fontTools.agl will import fontTools.misc.py23 which will output a deprecation warning - # ignore the warning for now - until the library fixed it - if index == 0: - import warnings - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - global toUnicode - from fontTools.agl import toUnicode - first = self.split_glyph_list(first) - second = self.split_glyph_list(second) - kern_list[index] = (first, second, key) - - hkern = {} - for first, second, key in kern_list: - for f in first: - for s in second: - hkern[f+s] = key - return hkern - - def split_glyph_list(self, glyph): - glyphs = [] - if len(glyph) > 1: - # glyph names need to be converted to unicode - # we need to take into account, that there can be more than one first/second letter in the very same hkern element - # in this case they will be commas separated and each first letter needs to be combined with each next letter - # e.g. - glyph_names = glyph.split(",") - for glyph_name in glyph_names: - # each glyph can have additional special markers, e.g. o.cmp - # toUnicode will not respect those and convert them to a simple letter - # this behaviour will generate a wrong spacing for this letter. - # Let's make sure to also transfer the separators and extensions to our json file - separators = [".", "_"] - used_separator = False - for separator in separators: - glyph_with_separator = glyph_name.split(separator) - if len(glyph_with_separator) == 2: - glyphs.append("%s%s%s" % (toUnicode(glyph_with_separator[0]), separator, glyph_with_separator[1])) - used_separator = True - continue - # there is no extra separator - if not used_separator: - glyphs.append(toUnicode(glyph_name)) - else: - glyphs.append(glyph) - return glyphs - - # the space character - def word_spacing(self): - xpath = "string(.//svg:glyph[@glyph-name='space'][1]/@*[name()='horiz-adv-x'])" - word_spacing = self.svg.xpath(xpath, namespaces=NSS) - try: - return int(word_spacing) - except ValueError: - return None - - # default letter spacing - def letter_spacing(self): - xpath = "string(.//svg:font[@horiz-adv-x][1]/@*[name()='horiz-adv-x'])" - letter_spacing = self.svg.xpath(xpath, namespaces=NSS) - try: - return int(letter_spacing) - except ValueError: - return None - - # this value will be saved into the json file to preserve it for later font edits - # additionally it serves to automatically define the line height (leading) - def units_per_em(self): - xpath = "string(.//svg:font-face[@units-per-em][1]/@*[name()='units-per-em'])" - units_per_em = self.svg.xpath(xpath, namespaces=NSS) - try: - return int(units_per_em) - except ValueError: - return None - - """ - def missing_glyph_spacing(self): - xpath = "string(.//svg:missing-glyph/@*[name()='horiz-adv-x'])" - return float(self.svg.xpath(xpath, namespaces=NSS)) - """ -- cgit v1.2.3