Letters to font extension ()

pull/1382/head
Kaalleen 2021-10-09 18:25:29 +02:00 zatwierdzone przez GitHub
rodzic 0224794a08
commit 5a1ad7e4e8
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
12 zmienionych plików z 323 dodań i 85 usunięć

Wyświetl plik

@ -24,6 +24,7 @@ from .lettering import Lettering
from .lettering_custom_font_dir import LetteringCustomFontDir
from .lettering_generate_json import LetteringGenerateJson
from .lettering_remove_kerning import LetteringRemoveKerning
from .letters_to_font import LettersToFont
from .object_commands import ObjectCommands
from .output import Output
from .params import Params
@ -55,6 +56,7 @@ __all__ = extensions = [StitchPlanPreview,
LetteringGenerateJson,
LetteringRemoveKerning,
LetteringCustomFontDir,
LettersToFont,
Troubleshoot,
RemoveEmbroiderySettings,
Cleanup,

Wyświetl plik

@ -3,70 +3,13 @@
# Copyright (c) 2010 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
import os
import sys
import inkex
from lxml import etree
import pyembroidery
from ..i18n import _
from ..stitch_plan import StitchPlan
from ..svg import PIXELS_PER_MM, render_stitch_plan
from ..svg.tags import INKSCAPE_LABEL
from ..stitch_plan import generate_stitch_plan
class Input(object):
def run(self, args):
embroidery_file = args[0]
self.validate_file_path(embroidery_file)
pattern = pyembroidery.read(embroidery_file)
stitch_plan = StitchPlan()
color_block = None
for raw_stitches, thread in pattern.get_as_colorblocks():
color_block = stitch_plan.new_color_block(thread)
for x, y, command in raw_stitches:
if command == pyembroidery.STITCH:
color_block.add_stitch(x * PIXELS_PER_MM / 10.0, y * PIXELS_PER_MM / 10.0)
if len(color_block) > 0:
if command == pyembroidery.TRIM:
color_block.add_stitch(trim=True)
elif command == pyembroidery.STOP:
color_block.add_stitch(stop=True)
color_block = stitch_plan.new_color_block(thread)
stitch_plan.delete_empty_color_blocks()
if stitch_plan.last_color_block:
if stitch_plan.last_color_block.last_stitch:
if stitch_plan.last_color_block.last_stitch.stop:
# ending with a STOP command is redundant, so remove it
del stitch_plan.last_color_block[-1]
extents = stitch_plan.extents
svg = inkex.SvgDocumentElement("svg", nsmap=inkex.NSS, attrib={
"width": str(extents[0] * 2),
"height": str(extents[1] * 2),
"viewBox": "0 0 %s %s" % (extents[0] * 2, extents[1] * 2),
})
render_stitch_plan(svg, stitch_plan)
# rename the Stitch Plan layer so that it doesn't get overwritten by Embroider
layer = svg.find(".//*[@id='__inkstitch_stitch_plan__']")
layer.set(INKSCAPE_LABEL, os.path.basename(embroidery_file))
layer.attrib.pop('id')
# Shift the design so that its origin is at the center of the canvas
# Note: this is NOT the same as centering the design in the canvas!
layer.set('transform', 'translate(%s,%s)' % (extents[0], extents[1]))
print(etree.tostring(svg).decode('utf-8'))
def validate_file_path(self, path):
# Check if the file exists
if not os.path.isfile(path):
inkex.errormsg(_('File does not exist and cannot be opened. Please correct the file path and try again.\r%s') % path)
sys.exit(1)
stitch_plan = generate_stitch_plan(embroidery_file)
print(etree.tostring(stitch_plan).decode('utf-8'))

Wyświetl plik

@ -174,7 +174,7 @@ class LetteringFrame(wx.Frame):
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.name)
self.font_chooser.Append(font.marked_custom_font_name)
def get_font_descriptions(self):
return {font.name: font.description for font in self.fonts.values()}

Wyświetl plik

@ -0,0 +1,81 @@
# Authors: see git history
#
# Copyright (c) 2021 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
import os
from pathlib import Path
import inkex
from inkex import errormsg
from ..commands import ensure_symbol
from ..i18n import _
from ..stitch_plan import generate_stitch_plan
from ..svg import get_correction_transform
from ..svg.tags import INKSCAPE_GROUPMODE, INKSCAPE_LABEL, SVG_PATH_TAG
from .base import InkstitchExtension
class LettersToFont(InkstitchExtension):
'''
This extension will create a json file to store a custom directory path for additional user fonts
'''
def __init__(self, *args, **kwargs):
InkstitchExtension.__init__(self, *args, **kwargs)
self.arg_parser.add_argument("-d", "--font-dir", type=str, default="", dest="font_dir")
self.arg_parser.add_argument("-f", "--file-format", type=str, default="", dest="file_format")
self.arg_parser.add_argument("-c", "--import-commands", type=inkex.Boolean, default=False, dest="import_commands")
def effect(self):
font_dir = self.options.font_dir
file_format = self.options.file_format
if not os.path.isdir(font_dir):
errormsg(_("Font directory not found. Please specify an existing directory."))
glyphs = list(Path(font_dir).rglob(file_format))
if not glyphs:
glyphs = list(Path(font_dir).rglob(file_format.lower()))
document = self.document.getroot()
for glyph in glyphs:
letter = self.get_glyph_element(glyph)
label = "GlyphLayer-%s" % letter.get(INKSCAPE_LABEL, ' ').split('.')[0][-1]
group = inkex.Group(attrib={
INKSCAPE_LABEL: label,
INKSCAPE_GROUPMODE: "layer",
"transform": get_correction_transform(document, child=True)
})
# remove color block groups if we import without commands
# there will only be one object per color block anyway
if not self.options.import_commands:
for element in letter.iter(SVG_PATH_TAG):
group.insert(0, element)
else:
group.insert(0, letter)
document.insert(0, group)
group.set('style', 'display:none')
# users may be confused if they get an empty document
# make last letter visible again
group.set('style', None)
# In most cases trims are inserted with the imported letters.
# Let's make sure the trim symbol exists in the defs section
ensure_symbol(document, 'trim')
self.insert_baseline(document)
def get_glyph_element(self, glyph):
stitch_plan = generate_stitch_plan(str(glyph), self.options.import_commands)
# we received a stitch plan wrapped in an svg document, we only need the stitch_plan group
# this group carries the name of the file, so we can search for it.
stitch_plan = stitch_plan.xpath('.//*[@inkscape:label="%s"]' % os.path.basename(glyph), namespaces=inkex.NSS)[0]
stitch_plan.attrib.pop(INKSCAPE_GROUPMODE)
return stitch_plan
def insert_baseline(self, document):
document.namedview.new_guide(position=0.0, name="baseline")

Wyświetl plik

@ -6,15 +6,18 @@
import json
import os
from copy import deepcopy
from random import randint
import inkex
from ..commands import ensure_symbol
from ..elements import nodes_to_elements
from ..exceptions import InkstitchException
from ..extensions.lettering_custom_font_dir import get_custom_font_dir
from ..i18n import _, get_languages
from ..stitches.auto_satin import auto_satin
from ..svg.tags import INKSCAPE_LABEL, SVG_PATH_TAG
from ..svg.tags import (CONNECTION_END, CONNECTION_START, INKSCAPE_LABEL,
SVG_PATH_TAG, SVG_USE_TAG, XLINK_HREF)
from ..utils import Point
from .font_variant import FontVariant
@ -220,6 +223,8 @@ class Font(object):
element.set('style', '%s%s%s' % (style.to_str(), stroke_width, dash_array))
self._ensure_command_symbols(destination_group)
return destination_group
def get_variant(self, variant):
@ -303,8 +308,39 @@ class Font(object):
position.x += self.horiz_adv_x.get(character, horiz_adv_x_default) - glyph.min_x
self._update_commands(node, glyph)
return node
def _update_commands(self, node, glyph):
for element, connectors in glyph.commands.items():
# update element
el = node.find(".//*[@id='%s']" % element)
# we cannot get a unique id from the document at this point
# so let's create a random id which will most probably work as well
new_element_id = "%s_%s" % (element, randint(0, 9999))
el.set_id(new_element_id)
for connector, symbol in connectors:
# update symbol
new_symbol_id = "%s_%s" % (symbol, randint(0, 9999))
s = node.find(".//*[@id='%s']" % symbol)
s.set_id(new_symbol_id)
# update connector
c = node.find(".//*[@id='%s']" % connector)
c.set(CONNECTION_END, "#%s" % new_element_id)
c.set(CONNECTION_START, "#%s" % new_symbol_id)
def _ensure_command_symbols(self, group):
# collect commands
commands = set()
for element in group.iterdescendants(SVG_USE_TAG):
xlink = element.get(XLINK_HREF, ' ')
if xlink.startswith('#inkstitch_'):
commands.add(xlink[11:])
# make sure all necessary command symbols are in the document
for command in commands:
ensure_symbol(group.getroottree().getroot(), command)
def _apply_auto_satin(self, group, trim):
"""Apply Auto-Satin to an SVG XML node tree with an svg:g at its root.

Wyświetl plik

@ -7,7 +7,8 @@ import os
import inkex
from ..svg.tags import INKSCAPE_GROUPMODE, INKSCAPE_LABEL
from ..svg.tags import (INKSCAPE_GROUPMODE, INKSCAPE_LABEL, SVG_GROUP_TAG,
SVG_PATH_TAG, SVG_USE_TAG)
from .glyph import Glyph
@ -60,7 +61,8 @@ class FontVariant(object):
def _load_glyphs(self):
svg_path = os.path.join(self.path, "%s.svg" % self.variant)
svg = inkex.load_svg(svg_path)
svg = inkex.load_svg(svg_path).getroot()
svg = self._apply_transforms(svg)
glyph_layers = svg.xpath(".//svg:g[starts-with(@inkscape:label, 'GlyphLayer-')]", namespaces=inkex.NSS)
for layer in glyph_layers:
@ -79,6 +81,29 @@ class FontVariant(object):
group.style.pop('display', None)
group.attrib.pop('display', None)
def _apply_transforms(self, svg):
# apply transforms to paths and use tags
for element in svg.iterdescendants((SVG_PATH_TAG, SVG_USE_TAG)):
transform = element.composed_transform()
if element.tag == SVG_PATH_TAG:
path = element.path.transform(transform)
element.set_path(path)
element.attrib.pop("transform", None)
if element.tag == SVG_USE_TAG:
oldx = element.get('x', 0)
oldy = element.get('y', 0)
newx, newy = transform.apply_to_point((oldx, oldy))
element.set('x', newx)
element.set('y', newy)
element.attrib.pop("transform", None)
# remove transforms after they have been applied
for group in svg.iterdescendants(SVG_GROUP_TAG):
group.attrib.pop('transform', None)
return svg
def __getitem__(self, character):
if character in self.glyphs:
return self.glyphs[character]

Wyświetl plik

@ -5,10 +5,11 @@
from copy import copy
from inkex import paths, transforms
from inkex import paths, transforms, units
from ..svg import get_guides
from ..svg.tags import SVG_GROUP_TAG, SVG_PATH_TAG
from ..svg import get_correction_transform, get_guides
from ..svg.tags import (CONNECTION_END, SVG_GROUP_TAG, SVG_PATH_TAG,
SVG_USE_TAG, XLINK_HREF)
class Glyph(object):
@ -38,6 +39,7 @@ class Glyph(object):
self.node = self._process_group(group)
self._process_bbox()
self._move_to_origin()
self._process_commands()
def _process_group(self, group):
new_group = copy(group)
@ -50,13 +52,21 @@ class Glyph(object):
new_group.append(self._process_group(node))
else:
node_copy = copy(node)
transform = -transforms.Transform(get_correction_transform(node, True))
if "d" in node.attrib:
node_copy.path = node.path.transform(node.composed_transform()).to_absolute()
node_copy.path = node.path.transform(transform).to_absolute()
# Delete transforms from paths and groups, since we applied
# them to the paths already.
node_copy.attrib.pop('transform', None)
if not node.tag == SVG_USE_TAG:
# Delete transforms from paths and groups, since we applied
# them to the paths already.
node_copy.attrib.pop('transform', None)
else:
oldx = node.get('x', 0)
oldy = node.get('y', 0)
x, y = transform.apply_to_point((oldx, oldy))
node_copy.set('x', x)
node_copy.set('y', y)
new_group.append(node_copy)
@ -72,11 +82,30 @@ class Glyph(object):
self.baseline = 0
def _process_bbox(self):
bbox = [paths.Path(node.get("d")).bounding_box() for node in self.node.iterdescendants(SVG_PATH_TAG)]
bbox = [paths.Path(node.get("d")).bounding_box() for node in self.node.iterdescendants(SVG_PATH_TAG) if not node.get(CONNECTION_END, None)]
left, right = min([box.left for box in bbox]), max([box.right for box in bbox])
self.width = right - left
self.min_x = left
def _process_commands(self):
# Save object ids with commmands in a dictionary: {object_id: [connector_id, symbol_id]}
self.commands = {}
for node in self.node.iter(SVG_USE_TAG):
xlink = node.get(XLINK_HREF, ' ')
if not xlink.startswith('#inkstitch_'):
continue
try:
connector = self.node.xpath(".//*[@inkscape:connection-start='#%s']" % node.get('id', ' '))[0]
command_object = connector.get(CONNECTION_END)[1:]
try:
self.commands[command_object].append([connector.get_id(), node.get_id()])
except KeyError:
self.commands[command_object] = [[connector.get_id(), node.get_id()]]
except IndexError:
pass
def _move_to_origin(self):
translate_x = -self.min_x
translate_y = -self.baseline
@ -87,3 +116,11 @@ class Glyph(object):
path = path.transform(transform)
node.set('d', str(path))
node.attrib.pop('transform', None)
# Move commands as well
for node in self.node.iter(SVG_USE_TAG):
oldx = units.convert_unit(node.get("x", 0), 'px', node.unit)
oldy = units.convert_unit(node.get("y", 0), 'px', node.unit)
x, y = transform.apply_to_point((oldx, oldy))
node.set('x', x)
node.set('y', y)

Wyświetl plik

@ -3,8 +3,9 @@
# Copyright (c) 2010 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
from .stitch_plan import stitch_groups_to_stitch_plan, StitchPlan
from .color_block import ColorBlock
from .stitch_group import StitchGroup
from .stitch import Stitch
from .generate_stitch_plan import generate_stitch_plan
from .read_file import stitch_plan_from_file
from .stitch import Stitch
from .stitch_group import StitchGroup
from .stitch_plan import StitchPlan, stitch_groups_to_stitch_plan

Wyświetl plik

@ -0,0 +1,74 @@
# 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 os
import sys
import inkex
import pyembroidery
from ..i18n import _
from ..svg import PIXELS_PER_MM, render_stitch_plan
from ..svg.tags import INKSCAPE_LABEL
from .stitch import Stitch
from .stitch_plan import StitchPlan
def generate_stitch_plan(embroidery_file, import_commands=True): # noqa: C901
validate_file_path(embroidery_file)
pattern = pyembroidery.read(embroidery_file)
stitch_plan = StitchPlan()
color_block = None
for raw_stitches, thread in pattern.get_as_colorblocks():
color_block = stitch_plan.new_color_block(thread)
for x, y, command in raw_stitches:
if command == pyembroidery.STITCH:
color_block.add_stitch(Stitch(x * PIXELS_PER_MM / 10.0, y * PIXELS_PER_MM / 10.0))
if len(color_block) > 0:
if not import_commands and command in [pyembroidery.TRIM, pyembroidery.STOP]:
# Importing commands is not wanted:
# start a new color block without inserting the command
color_block = stitch_plan.new_color_block(thread)
elif command == pyembroidery.TRIM:
color_block.add_stitch(trim=True)
elif command == pyembroidery.STOP:
color_block.add_stitch(stop=True)
color_block = stitch_plan.new_color_block(thread)
stitch_plan.delete_empty_color_blocks()
if stitch_plan.last_color_block:
if stitch_plan.last_color_block.last_stitch:
if stitch_plan.last_color_block.last_stitch.stop:
# ending with a STOP command is redundant, so remove it
del stitch_plan.last_color_block[-1]
extents = stitch_plan.extents
svg = inkex.SvgDocumentElement("svg", nsmap=inkex.NSS, attrib={
"width": str(extents[0] * 2),
"height": str(extents[1] * 2),
"viewBox": "0 0 %s %s" % (extents[0] * 2, extents[1] * 2),
})
render_stitch_plan(svg, stitch_plan)
# rename the Stitch Plan layer so that it doesn't get overwritten by Embroider
layer = svg.find(".//*[@id='__inkstitch_stitch_plan__']")
layer.set(INKSCAPE_LABEL, os.path.basename(embroidery_file))
layer.attrib.pop('id')
# Shift the design so that its origin is at the center of the canvas
# Note: this is NOT the same as centering the design in the canvas!
layer.set('transform', 'translate(%s,%s)' % (extents[0], extents[1]))
return svg
def validate_file_path(path):
# Check if the file exists
if not os.path.isfile(path):
inkex.errormsg(_('File does not exist and cannot be opened. Please correct the file path and try again.\r%s') % path)
sys.exit(1)

Wyświetl plik

@ -3,11 +3,10 @@
# Copyright (c) 2010 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
from inkex import transforms
from inkex.units import convert_unit
from ..utils import Point, cache, string_to_floats
from .tags import INKSCAPE_LABEL, SODIPODI_GUIDE, SODIPODI_NAMEDVIEW
from .units import get_doc_size, get_viewbox_transform
class InkscapeGuide(object):
@ -20,16 +19,15 @@ class InkscapeGuide(object):
def _parse(self):
self.label = self.node.get(INKSCAPE_LABEL, "")
doc_size = list(get_doc_size(self.svg))
# convert the size from viewbox-relative to real-world pixels
viewbox_transform = get_viewbox_transform(self.svg)
viewbox_transform = transforms.Transform(-transforms.Transform(viewbox_transform)).apply_to_point(doc_size)
self.position = Point(*string_to_floats(self.node.get('position')))
doc_size = self.svg.get_page_bbox()
# inkscape's Y axis is reversed from SVG's, and the guide is in inkscape coordinates
self.position.y = doc_size[1] - self.position.y
self.position = Point(*string_to_floats(self.node.get('position')))
self.position.y = doc_size.y.size - self.position.y
# convert units to px
unit = self.svg.unit
self.position.y = convert_unit(self.position.y, 'px', unit)
# This one baffles me. I think inkscape might have gotten the order of
# their vector wrong?

Wyświetl plik

@ -28,6 +28,7 @@ INKSCAPE_GROUPMODE = inkex.addNS('groupmode', 'inkscape')
CONNECTION_START = inkex.addNS('connection-start', 'inkscape')
CONNECTION_END = inkex.addNS('connection-end', 'inkscape')
CONNECTOR_TYPE = inkex.addNS('connector-type', 'inkscape')
INKSCAPE_DOCUMENT_UNITS = inkex.addNS('document-units', 'inkscape')
XLINK_HREF = inkex.addNS('href', 'xlink')

Wyświetl plik

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Letters to font</name>
<id>org.inkstitch.letters_to_font</id>
<param name="extension" type="string" gui-hidden="true">letters_to_font</param>
<effect needs-live-preview="false">
<object-type>all</object-type>
<effects-menu>
<submenu name="Ink/Stitch">
<submenu name="Font Management" />
</submenu>
</effects-menu>
</effect>
<param name="header" type="description" appearance="header" indent="1" >
Includes all letters of a predigitized embroidery font (one file for each letter) into the document in order to make it available for the Ink/Stitch lettering system.
</param>
<param name="file-description" type="description" indent="1" >
Embroidery files need to have the name of the letter right before the file extension. E.g. A.dst or Example_Font_A.dst will be recognized as the letter A.
</param>
<separator />
<spacer />
<param name="file-format" type="optiongroup" appearance="combo" gui-text="File format" indent="1">
{% for format, description, mimetype, category in formats %}
<option value="*.{{ format | upper }}">{{ format | upper }}</option>
{% endfor %}
</param>
<param type="path" name="font-dir" gui-text="Font directory" indent="1" mode="folder" filetypes="svg"/>
<spacer />
<param type="boolean" name="import-commands" gui-text="Import commands" indent="1">false</param>
<spacer />
<separator />
<param name="file-description" type="description" indent="1" >
&#9888; After running this function, drag the baseline into the desired position and place the letters accordingly.
Save your font in a separate folder. Then generate the json file (with "Autoroute Satin" unchecked).
</param>
<separator />
<script>
{{ command_tag | safe }}
</script>
</inkscape-extension>