Fix punctuation glyph overlap and line-end clipping

PIL draws full-width punctuation offset inside the advance box (e.g.
left bracket '(' ink sits at x+42..x+61 while its advance is only 0.45em),
so the next glyph overlapped it and a trailing bracket could clip. Shift
the draw x by the ink offset for narrow punctuation and add right padding
so line ends never clip. Verified: bracket gap restored, line end intact.
This commit is contained in:
AskaEth committed 2026-08-27 15:59:04 +08:00
1 parent 4fcc5acc85
commit d322404e5a
1 file changed
+16 -3
+16 -3
View File
@@ -339,13 +339,25 @@ def render_text(ly: dict, cw: int, ch: int) -> Optional[Tuple[Image.Image, Tuple
# faux bold (textFontStyle=1) via stroke width
stroke_w = max(1, int(size / 40)) if ly.get("textFontStyle") == "1" else 0
pad = stroke_w # room for the stroke so glyphs don't clip
raw_w, raw_h = max(1, int(round(tw)) + 2 * pad), max(1, int(round(th)) + 2 * pad)
tail = max(int(size * 0.3), 8) # right padding so the last glyph never clips
raw_w, raw_h = max(1, int(round(tw)) + 2 * pad + tail), max(1, int(round(th)) + 2 * pad)
img = Image.new("RGBA", (raw_w, raw_h), (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
for i, ln in enumerate(lines):
x = pad
for ch_ in ln:
draw.text((x, pad + int(round(i * line_h))), ch_, font=font,
off = 0
if ch_ in NARROW_PUNCT:
# PIL draws full-width punctuation offset inside the advance
# box (e.g. '(' ink sits at x+42..x+61). Shift the draw x so
# the ink starts at the layout x; otherwise it overlaps the
# next glyph or clips at line end.
t_img = Image.new("RGBA", (int(size) + 16, int(size) + 16), (0, 0, 0, 0))
ImageDraw.Draw(t_img).text((4, 4), ch_, font=font,
fill=(255, 255, 255, 255))
t_bb = t_img.getchannel("A").getbbox()
off = t_bb[0] - 4 if t_bb else 0
draw.text((x - off, pad + int(round(i * line_h))), ch_, font=font,
fill=fill, stroke_width=stroke_w, stroke_fill=fill)
x += char_advance(ch_) + ls
@@ -355,7 +367,8 @@ def render_text(ly: dict, cw: int, ch: int) -> Optional[Tuple[Image.Image, Tuple
# export for the title layers).
cx, cy = (l + r) / 2, (t + b) / 2
if prof.get("narrow"):
left_full = int(round(cx - raw_w / 2))
# center on the advance width (excluding the clip-padding tail)
left_full = int(round(cx - (tw + 2 * pad) / 2))
top_full = int(round(cy - raw_h / 2))
content = img.getchannel("A").getbbox()
if content: