Task #452: 단락 마지막 줄 trailing line_spacing 정합 (exam_kor pi=1↔pi=2)#454
Task #452: 단락 마지막 줄 trailing line_spacing 정합 (exam_kor pi=1↔pi=2)#454planet6897 wants to merge 21 commits into
Conversation
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 처리로 페이지 분배는 유지.
- 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 제외 보존)
…pacing 정합 (cherry-pick @planet6897 4 commits)
|
@planet6897 님 PR 감사드립니다. 메인테이너가 cherry-pick 으로 devel 에 적용 완료했습니다. 본 사이클 14번째 PR 입니다. PR #450 머지 후 후속 본질 정정 흐름을 정확히 이어가고 계십니다. 처리작성자 attribution 보존 본질 4 commits 분리 cherry-pick:
devel 머지 commit: 검증
광범위 byte 비교10 샘플 / 305 페이지 SVG 비교: 39 동일, 266 차이 (87.2% 영향). 차이 분포: kps-ai 71, aift 70, exam_* 48, 2025년 기부 25, k-water-rfp 23, synam-001 22, biz_plan 6, equation-lim 1. → paragraph_layout 의 단락 마지막 줄 처리 변경이라 본문 단락이 있는 모든 페이지에 영향. 작성자 PR 본문은 exam_kor 1페이지의 pi=1↔pi=2 step 정합만 명시했지만 실제 영향은 광범위. 시각 판정 정책 (작업지시자 결정)본 PR 단독 시각 판정 보류. 후속 PR (#457 Task #455, #461 Task #459/#462/#463/#468/#469) 가 같은 영역 (paragraph_layout / vpos / col_bottom) 에 누적 정정 진행 정황이라, 모든 PR 처리 후 통합 시각 검증 으로 결정. 이는 메모리 feedback_small_batch_release_strategy 의 빠른 회전 정책 + feedback_v076_regression_origin 의 광범위 변화 직접 시각 검증 게이트 균형. 본 PR 의 좋은 점
이슈 #452 도 함께 close 됩니다. 다음 PR (#457 Task #455) 처리도 같은 사이클로 이어가겠습니다. 감사합니다. |
- 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 침범
- 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 머지 후 작업지시자 직접)
Summary
paragraph_layout.rs:2511-2520의is_para_last_line분기 제거. 본문 단락 모든 줄에서y += lh + ls통일 →pagination/engine.rs의current_height += para_height누적과 정합is_cell_last_line && cell_ctx.is_some()) 만 trailing 제외 보존 — 셀 높이 모델 영향 없음samples/exam_kor.hwp1페이지 pi=1.line9↔pi=2.line0 step 15.34→24.50 px (단락내 step 과 동일, PDF 정합)문제
samples/exam_kor.hwp1페이지 좌측 단에서 pi=1("밑줄 긋기는 일상적으로...") → pi=2("통상적으로 독자는...") 전환 간격이 단락 내 줄 간격보다 좁게 렌더링됨.PDF (
samples/exam_kor.pdf) 측정값: 모든 줄 step 12.96 pt 균일 (= 1838 HU).원인
src/renderer/layout/paragraph_layout.rs:2511-2520의is_para_last_line분기가 단락 마지막 visible 줄에서 trailingline_spacing을 제외하고 y 를lh만 전진. 이 y 가layout_paragraph반환값 → 다음 단락y_start가 되어 다음 단락 첫 줄이 1 ls 만큼 위로 당겨짐.Task #332 stage 2 의 의도(typeset 의
height_for_fit와 layout 정합)가 layout 만 trailing 제외 → pagination/engine 의current_height += para_height누적과 1 ls drift 발생한 절반의 정합. 본 PR 은 layout 도 trailing 포함으로 통일하는 반대 방향 정합.수정 (코드)
src/renderer/layout/paragraph_layout.rs:2511-2520:검증 결과
정량 (exam_kor 1페이지)
자동 테스트
cargo test --lib --release: 1066 passed (회귀 0)cargo test --release --test svg_snapshot: 6/6 passed (UPDATE_GOLDEN=1으로 2건 baseline 재갱신)페이지 수 회귀 (10 종 샘플)
10/10 샘플 페이지 수 동일.
Task #332 회귀 점검
samples/21_언어_기출_편집가능본.hwppage 1 col 1 의 pi=26 + 보기 ①②③ (pi=27, pi=28, pi=29) 모두 page 1 col 1 에 fit. #332 회귀 0.이론적 안전성: pagination engine 의 fit 판정은
effective_trailing(마지막 단락 trailing ls 만 fit 시 제외) 사용 → 본 수정으로 fit 판정 로직 자체는 변하지 않음. layout y 시프트만 정합.golden SVG snapshot
Task #332 stage 5 에서 갱신된 baseline → 본 정합 baseline 으로 재갱신:
tests/golden_svg/issue-147/aift-page3.svg(TOC 페이지)tests/golden_svg/issue-157/page-1.svg(등기 양식 페이지)PNG 시각 검토: 두 페이지 모두 콘텐츠 정상 표시, 잘림/겹침/누락 없음.
부수 효과
LAYOUT_OVERFLOW경고 1건 (issue-157 pi=28, 10.9 px 오버플로): 페이지 마지막 단락의 trailing ls 가 col_bottom 을 살짝 넘는 cosmetic 효과. 빈 공간이므로 시각 무영향.Test plan
cargo build --release성공cargo test --lib --release1066 passedcargo test --release --test svg_snapshot6/6 passedrhwp export-svg samples/exam_kor.hwp -p 0 --debug-overlay→ pi=1↔pi=2 step = 24.50 px단계별 커밋
3dd8695Stage 1: 수행/구현 계획서 + baseline 측정aafccb1Stage 2: paragraph_layout trailing line_spacing 정합 + golden 갱신12a8b92Stage 3: 광범위 회귀 검증 — 페이지수/Task typeset/layout drift 통합 — 단일 advance 모델로 정합 (Task #331 재시도 기반) #332 회귀 05129faaStage 4: 최종 결과보고서 + 오늘할일 갱신관련
mydocs/plans/task_m100_452.mdmydocs/plans/task_m100_452_impl.mdmydocs/working/task_m100_452_stage{1,2,3}.mdmydocs/report/task_m100_452_report.md