kopia lustrzana https://github.com/inkstitch/inkstitch
Add batch lettering extension (#3589)
rodzic
de7d86e526
commit
9f91470ac7
|
@ -8,6 +8,7 @@ from .apply_palette import ApplyPalette
|
|||
from .apply_threadlist import ApplyThreadlist
|
||||
from .auto_run import AutoRun
|
||||
from .auto_satin import AutoSatin
|
||||
from .batch_lettering import BatchLettering
|
||||
from .break_apart import BreakApart
|
||||
from .cleanup import Cleanup
|
||||
from .commands_scale_symbols import CommandsScaleSymbols
|
||||
|
@ -83,6 +84,7 @@ extensions = [
|
|||
ApplyThreadlist,
|
||||
AutoRun,
|
||||
AutoSatin,
|
||||
BatchLettering,
|
||||
BreakApart,
|
||||
Cleanup,
|
||||
CommandsScaleSymbols,
|
||||
|
|
|
@ -0,0 +1,231 @@
|
|||
# Authors: see git history
|
||||
#
|
||||
# Copyright (c) 2025 Authors
|
||||
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
from copy import deepcopy
|
||||
from zipfile import ZipFile
|
||||
|
||||
from inkex import Boolean, Group, errormsg
|
||||
from lxml import etree
|
||||
|
||||
import pyembroidery
|
||||
|
||||
from ..extensions.lettering_along_path import TextAlongPath
|
||||
from ..i18n import _
|
||||
from ..lettering import get_font_by_name
|
||||
from ..output import write_embroidery_file
|
||||
from ..stitch_plan import stitch_groups_to_stitch_plan
|
||||
from ..svg import get_correction_transform
|
||||
from ..threads import ThreadCatalog
|
||||
from ..utils import DotDict
|
||||
from .base import InkstitchExtension
|
||||
|
||||
|
||||
class BatchLettering(InkstitchExtension):
|
||||
def __init__(self, *args, **kwargs):
|
||||
InkstitchExtension.__init__(self)
|
||||
|
||||
self.arg_parser.add_argument('--notebook')
|
||||
|
||||
self.arg_parser.add_argument('--text', type=str, default='', dest='text')
|
||||
self.arg_parser.add_argument('--separator', type=str, default='', dest='separator')
|
||||
|
||||
self.arg_parser.add_argument('--font', type=str, default='', dest='font')
|
||||
self.arg_parser.add_argument('--scale', type=int, default=100, dest='scale')
|
||||
self.arg_parser.add_argument('--color-sort', type=str, default='off', dest='color_sort')
|
||||
self.arg_parser.add_argument('--trim', type=str, default='off', dest='trim')
|
||||
self.arg_parser.add_argument('--use-command-symbols', type=Boolean, default=False, dest='command_symbols')
|
||||
self.arg_parser.add_argument('--text-align', type=str, default='left', dest='text_align')
|
||||
|
||||
self.arg_parser.add_argument('--text-position', type=str, default='left', dest='text_position')
|
||||
|
||||
self.arg_parser.add_argument('--file-formats', type=str, default='', dest='formats')
|
||||
|
||||
def effect(self):
|
||||
separator = self.options.separator
|
||||
if not separator:
|
||||
separator = '\n'
|
||||
text_input = self.options.text
|
||||
if not text_input:
|
||||
errormsg(_("Please specify a text"))
|
||||
return
|
||||
texts = text_input.replace('\\n', '\n').split(separator)
|
||||
|
||||
if not self.options.font:
|
||||
errormsg(_("Please specify a font"))
|
||||
return
|
||||
self.font = get_font_by_name(self.options.font)
|
||||
if self.font is None:
|
||||
errormsg(_("Please specify a valid font name"))
|
||||
return
|
||||
|
||||
if not self.options.formats:
|
||||
errormsg(_("Please specify at least one output file format"))
|
||||
return
|
||||
available_formats = [file_format['extension'] for file_format in pyembroidery.supported_formats()] + ['svg']
|
||||
file_formats = self.options.formats.split(',')
|
||||
file_formats = [file_format.strip().lower() for file_format in file_formats if file_format.strip().lower() in available_formats]
|
||||
if not file_formats:
|
||||
errormsg(_("Please specify at least one file format supported by pyembroidery"))
|
||||
return
|
||||
|
||||
self.setup_trim()
|
||||
self.setup_text_align()
|
||||
self.setup_color_sort()
|
||||
self.setup_scale()
|
||||
|
||||
self.generate_output_files(texts, file_formats)
|
||||
|
||||
# don't let inkex output the SVG!
|
||||
sys.exit(0)
|
||||
|
||||
def setup_trim(self):
|
||||
self.trim = 0
|
||||
if self.options.trim == "line":
|
||||
self.trim = 1
|
||||
elif self.options.trim == "word":
|
||||
self.trim = 2
|
||||
elif self.options.trim == "glyph":
|
||||
self.trim = 3
|
||||
|
||||
def setup_text_align(self):
|
||||
self.text_align = 0
|
||||
if self.options.text_align == "center":
|
||||
self.text_align = 1
|
||||
elif self.options.text_align == "right":
|
||||
self.text_align = 2
|
||||
elif self.options.text_align == "block":
|
||||
self.text_align = 3
|
||||
elif self.options.text_align == "letterspacing":
|
||||
self.text_align = 4
|
||||
|
||||
def setup_color_sort(self):
|
||||
self.color_sort = 0
|
||||
if self.options.color_sort == "all":
|
||||
self.color_sort = 1
|
||||
elif self.options.color_sort == "line":
|
||||
self.color_sort = 2
|
||||
elif self.options.color_sort == "word":
|
||||
self.color_sort = 3
|
||||
|
||||
def setup_scale(self):
|
||||
self.scale = self.options.scale / 100
|
||||
if self.scale < self.font.min_scale:
|
||||
self.scale = self.font.min_scale
|
||||
elif self.scale > self.font.max_scale:
|
||||
self.scale = self.font.max_scale
|
||||
|
||||
def generate_output_files(self, texts, file_formats):
|
||||
self.metadata = self.get_inkstitch_metadata()
|
||||
self.collapse_len = self.metadata['collapse_len_mm']
|
||||
self.min_stitch_len = self.metadata['min_stitch_len_mm']
|
||||
|
||||
# The user can specify a path which can be use for the text along path method.
|
||||
# The path should be labeled as "batch lettering"
|
||||
text_positioning_path = self.svg.findone(".//*[@inkscape:label='batch lettering']")
|
||||
|
||||
path = tempfile.mkdtemp()
|
||||
files = []
|
||||
for text in texts:
|
||||
stitch_plan, lettering_group = self.generate_stitch_plan(text, text_positioning_path)
|
||||
for file_format in file_formats:
|
||||
files.append(self.generate_output_file(file_format, path, text, stitch_plan))
|
||||
|
||||
self.reset_document(lettering_group, text_positioning_path)
|
||||
|
||||
temp_file = tempfile.NamedTemporaryFile(suffix=".zip", delete=False)
|
||||
|
||||
# in windows, failure to close here will keep the file locked
|
||||
temp_file.close()
|
||||
|
||||
with ZipFile(temp_file.name, "w") as zip_file:
|
||||
for output in files:
|
||||
zip_file.write(output, os.path.basename(output))
|
||||
|
||||
# inkscape will read the file contents from stdout and copy
|
||||
# to the destination file that the user chose
|
||||
with open(temp_file.name, 'rb') as output_file:
|
||||
sys.stdout.buffer.write(output_file.read())
|
||||
|
||||
os.remove(temp_file.name)
|
||||
for output in files:
|
||||
os.remove(output)
|
||||
os.rmdir(path)
|
||||
|
||||
def reset_document(self, lettering_group, text_positioning_path):
|
||||
# reset document for the next iteration
|
||||
parent = lettering_group.getparent()
|
||||
index = parent.index(lettering_group)
|
||||
if text_positioning_path is not None:
|
||||
parent.insert(index, text_positioning_path)
|
||||
parent.remove(lettering_group)
|
||||
|
||||
def generate_output_file(self, file_format, path, text, stitch_plan):
|
||||
text = text.replace('\n', '')
|
||||
output_file = os.path.join(path, f"{text}.{file_format}")
|
||||
|
||||
if file_format == 'svg':
|
||||
document = deepcopy(self.document.getroot())
|
||||
with open(output_file, 'w', encoding='utf-8') as svg:
|
||||
svg.write(etree.tostring(document).decode('utf-8'))
|
||||
else:
|
||||
write_embroidery_file(output_file, stitch_plan, self.document.getroot())
|
||||
|
||||
return output_file
|
||||
|
||||
def generate_stitch_plan(self, text, text_positioning_path):
|
||||
|
||||
self.settings = DotDict({
|
||||
"text": text,
|
||||
"text_align": self.text_align,
|
||||
"back_and_forth": True,
|
||||
"font": self.font.marked_custom_font_id,
|
||||
"scale": self.scale * 100,
|
||||
"trim_option": self.trim,
|
||||
"use_trim_symbols": self.options.command_symbols,
|
||||
"color_sort": self.color_sort
|
||||
})
|
||||
|
||||
lettering_group = Group()
|
||||
lettering_group.label = _("Ink/Stitch Lettering")
|
||||
lettering_group.set('inkstitch:lettering', json.dumps(self.settings))
|
||||
self.svg.append(lettering_group)
|
||||
lettering_group.set("transform", get_correction_transform(lettering_group, child=True))
|
||||
|
||||
destination_group = Group()
|
||||
destination_group.label = f"{self.font.name} {_('scale')} {self.scale * 100}%"
|
||||
lettering_group.append(destination_group)
|
||||
|
||||
text = self.font.render_text(
|
||||
text,
|
||||
destination_group,
|
||||
trim_option=self.trim,
|
||||
use_trim_symbols=self.options.command_symbols,
|
||||
color_sort=self.color_sort,
|
||||
text_align=self.text_align
|
||||
)
|
||||
|
||||
destination_group.attrib['transform'] = f'scale({self.scale})'
|
||||
|
||||
if text_positioning_path is not None:
|
||||
parent = text_positioning_path.getparent()
|
||||
index = parent.index(text_positioning_path)
|
||||
parent.insert(index, lettering_group)
|
||||
TextAlongPath(self.svg, lettering_group, text_positioning_path, self.options.text_position)
|
||||
parent.remove(text_positioning_path)
|
||||
|
||||
self.get_elements()
|
||||
stitch_groups = self.elements_to_stitch_groups(self.elements)
|
||||
stitch_plan = stitch_groups_to_stitch_plan(stitch_groups, collapse_len=self.collapse_len, min_stitch_len=self.min_stitch_len)
|
||||
ThreadCatalog().match_and_apply_palette(stitch_plan, self.get_inkstitch_metadata()['thread-palette'])
|
||||
|
||||
return stitch_plan, lettering_group
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
BatchLettering().run()
|
|
@ -6,7 +6,7 @@
|
|||
import json
|
||||
from math import atan2, degrees
|
||||
|
||||
from inkex import Boolean, Transform, errormsg
|
||||
from inkex import Transform, errormsg
|
||||
from inkex.units import convert_unit
|
||||
|
||||
from ..elements import Stroke
|
||||
|
@ -26,7 +26,7 @@ class LetteringAlongPath(InkstitchExtension):
|
|||
def __init__(self, *args, **kwargs):
|
||||
InkstitchExtension.__init__(self, *args, **kwargs)
|
||||
self.arg_parser.add_argument("--notebook")
|
||||
self.arg_parser.add_argument("-s", "--stretch-spaces", type=Boolean, default=False, dest="stretch_spaces")
|
||||
self.arg_parser.add_argument("-p", "--text-position", type=str, default='left', dest="text_position")
|
||||
|
||||
def effect(self):
|
||||
# we ignore everything but the first path/text
|
||||
|
@ -35,134 +35,7 @@ class LetteringAlongPath(InkstitchExtension):
|
|||
errormsg(_("Please select one path and one Ink/Stitch lettering group."))
|
||||
return
|
||||
|
||||
glyphs = [glyph for glyph in text.iterdescendants(SVG_GROUP_TAG) if glyph.label and len(glyph.label) == 1]
|
||||
if not glyphs:
|
||||
errormsg(_("The text doesn't contain any glyphs."))
|
||||
return
|
||||
|
||||
self.load_settings(text)
|
||||
|
||||
if glyphs[0].get('transform', None) is not None:
|
||||
glyphs = self._reset_glyph_transforms(text, glyphs)
|
||||
|
||||
path = Stroke(path).as_multi_line_string().geoms[0]
|
||||
hidden_commands = self.hide_commands(glyphs)
|
||||
space_indices, stretch_space, text_baseline = self.get_position_and_stretch_values(path, text, glyphs)
|
||||
self.transform_glyphs(glyphs, path, stretch_space, space_indices, text_baseline)
|
||||
self.restore_commands(hidden_commands)
|
||||
|
||||
def _reset_glyph_transforms(self, text_group, glyphs):
|
||||
font = get_font_by_id(self.settings.font)
|
||||
if font is not None:
|
||||
try:
|
||||
text_group = list(text_group.iterchildren(SVG_GROUP_TAG))[0]
|
||||
except IndexError:
|
||||
pass
|
||||
for glyph in text_group.iterchildren():
|
||||
text_group.remove(glyph)
|
||||
text = font.render_text(
|
||||
self.settings.text,
|
||||
text_group,
|
||||
None, # we don't know the font variant (?)
|
||||
self.settings.back_and_forth,
|
||||
self.settings.trim_option,
|
||||
self.settings.use_trim_symbols
|
||||
)
|
||||
return [glyph for glyph in text.iterdescendants(SVG_GROUP_TAG) if glyph.label and len(glyph.label) == 1]
|
||||
return glyphs
|
||||
|
||||
def get_position_and_stretch_values(self, path, text, glyphs):
|
||||
text_bbox = glyphs[0].getparent().bounding_box()
|
||||
text_baseline = text_bbox.bottom
|
||||
|
||||
if self.options.stretch_spaces:
|
||||
text_content = self.settings["text"]
|
||||
space_indices = [i for i, t in enumerate(text_content) if t == " "]
|
||||
text_bbox = text.bounding_box()
|
||||
text_width = convert_unit(text_bbox.width, 'px', self.svg.unit)
|
||||
|
||||
if len(text_content) - 1 != 0:
|
||||
path_length = path.length
|
||||
stretch_space = (path_length - text_width) / (len(text_content) - 1)
|
||||
else:
|
||||
stretch_space = 0
|
||||
else:
|
||||
stretch_space = 0
|
||||
space_indices = []
|
||||
|
||||
return space_indices, stretch_space, text_baseline
|
||||
|
||||
def hide_commands(self, glyphs):
|
||||
# hide commmands for bounding box calculation
|
||||
hidden_commands = []
|
||||
for glyph in glyphs:
|
||||
for group in glyph.iterdescendants(SVG_GROUP_TAG):
|
||||
if group.get_id().startswith("command_group") and group.style('display', 'inline') != 'none':
|
||||
hidden_commands.append(group)
|
||||
group.style['display'] = 'none'
|
||||
return hidden_commands
|
||||
|
||||
def restore_commands(self, hidden_commands):
|
||||
for command in hidden_commands:
|
||||
command.style['display'] = "inline"
|
||||
|
||||
def transform_glyphs(self, glyphs, path, stretch_space, space_indices, text_baseline):
|
||||
text_scale = Transform(f'scale({self.settings.scale / 100})')
|
||||
distance = 0
|
||||
old_bbox = None
|
||||
i = 0
|
||||
|
||||
for glyph in glyphs:
|
||||
# dimensions
|
||||
bbox = glyph.bounding_box()
|
||||
transformed_bbox = glyph.bounding_box(glyph.getparent().composed_transform())
|
||||
left = bbox.left
|
||||
transformed_left = transformed_bbox.left
|
||||
width = convert_unit(transformed_bbox.width, 'px', self.svg.unit)
|
||||
|
||||
# adjust position
|
||||
if old_bbox:
|
||||
distance += convert_unit(transformed_left - old_bbox.right, 'px', self.svg.unit) + stretch_space
|
||||
|
||||
if self.options.stretch_spaces and i in space_indices:
|
||||
distance += stretch_space
|
||||
i += 1
|
||||
|
||||
new_distance = distance + width
|
||||
|
||||
# calculate and apply transform
|
||||
first = path.interpolate(distance)
|
||||
last = path.interpolate(new_distance)
|
||||
|
||||
angle = degrees(atan2(last.y - first.y, last.x - first.x)) % 360
|
||||
translate = InkstitchPoint(first.x, first.y) - InkstitchPoint(left, text_baseline)
|
||||
|
||||
transform = Transform(f"rotate({angle}, {first.x}, {first.y}) translate({translate.x} {translate.y})")
|
||||
correction_transform = Transform(get_correction_transform(glyph))
|
||||
glyph.transform = correction_transform @ transform @ glyph.transform @ text_scale
|
||||
|
||||
# set values for next iteration
|
||||
distance = new_distance
|
||||
old_bbox = transformed_bbox
|
||||
i += 1
|
||||
|
||||
def load_settings(self, text):
|
||||
"""Load the settings saved into the text element"""
|
||||
|
||||
self.settings = DotDict({
|
||||
"text": "",
|
||||
"back_and_forth": False,
|
||||
"font": None,
|
||||
"scale": 100,
|
||||
"trim_option": 0,
|
||||
"use_trim_symbols": False
|
||||
})
|
||||
|
||||
if INKSTITCH_LETTERING in text.attrib:
|
||||
try:
|
||||
self.settings.update(json.loads(text.get(INKSTITCH_LETTERING)))
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
TextAlongPath(self.svg, text, path, self.options.text_position)
|
||||
|
||||
def get_selection(self):
|
||||
groups = list()
|
||||
|
@ -188,3 +61,155 @@ class LetteringAlongPath(InkstitchExtension):
|
|||
return [None, None]
|
||||
|
||||
return [groups[0], paths[0]]
|
||||
|
||||
|
||||
class TextAlongPath:
|
||||
'''
|
||||
Aligns an Ink/Stitch Lettering group along a path
|
||||
'''
|
||||
def __init__(self, svg, text, path, text_position):
|
||||
self.svg = svg
|
||||
self.text = text
|
||||
self.path = Stroke(path).as_multi_line_string().geoms[0]
|
||||
self.text_position = text_position
|
||||
|
||||
self.glyphs = [glyph for glyph in self.text.iterdescendants(SVG_GROUP_TAG) if glyph.label and len(glyph.label) == 1]
|
||||
if not self.glyphs:
|
||||
errormsg(_("The text doesn't contain any glyphs."))
|
||||
return
|
||||
|
||||
self.load_settings()
|
||||
|
||||
if self.glyphs[0].get('transform', None) is not None:
|
||||
self._reset_glyph_transforms()
|
||||
|
||||
hidden_commands = self.hide_commands()
|
||||
space_indices, stretch_space, text_baseline = self.get_position_and_stretch_values()
|
||||
start_position = self.get_start_position()
|
||||
self.transform_glyphs(start_position, stretch_space, space_indices, text_baseline)
|
||||
self.restore_commands(hidden_commands)
|
||||
|
||||
def _reset_glyph_transforms(self):
|
||||
font = get_font_by_id(self.settings.font)
|
||||
if font is not None:
|
||||
try:
|
||||
text_group = list(self.text.iterchildren(SVG_GROUP_TAG))[0]
|
||||
except IndexError:
|
||||
pass
|
||||
for glyph in text_group.iterchildren():
|
||||
text_group.remove(glyph)
|
||||
rendered_text = font.render_text(
|
||||
self.settings.text,
|
||||
text_group,
|
||||
None, # we don't know the font variant (?)
|
||||
self.settings.back_and_forth,
|
||||
self.settings.trim_option,
|
||||
self.settings.use_trim_symbols
|
||||
)
|
||||
self.glyphs = [glyph for glyph in rendered_text.iterdescendants(SVG_GROUP_TAG) if glyph.label and len(glyph.label) == 1]
|
||||
|
||||
def get_start_position(self):
|
||||
start_position = 0
|
||||
text_length = self.text_length()
|
||||
path_length = self.path.length
|
||||
if self.text_position == 'center':
|
||||
start_position = (path_length - text_length) / 2
|
||||
if self.text_position == 'right':
|
||||
start_position = path_length - text_length
|
||||
return start_position
|
||||
|
||||
def get_position_and_stretch_values(self):
|
||||
text_bbox = self.glyphs[0].getparent().bounding_box()
|
||||
text_baseline = text_bbox.bottom
|
||||
|
||||
if self.text_position == 'stretch':
|
||||
text_content = self.settings.text
|
||||
space_indices = [i for i, t in enumerate(text_content) if t == " "]
|
||||
text_bbox = self.text.bounding_box()
|
||||
text_width = convert_unit(text_bbox.width, 'px', self.svg.unit)
|
||||
|
||||
if len(text_content) - 1 != 0:
|
||||
path_length = self.path.length
|
||||
stretch_space = (path_length - text_width) / (len(text_content) - 1)
|
||||
else:
|
||||
stretch_space = 0
|
||||
else:
|
||||
stretch_space = 0
|
||||
space_indices = []
|
||||
|
||||
return space_indices, stretch_space, text_baseline
|
||||
|
||||
def text_length(self):
|
||||
return convert_unit(self.text.bounding_box().width, 'px', self.svg.unit)
|
||||
|
||||
def hide_commands(self):
|
||||
# hide commmands for bounding box calculation
|
||||
hidden_commands = []
|
||||
for glyph in self.glyphs:
|
||||
for group in glyph.iterdescendants(SVG_GROUP_TAG):
|
||||
if group.get_id().startswith("command_group") and group.style('display', 'inline') != 'none':
|
||||
hidden_commands.append(group)
|
||||
group.style['display'] = 'none'
|
||||
return hidden_commands
|
||||
|
||||
def restore_commands(self, hidden_commands):
|
||||
for command in hidden_commands:
|
||||
command.style['display'] = "inline"
|
||||
|
||||
def transform_glyphs(self, start_position, stretch_space, space_indices, text_baseline):
|
||||
text_scale = Transform(f'scale({self.settings.scale / 100})')
|
||||
distance = start_position
|
||||
old_bbox = None
|
||||
i = 0
|
||||
|
||||
for glyph in self.glyphs:
|
||||
# dimensions
|
||||
bbox = glyph.bounding_box()
|
||||
transformed_bbox = glyph.bounding_box(glyph.getparent().composed_transform())
|
||||
left = bbox.left
|
||||
transformed_left = transformed_bbox.left
|
||||
width = convert_unit(transformed_bbox.width, 'px', self.svg.unit)
|
||||
|
||||
# adjust position
|
||||
if old_bbox:
|
||||
distance += convert_unit(transformed_left - old_bbox.right, 'px', self.svg.unit) + stretch_space
|
||||
|
||||
if self.text_position == 'stretch' and i in space_indices:
|
||||
distance += stretch_space
|
||||
i += 1
|
||||
|
||||
new_distance = distance + width
|
||||
|
||||
# calculate and apply transform
|
||||
first = self.path.interpolate(distance)
|
||||
last = self.path.interpolate(new_distance)
|
||||
|
||||
angle = degrees(atan2(last.y - first.y, last.x - first.x)) % 360
|
||||
translate = InkstitchPoint(first.x, first.y) - InkstitchPoint(left, text_baseline)
|
||||
|
||||
transform = Transform(f"rotate({angle}, {first.x}, {first.y}) translate({translate.x} {translate.y})")
|
||||
correction_transform = Transform(get_correction_transform(glyph))
|
||||
glyph.transform = correction_transform @ transform @ glyph.transform @ text_scale
|
||||
|
||||
# set values for next iteration
|
||||
distance = new_distance
|
||||
old_bbox = transformed_bbox
|
||||
i += 1
|
||||
|
||||
def load_settings(self):
|
||||
"""Load the settings saved into the text element"""
|
||||
|
||||
self.settings = DotDict({
|
||||
"text": "",
|
||||
"back_and_forth": False,
|
||||
"font": None,
|
||||
"scale": 100,
|
||||
"trim_option": 0,
|
||||
"use_trim_symbols": False
|
||||
})
|
||||
|
||||
if INKSTITCH_LETTERING in self.text.attrib:
|
||||
try:
|
||||
self.settings.update(json.loads(self.text.get(INKSTITCH_LETTERING)))
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
|
|
|
@ -4,5 +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
|
||||
from .utils import get_font_by_id
|
||||
from .utils import get_font_by_id, get_font_by_name, get_font_list
|
||||
|
|
|
@ -52,3 +52,17 @@ def get_font_by_id(font_id):
|
|||
if font.id == font_id:
|
||||
return font
|
||||
return None
|
||||
|
||||
|
||||
def get_font_by_name(font_name):
|
||||
font_paths = get_font_paths()
|
||||
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.name == font_name:
|
||||
return font
|
||||
return None
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension translationdomain="inkstitch" xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Batch Lettering</name>
|
||||
<id>org.{{ id_inkstitch }}.output.batch_lettering</id>
|
||||
<param name="extension" type="string" gui-hidden="true">batch_lettering</param>
|
||||
|
||||
<param name="notebook" type="notebook">
|
||||
|
||||
<page name="options" gui-text="Options">
|
||||
<hbox>
|
||||
<vbox>
|
||||
<param name="text" type="string" gui-text="Text" appearance="multiline"
|
||||
gui-description="Enter the text. Each line of text will be exported to a separate file." />
|
||||
<param name="separator" type="string" gui-text="Custom separator"
|
||||
gui-description="Set a custom separator for multiline text export. Leave empty for line break." />
|
||||
<spacer />
|
||||
<separator />
|
||||
<spacer />
|
||||
<param name="font" type="string" gui-text="Font name"></param>
|
||||
<param name="scale" type="int" gui-text="Scale (%)" min="1" max="800"
|
||||
gui-text="The scale value must be within the scale range of the specified font.">100</param>
|
||||
<param name="color-sort" type="optiongroup" appearance="combo" gui-text="Color sort">
|
||||
<option value="off">Off</option>
|
||||
<option value="all">Whole text</option>
|
||||
<option value="line" >Line</option>
|
||||
<option value="word" >Word</option>
|
||||
</param>
|
||||
<param name="trim" type="optiongroup" appearance="combo" gui-text="Add trims">
|
||||
<option value="off">Never</option>
|
||||
<option value="line">after each line</option>
|
||||
<option value="word">after each word</option>
|
||||
<option value="glyph">after each letter</option>
|
||||
</param>
|
||||
<param name="use-command-symbols" type="boolean" gui-text="Use command symbols">false</param>
|
||||
<param name="text-align" type="optiongroup" appearance="combo" gui-text="Align Multiline Text">
|
||||
<option value="left">Left</option>
|
||||
<option value="center">Center</option>
|
||||
<option value="right">Right</option>
|
||||
<option value="block">Block (default)</option>
|
||||
<option value="letterspacing">Block (letterpacing)</option>
|
||||
</param>
|
||||
</vbox>
|
||||
<spacer />
|
||||
<separator />
|
||||
<spacer />
|
||||
<vbox>
|
||||
<param name="text-position" type="optiongroup" appearance="combo" gui-text="Lettering along path: text position"
|
||||
gui-description="Uses this text position when using lettering along path">
|
||||
<option value="left">Left</option>
|
||||
<option value="center">Center</option>
|
||||
<option value="right">Right</option>
|
||||
<option value="stretch">Stretch</option>
|
||||
</param>
|
||||
<spacer />
|
||||
<separator />
|
||||
<spacer />
|
||||
<param name="file-formats" type="string" gui-text="File formats" gui-description="Comma separated list of file formats" />
|
||||
</vbox>
|
||||
</hbox>
|
||||
</page>
|
||||
|
||||
<page name="info" gui-text="Help">
|
||||
<label>Use this extension to save multiple files with the given text.</label>
|
||||
<spacer />
|
||||
<label>When the document contains a path element labeled as "batch lettering" it will be used to place the text along this path. The path itself will be removed and won't be rendered.</label>
|
||||
<spacer />
|
||||
<label>More information on our website</label>
|
||||
<label appearance="url">https://inkstitch.org/docs/lettering/#batch-export</label>
|
||||
</page>
|
||||
</param>
|
||||
|
||||
<output>
|
||||
<extension>.zip</extension>
|
||||
<mimetype>application/zip</mimetype>
|
||||
<filetypename>{{ menu_inkstitch }}: batch lettering (.zip)</filetypename>
|
||||
<filetypetooltip>Create a zip with multiple files including embroidered text using Ink/Stitch</filetypetooltip>
|
||||
<dataloss>true</dataloss>
|
||||
</output>
|
||||
|
||||
<script>
|
||||
{{ command_tag | safe }}
|
||||
</script>
|
||||
</inkscape-extension>
|
|
@ -6,8 +6,11 @@
|
|||
<effect implements-custom-gui="true" show-stderr="true">
|
||||
<object-type>all</object-type>
|
||||
<icon>{{ icon_path }}inx/lettering.svg</icon>
|
||||
<menu-tip>Insert ready-to-embroider text into the document</menu-tip>
|
||||
<effects-menu>
|
||||
<submenu name="{{ menu_inkstitch }}" translatable="no" />
|
||||
<submenu name="{{ menu_inkstitch }}" translatable="no">
|
||||
<submenu name="Lettering" />
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
|
|
|
@ -8,13 +8,19 @@
|
|||
<icon>{{ icon_path }}inx/lettering_along_path.svg</icon>
|
||||
<menu-tip>Shapes a line of text onto a path</menu-tip>
|
||||
<effects-menu>
|
||||
<submenu name="{{ menu_inkstitch }}" translatable="no" />
|
||||
<submenu name="{{ menu_inkstitch }}" translatable="no">
|
||||
<submenu name="Lettering" />
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<param name="notebook" type="notebook">
|
||||
<page name="options" gui-text="Options">
|
||||
<param name="stretch-spaces" type="bool" gui-text="Stretch"
|
||||
gui-description="Expand glyph and word spacing to stretch lettering over the entire path">false</param>
|
||||
<param name="text-position" type="optiongroup" appearance="combo" gui-text="Text position">
|
||||
<option value="left">Left</option>
|
||||
<option value="center">Center</option>
|
||||
<option value="right">Right</option>
|
||||
<option value="stretch">Stretch</option>
|
||||
</param>
|
||||
</page>
|
||||
<page name="info" gui-text="Help">
|
||||
<label appearance="header">This extension bends an Ink/Stitch text to a path.</label>
|
||||
|
@ -24,7 +30,9 @@
|
|||
<label indent="1">* The text consists of only one line of text</label>
|
||||
<label indent="1">* The text should not be too large for the given path</label>
|
||||
<spacer />
|
||||
<label>The stretch option defines whether the spaces between glyphs should be expanded so that the text stretches over the entire path.</label>
|
||||
<label>In the text position dropdown menu, you can decide how the text will be placed on the path.
|
||||
When stretch is selected, the spaces between the glyphs will be expanded, so that the text stretches over the entire path.
|
||||
</label>
|
||||
<spacer />
|
||||
<label>More information on our website</label>
|
||||
<label appearance="url">https://inkstitch.org/docs/lettering/#lettering-along-path</label>
|
||||
|
|
Ładowanie…
Reference in New Issue