Task #352: dash 시퀀스 Justify 폭 부풀림 — leader-aware advance + elastic 분배#415
Task #352: dash 시퀀스 Justify 폭 부풀림 — leader-aware advance + elastic 분배#415planet6897 wants to merge 40 commits into
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
같은 paragraph 안에 TAC 컨트롤이 2개 이상 있을 때 두 번째 이후 그림의 pic_y가 paragraph 시작 y로 고정되어 표와 겹침. - pi=51 ci=0 (단독 그림): pic_y=94.49 (정상, 선행 TAC 없음) - pi=57 ci=1 (Table 뒤 그림): pic_y=578.09 (버그, y_offset=919.40 사용해야) 선행 TAC 존재 여부가 핵심 판별 조건임을 확인. Stage 2 구현 방향 확정.
같은 paragraph에 TAC 컨트롤(표/그림/도형) 2개 이상이 서로 다른 line_seg에 배치된 경우, 두 번째 이후 inline 그림이 첫 번째와 같은 y 좌표에 그려져 겹침/오버플로 발생하던 문제 수정. - layout.rs::layout_shape_item: 선행 TAC 컨트롤이 있으면 para_start_y를 진행된 y_offset으로 갱신하여 그림 y 좌표를 표 아래로 정확히 배치. - typeset.rs::typeset_table_paragraph: 선행 TAC 그림의 line_seg 높이를 current_height에 누적하고, 페이지 초과 시 다음 페이지로 분할. 기본 페이지네이션 엔진은 typeset.rs(TypesetEngine). engine.rs는 현재 RHWP_USE_PAGINATOR=1 fallback 경로이므로 typeset.rs만 수정. 회귀 테스트: 1023 passed, 0 failed. 샘플 비교: 7쪽 표 + 파이 차트 겹침 해소, 파이 차트가 8쪽 정상 배치.
closes edwardkim#402 검증 결과: - 7쪽: 표 + 파이 차트 겹침 해소 (PDF 일치) - 8쪽: 파이 차트 정상 배치 - cargo test 1023 passed, 0 failed - 10개 대표 샘플 LAYOUT_OVERFLOW 카운트 회귀 없음 - 페이지 수 27→30 (분할로 인한 정상 증가)
기존 task_404.md(영문 줄바꿈 역공학 미실행 초안)는 archives로 이동. 신규 task_404 = orphan heading vpos 기반 분할 추가.
TypesetState.page_first_vpos 필드 추가 + typeset_section 메인 루프에 진단 로그 삽입. 타겟 샘플 분석 결과: - pi=83 가설 확정: vpos overflow=886 HU, curr_h=906.6/avail=933.5 (cumulative fit) + next pi=84 표 190.9px fit 불가 → orphan - False positive 41건 중 40건은 wrap-around 페이지에서 vpos↔px 비율 어긋남 (페이지 8 pi=57 TAC 그림 + 빈 문단 19개) Stage 2 전략 재정의: heading-orphan 패턴 (current fit + next block 못 fit + vpos overflow + single column) 4조건 모두 만족 시 push. 단순 vpos overflow check은 다수 회귀 위험.
typeset_section 메인 루프에 vpos 기반 보정 추가. 5개 조건 AND (current fits + vpos overflow + next substantial + next doesn't fit + single column non-wrap) 으로 false positive 차단. 설계 변경: page_top_vpos 는 TypesetState 필드 대신 current_items 첫 item 의 para_index 로 즉시 계산 (typeset_paragraph 내부 페이지 flush 와 동기 안 되는 문제 회피). 검증: - pi=83 heading 이 페이지 9 → 페이지 10 으로 이동 (pi=84/85 표와 함께 배치) - 1073개 테스트 모두 통과 - 10개 샘플 LAYOUT_OVERFLOW: 회귀 없음 + 타겟 -15, kps-ai -1 개선
SVG 시각 검증으로 pi=83 "(7) 다수 기부자 현황" 이 페이지 10 첫 본문 라인으로 이동하고 후속 표 pi=84/85 와 함께 배치됨을 확인. 검증 기준 충족: 1. 페이지 9 SVG 에 pi=83 heading 미표시 2. 페이지 10 SVG 가 pi=83 + pi=84/85 표 함께 표시 3. 회귀 테스트 1073개 모두 통과 4. 10개 샘플 LAYOUT_OVERFLOW 회귀 없음 + 2개 샘플 개선 closes edwardkim#404
21페이지 2x1 표가 차트 아래로 ~400px 밀려나는 결함 분석. TopAndBottom Picture(vert=Para) 다음 문단의 vpos 보정에서 lazy_base 산출 시 차트 높이가 이중 반영되는 문제 진단. prev_has_overlay_shape 가드를 Control::Picture (non-TAC) + TopAndBottom/vert=Para 케이스로 확장하는 3단계 계획 수립.
차트 그림(pi=172, 170×111mm, TopAndBottom, vert=Para) 다음 문단의 vpos 보정에서 lazy_base가 차트 높이(31470 HU)만큼 낮게 산출되어 pi=174 (2x1 표) 가 y=948 → 표 자체 높이 합산 후 1049.7 로 밀려나 LAYOUT_OVERFLOW 21.8px 발생. 후속 17개 빈 문단(pi=175~191)도 연쇄 overflow + pi=192 (10x5 표) overflow 521.7px. 베이스라인: 1023 lib + 6 svg_snapshot 통과, LAYOUT_OVERFLOW 19건.
…opAndBottom/vert=Para) 기존 가드는 Control::Shape + InFrontOfText|BehindText 만 검사하여 Picture (그림) 컨트롤과 TopAndBottom 케이스를 처리하지 못함. Picture (non-TAC) 분기 추가 + TopAndBottom + vert_rel_to=Para 케이스 포함하도록 확장. 한컴이 후속 문단 vpos에 개체 높이를 반영하는 경우 sequential y_offset 이 이미 개체 바닥까지 진행된 상태에서 lazy_base 산출 시 prev_pi 텍스트 vpos_end 만 쓰면 차트 높이만큼 이중 점프 발생. 21페이지 LAYOUT_OVERFLOW 19건 -> 1건 (잔여는 별개 페이지네이션 결함). 2x1 표가 차트 바로 아래로 정상 위치 (PDF 21페이지와 일치). 1023 lib + 6 svg_snapshot 통과, 6개 샘플 무회귀.
cargo test --release 전체 10개 스위트 100% 통과 (1023 lib + 6 svg_snapshot + 통합 테스트, 실패 0). 10개 샘플 LAYOUT_OVERFLOW 비교: 6개 샘플 무회귀, 타겟 샘플 22→4 (-18) 개선. closes edwardkim#409
Task edwardkim#409 — prev_has_overlay_shape 가드를 Control::Picture (non-TAC) + TopAndBottom/vert=Para 케이스로 확장하여 차트 다음 문단/표가 차트 높이만큼 추가 점프하는 결함 해결. LAYOUT_OVERFLOW: 19 -> 1 (21페이지), 22 -> 4 (전체 문서). 1023 lib + 6 svg_snapshot + 통합 테스트 100% 통과. 6개 다른 샘플 무회귀.
prev_has_overlay_shape 가드를 Control::Picture (non-TAC) + TopAndBottom/vert=Para 케이스로 확장. LAYOUT_OVERFLOW: 22 -> 4, 21페이지 19 -> 1. 1023 lib + 6 svg_snapshot + 통합 테스트 100% 통과.
이슈 edwardkim#409 재오픈. v1 (layout 측 수정) 후 잔여: pi=191 헤딩 + pi=192 (10x5 표) 가 21페이지에 묶여 22페이지 SVG 에서 누락되는 결함. 근본 원인: typeset.rs::typeset_section 의 controls 루프가 비-TAC Picture/Shape 의 높이를 current_height 에 누적하지 않아 chart (419.6px) 가 페이지네이션에 미반영. Pagination 추정 used=803.3px (실제 layout y=1275.9px) → 모두 21페이지 packing. Stage 4: 컨트롤 루프 분기 확장 (TopAndBottom + vert=Para). Stage 5: 회귀 검증 + 통합 최종 보고서.
typeset.rs::typeset_section 의 controls 루프에 비-TAC + TopAndBottom + vert=Para 인 Picture/Shape 의 height + margin.bottom 을 current_height 에 누적하는 분기 추가. layout 의 calc_shape_bottom_y 와 동일한 산식. 22페이지에 (4) 헤딩 + 10x5 표 + 연령대별 차트 + 2x1 표가 PDF 와 동일하게 정상 출력. chart 관련 LAYOUT_OVERFLOW 전건 해소 (대상 샘플 4 -> 1, 잔여 1건은 본 변경과 무관한 기존 결함). 1023 lib + 6 svg_snapshot 통과, 6개 다른 샘플 무회귀.
전체 cargo test --release 11개 스위트 100% 통과 (실패 0). 6개 다른 샘플 LAYOUT_OVERFLOW 무회귀. 타겟 샘플 22 -> 1 (chart 관련 전건 해소). PR 초안을 v1 (layout) + v2 (pagination) 통합본으로 갱신. closes edwardkim#409
typeset.rs::typeset_section controls 루프에 비-TAC + TopAndBottom + vert=Para Picture/Shape 의 height + margin.bottom 을 current_height 에 누적. 22페이지 (4) 헤딩 + 10x5 표 정상 표시. LAYOUT_OVERFLOW (대상 샘플): v1 4 -> v2 1 (chart 관련 전건 해소). 1023 lib + 6 svg_snapshot + 9개 통합 = 11개 스위트 100% 통과. 6개 다른 샘플 무회귀.
typeset.rs::typeset_section 의 비-TAC TopAndBottom + vert=Para Picture/Shape 높이를 current_height 에 누적. 22페이지에 (4) 헤딩 + 10x5 표 정상 출력 (PDF 일치). 대상 샘플 LAYOUT_OVERFLOW 22 -> 4 (v1) -> 1 (v2). 11개 테스트 스위트 100% 통과, 6개 다른 샘플 무회귀. closes edwardkim#409
23페이지 차트(pi=208, TAC Picture, lh=23700 HU)가 SVG에서 24페이지로 밀리는 결함. 차트 시작 y=721.37 (vpos 1460593 -> +626.87px) 가 본문 안이지만 끝 1037.37 이 본문 1028 보다 9.37px 초과. PDF/HWP는 atomic (분할 불가 단일-line TAC) 항목에 대해 top-fit 시멘틱 사용 — 시작점이 본문 안이면 현재 페이지 배치 하고 하단 일부는 하단 여백(15mm)으로 흘림 허용. 우리는 strict bottom-fit 으로 판정 -> split -> 1-line 못 쪼갬 -> next page. Stage 6: typeset_paragraph 의 fit 분기에 atomic TAC top-fit 추가 Stage 7: 회귀 검증 + 통합 최종 보고서 v3
typeset_paragraph 의 fit 분기에 atomic TAC top-fit 추가. 단일 라인 + TAC Picture/Shape 항목은 시작점이 본문 안이고 끝이 60px (약 1.6cm) 이내 초과면 현재 페이지에 배치. HWP 의 atomic 항목 top-fit 시멘틱 (시작점이 본문 안이면 배치하고 하단 일부는 하단 여백으로 흘림 허용) 을 구현. 23페이지 차트(pi=208, lh=316px) 가 PDF 와 동일하게 표 아래에 정상 배치. 24페이지는 차트가 빠지고 정상 후속 콘텐츠로 시작. 대상 샘플 LAYOUT_OVERFLOW 동일 1건 유지 (chart 정상 배치 이므로 overflow 미발생). 6개 다른 샘플 무회귀. 1023 lib + 6 svg_snapshot 통과.
cargo test --release 11개 스위트 100% 통과 (실패 0). 6개 다른 샘플 무회귀. 타겟 샘플 LAYOUT_OVERFLOW: 22 -> 4 -> 1 -> 1 (chart 정상 배치). 21~24페이지 PDF 대조 전건 일치: - 21: 2x1 표 차트 직하 - 22: (4) 헤딩 + 10x5 표 - 23: 막대 차트 하단 - 24: 2x1 표 -> (6) 헤딩 -> 파이차트 closes edwardkim#409
typeset.rs::typeset_paragraph 의 fit 분기에 atomic TAC top-fit 추가. 단일 라인 + TAC Picture/Shape 항목은 시작점이 본문 안이고 끝이 60px 이내 초과면 현재 페이지에 배치 (HWP 시멘틱). 23페이지 차트(pi=208) 가 표 아래 정상 배치, 24페이지가 2x1 표 -> (6) 헤딩 -> 파이차트로 정상 시작 (PDF 일치). 11개 테스트 스위트 100% 통과, 6개 다른 샘플 무회귀.
23페이지 차트 정상 배치 + 24페이지 정상 콘텐츠 시작 (PDF 일치). 21~24 페이지 PDF 대조 전건 일치. closes edwardkim#409
- 수행 계획서, 구현 계획서, Stage 1 보고서 작성 - layout.rs:1422 분기에 RHWP_VPOS_DEBUG 진단 로그 추가 (env-gated) - 6개 샘플 page/lazy × applied/skip 분포 측정 - page_false 케이스를 4개 카테고리로 분류: A) 다단 우측 단 base≈7000 (target, 28건) ← fix 대상 B) vpos reset base≥60000 (합법 스킵, 20건) ← fix 후도 SKIP 유지 C) base=0 미세 drift (17건) ← 영향 없음 D) lazy_false 미세 drift ← 영향 없음 closes part of edwardkim#412 (Stage 1)
…irst_vpos 사용
핵심 변경 (layout.rs):
1. col_anchor_y = body_wide_reserved 푸시 직후 y_offset 캡처
- 첫 PageItem 의 실제 렌더링 y = vpos_page_base 좌표에 해당
- col_area.y 는 일반적으로 vpos=base 와 일치하지 않아 부정확
2. vpos_end 결정에 curr_first_vpos 우선 사용
- HWP 가 spacing_after 를 다음 paragraph first vpos 에 인코딩하므로
prev.vpos+lh+ls 보다 정확
- vpos reset 시에는 prev 기반 fallback
3. page_path / lazy_path 분리:
- page_path: col_anchor_y + (vpos_end - base) * scale
- lazy_path: col_area.y + (vpos_end - base) * scale (기존 유지)
검증 결과 (exam_eng.hwp):
- p1 우측 단 item 7 ①~⑤ 22.55 px 균일 (원래 보고된 버그 해결)
- p1 좌측 단 item 1 ①~⑤ 21.89 px 균일
- p2 우측 단 item 20 ①~⑤ 19.92 px 균일
- p2 우측 단 item 18 ①→② 잔존 (overlay shape bypass — 별도 task)
회귀:
- cargo test 6/6 통과
- golden snapshot: aift-page3 약 3.68 px shift (single ls 적용) — 의도된 개선,
issue edwardkim#147 본래 검증 목적과 무관해 골든 갱신
- issue-157: anchor 도입 후 자동 정상화
closes part of edwardkim#412 (Stage 2)
검증 대상 (7개 샘플 합계 268페이지): - exam_eng (8p), exam_kor (24p), exam_math (20p) - k-water-rfp (28p), 2025년 기부·답례품 (30p) - aift (78p), kps-ai (80p) 검증 결과: - cargo test 6/6 통과 - 단단 문서: 영향 없음 - 다단 문서: page_path 보정 정상화 (exam_kor 12건 false→true) - 시각 회귀 미관찰 (samples 1페이지 시각 비교 7건) 잔존 이슈: - exam_eng p2 item 18 ①→② (overlay shape bypass) → 별도 task 로 추적 권고 closes part of edwardkim#412 (Stage 3)
- mydocs/report/task_m100_412_report.md 작성 - mydocs/orders/20260428.md: edwardkim#412 항목 완료 갱신 closes edwardkim#412
원인: HY신명조 폰트 메트릭이 ASCII 하이픈을 0.83 em (12.747 px @ 15.31) 으로 저장. 29개 dash 시퀀스가 자연 폭 단계에서 153 px 초과, Justify Branch A 의 공백 압축(min_ews=-2.55px×3) 으로는 회수 불가하여 'of being…' 우측 잘림. 이슈 본문의 'Branch B 가 dash 시퀀스를 단어로 인식해 spread 가산' 가설은 기각 (interior_spaces=3 으로 Branch A 발동 확인). Stage 2 설계: 3 개 이상 연속 dash leader 시퀀스에만 좁은 advance (font_size × 0.3) 적용하여 1017 개 정상 dash 회귀 회피. 산출물: - mydocs/plans/task_m100_352.md (수행계획서) - mydocs/plans/task_m100_352_impl.md (구현계획서) - mydocs/troubleshootings/issue_352_root_cause.md (원인 보고서) - mydocs/working/task_m100_352_stage1.md (단계별 보고서)
3 개 이상 연속 dash 시퀀스를 leader 로 식별하여 좁은 advance(font_size * 0.3) 강제 적용. HY신명조 ASCII '-' 메트릭(0.83 em) 부풀림으로 인한 빈칸 라인 overflow 해소. text_measurement.rs: - is_dash_leader_run(chars, i) 헬퍼 추가 - estimate_text_width / compute_char_positions / estimate_text_width_unrounded 세 char_width 클로저에 적용 Q32 블랭크 라인 측정: dash advance: 12.11 → 4.36 px 29 dash 시퀀스 폭: 351 → 126 px (PDF ~135 px 와 근접) 'of being' 잘림 해소 자연 텍스트의 단발 dash(stimulus-driven, 32.- 등) 는 ≥3 조건 미충족으로 영향 없음. 1023 단위 테스트 전수 통과.
3 개 이상 연속 ASCII 하이픈 시퀀스의 dash 글리프 출력을 스킵하고, underline 이 없는 경우에 한해 단일 가로선으로 통합 렌더한다. 가운데점(is_middle_dot) 패턴과 동일한 폰트 비의존 시각 처리. svg.rs / web_canvas.rs: - cluster pre-pass 로 dash_run_groups 계산 - shadow + main loop 에서 dash leader 클러스터 스킵 - underline 이 설정된 run 은 dash leader 라인 생략 (이중선 방지) text_measurement.rs: - dash leader 좁은 폭 0.3 em → 0.32 em 미세 조정 (PDF ~135 px 일치) Q32 블랭크 라인 측정 (post Stage 3): dash 글리프 수: 29 → 0 가로선 수: 2 → 1 (underline 만) underline 폭: 126.5 → 134.94 px (PDF 목표 ~135 px 일치) 작업지시자 피드백 반영: '왜 2줄로 그려지나?' → underline 있을 때 dash leader 라인 생략 '폭이 조금 짧음' → 0.3 em → 0.32 em 전 테스트 1023 + 통합 테스트 통과. 회귀 없음.
Q32 dash 시퀀스 부풀림 수정 완료: - dash advance 12.11 → 4.65 px - 빈칸 폭 351 → 134.94 px (PDF ~135 일치) - 'of being' 우측 잘림 해소 - 글리프 0 + 단일 underline 으로 시각 정리 closes edwardkim#352
작업지시자 피드백 '폭이 조금 짧음 / 1.5 정도' 에 따라 PDF 를 직접 pdftotext -bbox-layout 으로 실측한 결과 dash 시퀀스 ~218 px (이슈 본문의 135 px 추정은 잘못). 0.32 em (134.94 px) 에서 0.5 em (210.85 px) 으로 조정. PDF 218 px 의 96.7%, 사용자 1.5x estimate 와도 부합. 한컴이 다른 ASCII 구두점에 적용하는 반각 강제 정책과 일관. text_measurement.rs: estimate_text_width / compute_char_positions / estimate_text_width_unrounded 세 char_width 클로저에 font_size * 0.5 적용 전 테스트 1023+ 통과.
Stage 2~3 의 좁은 dash advance 부작용 해결: dash leader 라인의 슬랙이 단어 공백에 균등 분배되어 단어 사이가 2~3 배 팽창하는 문제. PDF 직접 실측 (pdftotext -bbox-layout) 결과 PDF 는 dash 를 path/graphic 으로 그리며 라인 슬랙에 따라 가변 폭으로 elastic 하게 채움 (Q32 0.49 em, Q34 0.30 em — dash 수와 슬랙에 따라 다름). dash 단어가 PDF 텍스트 추출에 나오지 않음이 증거. 변경: - TextStyle 에 extra_dash_advance 필드 추가 (기본 0.0) - text_measurement.rs 세 char_width 클로저에 dash leader 만 좁은 base 0.3 em + extra_dash_advance 추가 - paragraph_layout.rs Justify 분기에 count_dash_leaders 헬퍼 추가 - 라인에 ≥3 dash leader + 양수 슬랙 시 슬랙을 dash 에 흡수 (공백 자연 폭) - 음수 슬랙 또는 leader 없으면 기존 Branch A 동작 (공백 압축/분배) p5 Q32 (s0p221 L6) 측정: leader_dashes=29, slack=78.21 px, per_dash_extra=2.70 px dash advance 12.11 → 7.06 px, 폭 351 → 204.7 px (PDF 218 의 94%) p6 Q33 단어 공백 회복: Baseline 83.6 px (압축) → Stage 3 125.7 px (팽창) → Stage 5 95.6 px (자연) 전 테스트 1023+ 통과.
Stage 2/5 의 leader-aware advance 와 elastic extra_dash_advance 가 EmbeddedTextMeasurer (Native) 와 estimate_text_width_unrounded 에만 적용되어 있어 WASM 환경에서는 Q32 이후 라인의 단어 공백 팽창이 그대로 재현되던 문제 해결. WasmTextMeasurer 의 두 char_width 클로저 (estimate_text_width line 577, compute_char_positions line 679) 에 동일 패턴 추가: - is_dash_leader_run 검사 → font_size * 0.3 좁은 base 클램프 - is_leader 시 style.extra_dash_advance 가산 paragraph_layout.rs (Justify 분기) 와 web_canvas.rs (시각 렌더) 는 이미 양 경로 공유라 변경 불필요. 검증: cargo build --release → Native PASS cargo check --release --target wasm32-unknown-unknown --lib → PASS cargo test --release → 1023 + 통합 전수 통과
- 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>
|
완료. `devel` 에 Task #352 핵심 7 commits 만 분리 cherry-pick 으로 머지됨 (commit `8daf988`). 이슈 #352 자동 close 됩니다 (PR 본문 `closes #352` 명시). 머지 commit (작성자 attribution 보존)
분리 cherry-pick 정황본 PR 의 40 commits 가 다른 OPEN PR 들 (#401, #406, #408, #410, #414) 의 변경분 누적 형태였습니다. 본 task #352 와 직접 관련된 7 commits 만 분리하여 cherry-pick 한 이유:
확인 결과: cherry-pick 후 `samples/synam-001.hwp` 5 페이지 회귀 정황 발생 안 함 (rows=0..5 정상 유지, 35 페이지 유지). 검증
평가5 단계 진단 (폰트 메트릭 → Justify 분배 → 시각 라인 통합 → PDF 측정 보정 → elastic 분배) + WASM 경로 동기화 모두 양호합니다. Q32 dash advance 12.11 → 7.06 px (PDF ~7.4 px 근접), 29 dash 폭 351 → 204.7 px (PDF ~218 px) 의 정량 결과도 정확합니다. 특히 Stage 3 의 공백 팽창 사용자 컴플레인을 Stage 5 의 elastic 분배로 정정하신 점 — Justify Branch 7 분기에 `extra_dash_advance` 일관 적용 — 이 깊이 있는 정정이었습니다. 다른 OPEN PR 안내본 PR 에 누적된 다른 task 들의 PR 흐름:
향후 다른 PR 작업 시 본 PR 처럼 누적되지 않도록, devel 에서 직접 분기하는 것이 검토 / 충돌 측면에서 깔끔합니다. 좋은 기여 감사합니다. |
배경
samples/exam_eng.hwp5 페이지 32번 문항 빈칸 라인:29개 ASCII 하이픈으로 구성된 빈칸이 PDF 대비 ~3.5 배 부풀어 후속 텍스트
'stimulus-driven',의 마지막 콤마가 우측 단 경계를 넘어 잘림.증상 (수정 전):
of being시작 x: 953 (단 우측 끝, 잘림 발생)근본 원인
1. HY신명조 폰트 메트릭의 ASCII 하이픈 폭
src/renderer/font_metrics_data.rs:3848의FONT_276_LATIN_0[13](HY신명조 ASCII '-') 가 853/1024 em ≈ 0.83 em 으로 비정상적으로 넓음:비교: 콤마(0x2C)=298, 마침표(0x2E)=298 — 일반 좁은 구두점 대비 dash 만 ~3 배 넓음.
2. Justify Branch A 의 공백 분배 부작용
29 dash × 12.11 px = 351 px 자연 폭으로 라인이 사용 가능 폭(408 px) 초과. Justify Branch A 가 음수 슬랙을 공백 압축으로 회수하려 하나 min-clamp(
-2.55 px × 3 spaces) 로 제한되어 약 153 px 잘림 발생.이슈 본문이 추정한 "Justify Branch B 가산" 가설(공백이 없는 라인의 글자 분배)은 틀림 —
interior_spaces=3이 정상 인식되어 Branch A 발동. 진짜 원인은 메트릭 자체.3. Stage 2/3 좁은 advance 적용 후 발생한 2 차 회귀
dash advance 를 좁히자 라인에 양수 슬랙이 발생, Branch A 가 슬랙을 공백에 균등 분배하면서 단어 사이 공백이 자연 폭의 2~3 배로 팽창 (작업지시자 피드백 "Q32 이후 폭이 2배"). Stage 5 에서 PDF 의 elastic leader 동작 모방으로 해소.
4. PDF 의 dash 처리 방식 확인
pdftotext -bbox-layout -f 6 -l 6 samples/exam_eng.pdf결과 PDF Q34 라인:dash 시퀀스가 단어로 추출되지 않는 것은 PDF 가 dash 를 path/line graphic 으로 직접 그렸다는 강한 증거. 우리 Stage 3 의 SVG
<line>대체 전략이 PDF 와 동일.PDF dash advance 가변성 측정:
→ PDF 는 dash leader 를 라인 슬랙에 따라 가변 폭으로 elastic 하게 채움.
변경
Stage 1 — 원인 확정 (
428b01d)paragraph_layout.rs:996와text_measurement.rs:209에 임시eprintln!(RHWP_DEBUG_352환경변수 게이트) 삽입하여 다음 확인:[45 × 29, 32, "of being…"](ASCII U+002D 29 개)interior_spaces=3≠ 0 → Branch A 발동 (Branch B 아님)text_w=561.00,avail=408.21→ 자연 폭 단계에서 153 px 초과embedded=Some(12.747)(= 0.833 em)문서:
mydocs/troubleshootings/issue_352_root_cause.md+ Stage 1 보고서.Stage 2 — leader-aware 좁은 advance (
69e420b)src/renderer/layout/text_measurement.rs:세
char_width클로저 (estimate_text_width,compute_char_positions,estimate_text_width_unrounded) 에 적용:≥ 3 연속 조건 덕분에 단발 dash(예: "stimulus-driven", "32.-") 영향 없음.
Stage 3 — dash run 시각 라인 통합 (
2248752)src/renderer/svg.rs(line 1882~) +src/renderer/web_canvas.rs(line 1284~):is_middle_dot패턴(svg.rs:1924) 모방하여 ≥ 3 연속 dash 클러스터를 감지, 글리프<text>출력 스킵 + 단일<line>으로 통합 렌더 (underline 부재 시).dash 글리프 자체 폭(scale 0.95 후 ~5.4 px) 이 advance(~4.4 px) 를 넘어 시각상 겹치는 문제도 동시 해결.
Stage 4 — 폭 미세 보정 (
7f45fc0+037cba6)작업지시자 피드백 ("폭이 조금 짧음 / 1.5 정도") 반영하여 PDF 직접 실측. 0.3 em (134.94 px) → 0.5 em (210.85 px) 로 조정. PDF 218 px 의 96.7% 일치.
Stage 5 — dash leader elastic Justify 분배 (
6926c4b)작업지시자 피드백 ("Q32 이후 폭이 2배 커짐") 의 진짜 원인 (단어 공백 팽창) 해결.
src/renderer/mod.rs— TextStyle 신규 필드:src/renderer/layout/text_measurement.rs— 세 char_width 클로저 갱신:(Stage 4 의 0.5 em 은 0.3 em 좁은 base 로 회수, elastic extra 로 PDF 수준 폭 도달)
src/renderer/layout/paragraph_layout.rsJustify 분기:세 분기 (Branch A, Branch B, Distribute, overflow, cell underflow) 의 반환 튜플을
(extra_word_sp, extra_char_sp, extra_dash_sp)3-tuple 로 확장.검증
Q32 측정 (s0 p221 L6, 5 페이지)
,우측 잘림p6 Q33 라인 단어 공백 회복 (Stage 5 핵심 회귀 해소)
y=414 의 "to be free, then" 13 글자 폭:
Stage 3 의 공백 폭증이 완전히 해소되고, 베이스라인 대비 +12 px 차이는 압축 해제 결과 (자연 폭 회복).
Stage 5 디버그 로그 (Q32)
각 dash advance = 0.3 em × 0.95 + 2.70 = 4.36 + 2.70 = 7.06 px ✓
29 dashes 폭 = 29 × 7.06 = 204.7 px ✓ (SVG underline x1=597.12, x2=801.84)
cargo test --release
전 테스트 스위트 100% 통과:
lib: 1023 passed, 0 failed, 1 ignoredsvg_snapshot: 6 passedtab_cross_run: 1 passed영향 범위 점검
문서 전체 dash 통계 (Stage 1 측정): HY신명조 dash 1017 개, Times New Roman 295 개.
is_dash_leader_run의 ≥ 3 조건 덕분에 단발 dash 미영향:비포함 / 백로그
cs(line_seg.cs) 필드 활용 — 별 이슈 (현재 미사용 확인)–(U+2013),—(U+2014) leader 처리 — 본 이슈 범위 외, 필요시is_dash_leader_run에 추가 가능_____underscore leader 일반화 — 우선순위 낮음커밋 목록
closes #352