Repository navigation
feat(MIT): mask-aware narrow-column wrap + safe-area centering (distance-transform interior + pole-of-inaccessibility) #179
Description
Parent
PRD #178 (Render Parity). This is the root-cause slice for "ours reads as a wide paragraph, theirs reads as manga".
What to build
Today bubble-fit wraps English to the balloon's bounding-box width (mw = w_box × 0.92), so a wide round balloon produces wide lines regardless of the original (tall-narrow) Japanese footprint. MangaTranslator instead wraps to a measured safe interior of the actual mask shape and centers text on a safe anchor (text_renderer.py:174-181, image_utils.py:173-348 — see docs/research/translator-deep-dissection.md).
Add a pure safe_area module: given a cleaned balloon mask, compute the distance-transform safe-area box (width/height) + centroid anchor, with a pole-of-inaccessibility fallback (use the max-distance pixel when dist(centroid) < 0.70 × max_dist) so text isn't centered in a conjoined-bubble neck. Thread the balloon mask into _bubble_fit_font_size so the wrap width is the measured interior width (not the bbox), and center the rendered block on the anchor (fix the current left-aligned aspect padding).
Opt-in behind the existing render.bubble_area_fit flag; off → byte-identical.
Acceptance criteria
- Pure
safe_area(mask) -> (safe_box, anchor)unit-tested with synthetic masks (round, conjoined-neck, irregular) — no PIL/ML imports; pole-of-inaccessibility fallback verified on a neck mask - Bubble-fit wraps to the measured interior width; tall-narrow source → narrower English column (verify on the One Punch-Man benchmark)
- Rendered block centered on the safe anchor (aspect padding no longer left-aligned)
-
bubble_area_fitoff → render byte-identical - PIPELINE.md §5 provenance updated; benchmark scorecard (
MIT/BENCHMARK.md) updated
Blocked by
None - can start immediately
Parent
PRD #178 (Render Parity) — slice ราก ของปัญหา "ของเราอ่านเป็นย่อหน้า ของเขาอ่านเป็นมังงะ"
สิ่งที่ต้องสร้าง
ตอนนี้ bubble-fit wrap อังกฤษตาม ความกว้าง bounding box ของบับเบิล (mw = w_box × 0.92) บับเบิลกลมกว้างจึงได้บรรทัดกว้างไม่ว่าต้นฉบับญี่ปุ่นจะสูงแคบแค่ไหน MangaTranslator wrap ตาม safe interior ที่วัดจากรูปทรง mask จริง แล้วจัดกึ่งกลางที่ anchor ปลอดภัย (text_renderer.py:174-181, image_utils.py:173-348 — ดู docs/research/translator-deep-dissection.md)
เพิ่ม pure module safe_area: จาก mask บับเบิลที่ clean แล้ว คำนวณ safe-area box (กว้าง/สูง) + centroid anchor พร้อม pole-of-inaccessibility fallback (ใช้ pixel ที่ distance สูงสุดเมื่อ dist(centroid) < 0.70 × max_dist) เพื่อไม่ให้ข้อความไปอยู่กลางคอคอดของบับเบิลเชื่อม แล้ว thread mask เข้า _bubble_fit_font_size ให้ wrap ตามความกว้าง interior ที่วัดได้ (ไม่ใช่ bbox) และจัดบล็อกข้อความกึ่งกลางที่ anchor (แก้ aspect padding ที่ตอนนี้ชิดซ้าย)
opt-in หลัง flag render.bubble_area_fit เดิม ปิด → byte-identical
เกณฑ์การยอมรับ
-
safe_area(mask) -> (safe_box, anchor)(pure) unit-test ด้วย mask สังเคราะห์ (กลม/คอคอด/ไม่สม่ำเสมอ) ไม่ import PIL/ML; ยืนยัน pole-of-inaccessibility บน mask คอคอด - bubble-fit wrap ตามความกว้าง interior; ต้นฉบับสูงแคบ → คอลัมน์อังกฤษแคบลง (ยืนยันบน benchmark One Punch-Man)
- บล็อกข้อความจัดกึ่งกลางที่ anchor (aspect padding ไม่ชิดซ้ายแล้ว)
-
bubble_area_fitปิด → byte-identical - อัปเดต PIPELINE.md §5 + scorecard ใน
MIT/BENCHMARK.md
ถูกบล็อกโดย
ไม่มี - เริ่มได้ทันที
Activity
feat(translation): render-parity port from MangaTranslator + SFX dete…
xenodeve commentedon Jun 13, 2026
✅ Done — delivered (bubble-fit) + superseded (clean-layout).
safe_area(mask) → (box, anchor)shipped insafe_area.py(distance-transform interior + pole-of-inaccessibility anchor), unit-tested intest/test_safe_area.py; bubble-fit wraps to the measured interior width. The narrow-column goal — English breaks where the source column did, not a wide paragraph — is now delivered more directly by the clean_layout rework:clean_wrap_widthwraps to the source footprint width (#264), verified in-app (dialogue ~2 words/line + hyphenation, narration narrow tall). Closing as delivered.TH — เสร็จ:
safe_areaในsafe_area.py(distance-transform interior + pole-of-inaccessibility), test ในtest_safe_area.py; เป้าหมาย narrow-column (อังกฤษตัดบรรทัดตามคอลัมน์ต้นฉบับ) ส่งมอบตรงกว่าโดย clean_layoutclean_wrap_width(#264) ยืนยันในแอป ปิด issuefeat(MIT): #180 step 3 — wire kinsoku into Knuth-Plass DP (pure, defa…