Skip to content

Task #455: 인라인 글상자(tac=true + TextBox) 가 있는 줄의 외부 본문 텍스트 누락 수정#457

Closed
planet6897 wants to merge 26 commits into
edwardkim:develfrom
planet6897:local/task455
Closed

Task #455: 인라인 글상자(tac=true + TextBox) 가 있는 줄의 외부 본문 텍스트 누락 수정#457
planet6897 wants to merge 26 commits into
edwardkim:develfrom
planet6897:local/task455

Conversation

@planet6897

Copy link
Copy Markdown
Contributor

Summary

  • samples/exam_kor.hwp 페이지 2 좌측 단의 문단 pi=33 line 2 에서 본문 39자가 누락되고 인라인 글상자 내부 "개화" 두 글자만 표시되던 버그 수정
  • paragraph_layout.rsskip_text_for_inline_shape 분기 제거 — 글상자 내부 텍스트와 외부 문단 본문을 혼동해 외부 본문까지 통째로 스킵하던 로직
  • 글상자 자체와 내부 텍스트는 shape_layoutinline_shape_position 경로로 별도 렌더되므로 외부 본문을 항상 렌더해도 중복 없음

증상 (수정 전 → 후)

수정 전 (좌측 단):

y=295.1 "서양의 과학과 기술... 이항로를 비롯한"
y=321.4 "개화"        ← 본문 39자 누락
y=347.6 "수없는 대세로 자리잡았다..."

수정 후:

y=295.1 "서양의 과학과 기술... 이항로를 비롯한"
y=321.4 "개화"        ← 글상자 (별도 패스)
y=322.6 "척사파의 주장은 개항 이후에도 지속되었지만, 는 거스를"   ← 본문 39자 복원 (글상자 좌·우 분리)
y=347.6 "수없는 대세로 자리잡았다..."

Root Cause

paragraph_layout.rs::layout_composed_paragraph 의 tac 분기 처리 블록에서:

// (제거 전) 잘못된 의도: "글상자 텍스트는 별도 패스에서 렌더되므로 스킵"
let skip_text_for_inline_shape = has_tac_shape && para.map(|p| {
    tac_offsets_px.iter().any(|(_, _, ci)| {
        if let Some(Control::Shape(s)) = p.controls.get(*ci) {
            s.drawing().map(|d| d.text_box.is_some()).unwrap_or(false)
        } else { false }
    })
}).unwrap_or(false);

if !skip_text_for_inline_shape {
    line_node.children.push(sub_run_node);  // tac 앞 텍스트
}
// ...
if !skip_text_for_inline_shape {
    line_node.children.push(sub_run_node);  // 마지막 tac 이후 텍스트
}

여기서 스킵된 "텍스트" 는 글상자의 외부 문단 본문 이지 내부 ("개화") 가 아니다. 외부 본문은 글상자 좌·우를 흐르는 일반 텍스트이며 항상 렌더되어야 한다.

Fix

Test Plan

  • cargo build --release — 빌드 성공
  • cargo test --release — 1117 passed, 1 ignored, 0 failed
  • rhwp export-svg samples/exam_kor.hwp — 20 페이지, 좌측 단 pi=33 line 2 의 39자 정상 렌더 확인
  • 회귀 검증 (페이지 수 동일):
    • exam_kor.hwp 20
    • exam_eng.hwp 8
    • 2010-01-06.hwp 6
    • exam_math_8.hwp 1
    • biz_plan.hwp 6
    • draw-group.hwp 1
    • atop-equation-01.hwp 1
    • equation-lim.hwp 1

Known Minor Issue

본문 글자 baseline y=322.6, 글상자 내부 "개화" baseline y=321.4 — 1.2px 차이. 줄 높이가 5mm 글상자에 맞춰 1417 HU 로 늘어난 상황에서 baseline 정렬을 본문 폰트 기준으로 재계산하지 않아 생기는 미세 어긋남. 시각상 큰 문제는 아니나 별도 이슈로 분리 가능.

Files Changed

  • src/renderer/layout/paragraph_layout.rs
  • mydocs/plans/task_m100_455.md
  • mydocs/plans/task_m100_455_impl.md
  • mydocs/working/task_m100_455_stage{1,2,3}.md
  • mydocs/report/task_m100_455_report.md
  • mydocs/report/task_m100_455_pr.md
  • mydocs/orders/20260429.md

