Skip to content

Task #445: 지문 박스/페이지 번호 박스 시각 결함 정정 (exam_kor)#450

Closed
planet6897 wants to merge 17 commits into
edwardkim:develfrom
planet6897:local/task445
Closed

Task #445: 지문 박스/페이지 번호 박스 시각 결함 정정 (exam_kor)#450
planet6897 wants to merge 17 commits into
edwardkim:develfrom
planet6897:local/task445

Conversation

@planet6897

Copy link
Copy Markdown
Contributor

Summary

  • 두 가지 시각적 결함 동시 정정 (한컴 PDF 비교 일치)
  • (1) layout.rs::build_single_column 에서 paragraph border merge 그룹을 col_area 바닥/꼭대기로 클램프 — border_fill_id 가 있는 PartialParagraph 의 세로 테두리가 col_bottom 을 초과해 페이지 바깥(p8 y=1671 / 페이지 1587 초과)까지 그려지던 버그 해소
  • (2) layout.rs::layout_header_footer_paragraphs 에서 머리말/꼬리말의 wrap=TopAndBottom + vert=Para 표 anchor 에 line_height/2 추가 — 페이지 번호 박스("1/20" 등) 가 column line 끝과 만나 "붙어 보이는" 문제 해소
  • closes 지문 박스(border_fill_id) 문단이 페이지 분할 시 세로 테두리가 col_bottom 을 초과해 그려짐 #445

변경 내용

결함 1 — Paragraph border 페이지 바깥 침범

samples/exam_kor.hwp 의 지문 박스(border_fill_id=7) 가 페이지 분할되는 경우, partial paragraph 의 border 세로 테두리가 col_bottom 너머까지 그려지고 일부 페이지에서는 페이지 자체(1587px)도 초과:

페이지 수정 전 line end y col_bottom (1422.93) 대비 페이지(1587) 대비
2 1452.7 +30 px OK
5 1506.5 +84 px OK
8 1671.3 +248 px +84 px 페이지 바깥
15 1595.8 +173 px +9 px 페이지 바깥

원인:

  • 페이지네이션이 respect_vpos_reset=false (CLI 기본) 로 동작 → vpos-reset 가 있는 paragraph 가 FullParagraph 로 처리되어 분할 라인이 col_bottom 너머에 layout 됨 (page 8 의 pi=297 가 91.7px 오버슈트)
  • layout.rs::expand_clip 이 자식 bbox bottom 으로 body-clip 을 확장만 (축소 없음) → clip 이 페이지 바깥까지 늘어남
  • paragraph border 가 그 영역에 그대로 그려짐

결함 2 — 페이지 번호 박스 위치

PDF 측정 (samples/hancomdocs-exam_kor.pdf A3 200dpi):

  • 페이지 번호 박스 top y: 380.6 mm (= body_bottom + 4.3mm)
  • column line - 박스 갭: 4.3 mm (16.0 px)

기존 SVG:

  • 박스 top y = 376.4 mm (= body_bottom)
  • 갭 = 0 mm (붙음)

원인:

  • 페이지 번호 박스는 꼬리말 paragraph 의 1×1 표 (wrap=TopAndBottom, vert=Para/off=0, vert_align=Top) 로 그려짐
  • 기존 compute_table_y_position 은 vert=Para + vert_align=Top 일 때 표 top 을 paragraph top 에 정렬
  • HWP 의 실제 동작: 첫 라인의 line_height/2 만큼 아래에 anchor (line center 기준)
  • 꼬리말 paragraph 의 line_height = 2480 HU = 16.5 px ≈ PDF 측정 갭 16.0 px

수정 (코드)

src/renderer/layout.rs::build_single_column — paragraph border merge 후 클램프:

let col_top = col_area.y;
let col_bot = col_area.y + col_area.height;
for g in groups.iter_mut() {
    if g.2 < col_top { g.2 = col_top; }
    if g.4 > col_bot { g.4 = col_bot; }
}
groups.retain(|g| g.4 > g.2);

src/renderer/layout.rs::layout_header_footer_paragraphs — 표 anchor offset:

let line_anchor_offset = if matches!(t.common.text_wrap, TextWrap::TopAndBottom)
    && matches!(t.common.vert_rel_to, VertRelTo::Para)
    && i == 0
{
    let lh_hu = para.line_segs.first().map(|ls| ls.line_height as i32).unwrap_or(0);
    hwpunit_to_px(lh_hu, self.dpi) / 2.0
} else { 0.0 };
let table_y = y_offset + line_anchor_offset;

검증 결과

페이지별 (exam_kor.hwp 20p)

페이지 수정 전 line end 수정 후 line end 박스 top line-박스 갭
1 1422.43 1422.43 1439.47 17.04 px
2 1452.73 1423.17 1439.47 16.29 px
5 1506.45 1423.17 1439.47 16.29 px
8 1671.28 1423.17 1439.47 16.29 px
15 1595.83 1422.93 1439.47 16.49 px

PDF 비교:

  • PDF column line end (모든 페이지): 376.3-376.4 mm = 1422-1423 px ✓ 일치
  • PDF 박스 top: 380.6 mm = 1438.9 px ✓ (우리 1439.47 — 0.6px 차)
  • PDF 갭: 4.3 mm = 16.0 px ✓ (우리 16.3-17.0 px)

column line 길이 (PDF 자연 차이 재현)

페이지 우리 SVG 길이 PDF 길이
p1 1131 px (299.3mm) 299.6 mm
p2~p20 1226 px (324.4mm) 324.6 mm

p1 vs p2+ 의 25mm 길이 차이는 PDF 도 동일 — 페이지 1 은 본문 내 타이틀(제 1 교시, 국어 영역) 이 있어 폴리곤 시작 y 가 더 아래임 (의도된 design).

회귀

  • cargo test --release: 1117 passed / 0 failed / 1 ignored
  • snapshot 갱신 1건: tests/golden_svg/issue-267/ktx-toc-page.svg (invisible 구조 rect height 5.34px 변경, 가시 변화 없음)
  • 다른 시험 샘플 (exam_eng / exam_math / exam_science / exam_social / k-water-rfp / KTX / aift) — 새로운 line-past-page-bottom 검출 0건

본 작업 범위 외 발견

  • respect_vpos_reset 정책: 페이지네이션의 vpos-reset 미존중이 paragraph 의 col_bottom 너머 layout 의 진짜 원인. 본 PR 의 클램프는 시각적 증상만 가림 — 텍스트 자체의 overflow (예: page 8 col 0 의 일부 텍스트가 col_bottom 너머 그려짐) 는 별도 이슈로 분리 검토 필요.
  • 진단 도중 도입했다가 PDF 비교 후 폐기한 시도 (모두 PDF 와 부적합):
    • master-page polygon 강제 클램프
    • 폴리곤 body_top..body_bottom 정규화
    • 콘텐츠-기반 column separator 단축

Test plan

  • cargo build --release 성공
  • cargo test --release 1117 passed, 0 failed
  • rhwp export-svg samples/exam_kor.hwp → p2/5/8/15 line y_end ≤ 1423 (col_bottom 이하)
  • page 1~20 박스 top y = 1439.47 (PDF 380.6mm 일치)
  • PDF (samples/hancomdocs-exam_kor.pdf) 200dpi 렌더 비교 — column line / 박스 / 갭 일치
  • 회귀 검증: 149 개 sample HWP 중 line-past-page-bottom 검출 0건
  • svg_snapshot 갱신 1건 (invisible rect, 가시 변화 없음)

단계별 커밋

  • c151156 Stage 1+2: paragraph border 가 col_bottom 너머로 그려지는 문제 수정
  • 10d8709 Stage 3: 머리말/꼬리말 wrap=TopAndBottom 표 anchor 위치 보정
  • 717ca1f Stage 4: 최종 결과보고서 + 오늘할일 갱신

관련

  • 수행계획서: mydocs/plans/task_m100_445.md
  • 구현 계획서: mydocs/plans/task_m100_445_impl.md
  • 단계별 보고서: mydocs/working/task_m100_445_stage{1,2,3}.md
  • 최종 보고서: mydocs/report/task_m100_445_report.md

planet6897 and others added 17 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 예정.
edwardkim added a commit that referenced this pull request Apr 29, 2026
- mydocs/pr/pr_450_review.md (Task #445 cherry-pick 검토, 작성자 진단 모드 변천 분석)
- mydocs/pr/pr_450_report.md (cherry-pick 머지 + 4 페이지 debug-overlay 시각 판정 통과)
- mydocs/orders/20260430.md (PR #450 완료 행)

검증: 1069 passed + svg_snapshot 6/6 + issue_418 1/1 + clippy 0 + WASM 4,186,281 bytes
광범위 byte 비교: 305 중 67 차이 (머리말/꼬리말 영역 영향)
시각 판정: 4 페이지 (2/5/8/15) 모두 작업지시자 분석 일치
본질: layout.rs paragraph border 클램프 + 머리말/꼬리말 표 anchor 정정 (시각 증상)
잔여 본질 작업 (respect_vpos_reset) 별도 이슈 분리 (작성자 명시)
edwardkim added a commit that referenced this pull request Apr 29, 2026
@edwardkim

Copy link
Copy Markdown
Owner

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

본 사이클 13번째 PR 입니다 — 가장 활발한 컨트리뷰터로서 exam_kor 정합 작업의 마지막 점검까지 정확히 짚어주셨습니다.

처리

작성자 attribution 보존 본질 3 commits 분리 cherry-pick (Task #435/#439 흡수 + merge commits 제외):

  • c151156 (← 452a41b) Stage 1+2: paragraph border 가 col_bottom 너머로 그려지는 문제 수정
  • 10d8709 (← c7979ef) Stage 3: 머리말/꼬리말 wrap=TopAndBottom 표 anchor 위치 보정
  • 717ca1f (← 8ff0212) Stage 4: 최종 결과보고서 + 오늘할일 갱신

devel 머지 commit: 7007093

검증

광범위 byte 비교

10 샘플 / 305 페이지 SVG 비교: 238 동일, 67 차이.

차이 분포 (작성자 명시 외 머리말/꼬리말 영역 영향):

  • 2025년 기부·답례품 30 페이지 (작성자 미명시 — wrap=TopAndBottom 표 anchor 영향)
  • exam_* 28 페이지 (정정 대상)
  • synam-001 5, kps-ai 2, k-water-rfp 1, aift 1 (머리말/꼬리말)

시각 판정 (작업지시자 직접) — 4 페이지 debug-overlay

페이지 overflow 결과
2 +30 px (col_bottom 대비) ✅ 분석 일치
5 +84 px ✅ 분석 일치
8 +248 px (+84 px 페이지 바깥) 가장 명확한 정정
15 +173 px (+9 px 페이지 바깥, col 1) ✅ 분석 일치

작업지시자 통찰 — 본 PR 의 본질

작업지시자께서 본 PR 검토 시 통찰을 짚으셨습니다:

"이 컨트리뷰터는 자신의 가설을 미세하게 변하는 상태를 관찰하는 모드로 진입중이라고 봅니다."

PR 본문에 작성자 본인이 명시하셨듯, 본 PR 은 본질 정정이 아닌 시각 증상 클램프 — paragraph border 가 col_bottom 너머로 그려지는 시각 증상을 정리하면서 본질 (respect_vpos_reset) 은 별도 이슈로 분리. 본질 정정 시도 (master-page polygon 클램프 / 폴리곤 정규화 / 콘텐츠-기반 column separator 단축) 가 PDF 비교에서 부적합 → 시각 증상 정정으로 우회한 패턴.

본 사이클 9 PR 의 본질 알고리즘 정정 흐름에서 PDF 정량 측정 정합 (200dpi ±1px) 으로 모드 전환된 정황을 메인테이너가 인지했습니다. 이는 회귀 위험 인식 + 작은 단위 회전 정책 부합 — 좋은 운영 판단입니다.

후속 PR 안내 (작성자 후속 본질 정정 계획)

후속 PR 진행 시 권장:

  1. 자기 fork base 정기 rebase: origin/devel 갱신 (cherry-pick 후) 으로 rebase 하면 본 PR 의 commits 가 다음 PR 에 누적되지 않음 — 메인테이너 분리 cherry-pick 부담 감소
  2. 본질 정정 (respect_vpos_reset) 진행 시 회귀 위험 큼 — 광범위 sample 검증 권장 (메인테이너 한컴 2010/2020 정답지 추가 활용 가능)
  3. 메모리 feedback_pdf_not_authoritative 균형: 본 PR 은 한컴독스 PDF 200dpi 의존 — 향후 본질 정정 시 한컴 2010/2020 환경 차이도 함께 점검 권장

이슈 #445 도 함께 close 됩니다. 본 사이클 13 PR 의 정성 + 활발한 진단 + 잔여 작업 분리 인식 모두 좋습니다. 감사합니다.

@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/task445 branch April 30, 2026 00:02
planet6897 added a commit to planet6897/rhwp that referenced this pull request Apr 30, 2026
devel 의 PR edwardkim#461 (Task edwardkim#459+edwardkim#462+edwardkim#463+edwardkim#468+edwardkim#469) 통합본과 local/devel 의
Task edwardkim#469+edwardkim#470+edwardkim#471 작업 충돌 해결.

해결 내용:
- src/renderer/layout.rs: ours (Task edwardkim#471 stroke_sig 비교 채택). devel 의
  bf_id 비교는 Task edwardkim#471 에서 정정한 결함이므로 ours 가 superset.
- src/renderer/layout/integration_tests.rs: ours (Task edwardkim#469/edwardkim#470/edwardkim#471
  통합 테스트 3건 모두 보존). devel 은 edwardkim#469 테스트만 포함.
- mydocs/orders/20260430.md: 양측 섹션 병합 (M100 박스 정합 + 잔존 이슈
  + PR 처리 PR edwardkim#450).

검증:
- cargo test 1072 + svg_snapshot 6 + issue_418 1 + 기타 PASS, FAIL 0.
jangster77 added a commit to jangster77/rhwp that referenced this pull request Apr 30, 2026
…Canvas→LayerTree 반영 (Task edwardkim#460 기반)

- upstream/devel 46 commits 병합 (PR edwardkim#446 set_field fix ~ PR edwardkim#456 Canvas→LayerTree)
- 소스 파일 자동 병합 성공 (layout.rs, paragraph_layout.rs, engine.rs)
- orders/20260430.md add/add 충돌만 수동 해소 (Task edwardkim#460 섹션 + PR edwardkim#450 섹션 통합)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@planet6897 planet6897 restored the local/task445 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