Task #398: 표 페이지 분할 시 rowspan>1 셀이 분할 단위 산정에서 누락#401
Conversation
- row_block_start/end 필드 + compute_row_blocks 헬퍼 - row_block_for / snap_to_block_boundary 메서드 - 신규 단위 테스트 7개 (rowspan 단일/겹침/비인접 + 폴백 + 스냅) 회귀 검증용 샘플 hwpx/pdf 동반 커밋.
- pagination/engine.rs::split_table_rows: pre-loop first_block_h, snap_to_block_boundary, cur/next 블록 단일성 가드 - typeset.rs::paginate_table: 동일 패턴 적용 (실제 SVG 내보내기 경로) - 다중 행 블록이 페이지에 들어가지 않으면 블록 전체를 한 단위로 배치 본 샘플 검증: 1쪽에서 표 분할 사라지고 2쪽에 표 전체 시작. cargo test --lib: 1023 passed.
- cargo test --tests: 1073 passed (lib 1023 + integration 50) - svg_snapshot 골든 6건 통과 (table-text, issue-147/157/267, form-002, deterministic) - cargo build --release 성공 - 본 샘플 외 다른 표 샘플 (table-vpos-01, 표-텍스트) 정상 closes edwardkim#398
- samples/synam-001.hwp 추가 — Task #398 정정의 회귀 검증용 표 샘플 - PR #401 검토 문서 (옵션 A — 작성자 재정정 요청) - 회귀 정황: pi=69 표의 셀[4] (r=2, rs=5) — 큰 rowspan 셀이 PR #401 의 rowspan 묶음 단위 분할 정책으로 페이지 5 잔여 공간에 안 들어감 → 35→37 페이지 증가 - 작성자가 본 샘플을 가지지 못해 회귀 검증 못한 정황 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
PR 검토 결과 안내드립니다. 본 PR 의 결함 분석 (rowspan>1 셀의 시각적 점유 영역이 분할 단위 산정에서 누락) + 정정 방향 (rowspan 묶음 단위 분할) 자체는 합리적입니다. CLAUDE.md 절차 준수 (수행/구현 계획서 + Stage 보고서 + 최종 보고서) 도 양호합니다. CI 통과 + cherry-pick 후 cargo test --lib 1038 passed 검증도 통과했습니다. 다만 메인테이너 환경의 회귀 검증 샘플에서 회귀가 발견되어 재정정이 필요합니다. 회귀 발견 — `samples/synam-001.hwp` 5 페이지본 PR cherry-pick 후 시각 비교 결과:
회귀 원인`pi=69` 표 (8행×3열) 의 셀[4]: ``` 이 셀은 rs=5 — 행 2 부터 5 행을 점유하는 큰 rowspan 셀. 본 PR 의 `compute_row_blocks` 가 이 셀을 보고 행 2~6 을 한 블록 으로 묶습니다. 결과:
본 PR 의 정책 (rowspan 묶음 단위 분할) 이 너무 보수적:
회귀 정황
재정정 방향 제안큰 rowspan 셀에 대한 보강이 필요합니다. 가능 방향: 옵션 1. rowspan 블록 max_height 가드블록 높이가 페이지 본문 영역의 일정 비율 (예: 50%) 을 넘으면 블록 분할 허용 — 셀 내부 분할로 fallback. 옵션 2. 첫 페이지 잔여 공간 우선 활용블록이 한 페이지에 안 들어가도 첫 페이지에 일정 행 (블록 시작 행 + n 행) 까지는 배치하고, 나머지는 다음 페이지에서 이어가는 형태 (현재 인트라-로우 분할과 비슷). 옵션 3. 블록 단위 분할은 작은 블록만특정 임계 (예: 블록 행 수 ≤ 3) 이하만 묶음 단위 분할, 그 외는 기존 행 단위 분할 유지. 작성자 환경에서 `samples/synam-001.hwp` 5 페이지 + 본 PR 의 본 샘플 (`samples/2025년 기부·답례품 실적 지자체 보고서_양식.hwpx`) 1 페이지 양쪽 모두 회귀 없이 정정 가능한 방향을 검토 부탁드립니다. 처리 절차
정리
좋은 기여 감사합니다. 큰 rowspan 셀 정황까지 다루는 정정 후 재제출 부탁드립니다. |
549efc5 to
0cf38d4
Compare
PR edwardkim#401 회귀: rs=5 셀 (synam-001 pi=69) 이 페이지에 안 들어가면 블록 전체를 다음 페이지로 미뤄 페이지 5 가 거의 비고 +2 페이지 발생 (35→37). 정책 변경: 블록 단위 분할은 size ≤ 3 (BLOCK_UNIT_MAX_ROWS) 보호 블록만 적용. 큰 rowspan (≥4 행) 은 행 경계 + 인트라-로우 분할 허용 (HanCom 호환). - height_measurer.rs: BLOCK_UNIT_MAX_ROWS 상수, snap_to_block_boundary 보호 블록만 후퇴 - pagination/engine.rs: cur_block_protected / next_block_protected 도입, intra-split gating - typeset.rs: 병행 코드 경로에 동일 정책 적용 검증: cargo test --lib 1023 passed, synam-001 35페이지 (devel 동일), 페이지 5 SVG 333KB, 기부답례품 PR edwardkim#401 원 목표 (pi=22 페이지 2 시작) 보존.
Task #398 v2 — synam-001.hwp 회귀 정정 최종 보고서배경PR #401 (Task #398) merge 검토 중 메인테이너가
원인: 본 PR 의 rowspan 묶음 단위 분할 정책이 큰 rowspan 셀 (rs=5, 행 2~6 묶음) 에 대해 별도 가드 없이 블록 전체를 다음 페이지로 미룸. 한컴은 큰 rowspan 셀이 페이지에 안 들어가면 셀 내부 분할을 허용함. 정정 방향메인테이너 피드백의 옵션 3 (블록 단위 분할은 작은 블록만) 채택. 임계값 = 3 행.
근거:
변경 내역
|
| 항목 | 결과 | 비고 |
|---|---|---|
cargo test --lib |
1023 passed, 0 failed | PR #401 baseline 유지 |
cargo test --release (전체) |
모든 suite passed | 회귀 없음 |
samples/synam-001.hwp 페이지 수 |
35 | devel/PDF 동일 ✅ |
| 페이지 5 pi=69 행 분할 | rows=0..5 + 4..8 | devel 동일 ✅ |
output/synam-001_005.svg 크기 |
333,125 bytes | devel 333 KB 동일 ✅ |
samples/2025년 기부·답례품 실적 지자체 보고서_양식.hwpx |
27 페이지, pi=22 페이지 2 시작 | PR #401 원 목표 보존 ✅ |
영향 범위
- 정책 변경은 rowspan 셀이 4 행 이상을 점유하는 경우에만 동작.
- 기존 단위 테스트 (
test_snap_to_block_boundary등) 는 size=2 블록만 사용하므로 영향 없음. - 기존 샘플 골든 테스트 영향 없음 (전체 1023 lib + 49 integration 테스트 통과).
처리 절차 후속
- PR Task #398: 표 페이지 분할 시 rowspan>1 셀이 분할 단위 산정에서 누락 #401 force-push 대상 (rebase + 본 정정 + samples/synam-001.hwp 회귀 점검 결과 포함).
- 메인테이너 재검토 후 머지 대기.
- PR #415 (Task #352 dash leader Justify, @planet6897) 옵션 A 처리 완료 - 본 PR 의 40 commits 중 Task #352 핵심 7 commits 만 분리 cherry-pick - 다른 OPEN PR (#401 등) 의 변경 누적 정황 + synam-001 회귀 회피 - 작업지시자 시각 판정 통과 (exam_eng Q32 dash 시퀀스) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PR #401 회귀: rs=5 셀 (synam-001 pi=69) 이 페이지에 안 들어가면 블록 전체를 다음 페이지로 미뤄 페이지 5 가 거의 비고 +2 페이지 발생 (35→37). 정책 변경: 블록 단위 분할은 size ≤ 3 (BLOCK_UNIT_MAX_ROWS) 보호 블록만 적용. 큰 rowspan (≥4 행) 은 행 경계 + 인트라-로우 분할 허용 (HanCom 호환). - height_measurer.rs: BLOCK_UNIT_MAX_ROWS 상수, snap_to_block_boundary 보호 블록만 후퇴 - pagination/engine.rs: cur_block_protected / next_block_protected 도입, intra-split gating - typeset.rs: 병행 코드 경로에 동일 정책 적용 검증: cargo test --lib 1023 passed, synam-001 35페이지 (devel 동일), 페이지 5 SVG 333KB, 기부답례품 PR #401 원 목표 (pi=22 페이지 2 시작) 보존.
- PR #401 v2 (Task #398, @planet6897) cherry-pick 머지 완료 - 1차 회귀 (synam-001 35→37 페이지) 작성자 재정정 후 v2 처리 - BLOCK_UNIT_MAX_ROWS=3 임계, 큰 rowspan (≥4행) 은 행 단위 분할 허용 - cherry-pick 5 commits + 1037→1044 passed + WASM 빌드 + 작업지시자 시각 판정 통과 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
완료. v2 정정 후 `devel` 에 cherry-pick 으로 머지됨 (commit `10fb5fe`). 이슈 #398 close 됩니다. 머지 commit (작성자 attribution 보존)
v2 정정 정책 평가`BLOCK_UNIT_MAX_ROWS = 3` 임계로 보호 블록 / 큰 rowspan 분리:
근거 분석 ("보호 블록 vs 큰 rowspan" 의 시각적 결합 손상 차이) 도 정확합니다. 메인테이너 피드백 옵션 3 을 정확히 채택하여 두 샘플 모두 회귀 없이 정정됐습니다. 검증
정황 정리1차 검토에서 발견된 회귀 (메인테이너 보유 `samples/synam-001.hwp` 5 페이지) 를 작성자가 본 저장소에 추가된 샘플 (commit `b1c97fe`) 로 회귀 검증 + 옵션 3 정책 채택해서 정확히 정정한 점 — 외부 컨트리뷰터의 환경 한계 극복의 좋은 사례입니다. 좋은 기여 감사합니다. |
본질: HWP TablePageBreak::RowBreak (행 단위 분할 허용) 정책이 Task #398 의 BLOCK_UNIT_MAX_ROWS=3 rowspan 보호 블록 정책과 모순. 표 정책 우선. 회귀 origin: PR #401 v2 (Task #398 Stage 2, commit 81db203) - samples/k-water-rfp.hwp 5쪽 표 pi=52 (4행, rs=2): rows=0..2 만 배치, used=196.3px (21%) — 잔여 482px 비어있음 정정: - MeasuredTable::allows_row_break_split() 메서드 추가 - snap_to_block_boundary 에 RowBreak 조기반환 - paginate_table (typeset.rs) 의 first/cur/next_block_protected 가드 - split_table_rows (engine.rs) 동일 3개소 가드 회귀 정정 확인: - k-water-rfp 5쪽: rows=0..2 → rows=0..4 (4행 모두 배치) - synam-001 5쪽/6쪽: rows=0..5 / 4..8 (PR #401 v2 정정 보존) 검증: - cargo test --lib: 1077 passed (1075 + 단위 테스트 2 추가) - svg_snapshot: 6/6, issue_418: 1/1, clippy: 0건 - 광범위 회귀 점검 10 샘플 / 232 페이지 — 영향 영역 k-water-rfp 5/6 만 - 작업지시자 시각 판정: synam-001 정상 + k-water-rfp 정정 후 승인 closes #474 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
본질: HWP TablePageBreak::RowBreak (행 단위 분할 허용) 정책이 Task edwardkim#398 의 BLOCK_UNIT_MAX_ROWS=3 rowspan 보호 블록 정책과 모순. 표 정책 우선. 회귀 origin: PR edwardkim#401 v2 (Task edwardkim#398 Stage 2, commit 81db203) - samples/k-water-rfp.hwp 5쪽 표 pi=52 (4행, rs=2): rows=0..2 만 배치, used=196.3px (21%) — 잔여 482px 비어있음 정정: - MeasuredTable::allows_row_break_split() 메서드 추가 - snap_to_block_boundary 에 RowBreak 조기반환 - paginate_table (typeset.rs) 의 first/cur/next_block_protected 가드 - split_table_rows (engine.rs) 동일 3개소 가드 회귀 정정 확인: - k-water-rfp 5쪽: rows=0..2 → rows=0..4 (4행 모두 배치) - synam-001 5쪽/6쪽: rows=0..5 / 4..8 (PR edwardkim#401 v2 정정 보존) 검증: - cargo test --lib: 1077 passed (1075 + 단위 테스트 2 추가) - svg_snapshot: 6/6, issue_418: 1/1, clippy: 0건 - 광범위 회귀 점검 10 샘플 / 232 페이지 — 영향 영역 k-water-rfp 5/6 만 - 작업지시자 시각 판정: synam-001 정상 + k-water-rfp 정정 후 승인 closes edwardkim#474 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
요약
MeasuredTable에 rowspan 묶음 블록 캐시(row_block_start/end) 와 헬퍼(row_block_for,snap_to_block_boundary) 추가pagination/engine.rs::split_table_rows와typeset.rs::paginate_table(실제 SVG 내보내기 경로) 양쪽에 블록 단위 분할 정책 적용closes #398
배경
samples/2025년 기부·답례품 실적 지자체 보고서_양식.hwpx1쪽 하단에 다음 표(pi=22, 3×3, 제목 "<2025년 기부·답례품 실적 분석보고서 요약>") 의 첫 행이 잘려 들어가고 rowspan=2 제목 셀의 텍스트가 본문 영역 끝에 겹쳐서 그려짐. PDF 원본은 표 전체를 2쪽으로 미룸.근본 원인
분할 단위를 개별 행으로 산정하면서 rowspan>1 셀의 시각적 점유 영역을 누락.
mt.row_heights[r]산출 시cell.row_span == 1셀만 집계 (height_measurer.rs:466)split_table_rows의first_row_h = mt.row_heights[0]가 17.27px (rs=1 셀만), 실제 첫 분할 단위는 행 0+1 묶음(rowspan=2 셀 점유) = 35.4px해결책
분할 가능 단위를 "행" → "rowspan 묶음 블록"으로 확장.
데이터 구조
MeasuredTable에 사전 계산된 블록 경계 캐시:row_block_start[r]: r 행을 포함하는 모든 rowspan 셀의 최소 시작 행row_block_end[r]: r 행을 포함하는 모든 rowspan 셀의 최대 종료 행 (exclusive)compute_row_blocks(table, row_count)헬퍼가 모든 셀의(row, row_span)을 검사하여 전이 폐포로 통합.mt.cells(page_break == CellBreak일 때만 채움) 가 아닌 모델의table.cells에서 직접 계산하여 모든 page_break 모드에 일관 적용.신규 메서드:
MeasuredTable::row_block_for(row) -> (start, end_exclusive, height)MeasuredTable::snap_to_block_boundary(end_row) -> usize분할 호출부 변경
Pre-loop 분할 판정:
first_row_h = mt.row_heights[0]→first_block_h = mt.row_block_for(0).2.Loop body:
find_break_row결과에snap_to_block_boundary적용 → rowspan 묶음 중간 분할 차단block_end == block_start + 1) 에서만 시도end_row = cur_b_end(블록 전체 한 단위로 배치)동일 패턴을
pagination/engine.rs와typeset.rs양쪽에 적용 (분할 로직 중복 — 향후 통합 고려, 별도 타스크).검증
본 샘플 회귀 (Task #398)
PartialTable pi=22 ci=0 rows=0..1 cont=false 3x3pi=21 (빈)(표 사라짐)PartialTable pi=22 ci=0 rows=1..3 cont=trueTable pi=22 ci=0 3x3 635.0x924.5px(표 전체)테스트
cargo test --libcargo test --test svg_snapshot(골든 6건)cargo test --tests(전체 통합)cargo build --releasesamples/table-vpos-01.hwpx회귀samples/표-텍스트.hwpx회귀신규 단위 테스트 (7건)
height_measurer.rs::tests:test_compute_row_blocks_all_single— rowspan 모두 1test_compute_row_blocks_rs2_at_row0— 행 0에 rs=2 셀test_compute_row_blocks_overlapping— 겹치는 rowspan 통합test_compute_row_blocks_disjoint— 비인접 rowspan 별개 블록test_row_block_for_basic— 같은 블록 내 모든 행이 동일 (start, end, height)test_row_block_for_empty_metadata— 빈 vec → 단일 행 폴백test_snap_to_block_boundary— 블록 중간 → 시작으로 후퇴의도된 동작 변경
다른 문서에서 rowspan>1 셀이 페이지 경계에 걸리는 경우, 표가 통째로 다음 페이지로 밀려나면서 빈 공간이 더 커질 수 있다. 이는 한글 호환 동작이며 의도된 변경.
변경 파일
src/renderer/height_measurer.rsMeasuredTable.row_block_start/end+compute_row_blocks+row_block_for/snap_to_block_boundary+ 단위 테스트 7건src/renderer/pagination/engine.rs::split_table_rowsfirst_block_h, snap, 단일성 가드src/renderer/typeset.rs::paginate_tablesamples/2025년 기부·답례품 실적 지자체 보고서_양식.{hwpx,pdf}mydocs/plans/task_m100_398{,_impl}.md,mydocs/working/task_m100_398_stage{1,2}.md,mydocs/report/task_m100_398_report.md,mydocs/orders/20260428.mdTest plan
cargo test --lib통과 (1023)cargo test --tests통과 (1073)cargo build --release성공