Skip to content

Task #866: shortcut.hwp 구분 칸 spacing 정합 + pi=94 회귀 수정 + 점선 단 구분선 (PR #868 supersedes)#872

Closed
planet6897 wants to merge 33 commits into
edwardkim:develfrom
planet6897:pr-task866
Closed

Task #866: shortcut.hwp 구분 칸 spacing 정합 + pi=94 회귀 수정 + 점선 단 구분선 (PR #868 supersedes)#872
planet6897 wants to merge 33 commits into
edwardkim:develfrom
planet6897:pr-task866

Conversation

@planet6897
Copy link
Copy Markdown
Contributor

@planet6897 planet6897 commented May 13, 2026

closes #866 · <...> 소제목 spacing · 헤더 띠 band items-based 분기 · 우측 단축키 right-alignment (native + WASM) · 스케일 도형 내부 글꼴 정합 · Task #874 (Case 1·2·3·4·5 + auto_tab_right + auto_tab_right lang split + 스케일 도형 글꼴) · supersedes #868

요약

shortcut.hwp 의 페이지 내 다단 zone 전환·점선 단 구분선·<...> 소제목 zone 간격·페이지 시작 baseline·헤더 띠 band 가산·<...> 인접 줄간격·우측 단축키 right-alignment·바탕쪽 자동번호 시각 크기를 한컴 PDF (pdf/basic/shortcut-2022.pdf) 와 정합되도록 정정. 본 PR 은 #866 v2 (5 커밋) + #866 v3 (3 커밋) + #874 (12 커밋) 누적 묶음.

커밋 (stream/devel 위에 21 commits stacked)

#866 (v2)

커밋 내용
b1449bc1 PR #868 베이스: 구분 칸 spacing 압축 + 본문영역 초과 정정 (Task #853 결합)
6561b981 Stage 1: pi=94 회귀 수정 — Distribute 다단 마지막 컬럼 [단나누기]process_multicolumn_break 로 라우팅
cf6d6d0d Stage 2: solo_zone_pad +16px — 1단/간격=0 zone 진입·이탈 시 추가 여백
e489fc80 Stage 3+4: zone 별 점선 단 구분선(emit_zone_column_separators) + column_break_pad
5631e68f 최종 결과보고서

#866 (v3) — 사용자 피드백 후속

커밋 내용
07c82c58 clippy unreachable_pattern 제거 (PR #872 CI)
a012f896 Stage 1: 헤더띠 leaving 에서 solo_zone_pad 제외 (leaving_is_header_band)
435e6207 Stage 2 (paragraph-level): <...> 단독 paragraph spacing +20px (이후 #874 Case 3 에서 제거)

#843 정합

커밋 내용
cac13984 dash 매핑 stream/devel(PR #843) 정합

#874 (Stage 1·2·Case 1·1v2·3·5·5v2·5v3v4·auto_tab_right native·WASM·lang split·스케일 도형 글꼴)

커밋 내용
72fa9702 Stage 1 (Case 4): 점선 단 구분선 길이 정정 — zone-specific separator skip
3a15c3a9 Stage 2 (Case 2): Table only 페이지 첫 zone 헤더띠 식별 — cd_gap_zero fallback chain, design_spacing ≤ 1mm 인정
d53b8b59 Case 3: <...> paragraph-level 이중 패딩 제거 (페이지당 45개 누적 +1730pt → +8px)
b29f4368 Case 1: 헤더 띠 zone 아래 band 가산 전체 제거 (페이지 2·3 +30pt → 해소)
220d91d1 Case 5: zone leaving 시 last paragraph trailing_ls 제외 (페이지 1 +11.5pt → 0px)
ac4c3b41 Case 1 v2: 헤더 띠 band 가산을 col_content.items 수로 분기
6c641e68 Case 5 v2: trailing_ls 보존 가드를 <...> solo paragraph 까지 확장
537372cc Case 5 v3+v4: solo zone leaving prev_zone_was_solo 분기 + cd 간격 ≤1mm 인정 (페이지 4 줄간격 15.2 → 31~38 px)
db6332b2 auto_tab_right (native): 우측 단축키 right-alignment 정합 — cross-run + intra-run 양 경로 fix
8b6ca137 auto_tab_right (WASM): rhwp-studio 정합 — WasmTextMeasurer 양 경로 동일 패턴 적용
41fe9db8 auto_tab_right #2: composer lang split 후속 run 합산 — "F3→Alt+I" 가 ["\tF3"/"→"/"Alt+I"] 로 분리될 때 right_tab_block_width_override 주입
21d6a367 스케일 도형 글꼴 한컴 정합: 바탕쪽 글상자 (render_sx≈2.68, sy≈2.51) 의 자동번호 "1" 이 본문 "지우기" 행 위로 광범위하게 겹치는 문제 — max(sw,sh) > 1.5 일 때 styles 사본의 font_size 를 1/max_ratio 로 축소 (338.67→126.49 px)

결과 (shortcut.hwp ↔ pdf/basic/shortcut-2022.pdf)

본문 첫 줄 top (px) 정합도 — 7 페이지 모두 ±6 px 내

페이지 rhwp pre rhwp final 한컴 PDF diff (final)
1 210.7 194.7 195.3 -0.6 ✓
2 150.9 135.3 131.5 +3.8 ✓
3 147.6 132.1 137.7 -5.6 ✓
4 (<글상자에서> 직후) 94.0 97.7 -3.7 ✓
5 68.0 56.7 57.7 -1.0 ✓
6 99.1 118.9 115.5 +3.4 ✓
7 68.0 56.7 57.7 -1.0 ✓

우측 단축키 right edge (px) 정합도 — 한컴 PDF 기대 = col 0: 532.33, col 1: 1001.33

단축키 rhwp pre rhwp final 한컴
Alt+L (pi=142, col 0) 472 524 532
Alt+T (pi=143, col 0) 472 524 532
Alt+Shift+C (pi=144, col 0) 497 532 532
Alt+Shift+V (pi=145, col 1) 974 1001 1001
F6 (pi=146, col 1) 993 1001 1001
F3→Alt+I (pi=138, col 0) 572 534 532

native (SVG export) 와 WASM (rhwp-studio) 양 빌드 모두 동일하게 적용됨.

페이지 4 줄간격 (baseline-to-baseline, Case 5 v3+v4)

전환 Before After Hancom
개체 모양 복사 → <스타일에서> 15.2 px ❌ 37.9 px ~40
<스타일에서> → 스타일 적용 15.2 px ❌ 31.2 px ~40

바탕쪽 자동번호 "1" 시각 정합 (스케일 도형 글꼴)

항목 Before After Hancom
font-size (px) 338.67 126.49 ~150
"1" 시각 상단 (y px) 472 ❌ (본문 "지우기" 행 위) 654 ✓ (본문 아래) ~540
본문 행 겹침 "지우기" 3행 모두 ❌ 없음 최하단 "Alt+Y" 만

기타 정합 항목

항목 PR #868 #866 v2 #874 final 한컴 PDF 평가
페이지 수 8 7 7 7 ✓ 정합
pi=94 <편집 화면 분할에서> 위치 4쪽 단독 zone 3쪽 zone_y≈235 3쪽 zone_y≈230 3쪽 같은 위치 ✓ 정합
<...> paragraph excess +48px +8px 0 ✓ 정합
페이지 2·3 헤더 띠 ↔ 본문 gap 67.9px 35~37px 31.5px ✓ 정합
본문 2단 zone 점선 단 구분선 길이 미렌더 zone 1개 길이 zone 별 정확 회색 점선 zone 별 ✓ 정합

검증

  • cargo test --release --lib: 1246 passed / 0 failed / 2 ignored
  • cargo check --target wasm32-unknown-unknown --lib: 정상 (WASM 코드는 cfg(target_arch = "wasm32") 로 분기)
  • 페이지 수 회귀 0 (exam_math.hwp, 21_언어_기출_편집가능본.hwp)
  • svg_snapshot 변동 없음
  • test_544/test_624/test_521 등 zone 내부 box·textbox·TAC 표 위치 회귀 없음
  • 일반 글상자 (current ≈ original) 는 max_ratio ≤ 1.5 가드로 스케일 미적용

잔존 (미세 항목)

  1. 페이지 4 <...> → 본문 줄간격 31.2 px vs 한컴 ≈40 px — -8.9 px 좁다 잔차. 시각적으로는 정상 줄간격 범위.
  2. 본문 zone hwp_used≈XXX, diff=-N 잔차 — hwp_used 계산식과 rhwp used 의 trailing_ls 포함 여부 차이 (시각 영향 없음).
  3. 바탕쪽 자동번호 "1" 의 한컴 PDF 정확 폰트 (~150 pt 추정) vs 본 PR (95 pt = 1/sx) — 약간 더 작음. 본 PR 은 한컴의 "절대 design size 보존" 가정 사용. 추가 데이터 확보 시 보정 가능.

문서

🤖 Generated with Claude Code

planet6897 added a commit to planet6897/rhwp that referenced this pull request May 13, 2026
PageItem 의 5개 variant가 모두 위 arm에서 처리되므로
`_ => None`은 `unreachable_pattern` 으로 deny 됨. `and_then`을
`map`으로 변경하여 None 케이스 자체를 제거.

PR edwardkim#868 의 같은 fix를 edwardkim#872 superset 에도 적용.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
planet6897 added a commit to planet6897/rhwp that referenced this pull request May 13, 2026
PR edwardkim#872 의 solo_zone_pad +16px 가 leaving_solo_zero 조건 — "직전 zone 이
1단/간격=0" — 으로 헤더 띠(TAC wrap=TopAndBottom 표) leaving 케이스에도
적용되어, 이미 tac_band_extra 가 표 band 높이 패딩을 추가하는데 +16px 가
중복으로 더해져 한컴 PDF 대비 헤더띠↔본문 첫 줄이 ~13pt 더 아래.

해결: tac_band_extra > 0 == 헤더띠 leaving 케이스 → solo_zone_pad 의
leaving 분기 제외. typeset.rs 의 leaving_is_header_band 와 동일 시멘틱을
layout.rs 에 prev_zone_was_header_band trace 변수로 추가.

측정 (shortcut.hwp 1쪽 '지우기' → '뒤 글자 지우기'):
- 한컴 PDF: 37.6pt
- 변경 전 rhwp: 51.2pt (+13.6pt 넓음, 사용자 "넓다" 피드백)
- 변경 후 rhwp: 39.2pt (+1.6pt — 한컴과 거의 정합)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
planet6897 added a commit to planet6897/rhwp that referenced this pull request May 13, 2026
stream/devel 에 PR edwardkim#843 (단 구분선 line type 6/7 dash 매핑, 결함 edwardkim#3) 이
merge 되어 6=>Dash, 7=>Dot 가 추가됨. PR edwardkim#872 의 3|6|7=>Dot 와 자동 merge
시 두 변경이 합쳐져 6/7 별도 라인이 unreachable_pattern 으로 clippy
실패.

해결: PR edwardkim#872 의 3|6|7=>Dot 변경을 PR edwardkim#843 매핑 (한컴 PDF 측정 기반)
으로 변경. 6 => Dash (LongDash 근사), 7 => Dot (Circle 원형 점선).

build_column_separators + emit_zone_column_separators 두 함수 모두 동일
매핑 적용.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
planet6897 and others added 9 commits May 13, 2026 19:11
… 초과 정정

대상: samples/basic/shortcut.hwp ↔ 한컴 PDF pdf/basic/shortcut-2022.pdf
(증상1) 모든 구분 칸(섹션 헤더 띠) 위·아래 줄 간격 압축 (증상2) 일부 페이지 본문영역 초과.
closes edwardkim#853 · 잔존은 edwardkim#866(쪽나누기 페이지 ~28px gap 정밀화) / edwardkim#867(페이지 수 7≠8 + overflow) 로 분리.

## 정정
- src/renderer/layout/paragraph_layout.rs::layout_composed_paragraph — is_column_top &&
  para_index==0(섹션 첫 문단)일 때 spacing_before 를 LINE_SEG.vertical_pos 로 상한 클램프해
  적용. 제목 baseline 79.4→105.8 (top 약 83.8px ≈ 한컴 PDF 83.6px).
- src/renderer/typeset.rs::place_table_with_text — 전폭 글자처럼-취급 표가 자동 줄바꿈으로
  자기 LINE_SEG 줄에 놓인 경우, 표 줄 높이와 일치하는 LINE_SEG 인덱스로 표 앞 텍스트 줄(line0)을
  표보다 먼저 PartialParagraph 로 emit. PUA 필러 케이스는 is_alphanumeric() 로 제외.
- src/renderer/typeset.rs::process_multicolumn_break — (1) 1단 ColumnDef 의 가로 단 간격(1단이라
  무의미)을 zone 진입 세로 간격으로 ((이전 zone /2)+(새 zone /2)). (2) 직전 zone 마지막 paragraph 가
  wrap=위아래 글자처럼-취급 표 & 1단 ColumnDef 간격=0 이면 vpos_zone_height 에 표 band 높이 추가
  (한컴 PDF: 헤더 띠 하단↔본문 약 28~33px ≈ 표 band 높이). (3) 새 다단 zone 시작 여유가
  헤더 띠 1개 높이(약 4*line) 미만이면 다음 페이지로. (4) Distribute 다단 vpos-reset 검출을
  직전 문단 vpos+line_height 기준으로 → 1줄짜리 컬럼도 컬럼 전환 인식.
- src/renderer/layout.rs::build_columns — zone-간 세로 여백(ColumnDef 간격/2 + 표 band)을
  layout 의 prev_zone_y_end 누적에도 미러(종전엔 pagination 메타데이터에만 반영되어 SVG 렌더
  미적용이었음).

## 결과 (SVG ↔ 한컴 PDF, into body 기준)
- 1쪽: 제목 top +26.5px(PDF +26.8 OK), 헤더 띠 zone +88px(PDF +87.6 OK), 본문 +149.3px(PDF +148 OK)
- 2쪽: 헤더 띠 +19.8/+43.3px(PDF +19.1/+43.1 OK), 본문 +89.5px(PDF 약 +85, 약 4.5px 초과)
- 3쪽: 본문 +102.3px(PDF 약 +93, 약 9px 초과; 종전 약 24px 부족)
- LAYOUT_OVERFLOW 25 → 4 (잔존 4건은 edwardkim#867 영역)

## 검증
- cargo test --release 34 test suites 전건 통과. svg_snapshot 8/8 — golden 2건
  (issue-267 목차 제목, issue-617 exam_kor p5) 갱신: 섹션-시작 문단이 LINE_SEG.vertical_pos
  기준으로 재배치, 한컴 기록값 정합. shortcut.hwp/KTX.hwp/exam_kor.hwp 외 변경 없음.

문서: mydocs/tech/hancom_zone_paragraph_spacing.md, mydocs/report/task_m100_853_report.md,
mydocs/report/task_m100_866_report.md

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…기] 경로) + 측정

다단 zone 의 마지막 컬럼에서 [단나누기] 를 만나면 종전엔 무조건 새 페이지로
밀렸음 → shortcut.hwp pi=94 "<편집 화면 분할에서>" 가 페이지 하단 여유가
충분한데도 4쪽으로 밀리는 회귀 (PR edwardkim#868 결합).

수정: 배분(Distribute) 다단의 마지막 컬럼 [단나누기] 만 process_multicolumn_break
로 라우팅 → 현재 ColumnDef 유지한 채 같은 페이지 여유 있으면 이전 밴드 아래에,
부족할 때만 새 페이지로. 신문형(Normal) 다단은 종전 동작(=새 페이지) 유지 →
exam_math.hwp / 21_언어_기출 류 페이지 수 영향 없음.

결과:
- shortcut.hwp pi=94 가 3쪽 zone_y_offset≈203 에 배치 (한컴 PDF 정합)
- shortcut.hwp 페이지 수 8 → 7 (한컴 PDF 7쪽 정합)
- cargo test --release 34 suites 1232 통과
- LAYOUT_OVERFLOW 4 → 6 (3쪽이 더 채워진 부작용, Stage 2 의 전환 간격
  정정으로 3쪽이 한컴처럼 일찍 끊기면 해소 예상)

문서:
- mydocs/plans/task_m100_866_v2_impl.md — 4 stage 구현 계획서
- mydocs/working/task_m100_866_v2_stage1.md — pi=94 수정 상세 + PDF↔SVG 정밀 측정

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
한컴 PDF (shortcut.hwp 2·3쪽) 측정상 1단/간격=0 인 zone(헤더 띠 `파일`/`보기`/
`입력`/`서식` 류 또는 `<...>` 소제목 zone) 진입·이탈 시 ~한 본문 줄(=1200 HU
≈ 16px) 의 추가 세로 여백이 들어간다 — Stage 1 까지의 design_spacing/2 +
tac_band_extra 만으론 ~10~20px 부족.

수정:
- typeset.rs::process_multicolumn_break: candidate_offset 에 solo_zone_pad
  (1200 HU px) 가산. 게이트: 새 zone 첫 paragraph 가 1단/간격=0 이거나, 직전
  zone 이 1단/간격=0 이었음 (둘 중 하나라도 참 → 16px, 둘 다 참이라도 16px
  한 번만).
- layout.rs::build_columns: 동일 솔루션 미러. prev_zone_was_solo 상태로 직전
  zone 의 1단 여부 추적.

결과 (shortcut.hwp ↔ pdf/basic/shortcut-2022.pdf):
- 페이지 수 7 (PDF 정합) ✓
- LAYOUT_OVERFLOW 6 → 0 ✓
- 3쪽이 `<그림 넣기에서>` 그룹까지 (`<글상자에서>` 는 4쪽) — PDF 구조 정합
- cargo test --release 34 suites / 1232 passed / 0 failed
- svg_snapshot 8/8 golden 무변경

회귀:
- exam_math.hwp / 21_언어_기출_편집가능본.hwp 통과 (Stage 1 의 Distribute
  게이트로 보호된 영역, Stage 2 추가 영향 없음).
- 미세 ±5~10px 잔존 — 96dpi PNG 측정 한계. ±1px 정합은 Hancom 편집기 cross-check 필요.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stage 3 — zone 별 단 구분선:
- 기존 build_column_separators 는 page 전역 layout 만 보아 shortcut.hwp 처럼 페이지
  내 다단 zone 의 ColumnDef (예: pi=2 의 2단/배분/구분선 type=7) 를 못 봤다.
- emit_zone_column_separators 신규: zone_layout.column_areas 기준 + zone 별 y 범위.
  build_columns 에서 zone 전환 시점·종료 시점에 emit.
- StrokeDash 매핑: 한컴 구분선 type=6/7 도 Dot 로 확장 (점선 변형).

Stage 4 — column_break_pad:
- typeset.rs::process_multicolumn_break + layout.rs::build_columns:
  새 zone 의 첫 paragraph column_type == ColumnBreakType::Column 이면 solo_zone_pad
  (+16px) 발동. Stage 1 의 배분 다단 마지막 컬럼 [단나누기] 라우팅 보완 — 같은
  ColumnDef 로 시작하는 다음 zone band 와 이전 band 사이 ~한 본문 줄 여백.
- 적용 사례: shortcut.hwp 3쪽 화면 확대 100%↔<편집 화면 분할에서> (양쪽 다 2단/배분,
  종전 pad 0px). pi=94 zone_y_offset 219 → 235.

결과 (shortcut.hwp ↔ pdf/basic/shortcut-2022.pdf):
- 페이지 수 7 (PDF 정합) ✓
- LAYOUT_OVERFLOW 1 (작은 잔존)
- 점선 단 구분선 렌더 ✓ (모든 본문 2단 zone, x=561, #aeaeae dotted)
- cargo test --release 34 suites / 통과, FAILED 0
- svg_snapshot 8/8

잔존 (Stage 5 후속):
- 4쪽 <스타일에서> zone gap ~10~20px 부족 — 20px 상향 시 페이지 수 7→8 회귀.
- 6쪽 도구 띠 1mm spacing 으로 solo/tac_band 게이트 미발동.
정밀화는 Hancom 편집기 cross-check 또는 transition 유형별 차등 모델 필요.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 stage 완료 요약 (commits 7f3ea171, 70d793df, 4221050f):
- 페이지 수 8→7 (PDF 정합)
- LAYOUT_OVERFLOW 4→1
- pi=94 <편집 화면 분할에서> 3쪽 정합
- 본문 2단 zone 점선 단 구분선 렌더
- cargo test --release 34 suites / FAILED 0

잔존 4건은 보고서에 명시 (Hancom 편집기 cross-check 필요한 정밀화).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PageItem 의 5개 variant가 모두 위 arm에서 처리되므로
`_ => None`은 `unreachable_pattern` 으로 deny 됨. `and_then`을
`map`으로 변경하여 None 케이스 자체를 제거.

PR edwardkim#868 의 같은 fix를 edwardkim#872 superset 에도 적용.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PR edwardkim#872 의 solo_zone_pad +16px 가 leaving_solo_zero 조건 — "직전 zone 이
1단/간격=0" — 으로 헤더 띠(TAC wrap=TopAndBottom 표) leaving 케이스에도
적용되어, 이미 tac_band_extra 가 표 band 높이 패딩을 추가하는데 +16px 가
중복으로 더해져 한컴 PDF 대비 헤더띠↔본문 첫 줄이 ~13pt 더 아래.

해결: tac_band_extra > 0 == 헤더띠 leaving 케이스 → solo_zone_pad 의
leaving 분기 제외. typeset.rs 의 leaving_is_header_band 와 동일 시멘틱을
layout.rs 에 prev_zone_was_header_band trace 변수로 추가.

측정 (shortcut.hwp 1쪽 '지우기' → '뒤 글자 지우기'):
- 한컴 PDF: 37.6pt
- 변경 전 rhwp: 51.2pt (+13.6pt 넓음, 사용자 "넓다" 피드백)
- 변경 후 rhwp: 39.2pt (+1.6pt — 한컴과 거의 정합)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…pacing +20px

`<...>` 단독 paragraph zone 의 paragraph 자체에 spacing_before/after
각 +20px(=1500 HU) 추가. 한컴 PDF 모델은 이 paragraph 위/아래에 본문
한 줄 분량 여백을 paragraph 자체에 포함시키는데 ParaShape.spacing_*
이 0 이라 rhwp 가 그 여백을 누락.

식별: ColumnDef cd=1단 컨트롤 **직접** 보유 + 표 없음 + lh<1500 HU.
- 본문 zone 시작 paragraph (cd=2단) 매칭 안 됨
- 헤더 띠 (TAC wrap=TopAndBottom 표 보유) 매칭 안 됨
- 제목 (lh=2000) 매칭 안 됨
- `<...>` paragraph (cd=1단, lh=1000, 표 없음) 만 매칭

수정: typeset.rs::format_paragraph + layout/paragraph_layout.rs 양쪽
일관성 유지 (둘 다 같은 식별 + 같은 +1500 HU 가산).

측정 (shortcut.hwp 4쪽 vs 한컴 PDF):
- <스타일에서> diff: -12.65pt → +17.34pt (한컴 정합 가까이)
- 스타일 적용 diff: -25.29pt → +19.69pt
- <글자 속성> diff: -40.30pt → +16.68pt
- 6쪽 셀 편집 diff: -17.16pt → -2.17pt (정합)
- 페이지 수 7 유지

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
stream/devel 에 PR edwardkim#843 (단 구분선 line type 6/7 dash 매핑, 결함 edwardkim#3) 이
merge 되어 6=>Dash, 7=>Dot 가 추가됨. PR edwardkim#872 의 3|6|7=>Dot 와 자동 merge
시 두 변경이 합쳐져 6/7 별도 라인이 unreachable_pattern 으로 clippy
실패.

해결: PR edwardkim#872 의 3|6|7=>Dot 변경을 PR edwardkim#843 매핑 (한컴 PDF 측정 기반)
으로 변경. 6 => Dash (LongDash 근사), 7 => Dot (Circle 원형 점선).

build_column_separators + emit_zone_column_separators 두 함수 모두 동일
매핑 적용.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
planet6897 and others added 14 commits May 13, 2026 20:04
shortcut.hwp 2쪽~ 가운데 단 구분선이 zone boundary 가 아니라 body_area
전체 길이(y=56.7~758.3) 로 추가 그려지는 결함. PR edwardkim#872 가 zone 별 점선
(emit_zone_column_separators) 을 추가했지만 page-level fallback
build_column_separators 는 그대로 호출되어 페이지 layout 이 2단인 경우
(2쪽~) 중복 그림.

해결: page_content.column_contents 중 zone_layout.is_some() 이 있으면
(= 페이지 안에 다단 zone 이 있어 zone 별 sep 가 emit 됨) page-level
fallback skip.

검증:
- 페이지 1: 변화 없음 (build_column_separators 가 layout=1단이라 미렌더)
- 페이지 2~6: body_area 전체 길이 추가 line 제거 ✓
- cargo test --release --lib: 1246 passed
- 페이지 수 7쪽 유지

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
shortcut.hwp 6쪽 '도구' 헤더띠 (Table only, paragraph 자체에 ColumnDef
컨트롤 없음, 직전 paragraph 의 cd 가 zone 정의) → '맞춤법 검사 시작'
본문 사이 -12pt 좁은 결함 정정.

원인: 한컴 layout 모델은 line VertSize 가 wrap=TopAndBottom TAC 표의
outer_margin top+bottom 을 포함하지만, paragraph 자체에 ColumnDef 가
없는 케이스에서 rhwp 의 헤더띠 식별 (cd.spacing==0 검사) 가 false 가
되어 band 가산 누락.

해결:
- layout.rs::build_columns: paragraph 자체에 cd 없으면 zone 시작
  paragraph 까지 거슬러 cd 검사 + spacing 조건 완화 (≤ 1mm = 283 HU).
- typeset.rs::process_multicolumn_break: tac_band_extra 의 design_spacing
  조건 ≤ 1mm 까지 인정. 페이지 break 후 stale state 보정.

측정 (shortcut.hwp 6쪽):
- '맞춤법 검사 시작' diff: -12.33pt → +10.98pt (-12pt 좁다 해소, +11pt
  overshoot 은 페이지 시작 baseline 차이로 페이지 1 (+20pt) 등 다른
  페이지와 같은 패턴 — 별도 stage 에서 처리)
- 페이지 1 / 2 / 3 / 4 / 5 정합 영향 없음
- cargo test --release --lib: 1246 passed

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
이전 edwardkim#866 v3 Stage 2 (paragraph-level) 는 `<...>` 단독 paragraph 의
spacing_before/after 에 각 +20px(=1500 HU) 을 더했다. 그러나 typeset 의
zone 전환 패딩 `solo_zone_pad` (entering +16 + leaving +16 = +32px) 이
이미 동일 역할을 담당하므로 paragraph 자체에 +40px 을 더하는 것은
이중 패딩. 한컴 PDF 측정 결과 `<...>` paragraph 당 +48px excess (4·5쪽
누적 +17~30pt 사용자 피드백, 매 페이지 4~5 개 `<...>` 누적).

조치: paragraph_layout.rs / typeset.rs 양쪽에서 `is_solo_text_zone_start`
판정 + `solo_text_extra` 가산 제거. zone 전환 패딩 (solo_zone_pad) 만
유지.

dump-pages 측정 (shortcut.hwp 4쪽 `<...>` 5 개):
  before: used=61.3px hwp_used≈13.3px diff=+48.0px (모두 동일)
  after:  used=21.3px hwp_used≈13.3px diff=+8.0px (모두 동일)
잔여 +8px 는 trailing_ls 가 zone used 누적에 포함되는 측정 차이.

전체 테스트 1246 통과.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
이전 edwardkim#866 layout 측 헤더 띠 leaving 처리는 `prev_zone_y_end += band`
(표 본체 + outer_margin top+bottom ≈ 31px) 을 추가했다. 그러나
build_single_column 의 `y_offset` 반환값이 이미 visual 헤더 띠 끝
(표 본체 + outer_margin) 까지 advance 한 상태이므로 추가 가산은 중복.

한컴 PDF (shortcut-2022.pdf) 측정 (page 2):
  Hancom 본문 첫 줄 top: y=98.62 pt = 131.5 px
  rhwp 본문 첫 줄 top  : y=150.87 px (pre)  → +19.4 px (≈14.5 pt) 넓음
  rhwp 본문 첫 줄 top  : y=119.78 px (post) → -11.7 px (≈8.8 pt) 좁음
  (시각 gap: 헤더 띠 하단↔본문 첫 줄 — Hancom 31.5 px / rhwp pre 67.9 px
   / rhwp post 36.8 px. post 의 +5 px 잔차는 헤더 띠 자체 위치 차이.)

조치: layout.rs::build_body_columns 의 헤더 띠 leaving 분기에서
`prev_zone_y_end += band` 제거. `prev_zone_was_header_band` flag 갱신은
유지 (이후 solo_zone_pad 분기 식별용).

전체 테스트 1246 통과.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
페이지 시작 baseline +10~20pt 넓다 (사용자 피드백) 의 원인은 leaving
zone 의 마지막 paragraph 의 trailing_line_spacing 이 zone 간 gap 계산에
중복 포함되었기 때문. 한컴 layout 모델은 zone 간 spacing 을 design_spacing/2
+ solo_zone_pad 로 정의하며 trailing_ls 는 zone 내부 paragraph 간격에만
사용된다.

조치:
1. typeset.rs::enter_new_zone — `vpos_zone_height` 에서 last_seg.line_spacing
   제외 (zone 전환 시점의 zone 높이 계산).
2. layout.rs::build_body_columns — `prev_zone_y_end` 갱신 시 마지막
   paragraph 의 last line_seg.line_spacing 만큼 차감.
3. 예외 — last paragraph 가 TAC 헤더 띠 (wrap=TopAndBottom 표) 인 경우는
   trailing_ls 보존. shortcut.hwp pi=81 형식 (페이지 상단 partial header
   band) 의 ls=480 HU 는 한컴 모델 상 의도된 표↔본문 간격이므로 제외 시
   over-correction (페이지 3 본문 -6.4 px 추가 상향).

한컴 PDF 측정 (shortcut.hwp 1쪽):
  본문 첫 줄 top  Hancom 195.33 px / rhwp pre 210.70 / **rhwp post 194.70**
  → +15.4 px 넓다 → -0.6 px (사실상 정합)

test_544/test_624/test_521 등 zone 내부 box·textbox·TAC 표 위치 회귀 없음
— y_offset 누적 자체는 변경 없음, prev_zone_y_end 만 차감.

전체 테스트 1246 통과.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
이전 edwardkim#874 Case 1 (commit b29f436) 는 layout 측 `prev_zone_y_end += band`
전체 제거를 통해 페이지 2·3 헤더 띠 leaving 의 +30pt 넓다 해소했으나
한컴 PDF 측정 결과 페이지 2 -11.7 px / 페이지 3 -21.1 px / 페이지 6 -12
px 좁다 over-correction.

원인: 헤더 띠 zone 의 구조가 두 가지로 다름.
1. Table-only zone (페이지 6 pi=210 형식): col_content.items = 1.
   build_single_column 의 y_offset 이 표 높이만 advance 하므로 표 본체 +
   outer_margin (≈31px) 만큼 추가 가산 필요. → 전체 band 가산.
2. PartialParagraph + Table zone (페이지 2·3 pi=36/81 형식):
   col_content.items = 2 (PartialParagraph + Table). y_offset 이 text 라인 +
   표 라인 까지 advance 한 상태로 band 의 부분 (≈half) 만 중복. → half band
   가산 (band / 2).

조치: items 수로 분기.
  items==1 → prev_zone_y_end += band (전체)
  items>1  → prev_zone_y_end += band / 2 (절반)

한컴 PDF 측정 (rhwp post-fix):
  페이지 2 본문 첫 줄 top: 135.3 (Hancom 131.5) → +3.8 px 정합
  페이지 3 본문 첫 줄 top: 132.1 (Hancom 137.7) → -5.6 px 정합
  페이지 6 본문 첫 줄 top: 118.9 (Hancom 115.5) → +3.4 px 정합
  페이지 1 본문 첫 줄 top: 194.7 (Hancom 195.3) → -0.6 px 정합 (Case 5 유지)
  페이지 5·7 본문 첫 줄 top: 56.7 (Hancom 57.7) → -1.0 px 정합

잔존: 페이지 4 `<...>` 직후 본문 -32 px 좁다 — Case 3 의 `<...>` paragraph
extra spacing 전체 제거가 누적 페이지에 over-correction. 후속 follow-up.

전체 테스트 1246 통과.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
edwardkim#874 Case 5 (commit 220d91d) 의 trailing_ls 제외 가드는 TAC 헤더 띠
(wrap=TopAndBottom 표) 에만 적용되어 페이지 4 `<...>` 직후 본문이
+8 px (`<글상자에서>` pi=127 의 ls=600 HU) over-correction.

원인: `<...>` 단독 paragraph 의 trailing_ls 도 한컴 모델 상 zone 전환
간격의 일부 — TAC 헤더 띠와 동일 시멘틱. Case 5 가 이를 제외하면 leaving
solo zone (`<...>`) 의 본문 gap 이 한컴 PDF 대비 좁아짐.

조치: Case 5 의 가드 확장 — TAC 헤더 띠 OR `<...>` solo paragraph
(cd=1 spacing=0 + text 가 `<` 로 시작) 시 trailing_ls 보존.

한컴 PDF 측정 (페이지 4 본문 첫 줄 top):
  Hancom 97.73 px / Case 5 86.02 (-11.7) / **Case 5 v2 94.02 (-3.7)** ✓

전체 7 페이지 본문 첫 줄 정합도 (rhwp post-final vs Hancom):
  페이지 1: 194.7 vs 195.3 → -0.6 px
  페이지 2: 135.3 vs 131.5 → +3.8 px
  페이지 3: 132.1 vs 137.7 → -5.6 px
  페이지 4: 94.0  vs 97.7  → -3.7 px (이번 수정)
  페이지 5: 56.7  vs 57.7  → -1.0 px
  페이지 6: 118.9 vs 115.5 → +3.4 px
  페이지 7: 56.7  vs 57.7  → -1.0 px
모든 페이지 ±6 px 내 정합.

전체 테스트 1246 통과.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lo 분기 + cd 간격 ≤1mm 확장

페이지 4 "개체 모양 복사" → `<스타일에서>` → "스타일 적용" 줄간격이
너무 좁다 (사용자 피드백, baseline-to-baseline 15.22 px) — 정상 body
줄간격 20 px 대비 5px 부족.

원인:
1. **Case 5 v3 (prev_zone_was_solo 분기)**: 이전 Case 5 의 trailing_ls
   subtract 는 모든 leaving zone 에 적용되어 본문 2단 zone (단 7,
   pi=144) 의 trailing_ls (6.67px) 도 차감 → body→`<...>` 전환에서
   solo_zone_pad +16 의 효과 일부 상쇄 → 좁아짐.
   해결: prev_zone_was_solo (현재 zone 이 solo 였을 때) 인 경우만 subtract
   적용. 본문 2단 zone leaving 에는 미적용.

2. **Case 5 v4 (solo_zero 인정 범위 ≤1mm 확장)**: shortcut.hwp 의 일부
   `<...>` 소제목 zone (pi=148 `<스타일에서>` 등) 은 ColumnDef cd=1
   spacing=1mm 으로 인코딩되어 strict `spacing == 0` 검사를 통과 못 함
   → solo_zone_pad +16 누락 → `<...>`→body 전환 좁아짐.
   해결: `new_zone_is_solo_zero` / `prev_zone_is_solo_zero` 의 spacing
   조건을 `< 4.0 px` (≈1mm) 까지 인정. typeset.rs::tac_band_extra 가
   `< 4.0 px` 까지 인정하는 것과 동일 시멘틱.

측정값 (페이지 4, baseline-to-baseline):
  개체 모양 복사 → `<스타일에서>`  : 15.22 → **37.89** (Hancom ≈40) ✓
  `<스타일에서>` → 스타일 적용     : 15.22 → **31.22** (Hancom ≈40, -8.92 px 잔차)

전체 7 페이지 본문 첫 줄 top 정합 변동 없음 (Case 5 + 가드만 추가).
전체 테스트 1246 통과.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
shortcut.hwp 의 우측 단축키 (Alt+P/Ctrl+P, Alt+Shift+C, Ctrl+W,H 등) 가
컬럼 우측 끝에 정렬되지 않고 ~27 px 좌측으로 밀려 렌더되는 결함.

원인 (2 가지 경로):
1. **cross-run pending mechanism** (paragraph_layout.rs:1075, 1514):
   `effective_pos = if tab_type == 1 && fill_type != 0 { effective_margin_left +
   available_width } else { tab_pos }`. auto_tab_right (fill_type=0) 케이스가
   `else` 로 가서 effective_margin_left (≈27 px) 가 누락. → fill_type 무관하게
   tab_type==1 이면 effective_margin_left 추가.

2. **intra-run tab handling** (text_measurement.rs):
   `\t` 가 run 시작에 오는 paragraph (개체 모양 복사\tAlt+Shift+C, CharShape
   boundary 가 `\t` 위치인 경우) 는 compute_char_positions / compute_total_width
   의 inline_tabs 분기를 탐. HWP5 의 ext[0] 는 Hancom 의 right-tab 결과 위치
   (= 우측 끝 - 한컴_seg_w) 이지만 한컴 폰트 metric 기준 — 우리 폰트의 seg_w
   가 다르면 좌측 이탈. auto_tab_right=true + 단일 tab 일 때 ext[0] 를 무시
   하고 col-relative right edge - our_seg_w 로 override.

조치:
- TextStyle 에 `text_start_offset` 필드 추가 (effective_margin_left 전달용).
- paragraph_layout.rs 의 cross-run 두 분기에서 tab_type==1 일 때
  effective_margin_left 변환 적용.
- text_measurement.rs 의 inline_tabs / has_custom_tabs 분기에서 auto_tab_right
  override 추가 — body_right_text_rel = text_start_offset + available_width -
  line_x_offset, total = body_right_text_rel - our_seg_w.

측정 (페이지 4 단축키 right edge, Hancom 기대 532.33 px):
  Alt+L  (pi=142): 472 → **524** ✓
  Alt+T  (pi=143): 472 → **524** ✓
  Alt+Shift+C (pi=144): 497 → **532** ✓
  Alt+Shift+V (pi=145, col 1, Hancom 1001): 974 → **1001** ✓
  F6 (pi=146): 993 → **1001** ✓
  Shift+Enter (pi=147): 1006 → **1006** (변동 없음, 이미 정합)

전체 테스트 1246 통과. shortcut.hwp 7 페이지 본문 첫 줄 top 정합 유지.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ignment 정합

직전 commit `db6332b2` 는 EmbeddedTextMeasurer (native) 만 정정. WASM 빌드
의 WasmTextMeasurer (rhwp-studio 가 사용) 는 동일 버그를 가짐:
1. inline_tabs LEFT (tab_type 0/1) 경로: Hancom 의 `ext[0]` 사전 계산 위치
   를 그대로 사용. 우리 폰트의 seg_w 와 차이로 좌측 이탈.
2. has_custom_tabs 경로: auto_tab_right (fill_type=0) 시 effective_margin_left
   (= text_start_offset) 변환 누락.

조치: EmbeddedTextMeasurer 와 동일 패턴 적용.
- `auto_tab_right` paragraph + 단일 tab → body_right_text_rel - our_seg_w override.
- `tab_type == 1` + (`fill_type != 0` || `auto_tab_right`) → text_start_offset
  + available_width - line_x_offset 사용.

`cargo check --target wasm32-unknown-unknown --lib` 정상.
native 테스트 1246 통과 (변경 없음 — WASM 코드는 cfg(target_arch="wasm32")).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ight-tab block_w 정합

shortcut.hwp p.4 pi=138 "상용구 등록\tF3→Alt+I" 의 단축키가 우측 정렬되어
col 0 우측 끝 (532.7 px) 을 넘어 ~38 px 만큼 더 우측으로 쏠려 렌더됨
(F 가 479.7 대신 516.7, I 가 col 0 우측 끝을 초과).

원인: composer 가 lang/script 경계로 run 을 쪼갠다 — "→" (U+2192,
lang 5/symbols) 가 "F3" (lang 1/Latin) 과 "Alt+I" (lang 1) 사이에 있어
run 2 "\tF3→Alt+I" 가 ["\tF3", "→", "Alt+I"] 3 sub-run 으로 분리됨.
text_measurement 의 `measure_segment_from` 은 현재 run 내부만 측정하므로
seg_w = width("F3") ≈ 16 px 로 과소 계산되어 우측 정렬 기준점이 38 px
우측으로 이동.

조치: 후속 run 합산을 paragraph_layout 이 미리 계산해 주입.
- `TextStyle.right_tab_block_width_override: Option<f64>` 신규 필드.
- paragraph_layout 의 est/render 두 pass 에서 "현재 run 의 last \t 가
  line 의 last tab + post-tab 콘텐츠가 후속 run 으로 이어짐" 케이스 감지 후
  post_tab_w (현재 run) + `right_tab_block_width` (후속 runs) 합산을 주입.
- text_measurement 의 estimate_text_width / compute_char_positions native+
  WASM 두 path 의 `override_to_right` 분기에서 주입값이 있으면 사용,
  없으면 종전 동작 유지.

검증:
- `cargo test --release --lib`: 1246 passed / 0 failed / 2 ignored
- `cargo check --target wasm32-unknown-unknown --lib`: 정상
- pi=138 F: 516.70 → 479.70 (-37 px); I 끝: ~572 → ~534 (col 0 우측 끝
  532.7 와 정합).
- pi=140 "상용구 내용 보기 Ctrl+F3" (lang split 없음, 동일 path) 회귀 없음.
shortcut.hwp 1 페이지 우측하단 자동번호 "1" 의 시각 상단이 본문 "지우기"
섹션 (col 1 의 Ctrl+BackSpace / Ctrl+Y / Alt+Y 행) 위로 광범위하게
올라와 한컴 PDF (`pdf/basic/shortcut-2022.pdf`) 와 정합 불일치.

조사:
- 바탕쪽 글상자 CharShape 3 base_size = 25400 (254 pt = 338.67 px).
- shape_attr: original_width=16135, current_width=43201, render_sx=2.677;
  original_height=13359, current_height=33571, render_sy=2.513. 즉
  도형이 원본 대비 2.5~2.7× 확대된 상태.
- 한컴 PDF 의 "1" 시각 높이 ≈ 115 px (font ≈ 95~150 pt) → 우리 렌더링
  보다 2.5~3× 작음. 한컴은 확대 도형 안 글꼴에 (original / current) 의
  역 비율을 적용해 절대 크기를 유지 (확대 전 design size 보존).

조치:
- `ResolvedStyleSet` 에 `Clone` 유도 (로컬 스타일 사본 생성용).
- `layout_textbox_content` 진입 시 shape_attr 의 max(sw, sh) > 1.5 일 때
  styles 사본을 만들고 모든 char_styles 의 font_size / letter_spacing 을
  1/max_ratio 로 축소. 본 글상자 호출 범위 내에서만 styles 가 교체되어
  본문/일반 도형 (sw/sh ≈ 1.0) 에는 영향 없음.

결과:
- "1" font-size: 338.67 px → 126.49 px (254 pt → ~95 pt).
- "1" 시각 상단이 본문 "지우기" 행보다 아래로 내려와 한컴 PDF 정합.
- `cargo test --release --lib`: 1246 passed / 0 failed / 2 ignored
- `cargo check --target wasm32-unknown-unknown --lib`: 정상.
…aph 셀 trailing ls 포함

aift.hwp p10 의 13×10 표 pi=123 마지막 행 ("4. 자연어 질의 기반 도면 검색
정확도") 이 한컴 PDF (`pdf/aift-2022.pdf`) 에서는 다음 페이지로 넘어가지만
rhwp 는 p10 마지막 행으로 잘못 분할.

조사:
- 표 cumulative_heights[6] = 555.24 px (rows 0~5 합계)
- avail_for_rows = 555.76 px
- 0.52 px 차이로 row 5 가 페이지에 fit → 한컴 분할 위치와 불일치.
- 한컴 PDF 는 row 4 (3.BOM 자동 작성) 까지만 p10 에 배치 (= rows 0~4 = 5
  행), row 5 (4.자연어 질의) 부터 다음 페이지로 분할.
- 셀 콘텐츠 높이 계산에서 다중 paragraph 셀 (예: cell[52] r=5,c=9 "공인
  시험기관을 통한 성능 검증 | 평가 데이터셋 구축 후 평가") 의 마지막
  paragraph 마지막 line 의 `line_spacing` 을 누락. 한컴은 이 trailing gap
  도 셀 콘텐츠 높이에 포함 (= 셀 하단 trailing gap 보존).
- cell[52] : 99.76 → 106.16 px (+6.4 px). row_heights[5] 동반 상승.
- cumulative_heights[6] : 555.24 → 561.64 px > avail 555.76 → row 5 다음
  페이지로 push.

조치:
- `height_measurer` 의 `is_cell_last_line` 가드를 `cell_para_count > 1`
  케이스에서만 우회. 단일 paragraph 셀은 종전 동작 유지 (회귀 방지).
- 두 calc 경로 (가로쓰기 일반 + 비-인라인 컨트롤 폴백) 모두 동일하게 적용.

검증:
- `cargo test --release --lib`: 1246 passed / 0 failed / 2 ignored
- `cargo check --target wasm32-unknown-unknown --lib`: 정상
- p10 PartialTable: rows=0..6 → rows=0..5 (5 rows on p10)
- p11 PartialTable: rows=6..13 → rows=5..13 (8 rows on p11)
- 한컴 PDF 시각 정합 (p10 마지막 행 = "3.BOM 자동 작성 정확도").
…e.common.height 모순 가드

aift.hwp p19~20 "기능 간 이벤트 연계 구성도 이미지" 표 pi=236 가 한컴 PDF
(`pdf/aift-2022.pdf`) 에서는 p19 하단에 들어맞지만 rhwp 는 p20 으로 push.

조사:
- table.common.height = 8464 HU (= 112.9 px), cell.height = 11753 HU
  (= 156.7 px). cell.height > table.common.height — 1×1 표에서 모순
  (단일 셀이 외곽 표 보다 클 수 없음).
- HWP 파일 inconsistency: 표 외곽 크기 8464 vs 셀 11753.
- p19 가용 잔여 = 118 px. 우리는 effective_height = 156.7 (cell.height)
  사용 → 118 < 156.7 → p20 push. 한컴 PDF 는 ~30 px 로 렌더 (content
  + pad 기반).
- 한컴은 cell.height > table.common.height 모순 케이스에서 cell.height
  를 무시하고 content+pad 로 행 높이 결정.

조치:
- `height_measurer::measure_table_impl` 1단계 (row_span==1 셀의 max
  height 추출) 에서 1×1 표 (row_count == 1 && col_count == 1 && cells
  == 1) + cell.height > table.common.height 일 때만 cell.height 적용을
  스킵. 2단계 (content+pad) 가 row_heights 를 채움.
- table_layout 의 rect 렌더링은 MeasuredTable.row_heights 를 경유하므로
  pagination 과 rendering 이 자연 일치.

결과:
- p19 마지막 행: "기능 간 이벤트 연계 구성도 이미지" 17.09 px 박스
  (한컴 PDF 시각 정합).
- 전체 페이지 수: 77 → 75 (한컴 PDF 74 와 -1 차이).
- `cargo test --release --lib`: 1246 passed / 0 failed / 2 ignored
- `cargo check --target wasm32-unknown-unknown --lib`: 정상

가드 범위는 1×1 단일 셀 표로 한정 — 다중 행/열 표는 종전 동작 유지
(rowspan/병합 셀 height 분배 회귀 방지).
planet6897 and others added 10 commits May 14, 2026 11:40
…t > table.common.height 모순 가드"

This reverts commit 1616a52.
…ght > 가용 시 페이지 경계 분할 허용

이전 edwardkim#5 fix (cell.height 클램프) 는 표 시각 크기를 17 px 로 축소 → 한컴 PDF
와 시각 정합 실패. 한컴은 1×1 표 (line_count == 1) 의 cell.height (156.7 px)
가 가용 공간 (118 px) 보다 클 때, 페이지 경계에서 셀을 잘라 다음 페이지로
연속 렌더 (intra-cell 분할 허용).

조사:
- typeset.rs `typeset_block_table` 의 첫 행 advance guard (line 2032~) 가
  `first_row_splittable = is_row_splittable(0)` 만 체크. 1 line 셀은
  is_row_splittable = false 라 가드가 advance_column_or_new_page 호출 →
  표 전체가 다음 페이지로 push.
- 한컴은 cell.height 빈 공간을 페이지 경계에서 잘라낼 수 있도록 force-split
  허용. line_count 와 무관.

조치:
- advance guard 에 `first_row_force_splittable` 조건 추가: `!first_block_protected
  && can_intra_split && remaining_on_page > 0`. force-split 케이스에서는
  가드가 advance 를 건너뛰고, 메인 split 루프의 force-split 분기 (line
  2153~) 가 셀을 가용 공간만큼 분할.

결과:
- aift.hwp p19 PartialTable: pi=236 rows=0..1 split_end=114.2 px → p19 하단에
  117.96 px 박스 (셀 상단 + 타이틀 텍스트 가시).
- 전체 페이지 수: 77 → 75 (한컴 PDF 74 와 -1 차이).
- `cargo test --release --lib`: 1246 passed / 0 failed / 2 ignored
- `cargo check --target wasm32-unknown-unknown --lib`: 정상.

잔존:
- p20 상단의 셀 빈 영역 연속 렌더 (한컴 PDF: 156.7 - 117.96 ≈ 39 px 박스)
  미발현. `remaining_content_for_row` 가 content_offset > content_height 시
  0 반환 → 루프 조기 종료. 추후 cell.height 기반 잔량 추적 필요 시 별도
  타스크.
…외 — 한컴 PDF 정합

aift.hwp p21 "협업 시스템 구성도 이미지" 표 pi=268 (1×1 placeholder, 156.7 px)
직후 pi=284 "코멘트 스레드 관리 (답글·멘션·해결 처리)" 가 9.6 px 부족으로 다음
페이지로 밀려 한컴 PDF 와 페이지 분할 불일치 (한컴 p21 끝에 위치).

조사:
- p21 hwp_used = 944.5 px (HWP 파일 vpos 기준), 우리 used = 959.9 px.
- 차이 +15.4 px 중 9.6 px = host 문단 line_spacing (= LINE_SEG.line_spacing).
- 비-TAC 표 의 `host_spacing.after = sa + outer_bottom + host_line_spacing`
  계산에서 host_line_spacing (9.6 px) 가 표 사이즈에 가산되어 다음 paragraph
  배치 공간 부족.
- 한컴은 표의 outer_margin_bottom 만 사용 (host paragraph line_spacing 은
  본문 라인 간 간격 의미. 표 외곽 spacing 아님).

조치:
- 비-TAC 1×1 placeholder 표 (col_count==1, paras 모두 컨트롤 없는 단일
  line_seg) 케이스만 host_line_spacing 제외. 일반 비-TAC 표 (다행/다열,
  내부 컨트롤 포함 셀) 는 종전 동작 유지 — 라인 분리 회귀 방지.

결과:
- p21 마지막 행: "코멘트 스레드 관리 (답글·멘션·해결 처리)" pi=284 (한컴 PDF
  시각 정합).
- 전체 페이지 수: 75 → **74** (한컴 PDF 와 일치).
- `cargo test --release --lib`: 1246 passed / 0 failed / 2 ignored
- `cargo check --target wasm32-unknown-unknown --lib`: 정상.
…=Para host vpos jump guard

aift.hwp p44 (한컴 PDF p38) 에서 pi=579/pi=581 비-TAC TopAndBottom 표가
서로 겹쳐 그려지는 결함 정합. 원인:

1. pi=579 의 host paragraph (text=" ", 1 line, TopAndBottom+vert=Para Table)
   first_vpos=33421 HU 가 비정상적으로 크다 (prev pi=578 vpos_end=5920 →
   forward jump 27501 HU ≈ 367 px). HWP 가 stale layout 의 vpos 를 인코딩한
   것으로 추정.
2. layout.rs:1830 의 vpos correction 이 이 값을 신뢰하여 pi=579 를 y=521
   로 jump (자연 flow 위치 y=154).
3. pi=580 (FullParagraph) 가 y=950 까지 밀려나고, pi=581 (TopAndBottom
   +vert=Para+v_offset=21.7 px) 의 anchored y = 971.68 px > body_bottom=726.6.
4. compute_table_y_position (table_layout.rs:1218) 의 clamp 가 pi=581 을
   y=726.6 로 끌어내려 pi=579 표 (y=549..912) 와 시각적으로 겹친다.

한컴 PDF 는 pi=579 를 자연 flow 위치(y~185)에 배치하므로 표가 모두
페이지에 fit 한다.

수정: layout.rs 의 vpos correction 단계에서 가드 추가.
- 현재 paragraph 가 TopAndBottom+vert=Para Table 을 anchor 하고
- forward jump (end_y - y_offset) > 100 px 면 stale vpos 로 간주, skip.

결과 (aift.hwp p44):
- pi=579 y: 521.2 → 154.5
- pi=579 표 y: 549.8 → 183.1 (한컴 PDF ~185 정합)
- pi=581 y: 722.1 → 598.3 (overlap 해소)
- pi=582/583/584 overflow: 38/66/161 px → 0/0/37.6 px

부차: tests/issue_554.rs 의 aift 페이지 카운트 expectation 77 → 74 갱신
(Task edwardkim#874 누적 정합 결과 한컴 PDF (pdf/aift-2022.pdf, 74p) 와 동일).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…onflict

# Conflicts:
#	src/renderer/typeset.rs
Stream/devel 머지 (e039450) 후 paragraph 위치/spacing 미세 변동으로
3개 svg_snapshot golden 이 불일치. UPDATE_GOLDEN=1 으로 현재 코드의
렌더링 결과로 갱신.

- tests/golden_svg/issue-267/ktx-toc-page.svg
- tests/golden_svg/issue-617/exam-kor-page5.svg
- tests/golden_svg/issue-677/bokhakwonseo-page1.svg

대부분 y 좌표 미세 shift (~7 px) — Task edwardkim#853 spacing_before 보정 등
누적 정합 결과. 시각적 의미 차이는 없음.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…페이지로 — page_avail 에 v_offset 차감

aift.hwp p44 (한컴 PDF p38) 의 pi=584 (TopAndBottom+vert=Para+v_offset=21.7px)
PartialTable 이 rows=0..2 (header + 행 1 partial split_end=41.6) 로 잘못
분할되어 행 1 ("o 사업화 형태:") 가 페이지 하단에 약 37.6 px 오버플로우.
한컴 PDF 는 헤더만 (rows=0..1) 분할 후 행 1 전체를 다음 페이지에 표시.

원인: typeset_block_table 의 page_avail 산출이 TopAndBottom+vert=Para
표의 v_offset 와 host_spacing.before 를 차감하지 않음 — layout 은 표를
cur_h + host_spacing.before + v_offset 위치에 배치하므로 split 결정 시
가용 공간이 과대 평가됨.

수정: 첫 fragment (!is_continuation) page_avail 에서 host_spacing.before
와 v_offset 차감. HwpUnit=u32 wrap (음수 vertical_offset) 처리를 위해
i32 캐스트 후 > 0 검사.

결과 (aift.hwp p44):
- pi=584 PartialTable: rows=0..2 (split_end=41.6) → rows=0..1 (no split)
- 행 1 "o 사업화 형태:" 가 page 45 에 정상 표시
- LAYOUT_OVERFLOW 37.6 px 해소

기존 회귀 차단:
- issue_713 (RowBreak 표 인트라-로우 분할 금지): v_offset u32 wrap 처리로 통과
- task554 aift 페이지 카운트 74p 유지

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…kim#290 회귀 차단 — inline LEFT tab 보호

db6332b (Task edwardkim#874 auto_tab_right) 의 override_to_right 가 inline
ext[2] high-byte 검사 없이 paragraph 의 auto_tab_right 만으로 강제 적용
되어, 명시적 LEFT inline tab (high-byte=1) 도 right-align 됨.

증상: exam_math.hwp p7 item 18 "18.\t수열..." 의 "수" 가 x=109.8 (정합)
→ x=290.8 (회귀, Task edwardkim#290 의 원래 BUG 상태) 로 이동.

원인: ext[2] high-byte 가 명시적 정렬 종류 (1=LEFT, 2=RIGHT, 3=CENTER,
4=DECIMAL) 를 인코딩. db6332b 의 override 가 high-byte=1 LEFT 도
ParaShape.auto_tab_right=true 면 right-align 강제. shortcut.hwp 의
ext[2] high-byte=2 RIGHT 와 동일 취급.

수정: text_measurement.rs 의 양쪽 inline_tabs 분기 (estimate_text_width
+ compute_char_positions) 에 inline_is_explicit_left 가드 추가 —
high-byte ∈ {1, 4} 이면 override 미적용.

검증:
- exam_math.hwp p7 item 18 "수" x=290.8 → 109.8 (Task edwardkim#290 정합)
- task290_exam_math_p7_item18_not_right_aligned ✓ pass
- shortcut.hwp p4 Alt+L right edge 524.28 (Hancom 기대 532) — Task edwardkim#874 유지
- 전체 테스트 통과

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Task edwardkim#874 edwardkim#10 (b6d91ff) 에서 추가한 `chars[i+1..].iter().any(|c| *c == '\t')`
2개소가 clippy 1.95.0 `manual_contains` lint 에 걸려 PR edwardkim#872 Build & Test 실패.

수정: `chars[i+1..].contains(&'\t')` 로 치환 (의미 동일, 더 효율적).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… 회귀 차단

b6d91ff 의 Task edwardkim#874 edwardkim#10 회귀 가드 (inline_is_explicit_left) 가
native EmbeddedTextMeasurer 의 2개소 (라인 265, 423) 에만 적용되어
WASM 경로 WasmTextMeasurer 2개소 (라인 740, 884) 는 가드 누락 상태.

증상: native (cargo build / SVG export / cargo test) 는 정합이나
rhwp-studio (WASM Canvas) 에서 exam_math.hwp p7 item 18 "18.\t수열..."
의 "수" 가 우측 끝 (~290) 으로 정렬 — Task edwardkim#290 의 원래 BUG 상태로 회귀.

원인: WasmTextMeasurer 의 override_to_right 가 inline_tab_type(ext)
(= ext[2] high-byte) 검사 없이 ParaShape.auto_tab_right 만으로 강제
적용. high-byte=1 LEFT / 4 DECIMAL 도 right-align 강제됨.

수정: WASM 두 경로 (estimate_text_width 라인 740, compute_char_positions
라인 884) 에 native 와 동일한 가드 추가:
  let inline_is_explicit_left = tab_type == 1 || tab_type == 4;
  override_to_right = ... && !inline_is_explicit_left;
(tab_type 은 이미 inline_tab_type 으로 high-byte 정규화된 값이므로
재추출 불필요. native 경로의 inline_type_hi 와 동일 의미.)

부수: clippy manual_contains 동시 정리 (`iter().any()` → `contains()`).

검증:
- cargo check --lib --target wasm32-unknown-unknown ✓
- cargo clippy --lib --target wasm32-unknown-unknown -- -D warnings:
  text_measurement.rs 0 errors (16 pre-existing errors 는 web_canvas.rs)
- task290_exam_math_p7_item18_not_right_aligned ✓ pass (native 회귀 없음)
- issue_595 (5), issue_301 (1) ✓ pass

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
edwardkim pushed a commit that referenced this pull request May 14, 2026
…closes #866)

PR #872 (@planet6897) GitHub diff 기반 cherry-pick.
mydocs 거버넌스 산출물 제외. supersedes PR #868.

#866 v2: 구분 칸 spacing 압축 + solo_zone_pad + zone별 점선 구분선
#866 v3: 헤더 띠 leaving 보정 + <...> paragraph spacing
#874: 헤더 띠 band 가산 제거 + auto_tab_right 정렬 + 스케일 도형 글꼴

검증:
- cargo test --release --lib: 1246 passed (회귀 0)
- SVG 7페이지 (한컴 2022 PDF 정합)
- 시각 판정 ★ 통과

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@edwardkim
Copy link
Copy Markdown
Owner

검토 + cherry-pick 머지 완료. 감사합니다.

처리 결과

재검증

검증 결과
cargo test --release --lib 1246 passed (회귀 0)
SVG shortcut.hwp 7페이지 (한컴 2022 PDF 정합)
시각 판정 ★ 통과

shortcut.hwp 정합성을 다각도로 정밀하게 맞춘 대규모 작업입니다. 수고하셨습니다.

@edwardkim edwardkim closed this May 14, 2026
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