Skip to content

Task #146: text-align.hwp SVG ↔ 한컴 PDF 렌더링 일치#256

Merged
edwardkim merged 12 commits into
edwardkim:develfrom
planet6897:devel
Apr 23, 2026
Merged

Task #146: text-align.hwp SVG ↔ 한컴 PDF 렌더링 일치#256
edwardkim merged 12 commits into
edwardkim:develfrom
planet6897:devel

Conversation

@planet6897

Copy link
Copy Markdown
Contributor

요약

text-align.hwp 1페이지 문서의 rhwp SVG 출력이 한컴 오피스 PDF 출력과 시각적으로 달라 이를 3가지 수정으로 일치시켰다. 원인 규명은 mutool draw -F stext 로 추출한 PDF 문자 좌표와 SVG 좌표를 1:1 비교해 확정.

수정 1: Geometric Shapes 블록 전각 처리

  • is_fullwidth_symbol 범위 리스트에 U+25A0-U+25FF (□■▲◆○ 등, 섹션 머리 기호) 추가
  • "□" 글자 폭이 반각으로 측정되어 후속 글자가 ≈em/2 좌측 붕괴하던 문제 해결
  • 결과: 제목 "국" 시작 x 94.40 → 105.40 (PDF 환산 105.39 와 0.01 px 일치)

수정 2: TAC 표 선행 텍스트 폭 반영

  • layout_table_itemcompute_tac_leading_width 헬퍼 추가
  • pagination 이 TAC 문단에 PageItem::Table 만 발행(FullParagraph 미발행)하는 경우 paragraph_layout 의 TAC 분기가 호출되지 않아 선행 공백이 표 x 좌표에 반영되지 않던 문제 해결
  • block 취급 TAC (너비 ≥ 90% seg_width) 도 커버하는 fallback 포함
  • 결과: 표 첫 셀 x 75.59 → 109.59 (PDF 환산 ≈112 와 2.4 px 이내)

수정 3: Heavy display face 를 visual bold 로 렌더

  • is_heavy_display_face 헬퍼 + TextStyle::is_visually_bold 메서드 추가
  • HY헤드라인M / HY견고딕 / HY견명조 / HY그래픽 등 face 자체가 굵은 display 폰트가 fallback 으로 regular 렌더되던 문제 해결
  • 결과: 제목이 PDF 와 시각적으로 근사한 굵기로 렌더

기각된 초기 가설 (좌표 정밀 비교로 확정)

  • Justify SVG 미반영 → 실제는 정상 동작 (0.12 pt 오차)
  • Hanging indent 어긋남 → 실제는 정상 동작 (0.04 pt 오차)
  • 자간×공백 상호작용 → 실제 원인 아님 (진짜 원인은 □ 글자 폭)

변경 요약

영역 파일 변경
소스 src/renderer/layout/text_measurement.rs 1줄 추가 (U+25A0-U+25FF)
소스 src/renderer/layout.rs tbl_inline_x 분기 + 헬퍼 함수
소스 src/renderer/style_resolver.rs is_heavy_display_face
소스 src/renderer/mod.rs TextStyle::is_visually_bold
소스 src/renderer/svg.rs 4곳 bold 분기 치환
테스트 src/renderer/layout/tests.rs 신규 6건
스냅샷 tests/golden_svg/form-002/page-0.svg □ 이후 +9px 갱신
샘플 samples/text-align.hwp 신규 편입
문서 mydocs/{plans,working,report,orders}/... 계획서 v2~v4 + 단계 보고서 6건 + 결과보고서 v2·v4 + 오늘할일

Test plan

  • cargo test --lib 933 passed (신규 6건 포함, 기존 실패 14건은 본 PR 무관)
  • cargo test --test svg_snapshot 3 passed (form-002 golden 갱신 반영)
  • cargo clippy --lib -- -D warnings clean
  • samples/text-align.hwp 150dpi 렌더 PDF 와 좌표 ±3 px 일치
  • 스모크 스위프: exam_kor.hwp (25페이지), biz_plan.hwp (6페이지), text-align.hwp — 회귀 없음
  • 리뷰어: samples/text-align.hwp 출력 이미지로 시각 확인
  • 릴리즈 태그 발행

관련 문서

  • 최종 결과보고서: mydocs/report/task_m100_146_report_v4.md
  • 계획서: mydocs/plans/task_m100_146_v{2,3,4}{_impl,}.md
  • 단계별 보고서: mydocs/working/task_m100_146_stage{1..6}.md

planet6897 and others added 12 commits April 23, 2026 11:23
- samples/text-align.hwp 신규 편입
- text-align.pdf ↔ SVG 150dpi 비교 파이프라인 명령 고정
- 버그 후보 3건(공백 advance 누락/Justify 미반영/hanging indent) 수치 베이스라인 확보
- 코드 변경 없음 (조사·문서 단계)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
is_fullwidth_symbol 범위 리스트에 U+25A0-U+25FF (□■▲◆○ 등
Geometric Shapes 블록) 추가. HWP 섹션 머리 기호로 빈번히 쓰이며,
PDF(한컴) 폰트 메트릭은 이를 전각으로 측정하나 rhwp 는 반각(em/2) 으로
측정해 후속 글자가 ≈em/2 만큼 좌측으로 붕괴하던 문제 해결.

- 수정: text_measurement.rs:845 is_fullwidth_symbol 에 한 줄 추가
- 테스트: tests.rs 에 Geometric Shapes 전각 검증 + "□ 가" 회귀 방지 2건

검증 (samples/text-align.hwp):
- 제목 "국" 시작 x: 94.40 → 105.40 (PDF 환산 105.39 와 0.01px 일치)
- cargo test --lib: 929 passed (신규 2건 포함)
- cargo clippy --lib -- -D warnings: clean

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 수행계획서 v2: 원본 ②③(Justify/Hanging indent) 은 PDF 좌표 비교 결과
  실제 버그 아님이 확인되어 범위 축소. 핵심은 ① 공백 advance 가 아닌
  □ 글자 폭 측정 오류 (is_fullwidth_symbol 범위 누락).
- 구현계획서 v2: 2단계 (소스 + 테스트, 통합 검증)
- 단계2 보고서: 수정 내역, 좌표 비교, 영향 정량화

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- form-002 page 0 golden 업데이트: "□ 개념" / "□ 개발내용" 섹션 머리에서
  □ 전각 인식으로 후속 글자 +9px 정상 advance (2 위치 × 3줄 = 6줄 변경)
- 최종 결과보고서: 원인 규명(가설 ② Justify / ③ hanging indent 기각 →
  실제 원인은 is_fullwidth_symbol 범위 누락), 수정 내역, 검증 결과
- orders/20260423 완료 상태로 갱신

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pagination 이 TAC 표 문단에 PageItem::Table 만 발행하고 FullParagraph
를 발행하지 않아 paragraph_layout 의 TAC 분기가 호출되지 않는 경우
(선행 공백만 있는 TAC 표, 또는 너비 ≥ 90% seg_width 로 block 분류된
TAC 표), layout_table_item 의 inline_shape_position 이 비어있어 표가
body_left(col_area.x) 에 붙는 문제 수정.

layout_table_item 의 tbl_inline_x 분기에 is_tac 케이스 추가:
composed.lines[0] 의 runs 에서 target TAC 이전 텍스트 폭을 직접 합산.
block 취급 TAC (tac_controls 빈 상태) 에서는 line 0 전체 합산 fallback.

- 수정: layout.rs tbl_inline_x 분기 + compute_tac_leading_width 헬퍼
- 테스트: tests.rs 에 block/inline 두 케이스 검증 2건
- 검증 (samples/text-align.hwp): 표 첫 셀 x 75.59 → 109.59 (PDF 환산 112 와 ±3px 수렴)
- cargo test --lib: 931 passed (신규 2건 포함)
- cargo test --test svg_snapshot: 3 passed (기존 golden 영향 없음)
- cargo clippy --lib -- -D warnings: clean

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- v3 수행·구현계획서: TAC 표 선행 공백 x 좌표 보정 스코프 (2단계)
- 단계4 보고서: 수정 내역, 원인 재확인(composer is_tac_table_inline
  90% 임계치로 block 분류된 TAC 표에서 tac_controls 비어있는 케이스),
  검증 결과

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
HY헤드라인M / HY견고딕 / HY견명조 / HY그래픽 등 face 이름 자체가
굵은 display 폰트들은 HWP CharShape.bold=false 로 저장되어도
PDF(한컴) 출력에서는 heavy face 자체 weight 로 굵게 렌더된다.
rhwp SVG 는 해당 face 가 설치되지 않은 환경에서 Malgun Gothic 등
regular fallback 으로 떨어지며 시각적 bold 가 소실.

- style_resolver.rs: is_heavy_display_face() 헬퍼 추가 (8개 face)
- mod.rs: TextStyle::is_visually_bold() 메서드 (bold || heavy_face)
- svg.rs: 4곳 font-weight="bold" 분기를 is_visually_bold() 로 치환
- tests.rs: heavy face 매칭 + 체인 기준 primary face 판정 2건 추가

검증 (samples/text-align.hwp):
- 제목 "□ 국어 변화..." 에 font-weight="bold" 속성 추가됨
- Chrome headless 150dpi 렌더에서 제목이 PDF 와 시각적으로 근사한 굵기
- cargo test --lib: 933 passed (신규 2건 포함)
- cargo test --test svg_snapshot: 3 passed (기존 golden 영향 없음)
- cargo clippy --lib -- -D warnings: clean

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- v4 수행·구현계획서: heavy display face 시각 bold 근사 스코프 (2단계)
- 단계5 보고서: 수정 내역, is_heavy_display_face 헬퍼, TextStyle::is_visually_bold,
  svg.rs 4곳 분기, 시각 검증 결과

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 최종 결과보고서 v4: v1~v4 전체 여정, 좌표·시각 수렴 표, 커밋 이력,
  잔여 범위, 종결 승인 요청
- 단계6 보고서: 테스트 재확인(933 passed, clippy clean, svg_snapshot 3
  passed), 스모크 스위프(exam_kor/biz_plan/text-align) 회귀 없음
- orders/20260423.md: v3/v4 단계 체크박스 + 머지 대기 상태

종결 항목:
- 수행계획서 v1~v4, 구현계획서 v1~v4
- 단계별 보고서 stage1~stage6 (6건)
- 신규 단위 테스트 6건, 신규 샘플 1건, svg_snapshot golden 1건 갱신

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…etric Shapes + TAC 표 선행 공백 + heavy face bold)
@edwardkim edwardkim merged commit 9efc250 into edwardkim:devel Apr 23, 2026
6 checks passed
@edwardkim

Copy link
Copy Markdown
Owner

@planet6897 감사합니다. 머지 완료했습니다.

이번 PR 에서 특히 인상적이었던 점:

진단 방법의 엄밀성. mutool draw -F stext 로 PDF 문자 좌표를 추출하여 SVG 좌표와 1:1 비교하신 접근은 그 자체가 이슈 #253 (한컴 PDF 기준 Visual Diff 하네스 구상) 의 실제 사례가 됩니다. 기여자님이 로컬에서 이미 돌리고 계신 이 파이프라인을 공식 인프라로 편입하는 논의가 @seanshin 님 PR #251 리뷰에서도 나왔고, 관련 논의가 계속될 예정입니다.

기각된 초기 가설을 기록해주신 것도 큰 자산이 됩니다. Justify 미반영 / Hanging indent 어긋남 가설을 0.04~0.12 pt 오차로 정상 동작임을 실측으로 확인하신 기록은 다른 기여자가 같은 가설을 다시 검증하지 않아도 되게 해줍니다.

수행계획서 v1~v4 점진 확장 + 단계별 6개 보고서 + v4 최종 보고서까지 하이퍼-워터폴 구조를 완전히 따라주셔서 리뷰가 수월했습니다.

검증 결과 (로컬 최신 devel + PR #256 머지 후):

  • cargo test --lib: 947 passed (신규 단위 테스트 6건 포함)
  • cargo clippy --lib -- -D warnings: 0 warning
  • cargo test --test svg_snapshot: 3 passed (form-002 golden 갱신 반영)

#146 close 수동 완료 (GitHub auto-close 가 이번엔 작동 안 했습니다).

다음 기여 때 참고용 소소한 팁: 이번처럼 기여자 fork 의 devel 브랜치에서 직접 작업하셔도 큰 문제는 없었습니다만, 가능하시면 feature/taskN 같은 feature 브랜치에서 PR 을 올려주시면 추후 여러 변경을 동시에 올릴 때 diff 가 더 깔끔해집니다. 이번에도 잘 처리되었으니 강제사항은 아닙니다.

다시 한 번 감사합니다.

seanshin pushed a commit to seanshin/rhwp that referenced this pull request Apr 24, 2026
- README: devel 섹션 추가 (edwardkim#157/edwardkim#267/edwardkim#256 PR 반영)
- plans/ → plans/archives/: task_m100_267{,_impl}.md 이동
seanshin pushed a commit to seanshin/rhwp that referenced this pull request Apr 26, 2026
- 최근 변경: v0.7.3 → v0.7.6 (2026-04-26) 교체
  - PR edwardkim#266 (Task edwardkim#157), edwardkim#273 (Task edwardkim#267), edwardkim#282 (Task edwardkim#279) by @seanshin
  - PR edwardkim#256, edwardkim#327, edwardkim#341, edwardkim#343 by @planet6897
  - PR edwardkim#334, edwardkim#335 by @oksure, PR edwardkim#339 by @postmelee
- devel 섹션: 머지된 항목 제거, 현재 분석 중(edwardkim#362/edwardkim#345) + 계획 중(edwardkim#150/edwardkim#253) 반영
- 테스트 수: 891+ → 1000+
- README_EN.md 동일 내용 영문 반영
edwardkim added a commit that referenced this pull request Apr 30, 2026
mydocs/feedback/:
  - manual_currency_audit.md (매뉴얼 현행화 감사 결정 요청 — 2026-04-23)
  - open_issues_priority.md (열린 이슈 37건 우선순위 — 2026-04-22)
  - pr165_merge_decisions.md (PR #165 Skia + Layered Renderer 머지 충돌 결정 — 2026-04-21)
  - self_censor_audit.md (외부 공개 문서 자기검열 감사 — 2026-04-22)

mydocs/pr/:
  - archives/pr_256_review.md + review_impl.md (PR #256 검토 + 구현 계획)
  - pr_273_review.md (PR #273 Task #267 right tab 처리 검토)

이전 사이클 작성 후 미커밋 보관 — devel 머지 + push 진행 시 함께 커밋.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jangster77 pushed a commit to jangster77/rhwp that referenced this pull request Apr 30, 2026
mydocs/feedback/:
  - manual_currency_audit.md (매뉴얼 현행화 감사 결정 요청 — 2026-04-23)
  - open_issues_priority.md (열린 이슈 37건 우선순위 — 2026-04-22)
  - pr165_merge_decisions.md (PR edwardkim#165 Skia + Layered Renderer 머지 충돌 결정 — 2026-04-21)
  - self_censor_audit.md (외부 공개 문서 자기검열 감사 — 2026-04-22)

mydocs/pr/:
  - archives/pr_256_review.md + review_impl.md (PR edwardkim#256 검토 + 구현 계획)
  - pr_273_review.md (PR edwardkim#273 Task edwardkim#267 right tab 처리 검토)

이전 사이클 작성 후 미커밋 보관 — devel 머지 + push 진행 시 함께 커밋.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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