diff options
Diffstat (limited to 'lib/extensions')
| -rw-r--r-- | lib/extensions/lettering_svg_font_to_layers.py | 183 |
1 files changed, 146 insertions, 37 deletions
diff --git a/lib/extensions/lettering_svg_font_to_layers.py b/lib/extensions/lettering_svg_font_to_layers.py index be8a7f16..81586195 100644 --- a/lib/extensions/lettering_svg_font_to_layers.py +++ b/lib/extensions/lettering_svg_font_to_layers.py @@ -19,70 +19,151 @@ # # # Adapted for the inkstitch lettering module to allow glyph annotations for characters -# in specific positions or settings. Changes: see git history +# in specific positions or settings, also allows scaling.Changes: see git history """Extension for converting svg fonts to layers""" from inkex import Layer, PathElement, errormsg from .base import InkstitchExtension +from ..svg import PIXELS_PER_MM -class LetteringSvgFontToLayers(InkstitchExtension): - """Convert an svg font to layers""" - def add_arguments(self, pars): +class LetteringSvgFontToLayers(InkstitchExtension): + """ + An Ink/Stitch extension for converting SVG font glyphs into separate layers. + This extension processes an SVG font embedded in an SVG document, scaling and converting each glyph into its own layer for + further manipulation or embroidery digitization. It provides options to control the number of glyphs processed, + the reference character for scaling, and the desired reference height in millimeters. + Attributes: + None explicitly defined. + Methods: + add_arguments(pars): + Adds command-line arguments for glyph count, reference character, and reference height. + scale_hkerning(scale_by): + flip_cordinate_system(elem, emsize, baseline, scale_by): + Scales and translates the element's path to match the desired coordinate system. + reference_size(font, reference): + set_view_port(font, scale_by): + Sets the SVG viewport size and viewBox based on the scaled em size, return the width (= height) of the viewport + set_guide_lines(font, scale_by): + Adds guide lines for baseline, ascender, caps, x-height, and descender to the SVG. Return the baseline position + convert_glyph_to_layer(glyph, emsize, baseline, scale_by, hide_layer): + Converts a single glyph into a new SVG layer, applying scaling and coordinate transformation. + effect(): + Main entry point for the extension. Processes the SVG font, scales glyphs, creates layers, and applies kerning scaling. + + Usage: + This extension is intended to be used within the Ink/Stitch environment as a command-line or GUI extension for converting + SVG font glyphs into layers suitable for embroidery design. + """ + + def add_arguments(self, pars) -> None: + """ Adds command-line arguments for glyph count, reference character, and reference height. + """ pars.add_argument( "--count", type=int, - default=30, - help="Stop making layers after this number of glyphs.", + default=150, + help="Stop making layers after this number of glyphs." + ) + pars.add_argument( + "--reference", + type=str, + default="M", + help="Reference character for size" + ) + pars.add_argument( + "--height", + type=float, + default=20.0, + help="Reference character height in mm" ) - def flip_cordinate_system(self, elem, emsize, baseline): - """Scale and translate the element's path, returns the path object""" + def scale_hkerning(self, scale_by) -> None: + """ + Scales all horizontal kerning (hkern) values in the font by the given factor. + """ + font = self.svg.defs.findone("svg:font") + for hkern in font.findall("svg:hkern"): + k = hkern.get("k", None) + if k is not None: + k = round(float(k) * scale_by, 2) + hkern.set(("k"), str(k)) + + def flip_cordinate_system(self, elem, emsize, baseline, scale_by): + """ + Scale and translate the element's path, returns the path object + """ path = elem.path - path.scale(1, -1, inplace=True) - path.translate(0, int(emsize) - int(baseline), inplace=True) + path.scale(scale_by, -scale_by, inplace=True) + path.translate(0, float(emsize) - float(baseline), inplace=True) return path - def effect(self): - # Current code only reads the first svgfont instance - font = self.svg.defs.findone("svg:font") - if font is None: - return errormsg("There are no svg fonts") - # setwidth = font.get("horiz-adv-x") + def reference_size(self, font, reference): + """ + Returns the height of the reference glyph used for scaling. + """ + for glyph in font.findall("svg:glyph"): + if glyph.get("glyph-name") == str(reference): + path = glyph.path + return path.bounding_box().height + break + + def set_view_port(self, font, scale_by) -> float: + """ + Sets the SVG viewport size and viewBox based on the scaled em size, return the width (= height) of the viewport + """ + fontface = font.findone("svg:font-face") + + emsize = fontface.get("units-per-em") + emsize = round(float(emsize) * scale_by, 2) + fontface.set("units-per-em", str(emsize)) + + self.svg.set("width", str(emsize)) + self.svg.set("height", str(emsize)) + self.svg.set("viewBox", "0 0 " + str(emsize) + " " + str(emsize)) + return emsize + + def set_guide_lines(self, font, scale_by) -> float: + """ + Adds guide lines for baseline, ascender, caps, x-height, and descender to the SVG. Return the baseline position_ + """ + baseline = font.get("horiz-origin-y") + horiz_adv_x = round(float(font.get("horiz-adv-x", 0)) * scale_by, 2) + font.set("horiz-adv-x", str(horiz_adv_x)) + + fontface = font.findone("svg:font-face") if baseline is None: baseline = 0 + else: + baseline = float(baseline) * scale_by - guidebase = self.svg.viewbox_height - baseline + guidebase = (self.svg.viewbox_height) - float(baseline) - fontface = font.findone("svg:font-face") + caps = round(float(fontface.get("cap-height", 0)) * scale_by, 2) + xheight = round(float(fontface.get("x-height", 0)) * scale_by, 2) + ascender = round(float(fontface.get("ascent", 0)) * scale_by, 2) + descender = round(float(fontface.get("descent", 0)) * scale_by, 2) - emsize = fontface.get("units-per-em") - - # TODO: should we guarantee that <svg:font horiz-adv-x> equals <svg:font-face units-per-em> ? - caps = float(fontface.get("cap-height", 0)) - xheight = float(fontface.get("x-height", 0)) - ascender = float(fontface.get("ascent", 0)) - descender = float(fontface.get("descent", 0)) + fontface.set("cap-height", str(caps)) + fontface.set("x-height", str(xheight)) + fontface.set("ascent", str(ascender)) + fontface.set("descent", str(descender)) - self.svg.set("width", emsize) self.svg.namedview.add_guide(guidebase, True, "baseline") self.svg.namedview.add_guide(guidebase - ascender, True, "ascender") self.svg.namedview.add_guide(guidebase - caps, True, "caps") self.svg.namedview.add_guide(guidebase - xheight, True, "xheight") - self.svg.namedview.add_guide(guidebase + descender, True, "decender") + self.svg.namedview.add_guide(guidebase - descender, True, "decender") - count = 0 - for glyph in font.findall("svg:glyph"): - hide_layer = count != 0 - self.convert_glyph_to_layer(glyph, emsize, baseline, hide_layer=hide_layer) - count += 1 - if count >= self.options.count: - break + return baseline - def convert_glyph_to_layer(self, glyph, emsize, baseline, hide_layer): + def convert_glyph_to_layer(self, glyph, emsize, baseline, scale_by, hide_layer): + """ + Converts a single glyph into a new SVG layer, applying scaling and coordinate transformation. + """ unicode_char = glyph.get("unicode") glyph_name = glyph.get("glyph-name").split('.') @@ -104,13 +185,41 @@ class LetteringSvgFontToLayers(InkstitchExtension): layer = self.svg.add(Layer.new(f"GlyphLayer-{unicode_char}{typographic_feature}")) - # glyph layers (except the first one) are innitially hidden + # glyph layers (except the first one) are initially hidden if hide_layer: layer.style["display"] = "none" # Using curve description in d attribute of svg:glyph path = layer.add(PathElement()) - path.path = self.flip_cordinate_system(glyph, emsize, baseline) + path.path = self.flip_cordinate_system(glyph, emsize, baseline, scale_by) + + def effect(self) -> None: + # Current code only reads the first svgfont instance + font = self.svg.defs.findone("svg:font") + if font is None: + return errormsg("There are no svg fonts") + + reference_size = self.reference_size(font, self.options.reference) + if reference_size is None: + return errormsg("Reference glyph not found in the font") + + scale_by = self.options.height * PIXELS_PER_MM / reference_size + emsize = self.set_view_port(font, scale_by) + baseline = self.set_guide_lines(font, scale_by) + + count = 0 + for glyph in font.findall("svg:glyph"): + hide_layer = count != 0 + hax = glyph.get("horiz-adv-x", None) + if hax is not None: + hax = round(float(hax) * scale_by, 2) + glyph.set(("horiz-adv-x"), str(hax)) + self.convert_glyph_to_layer(glyph, emsize, baseline, scale_by, hide_layer=hide_layer) + count += 1 + if count >= self.options.count: + break + + self.scale_hkerning(scale_by) if __name__ == "__main__": |
