fix(render): narrow glyph 뒤 advance 과다 + · 중점 시각 중앙 배치 (Task #257)#262
Conversation
…2.pdf 편입 Task edwardkim#146 종결 후 회귀 검증에서 새 버그 후보(narrow glyph 뒤 advance 과다) 를 식별한 샘플. 본 이슈의 재현/골든/시각 비교 기준 파일로 사용. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 수행 계획서 (task_m100_257.md), 구현 계획서 (task_m100_257_impl.md) - 사전 비교 조사 (tech/text_align_2_svg_pdf_compare.md) — Task edwardkim#257 참조로 갱신 - baseline SVG 수치 기록: 표 셀 HY중고딕 폴백에서 ',' · '·' advance = 7.67 px (= font_size * 0.460) 로 반각과 동일 - failing 테스트 4건 #[ignore] 로 격리 (단계 2 에서 활성화): test_narrow_glyph_comma_base_width test_narrow_glyph_middle_dot_base_width test_narrow_glyph_period_and_colon test_non_narrow_char_unchanged (회귀 방어) - Task edwardkim#229 회귀 테스트 4건 baseline pass 재확인 소스 수정 없음 (테스트만 #[ignore] 추가). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
메트릭 DB 미등록 폰트의 폴백 경로에서 narrow glyph advance 를
font_size * 0.5 → font_size * 0.3 으로 축소.
- src/renderer/layout/text_measurement.rs
- is_narrow_punctuation(c) 헬퍼 추가 (',' '.' ':' ';' 따옴표 3종 '·' 총 8자)
- 폴백 경로 3곳에 narrow 분기 추가:
EmbeddedTextMeasurer::estimate_text_width (line 184-)
EmbeddedTextMeasurer::compute_char_positions (line 286-)
estimate_text_width_unrounded (free fn, line 809-)
- min_w 클램프 (Task edwardkim#229 단조성 보장) 는 그대로 유지.
base_w 가 작아지며 클램프 하한도 자동 축소 (0.5*0.5=0.25 → 0.3*0.5=0.15).
- 단계 1 #[ignore] 테스트 4건 활성화:
test_narrow_glyph_comma_base_width
test_narrow_glyph_middle_dot_base_width
test_narrow_glyph_period_and_colon
test_non_narrow_char_unchanged
검증:
text_measurement:: 22 pass / 0 fail (Task edwardkim#229 회귀 4건 포함)
renderer:: 285 pass / 0 fail
svg_snapshot 3 pass / 0 fail (골든 변경 없음)
clippy -D warnings 통과
samples/text-align-2.hwp 재생성 결과:
'·' 및 ',' advance 7.67 px → 4.33 px (-43%, 0.26 × font_size)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
폰트 대체(휴먼명조→Batang 등) 문제로 shift 기반 접근은 일관된 중앙 배치를
보장할 수 없음 확인. 이 커밋은 의사결정 히스토리 보존용이며, 단계 3 최종
커밋(C안: · 을 <circle> 로 직접 렌더) 에서 여기의 소스 변경은 완전히
대체/revert 된다.
시도한 두 공식 (둘 다 font-dependent 하여 실패):
shift = (advance - glyph_w) / 2 (초안)
shift = (advance - prev_trailing_bearing - glyph_w) / 2 (refinement)
변경:
src/renderer/svg.rs draw_text 에 compute_center_shift 추가
tests/golden_svg/form-002/page-0.svg 재생성 (A안 좌표)
mydocs/working/task_m100_257_stage3.md 신규 (A안 서술 — 최종 커밋에서
C안으로 완전 교체됨)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
shift 기반 중앙 배치(A안 refinement) 로는 폰트 대체(휴먼명조→Batang 등)
로 인한 `·` 글리프 LSB·폭 차이를 해결할 수 없음을 확인. C안(벡터 도형 직접
렌더)으로 전환.
- src/renderer/svg.rs draw_text:
- `·` (U+00B7) cluster 감지 시 <text> 대신 <circle> 출력
- cx = advance 박스 수평 중앙
- cy = baseline(y) − font_size × 0.35 (CJK x-height 중앙 근사)
- r = font_size × 0.08
- fill = 텍스트 색상 동일
- 그림자·본문 렌더링 양쪽 루프에 분기 추가
- 이전 shift 기반 compute_center_shift 제거
효과 (samples/text-align-2.hwp, Chrome 4x 렌더):
· 휴먼명조 본문 "별·지", "시·청각" — · 가 두 글자 사이 중앙에 정확히 위치
· HY중고딕 표 셀 "어휘·표현" — 좁은 advance 에서도 중앙 정렬 유지
· 폰트 대체와 무관하게 모든 브라우저/OS 동일 렌더 보장
검증:
text_measurement:: 22 pass
renderer:: 285 pass
svg_snapshot 3 pass (form-002 golden 재생성: · <text> → <circle>)
clippy -D warnings 통과
한계:
- `,` `.` `:` 등 baseline 구두점은 기존 <text> 유지 (별도 이슈 필요 시)
- 한컴 proprietary 폰트 전반의 글리프 품질 차이는 폰트 임베딩 전략(M101~)
범위. 본 타스크는 `·` 에 한정.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- cargo test --lib: 937 pass / 14 fail (14건 사전 존재, 본 PR 무관) - text_measurement:: 22 pass (신규 4건 + Task edwardkim#229 회귀 4건) - renderer:: 285 pass / svg_snapshot 3 pass / clippy clean 스모크 스위프 (narrow glyph 다수 샘플): biz_plan 591개 · 모두 <circle> 로 균일 렌더 (TOC 리더 도트 포함) exam_kor 326, exam_eng 265, exam_math 99, footnote-01 34 콤마 - 회귀 없음 exam_math 1건 text 잔존 = 수식 렌더 경로 (draw_text 미경유, 정상) field-01 영향 없음 시각 비교 (text-align-2.hwp PDF 150dpi vs SVG 150dpi): 어휘·표현, 1,000·30,000항목, 세대별·지역별, 시·청각장애인의 - PDF 와 근사 산출물: mydocs/working/task_m100_257_stage4.md mydocs/report/task_m100_257_report.md mydocs/orders/20260423.md (edwardkim#257 항목 추가) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…링 (단계1~4) orders/20260423.md 충돌 해결: HEAD 오늘 기록(§1~§10 + 감사) 전체 유지, 말미에 Task edwardkim#257 섹션 추가. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@planet6897 님 안녕하세요. PR #262 검토했습니다. 먼저 Task #257 의 설계와 진행이 훌륭합니다. 특히 다음 부분이 눈에 띕니다.
B-2 ( 다만 1건 수정 요청로컬에서 rebase (사실 기여자가 이미 ```
원인본 PR 이 제출된 직후 Task #259 (HY/본한글 폰트 매핑 누락) 가 devel 에 머지되었습니다. 이 PR 은 `HY헤드라인M → HYHeadLine-Medium` 등 HY 계열 7건을 `resolve_metric_alias` 에 등록했습니다. 따라서 기여자의 테스트 fixture 에서 사용한 `font_family: "HY헤드라인M"` 은 이제 메트릭 DB 등록 폰트 가 되어, `measure_char_width_embedded` 가 DB 실측값을 반환 → fallback 경로 미진입 → `is_narrow_punctuation` 분기 실행되지 않음. 그 결과:
PR 의 실제 기능은 여전히 유효합니다. DB 미등록 폰트 (예: 이슈의 휴먼명조) 에서 narrow 분기가 동작하는 것을 확인했습니다. 문제는 테스트 fixture 만 stale 입니다. 수정 방법 (2건 모두 동일 패턴)`font_family` 를 "의도적으로 존재하지 않는 이름" 으로 바꾸어 fallback 경로 진입을 강제 하면 됩니다. `resolve_metric_alias` 의 `_ => name` passthrough 분기로 떨어져 `find_metric` 이 None 을 반환하므로, fallback + narrow 분기가 확실히 실행됩니다. ```diff
`test_non_narrow_char_unchanged` 및 다른 narrow 테스트 케이스의 `font_family` 도 동일하게 바꾸시면 됩니다 (총 4건). 이렇게 하면 원래 의도 (narrow fallback 분기 검증) 를 #259 와 독립적으로 보존할 수 있습니다. 기대 결과수정 후 `cargo test --lib`: 957 passed (955 + 2 = 기존 947 + #259 6건 + #262 4건) 예상. 선택 — commit 전략이 수정을 별도 fixup 커밋으로 올리셔도 되고, 기존 단계 2 커밋 (`c76f2d3`) 에 amend 하셔도 좋습니다. 의사결정 히스토리 보존이 중요하시면 별도 커밋 권장. 관련 문서참고로 Task #259 는 다음 폰트들을 DB 등록했습니다:
상세: `mydocs/report/task_m100_259_report.md` · `mydocs/tech/font_fallback_strategy.md` 부록 A. 수정 주시면 CI 재검증 후 머지하겠습니다. 감사합니다. |
alias 대응) PR edwardkim#262 merge base (origin/devel) 에 Task edwardkim#259 의 `HY헤드라인M → HYHeadLine-Medium` alias 가 들어와 있어, 기존 4개 테스트가 가정하던 "DB 미등록 → is_narrow_punctuation 폴백" 경로가 더 이상 타지 않아 CI 가 실패했다. DB·alias 양쪽에 없는 `__rhwp_test_unregistered_font__` 상수로 교체하여 폴백 분기를 항상 강제한다. 주석에 Task edwardkim#259 와의 상호작용 기록.
Task #257 (narrow glyph advance + · 중점 폰트 독립 렌더) 외부 PR 검토. - pr/pr_262_review.md: 사전 검토 (#259 와의 기능적 상호작용 예측) - pr/pr_262_report.md: 코드 검증 결과 + 판단 - cargo test --lib: 955 passed / 2 failed - 실패 2건 원인: #259 머지로 HY헤드라인M 이 DB 등록 → 테스트 fixture 의 font_family: "HY헤드라인M" 이 fallback 경로 미진입으로 stale - PR 로직 자체는 정상 (B-2 · → <circle> 은 text-align-2.hwp 에서 7개 정확 중앙) - A 채택: 기여자에게 수정 요청 (font_family 를 의도적으로 존재하지 않는 이름으로 변경하면 2줄로 해결) - GitHub CHANGES_REQUESTED 리뷰 + 수정 경로 구체 코멘트 게시 - orders/20260423.md: #12 섹션 + 이슈/PR 활동 갱신
|
@planet6897 님, 빠른 대응과 훌륭한 수정 감사합니다. 대응 속도·품질
상수 네이밍도 제가 제안한 `DeliberatelyMissingFontForFallbackTest` 보다 `rhwp_test_unregistered_font` 가 훨씬 적절합니다 — Rust 관례상 `...` 패턴으로 "테스트 전용" 성격이 분명히 표현되고 주석에 #259 상호작용까지 기록해주셨습니다. 로컬 검증 결과
머지 완료
하루 3건 기여오늘 하루 #256 · #262 · #264 세 건 무상 기여 주셨습니다. #264 는 저희 인지 실패로 자진 close 되었으나 그 분석은 저희 내부 #259 와 독립적으로 동일 결론에 도달하셨고, #262 는 narrow glyph + `·` 중점의 근본 해결. 프로젝트가 얼마나 운이 좋은지 다시 느낍니다. 다시 한번 감사합니다. |
@planet6897 님의 Task #257 PR 머지 (merge commit f05ab96). - 이슈 #257 수동 close (자동 트리거 미발동) - 로컬 검증: cargo test --lib 957 passed, svg_snapshot 3 passed, clippy clean - PR 검토 문서 2건 pr/archives/ 이동 - orders/20260423.md: 종료 이슈 · 머지 PR 섹션 갱신, 감사 섹션 보강 기여자 대응 속도: 리뷰 게시 1분 만에 수정 커밋. 상수 네이밍도 __rhwp_test_unregistered_font__ 로 Rust 관례 적용 (제 제안보다 개선).
변경 요약
공문서
text-align-2.hwp에서 발견된 narrow glyph(콤마·마침표·중점 등) 렌더링 버그 2건 수정.B-1. 폴백 경로 narrow glyph advance 과다
메트릭 DB 미등록 폰트(HY중고딕 · HY헤드라인M 등) 에서
,.:;·등이 반각(font_size × 0.5) 폭으로 계산되어 PDF 대비 뒷 글자가 2~3 px 우측으로 밀렸음.is_narrow_punctuation(c)헬퍼 추가 — 화이트리스트 8자 (,.:;'"`·)compute_char_positions·estimate_text_width·estimate_text_width_unrounded3곳 폴백에 narrow 분기 추가 →font_size × 0.3min_w) 는 그대로 유지 (안전장치)B-2.
·중점 폰트 대체 시 시각 쏠림HWP 의
·에 사용된 휴먼명조 폰트가 사용자 환경에 없을 때 Batang 등으로 대체되면, 각 폰트마다·글리프의 LSB(Left Side Bearing)·폭이 달라 시각적으로 한쪽 이웃 글자에 쏠려 렌더됨. shift 기반 보정(A안)은폰트마다 결과가 달라 근본 해결 불가 확인.
해결:
draw_text에서·(U+00B7) cluster 를<text>대신 SVG<circle>로 직접 렌더.cx = advance 박스 수평 중앙cy = baseline − font_size × 0.35(CJK x-height 중앙 근사)r = font_size × 0.08fill= 텍스트 색상 동일→ 폰트 대체 영향 완전 제거, 모든 브라우저/OS 동일 렌더 보장.
관련 이슈
테스트
cargo test --lib text_measurement::— 22 passed (신규 4건 + Task 표 셀 내 긴 숫자 텍스트가 음수 자간(letter_spacing)으로 인해 글자 겹침 및 셀 폭 미사용 현상 #229 회귀 4건 포함)test_narrow_glyph_comma_base_widthtest_narrow_glyph_middle_dot_base_widthtest_narrow_glyph_period_and_colontest_non_narrow_char_unchanged(회귀 방어)cargo test --lib renderer::— 285 passedcargo test --test svg_snapshot— 3 passed (form-002golden 재생성)cargo clippy --lib -- -D warnings— cleansamples/text-align-2.hwp) SVG 내보내기 확인 — PDF 150dpi 와 시각 근사biz_plan(591개·균일 렌더),exam_kor·exam_eng·exam_math·footnote-01·field-01회귀 없음·렌더 분기는 SVG 출력 공통 경로, WASM Canvas 경로는 변경 없음)변경 수치
text-align-2.hwp주요 위치 advance 수렴:, → 0· → 표별·지좌/우 gap<circle>)변경 파일
src/renderer/layout/text_measurement.rsis_narrow_punctuation헬퍼 + 폴백 분기 3곳 + 단위 테스트 4건src/renderer/svg.rsdraw_text에·→<circle>렌더 분기 (그림자·본문 양쪽)tests/golden_svg/form-002/page-0.svg·<text>→<circle>반영samples/text-align-2.hwp,.pdfmydocs/plans/task_m100_257.md·_impl.mdmydocs/working/task_m100_257_stage{1..4}.mdmydocs/report/task_m100_257_report.mdmydocs/tech/text_align_2_svg_pdf_compare.mdmydocs/orders/20260423.md하위 호환성
is_narrow_punctuation은 crate-private.·표현이<text>→<circle>로 바뀜. 표준 SVG 이므로 모든 파서·뷰어 호환.설계 결정 히스토리
단계 3 에서 A안(shift 기반 중앙 배치) 을 두 차례 시도 후 "폰트 대체 문제는 metric 보정으로 해결 불가" 결론. C안(벡터
<circle>) 으로 전환한 판단 근거를mydocs/working/task_m100_257_stage3.md§1 에 기록.실패한 A안 2커밋은 의사결정 히스토리 보존 목적으로 1커밋으로 squash 하여 남겨 둠 (
010647b Task #257 단계3 시도: A안 shift 기반 · 중앙 배치 (최종 철회)).후속 (이번 PR 범위 밖)
mydocs/tech/font_fallback_strategy.md범위,.:등 다른 narrow 구두점 벡터 렌더 확장 — 필요 시 별도 이슈