Closes #455.

planet6897 and others added 26 commits April 29, 2026 11:54
exam_kor.hwp 24페이지 → 20페이지 정합 작업 1단계.

- 페이지별 단별 used/hwp_used/diff CSV (output/debug/task435/)
- 회귀 대상 5문서 페이지 수 (exam_kor 24, exam_eng 8, k-water-rfp 28, hwpspec 177, synam-001 35)
- RHWP_TYPESET_DRIFT 출력 캡처 (pi=0.30, pi=1.25 split 진단)
- compute_body_wide_top_reserve_for_para 산정 경로 추적

핵심 진단: col 1 reserve 306.1px (HWP 실제 94.5px 대비 +211.6px 과대).
원인: Paper-rel 좌표를 body-rel 변환 없이 그대로 reserve 에 누적.
Stage 2 에서 typeset.rs:2127-2172 의 VertRelTo::Paper 분기 정정.

수행계획서/구현계획서/Stage 1 보고서 포함.
compute_body_wide_top_reserve_for_para 의 VertRelTo::Paper 분기에서
body-rel 변환 누락 정정. body 와 일부만 겹치는 (header→body 침범)
케이스에서 Paper-rel 좌표를 그대로 reserve 에 누적하던 버그 수정.

수정 전 reserve = shape_y_offset(paper) + h + outer_bottom = 306.1 px
수정 후 reserve = max(0, shape_top_abs - body_top) ... = 94.4 px

결과:
- exam_kor.hwp: 24 → 22 페이지 (page 2, 15 orphan 해소)
- pi=0.30, pi=1.25 split → FullParagraph
- 회귀: exam_eng 8, k-water-rfp 28, hwpspec 177, synam-001 35 유지
- cargo test: 1062 passed

Stage 3 에서 일반 페이지 누적 -100~-300px 정정 (22 → 20).
원래 가설 ("표/도형 후 컬럼 잔여 공간 산정 부족") 재검토.
RHWP_TYPESET_DRIFT 분석 결과 diff 메트릭은 typeset cur_h 누적
(height_for_fit, trail_ls 제외) vs hwp_used (last line vpos+lh)
의 좌표계 차이를 측정하는 것일 뿐, "rhwp 가 채울 수 있는데 못 채운
잔여 공간" 이 아님을 확인.

실제 22→20 페이지 단축 장애물:
1. 섹션 1 페이지 14: Square wrap 표 + col 0 over-fill (1225>1211)
   → col 1 under-use (64px)
2. 섹션 1 페이지 15: 단일 컬럼 출력 (단정의는 2단인데 단 1 누락)
3. 섹션 2 페이지 18: pi=11 split + pi=13 [단나누기] orphan-like

3가지 전부 해결 시 22→19 가능 (목표 20 도달).

옵션 A/B/C 결정 필요 (현 상태 종료 / Stage 4 확장 / Stage 4 부분).
옵션 A 종료: 24→22 페이지 (Stage 2 col 1 reserve 정정).
잔여 22→20 미달성, 3가지 별도 메커니즘 (Square wrap over-fill,
단일 컬럼 출력 버그, col 0 cur_h over-advance) 별도 task 분리 권고.

edwardkim#393 (옵션 A) 본 task Stage 2 로 적용 완료, close 가능.
수행계획서(task_m100_439.md) + 단계1 진단 보고서.

핵심 발견:
- 이슈 가설의 engine.rs:702-711 는 fallback 경로 (RHWP_USE_PAGINATOR=1).
  기본 활성 엔진은 typeset.rs::TypesetEngine.
- 실제 버그 위치: typeset.rs::place_table_with_text (1400-1467).
  engine.rs 의 !is_wrap_around_table 가드 (engine.rs:1349, 1422) 누락.
- 페이지 14 col 0 used=1225.8 (본문 1211.3 초과 +14.5) 정확히 재현.

코드 변경 없음 — 임시 디버그 코드는 모두 revert.
place_table_with_text 의 정확한 cur_h 누적 추적 (디버그 후 revert):
- Square wrap 4 표가 pre_height + table_total 합산 → +244.88 px 과다
- HWP 의도: max(호스트 텍스트, 표 + v_offset) 만 누적

수정안: typeset.rs::place_table_with_text 에서
  current_height += max(pre_height, v_off + table_total) (Square wrap 시)

코드 변경 없음 — 임시 디버그 println 모두 revert.
typeset.rs::place_table_with_text 에서 Square wrap (어울림) 표일 때
current_height 누적을 pre_height + table_total → max(pre_height, v_off + table_total)
로 변경.

호스트 문단 텍스트와 어울림 표는 같은 수직 영역을 공유하므로
더 큰 쪽만 한 번 누적해야 HWP layout 의도와 일치.
PageItem 자체는 PartialParagraph + Table 모두 push (layout 렌더링 보존).

검증:
- 페이지 14 col 0 used 1225.8 → 1036.1 px (≤ 1211.3 충족)
- exam_kor.hwp 22 → 20 페이지 (목표 ≤ 21 충족)
- 회귀 샘플 6 종 (exam_eng/math, 21언어, aift, 2010-01-06, biz_plan) 페이지 수 동일
- cargo test 1066 개 모두 통과
- SVG 렌더링 정상

closes edwardkim#439
Stage 4 회귀 검증 결과:
- 149 개 sample HWP 전수 페이지 수 비교: exam_kor 만 22→20 변경, 148개 동일
- cargo test 1066 passed
- DoD 전부 충족

본 task 완료. closes edwardkim#439.
typeset.rs::place_table_with_text 의 Square wrap 표 누적 정책을
pre_height + table_total → max(pre_height, v_off + table_total) 로 변경.

효과:
- exam_kor.hwp 페이지 14 col 0 used 1225.8 → 1036.1 px (over-fill 해소)
- exam_kor.hwp 22 → 20 페이지
- 149 개 sample 중 exam_kor 만 변화, 148 개 동일 (회귀 0건)
- cargo test 1066 passed

closes edwardkim#439
…문제 수정

- 수행/구현 계획서 + Stage 1·2 보고서 작성
- src/renderer/layout.rs: paragraph border merge 그룹을 col_area 바닥/꼭대기로 클램프
- exam_kor p2/5/8/15 의 세로 구분선이 PDF 와 일치하는 길이로 정상화
  (p8: 1671 → 1425, 246px 단축, 페이지 바깥 침범 해소)
- vpos-reset 미존중으로 인한 텍스트 자체의 overflow 는 별도 이슈로 분리
- snapshot 갱신: tests/golden_svg/issue-267/ktx-toc-page.svg
  (invisible 구조 rect 의 height 5.34px 변화, 가시 변화 없음)
페이지 번호 박스가 column divider line 과 붙어 보이는 문제 해결.

원인: 꼬리말 paragraph 의 vert=Para + wrap=TopAndBottom 표가 paragraph
top 에 배치되어 본문 바닥과 같은 y 에 위치. HWP 의 실제 동작은 첫 라인의
line_height/2 만큼 아래에 anchor 되어 본문과 시각적 갭 형성.

수정: layout_header_footer_paragraphs 에서 해당 조건의 첫 paragraph 표는
y_offset 에 line_height/2 (px) 를 더하여 배치.

검증 (exam_kor.hwp 20p):
- 박스 top y: 1422.93 → 1439.47 (PDF 380.6mm 와 일치)
- column line - 박스 갭: 0px → 16.3-17.0px (PDF 16.0px 와 일치)
- column line 길이/위치는 PDF 자연 그대로 유지 (p1 1131px, p2+ 1226px)
- cargo test --release: 1117 passed, 0 failed
이슈 edwardkim#445 의 두 시각적 결함 (paragraph border 페이지 바깥 침범 + 페이지
번호 박스가 column line 에 붙음) 모두 PDF 와 일치하도록 수정 완료.

승인 후 local/devel 머지 + edwardkim#445 close 예정.
- 수행계획서 (mydocs/plans/task_m100_452.md): 옵션 A 선언, 4단계 분해
- 구현 계획서 (mydocs/plans/task_m100_452_impl.md): paragraph_layout.rs:2511-2520
  is_para_last_line 분기 제거, is_cell_last_line 만 보존
- Stage 1 보고서 (mydocs/working/task_m100_452_stage1.md):
  - exam_kor pi=1.line9↔pi=2.line0 step = 15.34px (버그) ↔ 단락내 24.51px
  - 10종 샘플 페이지 수 baseline 캡처 (/tmp/task_452_baseline/)
  - 21_언어 p1 col 1 pi=26+보기①②③ fit 확인 (edwardkim#332 회귀 baseline)
- orders 갱신: 버그 섹션 edwardkim#452 항목 추가
… + golden 갱신

- src/renderer/layout/paragraph_layout.rs:2511-2519: is_para_last_line 분기
  제거. is_cell_last_line(셀 내) 만 trailing 제외 보존, 본문 단락은 모든 줄에서
  y += lh + ls 통일. pagination/engine.rs 의 current_height 누적과 정합.
- 검증: exam_kor 1페이지 pi=1.line9↔pi=2.line0 step = 15.34 → 24.50 px
  (단락내 step 과 동일). cargo test --lib 1066 passed.
- golden SVG 2건 baseline 갱신 (Task edwardkim#332 에서 갱신된 것을 본 정합으로 재갱신):
  - tests/golden_svg/issue-147/aift-page3.svg
  - tests/golden_svg/issue-157/page-1.svg
- LAYOUT_OVERFLOW 메시지: 페이지 마지막 단락의 trailing ls (~10.9 px)
  가 col_bottom 을 살짝 넘으나 빈 공간이므로 시각 무영향. pagination
  engine 의 effective_trailing 처리로 페이지 분배는 유지.
paragraph_layout.rs:2511-2520 의 is_para_last_line 분기 제거. 본문 단락
모든 줄에서 y += lh + ls 통일 → pagination/engine.rs 의 current_height
누적과 정합. is_cell_last_line(셀 내) 만 trailing 제외 보존.

효과: exam_kor pi=1.line9↔pi=2.line0 step 15.34→24.50 px (단락내 step 과
동일, PDF 정합). 10종 샘플 페이지 수 변동 0. Task edwardkim#332 회귀 0 (21_언어
p1 col 1 pi=26+보기①②③ fit 유지). cargo test --lib 1066 passed +
svg_snapshot 6/6 (issue-147/157 baseline 재갱신).

closes edwardkim#452
paragraph_layout 의 skip_text_for_inline_shape 분기 제거. 인라인 글상자가 있는
줄에서 외부 문단 본문이 통째로 스킵되던 버그를 수정.

원인: skip_text_for_inline_shape 가 글상자 내부 텍스트와 외부 문단 본문 텍스트를
혼동하여, 외부 본문(글상자 좌·우의 일반 텍스트)도 함께 스킵하고 있었음.
글상자 자체와 내부 텍스트는 shape_layout 의 inline_shape_position 경로로 별도
렌더되므로 외부 본문을 항상 렌더해도 중복되지 않는다.

검증: samples/exam_kor.hwp 페이지 2 좌측 단 line 2 의 본문 39자(척사파의 ...
거스를) 가 글상자 좌·우로 분리 렌더되어 복원됨. 페이지 수 20 유지, 전체
테스트 1117 passed.
Stage 3 회귀 검증 결과 (1117 tests passed, 페이지 수 회귀 0).
Stage 4 최종 결과보고서 및 PR 메시지(`mydocs/report/task_m100_455_pr.md`) 작성.
오늘할일에 Task edwardkim#455 항목 추가.
edwardkim added a commit that referenced this pull request Apr 29, 2026
- mydocs/pr/pr_454_review.md (Task #452 cherry-pick 검토, 광범위 영향 87% 정황)

검증: 1069 passed + svg_snapshot 6/6 (golden 2건 갱신) + issue_418 1/1 + clippy 0
광범위 byte 비교: 305 중 266 차이 (87% 영향, paragraph_layout 변경)
시각 판정: 후속 PR (#457, #461) 처리 후 통합 검증 (작업지시자 결정)
본질: paragraph_layout::is_para_last_line 분기 제거 (셀 마지막 줄만 trailing 제외 보존)
edwardkim added a commit that referenced this pull request Apr 29, 2026
- mydocs/pr/pr_457_review.md (Task #455 cherry-pick 검토, 영향 범위 한정 4.3%)

검증: 1069 passed + svg_snapshot 6/6 + issue_418 1/1 + clippy 0
광범위 byte 비교: 305 중 13 차이 (4.3% 영향, exam_* 11 + synam-001 1 + k-water-rfp 1)
시각 판정: PR #461 처리 후 통합 검증 (작업지시자 결정)
본질: paragraph_layout::skip_text_for_inline_shape 가드 제거
@edwardkim

Copy link
Copy Markdown
Owner

@planet6897 님 PR 감사드립니다. 메인테이너가 cherry-pick 으로 devel 에 적용 완료했습니다.

본 사이클 15번째 PR 입니다. PR #454 (Task #452) 머지 직후 Task #455 본질 2 commits 분리 처리.

처리

작성자 attribution 보존 본질 2 commits 분리 cherry-pick:

  • d87af59 (← c30294e) Stage 2: 인라인 글상자(tac=true + TextBox) 외부 본문 텍스트 누락 수정
  • bfe763f (← b6a95a5) Stage 3+4: 최종 결과보고서 + PR 메시지 + 오늘할일 갱신

devel 머지 commit: a5f1b9d

검증

광범위 byte 비교

10 샘플 / 305 페이지 SVG 비교: 292 동일, 13 차이 (4.3% 영향).

차이 분포: exam_* 11, synam-001 1, k-water-rfp 1.

→ PR #454 의 광범위 영향 (87%) 과 대조적 — Task #455 는 매우 정밀 정합 (특정 결함 영역만). 작성자 PR 본문 명시 (exam_kor 페이지 2 좌측 단 pi=33 line 2 본문 39자 복원) 와 정합.

시각 판정 정책

본 PR 단독 시각 판정 보류 (PR #454 와 동일 정책). 후속 PR (#461 5 Tasks) 처리 후 통합 시각 검증 진행.

본 PR 의 좋은 점

  1. 정확한 본질 진단: skip_text_for_inline_shape 가 외부 본문을 글상자 내부와 혼동한 정황 정확 식별
  2. 변경 범위 한정: 단일 파일 가드 제거만 — 영향 범위 13 페이지로 한정
  3. 알려진 minor 정황 명시: baseline 1.2px 미세 어긋남 별도 이슈 분리 가능 — 메인테이너 운영 철학 (작은 단위 회전 + 잔여 작업 분리) 정확 인지

이슈 #455 도 함께 close 됩니다. 다음 PR (#461 Task #459 + 4 Tasks) 처리도 같은 사이클로 이어가겠습니다. 감사합니다.

@edwardkim edwardkim closed this Apr 29, 2026
edwardkim added a commit that referenced this pull request Apr 29, 2026
- mydocs/pr/pr_461_review.md (5 Tasks 분리 cherry-pick 검토, 광범위 영향 86% 정황)

검증: 1070 passed + svg_snapshot 6/6 (golden 4건 갱신) + issue_418 1/1 + clippy 0
광범위 byte 비교: 305 중 263 차이 (86% 영향, Task #463 깊은 누적 정정)
시각 판정: PR #454 + #457 + #461 통합 검증 (작업지시자 결정)

본질 5 Tasks:
- Task #459: 다단 후속 페이지 vpos-reset (PR #450 잔여 본질)
- Task #462: TAC Picture 인라인 line advance
- Task #463: 셀 leakage + 박스 geometry + 들여쓰기 + TAC crop + 바탕쪽 (Stage 1~8)
- Task #468: cross-column 박스 partial 플래그
- Task #469: cross-column partial 박스 col_top/col_bot 침범
@planet6897 planet6897 deleted the local/task455 branch April 30, 2026 00:02
edwardkim added a commit that referenced this pull request Apr 30, 2026
- mydocs/pr/pr_456_review.md (P2 cherry-pick 검토, SVG 100% byte 동일)

검증: 1075 passed (+5 Canvas parity test) + svg_snapshot 6/6 + issue_418 1/1 + clippy 0
WASM: 4,206,022 bytes (+19,741, paint 모듈 Canvas replay 추가)
광범위 byte 비교: 305/305 byte 동일 (SVG legacy 경로 0 영향) ✅

본질 (PR #419 의 P2):
- Canvas 렌더 경로를 PageLayerTree replay 로 전환
- legacy 경로는 renderPageCanvasLegacy 로 보존 (fallback)
- LayerBuilder leaf children 보존 정정
- Canvas parity test 추가 (CI 통합)

시각 판정: 통합 검증 (PR #454 + #457 + #461 + #456 머지 후 작업지시자 직접)
@planet6897 planet6897 restored the local/task455 branch May 3, 2026 23:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants