From 3568472eeb3ea5d75ef7e9b29a8d0ca14d358c20 Mon Sep 17 00:00:00 2001 From: AskaEth Date: Thu, 27 Aug 2026 15:43:41 +0800 Subject: [PATCH] Fix group offsets and TypeLayer boundingBox MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Group children render offset by the group bbox movement (the app moves groups without updating child layer bboxes). Apply the group offset using only the group's OWN layers (exclude nested groups' layers from the inner bbox, which wrongly shifted parent groups like group_167). - TypeLayer boundingBox is relative to the transform origin; set it to (0,0)-(w,h) so text renders at the ppl boundingRect (verified: display pos = transform x boundingBox). Fixes: overlapping groups 178/181, '最佳记录' shifted up, shape_168 dropped down, overall diff vs app render 11.84 -> 8.84. --- ppl2psd.py | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/ppl2psd.py b/ppl2psd.py index d2fdf3a..1a269e9 100644 --- a/ppl2psd.py +++ b/ppl2psd.py @@ -527,9 +527,17 @@ def build_type_layer_tysh(ly: dict, font_ps: str) -> bytes: res["FontSet"][0]["Name"] = String(font_ps) - l, t, rr, bb = bounding_rect(ly) + l, t, rr, bb2 = bounding_rect(ly) tysh.transform = [1.0, 0.0, 0.0, 1.0, float(l), float(t)] - tysh.left, tysh.top, tysh.right, tysh.bottom = int(l), int(t), int(rr), int(bb) + tysh.left, tysh.top, tysh.right, tysh.bottom = int(l), int(t), int(rr), int(bb2) + # boundingBox is relative to the transform origin; PS shows the text at + # transform(boundingBox). Set it to (0,0)-(w,h) so the text lands on the + # ppl boundingRect (verified: l.psd display pos = transform x boundingBox). + bbd = td[b"boundingBox"] + bbd[b"Left"].value = 0.0 + bbd[b"Top "].value = 0.0 + bbd[b"Rght"].value = float(rr - l) + bbd[b"Btom"].value = float(bb2 - t) return tysh.tobytes() @@ -725,6 +733,23 @@ def convert(ppl_path: str, out_path: str, preview_path: Optional[str] = None, group_ranges[gid] = [i, i] else: group_ranges[gid][1] = i + # The app renders group children offset by the group bbox movement + # (moving a group updates the group bbox but not the child layer + # bboxes). Apply that offset so children land where the app shows them. + group_offset = {} + for gid, (first, last) in group_ranges.items(): + g = group_map.get(gid) + if not g or "boundingRect" not in g: + continue + gl, gt, _, _ = bounding_rect(g) + # only this group's OWN layers (nested groups' layers excluded) + own = [i for i in range(first, last + 1) + if layers[i].get("groupId") == gid] + if not own: + continue + inner_l = min(bounding_rect(layers[i])[0] for i in own) + inner_t = min(bounding_rect(layers[i])[1] for i in own) + group_offset[gid] = (gl - inner_l, gt - inner_t) rendered = [] skipped = 0 @@ -763,6 +788,9 @@ def convert(ppl_path: str, out_path: str, preview_path: Optional[str] = None, skipped += 1 continue img, (lx, ly_) = res + if gid and gid in group_offset: + lx = int(lx + group_offset[gid][0]) + ly_ = int(ly_ + group_offset[gid][1]) visible = ly.get("visible", "1") != "0" opacity = int(ly.get("alpha", "255") or 255) name = f"{ly['_type']}_{ly.get('objectId', i)}"