From d322404e5a0c0ea7ec01f8a6298710dda06ea6ab Mon Sep 17 00:00:00 2001 From: AskaEth Date: Thu, 27 Aug 2026 15:59:04 +0800 Subject: [PATCH] Fix punctuation glyph overlap and line-end clipping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- ppl2psd.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/ppl2psd.py b/ppl2psd.py index 77a36e8..589671d 100644 --- a/ppl2psd.py +++ b/ppl2psd.py @@ -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: