Skip to content

Task #439: Square wrap 표 직후 col 0 over-fill 정정 (exam_kor 22→20)#443

Closed
planet6897 wants to merge 11 commits into
edwardkim:develfrom
planet6897:local/task439
Closed

Task #439: Square wrap 표 직후 col 0 over-fill 정정 (exam_kor 22→20)#443
planet6897 wants to merge 11 commits into
edwardkim:develfrom
planet6897:local/task439

Conversation

@planet6897

Copy link
Copy Markdown
Contributor

Summary

  • typeset.rs::place_table_with_text (1400-1495) 의 Square wrap (어울림) 표 누적 정책을 pre_height + table_totalmax(pre_height, v_off + table_total) 로 변경
  • exam_kor.hwp 22→20 페이지 단축 (페이지 14 + 15 통합)
  • 페이지 14 col 0 over-fill (1225.8 > 1211.3, +14.5px) 해소
  • 페이지 14 col 1 items 2개 → 18개 정상 채워짐
  • closes Square wrap 표 직후 col 0 over-fill (exam_kor 페이지 14 단 0 1225>1211) #439

변경 내용

버그

samples/exam_kor.hwp 페이지 14 col 0 의 4 개 Square wrap 표 (pi=33, 37, 40, 47, 모두 wrap=어울림 vert=문단 1.4mm) 처리 시:

호스트 문단 텍스트가 PartialParagraph 로 push 되며 그 높이가 current_height 에 누적되고, 이어서 표가 push 되며 table_total_height 도 추가 누적된다. 두 누적이 합산 되지만 HWP layout 에서 어울림 표는 호스트 텍스트와 같은 수직 영역 을 공유한다 (어울림 = 본문이 표 옆을 흐름).

페이지 14 col 0 의 4 표 누적 측정 (place_table_with_text 임시 디버그, 사후 revert):

pi pre_h (host) table_total v_off_px 합산 (현재) max (정답) 차이
33 98.03 84.93 5.28 182.96 98.03 -84.93
37 73.52 60.75 5.28 134.27 73.52 -60.75
40 49.01 38.45 5.28 87.46 49.01 -38.45
47 73.52 60.75 5.28 134.27 73.52 -60.75
294.08 244.88 538.96 294.08 -244.88

페이지 14 col 0 used = 1225.8 px = 정상 누적 + 244.88 px 과다 누적 = 1211.3 본문 한계 +14.5px 초과 → pi=48 fit 실패 → col 1 advance → col 1 64.3 px (pi=48,49 만) → pi=50+ 페이지 15 로 강제 이동.

수정

+        // [Task #439] Square wrap (어울림) 표 식별.
+        let is_wrap_around_table = !table.common.treat_as_char
+            && matches!(table.common.text_wrap, crate::model::shape::TextWrap::Square);
+
         let is_first_table = !para.controls.iter().take(ctrl_idx)
             .any(|c| matches!(c, Control::Table(_)));
-        if pre_table_end_line > 0 && is_first_table {
-            let pre_height: f64 = fmt.line_advances_sum(0..pre_table_end_line);
+        let pre_height: f64 = if pre_table_end_line > 0 && is_first_table {
+            let h = fmt.line_advances_sum(0..pre_table_end_line);
             st.current_items.push(PageItem::PartialParagraph { ... });
-            st.current_height += pre_height;
-        }
+            h
+        } else { 0.0 };

         st.current_items.push(PageItem::Table { ... });
-        st.current_height += table_total_height;
+
+        // [Task #439] 누적 정책:
+        // - Square wrap (어울림): max(pre_height, v_off + table_total)
+        // - 그 외 (TopAndBottom 등): pre_height + table_total 합산 (기존 동작)
+        if is_wrap_around_table && pre_height > 0.0 {
+            let v_off_px = crate::renderer::hwpunit_to_px(vertical_offset as i32, self.dpi);
+            let table_bottom = v_off_px + table_total_height;
+            st.current_height += pre_height.max(table_bottom);
+        } else {
+            st.current_height += pre_height + table_total_height;
+        }

PageItem 자체는 PartialParagraph + Table 모두 push 유지 (layout 렌더링 보존).

결과 메트릭

메트릭 Before After
exam_kor.hwp 페이지 수 22 20
페이지 14 col 0 used 1225.8 px (+14.5 over) 1036.1 px (under)
페이지 14 col 1 items 2 18
페이지 14 col 1 used 64.3 px 1016.9 px
페이지 14 + 15 통합 분리 통합

회귀 검증

149 개 sample HWP 전수 페이지 수 비교 (local/devel ↔ fix 후):

$ diff baseline.txt after.txt
133a134
>   20  samples/exam_kor.hwp
136d136
<   22  samples/exam_kor.hwp

149 개 중 exam_kor.hwp 만 22→20 변경, 나머지 148 개 페이지 수 동일 ✓

특별 확인 (Square wrap 표 보유 문서):

문서 Before After 결과
exam_kor.hwp (대상) 22 20 -2 (목표 ≤21 달성)
exam_math.hwp (pi=27 Square wrap) 20 20
21_언어_기출_편집가능본.hwp (pi=299 Square wrap, 220px) 15 15
exam_eng.hwp (Square wrap 없음) 8 8

cargo test --release --lib: 1066 passed, 0 failed

부수 발견 — 활성 페이지네이션 엔진

이슈 본문은 src/renderer/pagination/engine.rs:702-711prev_is_table 분기 (trailing line_spacing 제외 로직) 를 원인으로 추정했으나, engine.rs::Paginator 는 활성 엔진이 아니다.

src/document_core/queries/rendering.rs:882-908:

let use_paginator = std::env::var("RHWP_USE_PAGINATOR").map(|v| v == "1").unwrap_or(false);
let mut result = if use_paginator {
    paginator.paginate_with_measured_opts(...)        // engine.rs (fallback)
} else {
    typesetter.typeset_section(...)                    // typeset.rs (default)
};

기본 활성 엔진은 typeset.rs::TypesetEngine. engine.rs 는 환경변수로만 활성화되는 legacy 경로. Stage 1 진단으로 발견 (engine.rs::place_table_fits 에 임시 eprintln! 삽입 → stderr 0 줄 = 호출되지 않음 확인).

흥미로운 점: engine.rs::place_table_fits (라인 1349, 1422) 에는 !is_wrap_around_table 가드가 있어 Square wrap 시 PartialParagraph 자체를 push 하지 않는다. 반면 typeset.rs::place_table_with_text 에는 그 가드가 없었다 — 두 엔진 사이의 시멘틱 누락이 본 버그의 근본 원인.

잔여 작업 (별도 이슈 권장)

  • engine.rs (legacy fallback) 의 Square wrap 처리 정합성: 본 fix 와 동일한 max() 정책 적용 (현재 RHWP_USE_PAGINATOR=1 시 25 페이지 출력 = 별개 회귀). 우선순위 낮음.
  • HWP 21 vs rhwp 20 페이지 차이 시각 검증 (작업지시자 PDF 비교 후 추가 task 분리 여부 결정).

Test plan

  • cargo build --release --bin rhwp 성공
  • cargo test --release --lib 1066 passed, 0 failed
  • rhwp dump-pages samples/exam_kor.hwp -p 13 → 단 0 used 1225.8 → 1036.1 px (over-fill 해소)
  • rhwp dump-pages samples/exam_kor.hwp → 22 → 20 페이지
  • rhwp export-svg samples/exam_kor.hwp -p 13 → SVG 정상 (56 rect, 2205 text 요소)
  • 회귀 검증: 149 개 sample HWP 페이지 수 비교 (148개 동일)
  • place_table_with_text 디버그 로그로 4 표 누적 정량 확인 (538.96 → 294.08, -244.88 px)

단계별 커밋

  • 3884305 Stage 1: 베이스라인 측정 + 진단 (활성 엔진 확인)
  • 4ea2746 Stage 2: 원인 확정 + 구현 계획서
  • 99f1596 Stage 3: Square wrap 표 누적 정책 max 적용
  • df2aff3 Stage 4: 최종 결과보고서 + 오늘할일 갱신
  • 50b5cab Merge local/task439

관련

  • 수행계획서: mydocs/plans/task_m100_439.md
  • 구현 계획서: mydocs/plans/task_m100_439_impl.md
  • 단계별 보고서: mydocs/working/task_m100_439_stage{1,3}.md
  • 최종 보고서: mydocs/report/task_m100_439_report.md

planet6897 and others added 11 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.
edwardkim added a commit that referenced this pull request Apr 29, 2026
- mydocs/pr/pr_443_review.md (Task #439 cherry-pick 검토)
- mydocs/pr/pr_443_report.md (cherry-pick 머지 결정 + SVG/Canvas 시각 판정 통과)
- mydocs/orders/20260429.md (PR #443 완료 행 추가)

검증: 1066 passed + svg_snapshot 6/6 + issue_418 1/1 + clippy 0 + WASM 4,178,523 bytes
광범위 byte 비교: 298/307 동일, 9 차이 (exam_kor 한정 — 의도된 정정)
시각 판정: 작업지시자 SVG + Canvas 양 경로 통과
본질: typeset.rs Square wrap 표 누적 정책 max 적용 (32 lines)
exam_kor 22→20 (페이지 14+15 통합), col 0 1225.8→1036.1px, col 1 64.3→1016.9px
@planet6897 본 사이클 9번째 PR (가장 활발), PR #442 머지 직후 잔여 작업 즉시 처리
@edwardkim

Copy link
Copy Markdown
Owner

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

PR #442 머지 직후 잔여 #439 작업 즉시 처리하신 정황 — 본 사이클 9번째 PR 입니다. 작은 단위 회전 + 정밀 진단 + 누적 개선의 표본입니다.

처리

작성자 attribution 보존 본질 4 commits cherry-pick (Task #435 4 commits 는 PR #442 흡수 완료 + merge commit 3 제외):

  • 3884305 (← 93348d9) Stage 1: 베이스라인 측정 + 진단 (활성 엔진 확인)
  • 4ea2746 (← eeca209) Stage 2: 원인 확정 + 구현 계획서
  • 99f1596 (← f37f616) Stage 3: Square wrap 표 누적 정책 max 적용 (32 lines)
  • df2aff3 (← 033781c) Stage 4: 최종 결과보고서 + 오늘할일 갱신

devel 머지 commit: c6a6b04

검증

광범위 byte 단위 비교

10 샘플 / 307 페이지 SVG 비교: 298/307 동일, 9/307 차이 (exam_kor 한정).

차이 분포:

  • exam_kor 9 페이지 (페이지 14+15 통합 + 후속 페이지 재조판)
  • 다른 9 샘플 (aift / biz_plan / exam_eng / exam_math / k-water-rfp / kps-ai / 2025년 기부 / synam-001 / equation-lim) 회귀 0건

작성자 본인 149 개 sample 검증 결과 (148 동일) 와 정합.

시각 판정 (작업지시자 직접)

경로 결과
SVG 내보내기 통과 ✅
Canvas (rhwp-studio) 통과 ✅

페이지 14 통합 (col 0 1036.1 px under, col 1 1016.9 px 18 items), 페이지 15 사라짐 (통합) 정상 확인.

작업지시자 코멘트:

"시각적 판정은 svg, canvas 모두 통과입니다. 점점 개선이 되어가고 있는게 보입니다. 좋은 공략 전략입니다."

→ 22→20 정합 작업의 분리 + 즉시 처리 전략 검증.

본 PR 의 좋은 점

  1. 부수 발견 정확 — 이슈 본문 추정 (engine.rs::Paginator) 의 함정을 정확히 식별. 활성 엔진 (typeset.rs::TypesetEngine) 에 정정
  2. 정량 측정 — 페이지 14 col 0 의 4 표 누적 244.88 px 과다 정확 산출 (pi 별 수치 모두 명시)
  3. 메트릭 정합 — col 0 1225.8→1036.1 (-189.7), col 1 64.3→1016.9 (+952.6) — 두 단으로 균형 분배
  4. 149 개 샘플 회귀 검증 — 본인 광범위 점검 (메인테이너 10 샘플 byte 비교와 정합)
  5. PR Task #435: exam_kor.hwp 24→22 페이지 정합 (#393 옵션 A col 1 reserve 정정) #442 연속 처리 — 22→20 정합 잔여 작업 즉시 처리 (메인테이너 머지 직후)
  6. 케이스별 명시 가드is_wrap_around_table 명시 + Square wrap 만 max 정책 (TopAndBottom 등 기존 동작 보존). feedback_hancom_compat_specific_over_general 부합

이슈 #439 도 함께 close 됩니다. 잔여 작업 (#440, #441) 도 같은 집요함으로 이어가실 것 같습니다. 감사합니다.

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