Skip to content

fix(typeset): 글 host 의 양수 오프셋 자리차지 표를 밀린 저장 줄이 아니라 문단 원점에서 잰다 (#8156) - #8159

Closed
planet6897 wants to merge 3 commits into
edwardkim:develfrom
planet6897:fix/8156-offset-float-from-para-origin
Closed

planet6897 wants to merge 3 commits into
edwardkim:develfrom
planet6897:fix/8156-offset-float-from-para-origin

Conversation

@planet6897

@planet6897 planet6897 commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

요약

글이 있는 host 문단에 문단 기준 자리차지 표가 둘(T3 오프셋 0, T4 양수 오프셋) 있는 HWP 저장본에서 T4 가 정본보다 약 88px 아래에 그려지던 문제를 고친다. T4 의 세로 오프셋을 문단 원점이 아니라 T3 아래로 밀려 저장된 host 첫 줄에서 쟀기 때문이다.

원인 — 생산 → 소비 경로

  1. 같은 문단의 오프셋 0 표 T3 는 흐름을 소비한다. 한/글은 host 첫 줄을 T3 띠 아래에 저장한다(K4: 문단 원점 3200HU, host 줄 vpos 9766HU = 3200 + T3 띠 6566HU).
  2. typeset/table/block/entry.rs 의 통째 배치는 저장 사다리가 인증한 그 첫 줄 원점(source_text_origin)을 글줄 원점이자 표 앵커로 from_stored_host / from_stored_head_host 에 넘겼다. 이 합성은 제어문자가 글 앞이라 from_stored_head_host 갈래를 탄다.
  3. 그 결과 T4 의 table_top 이 T3 띠(87.5px)만큼 내려가 확정 배치로 기록되고(record_paragraph_float_placement), layout 이 그 값을 그대로 그렸다.
  4. T3 는 문단 원점(placement_para_start_height)에 놓인다. 같은 문단의 두 공동 앵커 표가 서로 다른 원점에서 재였다.

수정

같은 문단에 앞선 공동 앵커 자리차지 표가 있으면(has_preceding_coanchored_float) 두 생성자에 문단 원점을 넘긴다.

  • 글줄은 종전대로 저장 원점(stored_host_origin)을 쓴다. 글줄 위치는 바뀌지 않는다.
  • T4 의 자연 위치가 앞 표의 띠와 겹칠 때 아래로 쌓는 처리는 기존 HostPlacementConstraint 가 그대로 맡는다(K1·K3).
  • 앞 표가 없는 host(단일 양수 오프셋 표)는 이 분기를 타지 않는다.

검증

입력은 #7687 합성 K1~K4(samples/issue7687/gen_titled_host_after_band.py)를 한/글 2020 으로 HWP 저장한 samples/issue8156/titled-host-hancom2020-saved.hwp(SHA-256 0fe65f11…fd776)다. 정본은 같은 HWP 의 한/글 2020 Print pdf/issue8156/titled-host-hancom2020-saved-2020.pdf(4쪽, 5ce45d9d…05df8)다.

직접 좌표 (96dpi px)

위치 한/글 정본 #8157 만 이 PR
K1 T4 윗변 492.4 700.5 492.9
K2 T4 윗변 612.3 700.5 612.9
K3 T4 윗변 471.2 679.1 471.6
K4 T4 윗변 298.4 386.3 298.7
K4 host 「K4 제목」 262.6 262.5 262.5 (불변)
K4 T3 윗변 178.5 178.7 178.7 (불변)

PDF 텍스트 상자 대조: 네 쪽 모든 표 셀 글자·본문 줄이 정본과 0.5px 안에서 맞는다.

Visual Sweep — 전쪽 TSV (tolerant_content_match_percent, raw 2px 실루엣)

전체 쪽수 rhwp 4 = 정본 4.

출력 1쪽 2쪽 3쪽 4쪽
Native 수정 전(#8157 만) 73.37 71.81 68.73 53.07
Native 이 PR 100.00 100.00 100.00 100.00
fresh WASM 이 PR 100.00 100.00 100.00 100.00

대표 이미지 (head 19626cce2)

K4(4쪽, 앞 빈 host 없음) — 수정 전 / 수정 후 review (Native)

K4 review Native 수정 전

K4 review Native 이 PR

K4 overlay (이 PR, Native / fresh WASM)

K4 overlay Native

K4 overlay WASM

K1(1쪽, T4 가 앞 빈 host 의 T2 띠 아래에 쌓이는 경우) — 수정 전 / 수정 후 review (Native), overlay (이 PR, Native / fresh WASM)

K1 review Native 수정 전

K1 review Native 이 PR

K1 overlay Native

K1 overlay WASM

직접 판독: K4 의 T4 는 「K4 제목」 줄 아래·「K4 글 1」 위에, K1 의 T4 는 T2 바로 아래에 놓여 정본 괘선과 겹친다. 글줄·T3 는 전후 같다. 남은 차이는 글리프 굵기뿐이다.

회귀 검사

tests/cases/issue_8156_saved_host_offset_float_anchor.rs::saved_titled_host_measures_offset_float_from_paragraph_origin 를 추가했다. 기대값은 원본 속성(오프셋 9000HU, 바깥 여백 283HU)과 저장 줄에서 정한 관계이며, 정본 PDF 와 0.6px 안에서 맞는다.

  • K2·K4: T4 − T3 = 오프셋, host 첫 글줄 = T3 아랫변 + 아래 바깥 여백(저장 줄 보존), host 글줄은 T4 위, T4 뒤 첫 글줄 = T4 아랫변 + 아래 바깥 여백
  • K1·K3: T4 윗변 = T2 아랫변 + 아래·위 바깥 여백, host 글줄 = T4 아랫변 + 아래 바깥 여백
  • 쪽수 4
결과
수정 전 (#8157 만) FAIL K2 T4 윗변 − T3 윗변: 실제 207.6, 기대 120.0
수정 후 PASS
#7687 검사 2건 전후 PASS

영향 범위

samples 전수 1,318 문서와 별도 10k 코퍼스 9,991 문서의 전쪽 SVG 를 수정 전(#8157 만)·후로 해시 비교했다. 바뀐 문서 0, 오류 0. 이 형상(글 host + 앞 공동 앵커 표 + 양수 오프셋 표 + 저장 줄)은 새 HWP 표본에서만 나타난다. 저장 줄 없는 issue7687/titled-host.hwpx 와 한글문서파일형식은 바이트 단위로 같다.

게이트 (head 5b5e42912, base f7abae4a8)

남은 것 / 비범위

🤖 Generated with Claude Code

최신 devel rebase (2026-10-23, #8173 절차)

  • 기준 upstream/devel def947c21 로 충돌 없이 rebase(fix(typeset): 빈 공동 앵커 host 자리차지 표의 띠·흐름에 바깥 여백을 거듭 더하지 않는다 (#7687) #8157 의 코드 커밋을 그대로 아래에 둔 적층 유지). head c5d49433d (이전 19626cce2). 위 SHA 고정 URL 은 새 head 로 갱신했다(같은 파일).
  • 로컬 검증(head c5d49433d, 빌드 전 소스 touch): fmt · Clippy 3종 · workspace build · manifest --check · unit tier 통과, nextest 11,478건 중 11,477 통과(실패 1건 issue_6520_svg_layer_contract::subset_font_bytes_are_deterministic_for_multiple_codepoints 는 이 host 의 시스템 한컴 글꼴 탓으로 base 에서도 같은 assertion 실패, 무관).
  • Visual Sweep(2px 실루엣 raw, --compat 2022, base = devel def947c21 + 무관 커밋, 바이너리·WASM sha256 확인):
입력 쪽수 rhwp/한/글 base Native / WASM 최저 head Native / WASM 최저 0.1%p 넘게 내린 쪽
samples/issue8156/titled-host-hancom2020-saved.hwp 4/4 53.07 / 52.33 (1–4쪽 미달) 100.00 / 100.00 없음
samples/issue7687/titled-host.hwpx 4/4 76.86 / 72.87 (1–3쪽 미달) 95.09 / 94.84 없음

@edwardkim edwardkim added bug Something isn't working layout 문서 레이아웃/페이지네이션: 표·그림 배치, float/anchor, 줄·행 높이, 겹침·클리핑 rendering SVG/Canvas/PDF 등 렌더 출력과 시각 표시: 그리기, 표시 누락, bbox, paint 정합성 table table rendering and edit labels Oct 22, 2026 — with ChatGPT Codex Connector
@edwardkim edwardkim added this to the v1.0.0 milestone Oct 22, 2026 — with ChatGPT Codex Connector
…#7687)

빈 host 에 자리차지 표가 둘(오프셋 0 / 양수)인 문단은 edwardkim#7687 이후 글 있는 host 와 같은
visible float 배치(place_table_with_text)를 탄다. 그 배치는 표 원점에 바깥 위 여백을,
흐름 끝·배제 띠 끝에 바깥 아래 여백을 스스로 더한다. 그런데 통째 배치 호출부는 이 host 에
흐름용 높이(표 + host 앞 간격)와 RowBreak 흐름 꼬리(아래 여백)를 넘겨, 오프셋 0 표의 흐름
끝과 양수 오프셋 표의 배제 띠 끝이 바깥 여백만큼 더 내려갔다.

한글문서파일형식 20쪽 pi=130: 둘째 표 띠 끝 379.72px — 저장 사다리(pi=132 vpos 28197)와
배치는 375.96px. 뒤 빈 문단이 그 띠로 뛰어 pi=133 「3.2.12. 참고문헌」 host 원점이 3.76px
내려갔고, 통합 뒤 배치가 typeset 확정 원점을 쓰면서 그 host 의 둘째 표(7×2)·제목·뒤 본문이
정본보다 +4.3px 아래에 그려졌다. 첫 표(오프셋 0)는 배치 흐름이 놓아 제자리였다.

글 있는 host 처럼 표 상자 높이(effective_height)만 넘기고 흐름 꼬리를 붙이지 않는다.

- 한글문서파일형식 20쪽: 둘째 표 686.0 → 682.3(정본 681.7), 「3.2.12.」 853.9 → 850.2(849.5)
- 합성 K1~K4(gen_titled_host_after_band.py, 한/글 2020 Print): K1~K3 T4·뒤 글줄 +3.8px → 정본 ±0.5px,
  K4(앞 빈 host 없음) 불변
- 회귀: titled_host_after_empty_host_band_keeps_its_float_offsets — 수정 전 FAIL(K1 T4 496.7, 기대 492.9)
…kim#8156)

같은 문단의 앞 자리차지 표(오프셋 0)가 흐름을 소비하면 한/글은 host 첫 줄을 그 띠
아래에 저장한다. 통째 배치(entry.rs)는 저장 사다리가 인증한 그 첫 줄 원점
(source_text_origin)을 글줄 원점이자 표 앵커로 함께 넘겨, 뒤 양수 오프셋 표가 앞 표의
띠(바깥 여백 포함)만큼 아래로 내려갔다. 오프셋 0 표는 문단 원점
(placement_para_start_height)에 놓이므로 같은 문단의 두 표가 서로 다른 원점에서 쟀다.

앞에 공동 앵커 자리차지 표가 있으면 from_stored_host / from_stored_head_host 에 문단
원점을 넘긴다. 글줄은 종전대로 저장 원점(stored_host_origin)을 쓴다. 앞 표 띠와 겹치는
경우의 쌓기는 HostPlacementConstraint 가 그대로 맡는다.

- 합성 K1~K4 HWP 저장본(한/글 2020 Print): T4 윗변 700.5/700.5/679.1/386.3
  → 492.9/612.9/471.6/298.7 (정본 492.4/612.3/471.2/298.4). 글줄·T3 불변
- samples 1,318 · 10k 코퍼스 9,991 문서 SVG A/B: 변화 0
- 회귀: saved_titled_host_measures_offset_float_from_paragraph_origin
  — 수정 전 FAIL(K2 T4−T3 207.6, 기대 120.0)
@planet6897
planet6897 force-pushed the fix/8156-offset-float-from-para-origin branch from 19626cc to c5d4943 Compare October 23, 2026 12:55
@jangster77
jangster77 self-requested a review October 24, 2026 07:24
jangster77 added a commit that referenced this pull request Oct 25, 2026
기존 planet6897 PR #8111·#8141·#8152·#8157·#8159·#8163·#8170·#8176·#8185·#8193의 기능 commit 49개와 통합 보정을 반영합니다. 중복 종료된 #8130의 임시 2개 적용은 제거했으며 기능 적용 수에서 제외합니다.

메인터너 보정은 현재 셀 글줄·물리 프레임·선언 행 높이와 선택 span 정렬의 일관성, 공백·출력 경계 및 요청한 회귀 제외를 처리합니다. sample16 계열과 행정업무운영 편람 HWP/HWPX는 사용자 지시로 검토에서 제외했습니다.

검증 code head 673c1d5f7cdcb5573b793ba2d2abaaedd2602134의 전체 nextest는 11,481 PASS/0 FAIL/45 skip이며 필수 로컬 게이트 14개를 통과했습니다. Native/fresh WASM 각35조건·2,041쪽의 최저 raw 91.87422%/91.82753%,90% 미달·고정 devel 대비0.1%p 이상 하락은 없습니다. 실제 Native15/Canvas2D15/지원CanvasKit12와 초기화 JS/WASM 응답을 확인했습니다. Mac WASM은 no-opt 로컬 대체 빌드입니다.

CI 보정은 편람 Gym 과제8건 제거에 따른 원장 기대 건수와 삭제된 case를 참조한 --print 대상 한 줄입니다. 제품 Rust·Studio source는 로컬 검증 head와 같습니다. 최종 PR head b1e93d8, 테스트 merge 41a30c3eeb6e50464b6806beeaa20dbaa23a77a8의 Full CI·CodeQL·Render Diff·Proptest·Adapter·Gym 검사가 모두 통과했습니다. Rust4 shard 합계11,288 PASS/0 FAIL/45 skip입니다.

원 PR별 검토 기록, 대응하는 review/overlay PNG·전쪽 TSV와 오늘할일을 포함합니다. 머지 후 사용자 지정 옵션2로 실제 CI·merge·후속 처리 기록을 보완합니다. 대표 #7445 전체 해결을 주장하지 않습니다.

Refs #8099, #8105, #7687, #8156, #7874, #8169, #8167, #8172, #4599, #7445, #7765, #7881

Co-authored-by: Jaeuk Ryu <35355437+planet6897@users.noreply.github.com>
@jangster77

Copy link
Copy Markdown
Collaborator

기여해 주셔서 감사합니다. 이 PR의 기능을 검토·체리픽 통합하여 PR #8228로 devel에 반영했습니다.

  • 확인한 원 contributor head: c5d49433d00bb46f2b88f30de257637c0b6d8c40. 원 source branch의 후속 변경 없이 같은 head임을 다시 확인했습니다.
  • 통합 merge: 3e02fe2749866a8453af3eb3901b9c1a674cccaf; 개별 PR 검토 기록은 문서 PR #8231에 확정했습니다.
  • 최종 head b1e93d8de2d4842f725415aee19bd5b98ecfb8b3의 Full CI·CodeQL·Render Diff·Proptest·Adapter·Gym 계약은 모두 성공했습니다.
  • 로컬 runtime source 673c1d5f7cdcb5573b793ba2d2abaaedd2602134에서 전체 nextest11,481 PASS/0 FAIL/45 skip, 별도 CI A/B/C/D 합계11,288 PASS/0 FAIL/45 skip입니다. 로컬과 CI 실행 수를 구분합니다.
  • 공통 전쪽 검증은 Native/fresh WASM 각각35조건·2,041쪽, 최저91.87422%/91.82753%, 90% 미만·누락·동일 조건 base 대비0.1%p 이상 하락0쪽입니다. 해당 원 PR에 대응하는 입력과 직접 판독은 아래 및 개별 기록으로 구분했습니다.

원 기여에서는 양수 오프셋 자리차지 표의 원점을 밀린 저장 줄이 아닌 문단 소유 정보로 결정합니다.

원 기여와 통합 보정

#8157과 같은 4d06f563a6ba38c3d77cd8cbb81c02b9913f4c19는 중복 제외하고 고유 2개 기능 commit을 적용했습니다.

원 기여 기능과 메인터너의 통합 보정 범위는 위 개별 검토 문서의 적용 commit·소비 경로·실제 회귀 결과에 구분했습니다. 다른 PR의 문서나 제외한 sample16·편람을 이 PR의 개선 증거로 대신하지 않았습니다.

해당 원 PR의 전쪽 비교

조건 Native 쪽수·최저 / TSV fresh WASM 쪽수·최저 / TSV
saved-host 4쪽 / 100.00000% 4쪽 / 100.00000%

전체 페이지 90% 미만·누락 0쪽입니다. 같은 조건의 고정 base 대비 0.1%p 이상 하락은 0쪽이며, 쪽수 개선은 본문 소유·순서 보존을 따로 확인했습니다. 실제 입력·PDF SHA-256과 증적 바이트.

이 원 PR에 대응하는 정식 회귀 1건의 실제 PASS는 개별 검토 기록에 기재했습니다.

직접 확인한 증적

직접 판독: K1에서 앞 빈 host의 띠와 T4를, K4에서 앞 빈 host가 없는 T3/T4 상대 오프셋과 host/뒤 글줄을 확인했습니다.

#8159 saved-host p4 Native review
#8159 saved-host p4 Native overlay
#8159 saved-host p4 fresh WASM review
#8159 saved-host p4 fresh WASM overlay

Visual Sweep 비교·병합 판정 방법과 공통 producer·해시·잔여 원장을 연결합니다. 일치율은 내용 픽셀 중심 자동 비교 보조값이며 사람 판독의 정확도나 완전한 픽셀 동일성을 뜻하지 않습니다.

이 원 PR은 별도 직접 merge가 아니라 통합 PR에 반영했으므로 종료하겠습니다. contributor의 원격 브랜치는 유지합니다. 관련 이슈 전체의 완료 여부는 개별 증거 범위로 별도 판단하며 이 PR 종료만으로 일괄 종료하지 않습니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working layout 문서 레이아웃/페이지네이션: 표·그림 배치, float/anchor, 줄·행 높이, 겹침·클리핑 rendering SVG/Canvas/PDF 등 렌더 출력과 시각 표시: 그리기, 표시 누락, bbox, paint 정합성 table table rendering and edit

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants