Lettering along path: fix multipart glyphs and rtl (#3709)

* lettering along path: fix multipart glyphs and rtl
* improve handling of multiline text
pull/3718/head
Kaalleen 2025-05-09 22:39:57 +02:00 zatwierdzone przez GitHub
rodzic d41b99dba8
commit 64dbac45a1
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
2 zmienionych plików z 80 dodań i 76 usunięć

Wyświetl plik

@ -73,32 +73,33 @@ class TextAlongPath:
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]
self.glyphs = [glyph for glyph in self.text.iterdescendants(SVG_GROUP_TAG) if glyph.get('inkstitch:letter-group', '') == 'glyph']
if not self.glyphs:
errormsg(_("The text doesn't contain any glyphs."))
return
self.load_settings()
self.font = get_font_by_id(self.settings.font)
if self.font is None:
errormsg(_("Couldn't identify the font specified in the lettering group."))
return
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.glyphs_along_path()
self.restore_commands(hidden_commands)
def _reset_glyph_transforms(self):
font = get_font_by_id(self.settings.font)
if font is not None:
if self.font is not None:
try:
text_group = list(self.text.iterchildren(SVG_GROUP_TAG))[0]
except IndexError:
pass
for glyph in text_group.iterchildren():
glyph.delete()
rendered_text = font.render_text(
rendered_text = self.font.render_text(
self.settings.text,
text_group,
None, # we don't know the font variant (?)
@ -106,41 +107,84 @@ class TextAlongPath:
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]
self.glyphs = [glyph for glyph in rendered_text.iterdescendants(SVG_GROUP_TAG) if glyph.get('inkstitch:letter-group', '') == 'glyph']
def get_start_position(self):
def glyphs_along_path(self):
path = self.path
for text_line in self.text.getchildren()[0].iterchildren():
self.transform_glyphs(path, text_line)
# offset path for the next line
path = path.offset_curve(self.font.leading)
def transform_glyphs(self, path, line):
line_bbox = line.bounding_box()
text_baseline = line_bbox.bottom
text_width = line_bbox.width
if self.text_position == 'stretch':
num_spaces = len(line) - 1
line_glyphs = [glyph for glyph in line.iterdescendants(SVG_GROUP_TAG) if glyph.get('inkstitch:letter-group', '') == 'glyph']
total_stretch_spaces = len(line_glyphs) - 1 + num_spaces
stretch_space = (path.length - text_width) / total_stretch_spaces
else:
stretch_space = 0
start_position = self.get_start_position(text_width, path.length)
text_scale = Transform(f'scale({self.settings.scale / 100})')
distance = start_position
old_bbox = None
words = line.getchildren()
if self.font.text_direction == "rtl":
words.reverse()
for word in words:
glyphs = word.getchildren()
if self.font.text_direction == "rtl":
glyphs.reverse()
for glyph in glyphs:
# dimensions
bbox = glyph.bounding_box()
transformed_bbox = glyph.bounding_box(word.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
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
distance += stretch_space
def get_start_position(self, text_length, path_length):
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 text_length(self, line):
return convert_unit(line.bounding_box().width, 'px', self.svg.unit)
def hide_commands(self):
# hide commmands for bounding box calculation
@ -156,46 +200,6 @@ class TextAlongPath:
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"""

Wyświetl plik

@ -47,7 +47,7 @@ def get_font_by_id(font_id):
continue
for font_dir in font_dirs:
font = _get_font_from_path(font_path, font_dir)
if font and font.id == font_id:
if font and font_id in [font.id, font.marked_custom_font_id]:
return font
return None
@ -61,7 +61,7 @@ def get_font_by_name(font_name):
continue
for font_dir in font_dirs:
font = _get_font_from_path(font_path, font_dir)
if font and font.name == font_name:
if font and font_name in [font.name, font.marked_custom_font_name]:
return font
return None