Skip to content

Task #902: WMF renderer 근본 개선 + LO emfio 포팅 + WASM RasterPlayer (v2)#918

Closed
jangster77 wants to merge 48 commits into
edwardkim:develfrom
jangster77:local/task902
Closed

Task #902: WMF renderer 근본 개선 + LO emfio 포팅 + WASM RasterPlayer (v2)#918
jangster77 wants to merge 48 commits into
edwardkim:develfrom
jangster77:local/task902

Conversation

@jangster77

@jangster77 jangster77 commented May 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

issue #902 ("WMF 텍스트가 한컴 viewer 와 비교 시 크기/간격 미세 차이") 해결.

v1 → v2 → Phase 3 (메인테이너 요청 8개 반영) → Stage 33-A (최종 root-cause fix) 까지 점진 포팅.

본 PR 의 scope: HWP3 기준

본 변경의 검증/구현 기준은 HWP3 파일 (sample16 등) 입니다.

  • samples/hwp3-sample16.hwp 의 paragraph 394 WMF (4.7MB, 20869 records) 정합 검증
  • sample14/17/18 의 WMF 도 회귀 없이 정상 렌더링

HWPX / HWP5 의 WMF 처리는 추가 분석 필요 (별도 follow-up task):

  • HWPX 의 WMF binary 저장/참조 구조 차이 확인 — #935 (실제 회귀 확인됨: WMF 누락, SVG 156KB)
  • HWP5 OLE 복합 문서 안의 WMF 처리 — #936 (실제 회귀 확인됨: Stage 33-A inline 미적용)

핵심 변경

  1. DX byte-aware indexing fix (Stage 3) — ROOT CAUSE: WMF EXTTEXTOUT 의 DX 배열이 MBCS byte index 인데 grapheme index 로 접근하던 버그
  2. POLYPOLYGON fill-rule 정합 (Stage 11) — 다중 서브폴리곤을 단일 path 로 합성
  3. LibreOffice emfio 포팅 (Stage 12~23) — Native RasterPlayer (tiny-skia + fontdue + LO mtftools.cxx 알고리즘)
  4. LibreOffice opt-in (Stage 17) — RHWP_WMF_USE_LIBREOFFICE=1 로 LO headless 변환 사용
  5. WASM RasterPlayer (Stage 28~31) — rhwp-studio 에서도 동작
  6. object_table lowest-free-slot fix (Stage 30) — WMF spec §3.1.4 정합
  7. Phase 3: 메인테이너 요청 8개 반영 — cfg gate / native target deps / 폰트 임베드 제거 / MPL 2.0 헤더 / wasm-opt 활성화 / resvg optional
  8. Stage 33-A: SetTextAlign vertical bits 버그 + inline SVG 임베드
    • mode & VTA_TOP(=0x0000) == 0 가 항상 true 라서 TA_BASELINE 인 모드도 VTA_TOP 으로 잘못 매핑 → baseline 이 cell-top 보정만큼 (~font_size × 0.8) 하강 → 박스 외부 표시 회귀
    • vertical bits (0x0018 mask) 값 분기로 BASELINE → BOTTOM → TOP 우선순위 정합
    • WMF → SVG 결과를 nested <svg> inline 임베드 (sandboxed <image data:svg> 우회) + id prefix
    • 옵션 2 의 woff2 base64 임베드 제거 — root cause fix 가 진짜 해법

Architecture

환경 WMF 렌더링 경로
rhwp CLI native (default) resvg → PNG embed (native-skia feature)
rhwp CLI native (LO opt-in) LibreOffice headless 변환
rhwp CLI native (no native-skia) SVG inline embed
rhwp-studio (WASM) WMF → SVG (inline <svg>) → Canvas2D

메인테이너 8개 요청 모두 충족

# 조건 상태
1 raster cfg gate #[cfg(not(target_arch = "wasm32"))]
2 tiny-skia + fontdue → native target deps
3 include_bytes!(NanumGothic.ttf) 제거 ✅ Phase 3
4 include_bytes!(NanumGothic-Regular.woff2) 제거 ✅ Stage 33-A
5 wasm-opt = false 제거 ✅ Phase 3
6 resvg optional
7 MPL 2.0 file-level 헤더 (raster/*.rs)
8 WASM ~4.5 MB 4.35 MB (실측)

라이센스

  • LO emfio 알고리즘 참조: MPL 2.0 file-level reciprocity
  • rhwp 본체: MIT

Test plan

  • cargo test --release --lib: 1275 passed / 0 failed
  • cargo clippy: clean
  • HWP3 sample16 페이지 18 박스 내부 텍스트 정합 (rsvg-convert 시각 검증)
  • WASM 빌드 (wasm-pack): pkg/rhwp_bg.wasm 4.35 MB (devel 4.5 MB 이하 유지)
  • LO 미설치 PC 에서도 정상 렌더링 (외부 폰트 chain — --font-style / 시스템 폰트)
  • HWPX 의 WMF 누락 (follow-up)#935
  • HWP5 의 WMF inline 미적용 (follow-up)#936

잔존 한계 / 별도 follow-up task

  • 페이지네이션 페이지 수 불일치 (예: hwp3-sample16.hwp — 한컴오피스 38 페이지, rhwp 64 페이지) — #927
  • HWP3 표 셀 내 inline 사각형 shape 주변 텍스트 중복 (exam_kor 5p '확산 모델' 다이어그램, pre-existing devel 회귀, WMF 무관) — #928
  • hwp3-sample19.hwp 미오픈 (pre-existing HWP3 parser 실패, WMF 무관) — 별도 issue
  • HWPX 의 WMF binary 누락 (#935) — BinData ZIP entry 추출/참조 실패로 SVG 출력 156KB (HWP3 의 5%)
  • HWP5 의 WMF inline 미적용 (#936) — Stage 33-A inline 경로 HWP3 전용 동작, HWP5 는 <image data:> 경로 잔존
  • 일부 사용 빈도 낮은 WMF records (Region/Palette 등) 미구현
  • font escapement 의 actual rotation rendering 미구현 (state 만 추적)

closes #902

🤖 Generated with Claude Code

@edwardkim

Copy link
Copy Markdown
Owner

검토 완료. WMF SVG 개선 + RasterPlayer 포팅 모두 가치 있는 작업입니다. 다만 WASM 크기 영향(4.5MB → 11.0MB)과 아키텍처 정합에 대해 전략 조정이 필요합니다.

핵심 정책

rhwp의 Skia/래스터 엔진은 headless Linux CLI 전용입니다. 웹(WASM)에는 포함하지 않습니다. 웹 에디터(rhwp-studio)는 Canvas2D + SVG converter 경로를 사용합니다.

WASM 크기 영향

상태 크기
현재 devel 4.5 MB
PR #918 그대로 11.0 MB (2.5배)

원인:

  • include_bytes!("NanumGothic.ttf") — 4.2MB TTF가 WASM에 직접 컴파일
  • include_bytes!("NanumGothic-Regular.woff2") — 1.5MB woff2 SVG 임베딩
  • wasm-opt = false — 전체 WASM 최적화 비활성 (+900KB)
  • tiny-skia + fontdue — 래스터 렌더러가 WASM에 불필요하게 포함

권고 전략: 분리 머지

Phase 1 (즉시 머지 가능): SVG converter 개선

wmf/converter/svg/ 3파일만 — DX byte-aware indexing fix + POLYPOLYGON fill-rule. WASM/native 모두 혜택, 크기 무영향.

Phase 2 (즉시 머지 가능): fixture + examples

sample17/18/19 + PDF + 예제 스크립트. 코드 무관.

Phase 3 (수정 요청): RasterPlayer native 전용

다음 수정 후 머지 가능합니다:

  1. src/wmf/converter/raster/ 전체를 cfg(not(target_arch = "wasm32")) 가드 — 래스터 엔진이 WASM 빌드에서 제외
  2. tiny-skia + fontdue[target.'cfg(not(target_arch = "wasm32"))'.dependencies]에 배치
  3. include_bytes!("NanumGothic.ttf") 제거 — native CLI는 --font-path로 외부 폰트 경로 지정. 임베딩 대신 외부 경로를 선호합니다
  4. include_bytes!("NanumGothic-Regular.woff2") 제거 — SVG 경로도 외부 폰트 사용
  5. wasm-opt = false 제거 — RasterPlayer가 WASM에서 빠지면 불필요
  6. resvg optional 유지 (또는 cfg(not(target_arch = "wasm32")) conditional)

WASM 크기 목표

Phase 3 수정 후 WASM 크기가 ~4.5MB 유지되어야 합니다.

라이센스

LO emfio 알고리즘 포팅 코드(raster/*.rs)에 MPL 2.0 file-level 헤더가 필요합니다. rhwp 본체(MIT)와 file-level reciprocity로 호환됩니다.

진행 방향

Phase 1 + 2는 본 PR에서 메인테이너가 바로 cherry-pick할 수 있습니다. Phase 3는 위 수정 사항을 반영한 후 다시 리뷰하겠습니다.

의견이 있으시면 알려주세요.

@edwardkim edwardkim self-requested a review May 16, 2026 02:28
@edwardkim edwardkim added the enhancement New feature or request label May 16, 2026
@edwardkim edwardkim added this to the v1.0.0 milestone May 16, 2026
jangster77 added a commit to jangster77/rhwp that referenced this pull request May 16, 2026
메인테이너 (edwardkim) PR edwardkim#918 review 피드백 반영:

1. RasterPlayer 를 native 전용으로 분리 (WASM 빌드 크기 보존):
   - src/wmf/converter/mod.rs: raster module 에 #[cfg(not(target_arch = "wasm32"))] 가드
   - Cargo.toml: tiny-skia + fontdue 를 [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 로 이동
   - src/renderer/svg.rs: rasterize_wmf_direct, rasterize_wmf_direct_pub 에 cfg 가드
   - src/renderer/web_canvas.rs: WASM RasterPlayer 사용 (Stage 29) 제거 → 기존 SVG 경로 복원

2. NanumGothic 임베드 제거 (외부 폰트 경로 선호):
   - ttfs/embedded/NanumGothic.ttf 삭제 (4.7 MB)
   - ttfs/embedded/LICENSE.md 삭제
   - src/wmf/converter/raster/text.rs: include_bytes! 제거, 시스템 폰트 검색만 사용
   - sample16 화면 visual quality 다소 저하 (사용자가 --font-path 로 NanumGothic.ttf
     경로 지정 시 native 환경 quality 복원)

3. @font-face base64 embed 제거 (Stage 24):
   - src/wmf/converter/svg/mod.rs: build_font_face_style 함수 제거
   - WASM SVG 경로 의 NanumGothic woff2 (340 KB) embed 제거
   - 브라우저 fontconfig fallback 사용 (rhwp-studio 기본 폰트)

4. inline SVG embed 제거 (Stage 25):
   - src/renderer/svg.rs: wmf_svg_to_inline 함수 제거
   - WASM dispatcher: convert_wmf_to_svg → image href data URL (기존 방식)

5. wasm-opt = false 제거:
   - Cargo.toml: [package.metadata.wasm-pack.profile.release] 섹션 삭제
   - RasterPlayer 가 WASM 에서 빠지면 wasm-opt 안전

6. resvg optional 복원:
   - Cargo.toml: resvg = { version = "0.47", optional = true }
   - native-skia feature 에 dep:resvg 추가
   - src/renderer/svg.rs: rasterize_wmf_svg_to_png 에 #[cfg(feature = "native-skia")] 가드

7. MPL 2.0 file-level 헤더 추가 (LO emfio 포팅 파일):
   - src/wmf/converter/raster/{state.rs,player.rs,text.rs}: MPL 2.0 헤더
   - mod.rs 는 이미 헤더 있음

8. examples/wmf_to_svg.rs 제거 (Stage 24 디버그용, 더이상 불필요)

WASM 빌드 크기:
- 이전 (Phase 2): 11 MB (NanumGothic 4.7 MB + tiny-skia + fontdue 임베드)
- 본 Phase 3: **4.54 MB** (목표 ~4.5 MB 충족)

native 회귀: cargo test --release --all-targets — 1413 passed / 0 failed
WASM 빌드: wasm-pack build --release — pkg/rhwp_bg.wasm 4.54 MB

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@jangster77

Copy link
Copy Markdown
Collaborator Author

wmf 수정으로 kor_exam.hwp 실패 원인 분석 후 다시 update 하겠습니다.

jangster77 added a commit to jangster77/rhwp that referenced this pull request May 16, 2026
메인테이너 (edwardkim) PR edwardkim#918 review 피드백 반영:

1. RasterPlayer 를 native 전용으로 분리 (WASM 빌드 크기 보존):
   - src/wmf/converter/mod.rs: raster module 에 #[cfg(not(target_arch = "wasm32"))] 가드
   - Cargo.toml: tiny-skia + fontdue 를 [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 로 이동
   - src/renderer/svg.rs: rasterize_wmf_direct, rasterize_wmf_direct_pub 에 cfg 가드
   - src/renderer/web_canvas.rs: WASM RasterPlayer 사용 (Stage 29) 제거 → 기존 SVG 경로 복원

2. NanumGothic 임베드 제거 (외부 폰트 경로 선호):
   - ttfs/embedded/NanumGothic.ttf 삭제 (4.7 MB)
   - ttfs/embedded/LICENSE.md 삭제
   - src/wmf/converter/raster/text.rs: include_bytes! 제거, 시스템 폰트 검색만 사용
   - sample16 화면 visual quality 다소 저하 (사용자가 --font-path 로 NanumGothic.ttf
     경로 지정 시 native 환경 quality 복원)

3. @font-face base64 embed 제거 (Stage 24):
   - src/wmf/converter/svg/mod.rs: build_font_face_style 함수 제거
   - WASM SVG 경로 의 NanumGothic woff2 (340 KB) embed 제거
   - 브라우저 fontconfig fallback 사용 (rhwp-studio 기본 폰트)

4. inline SVG embed 제거 (Stage 25):
   - src/renderer/svg.rs: wmf_svg_to_inline 함수 제거
   - WASM dispatcher: convert_wmf_to_svg → image href data URL (기존 방식)

5. wasm-opt = false 제거:
   - Cargo.toml: [package.metadata.wasm-pack.profile.release] 섹션 삭제
   - RasterPlayer 가 WASM 에서 빠지면 wasm-opt 안전

6. resvg optional 복원:
   - Cargo.toml: resvg = { version = "0.47", optional = true }
   - native-skia feature 에 dep:resvg 추가
   - src/renderer/svg.rs: rasterize_wmf_svg_to_png 에 #[cfg(feature = "native-skia")] 가드

7. MPL 2.0 file-level 헤더 추가 (LO emfio 포팅 파일):
   - src/wmf/converter/raster/{state.rs,player.rs,text.rs}: MPL 2.0 헤더
   - mod.rs 는 이미 헤더 있음

8. examples/wmf_to_svg.rs 제거 (Stage 24 디버그용, 더이상 불필요)

WASM 빌드 크기:
- 이전 (Phase 2): 11 MB (NanumGothic 4.7 MB + tiny-skia + fontdue 임베드)
- 본 Phase 3: **4.54 MB** (목표 ~4.5 MB 충족)

native 회귀: cargo test --release --all-targets — 1413 passed / 0 failed
WASM 빌드: wasm-pack build --release — pkg/rhwp_bg.wasm 4.54 MB

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
jangster77 and others added 19 commits May 16, 2026 16:54
WMF unit scale 정합 — 한컴 사적 WMF 의 SetWindowExt 비표준 비례.
Task edwardkim#896 Stage 9 분리. 새 다양한 sample + PDF 자료 활용:
- hwp3-sample14/16/17/18/19 의 HWP3/HWP5/HWPX + PDF
- 다중 sample WMF binary pattern 분석 → 정합 ratio 도출 시도

Fix 방향 후보: α/β/γ/δ (이슈 본문) + ε (다중 sample pattern).
timebox ~3일. 정확 ratio 부재 시 부분 정합 또는 won't-fix.
가용 자료: hwp3-sample14/16/17/18 의 HWP3/HWP5/HWPX + PDF.

sample16 paragraph 394 WMF 정밀 측정:
- WMF binary: SetMapMode(MM_ANISOTROPIC) + SetWindowExt(56, 72) + 미사용 ViewportExt
- rhwp viewBox: 6333×4212 (Task edwardkim#860 자동 확장)
- font-size 117 effective: ~11.44 px @ 96 DPI = 8.6 pt
- PDF 페이지 18 의 WMF 영역: raster vector graphics — text 추출 불가
- 시각 추정: rhwp 8.6 pt vs 한컴 ~10 pt, 약 16% 작음

PDF 의 WMF text 추출 한계로 정밀 ratio 측정 어려움.

Stage 2 방향 후보:
- α: 정밀 reverse engineering
- β: viewBox 알고리즘 변경
- γ: 추정 ratio 단순 적용
- ε (신규): 작업지시자 시각 판정 기반 ratio (timebox 내 최선)
… 오류 정정

Task edwardkim#896 Stage 9 (issue edwardkim#902 본문) 분석 오류 발견:
- 잘못된 기록: SETWINDOWEXT (y=72, x=56) — '비표준 비례'
- 실제: SETWINDOWORG (X=56, Y=72), SETWINDOWEXT (X=6333, Y=4161)
- WMF spec param order: Y first, X second

따라서 WindowExt(6333, 4161) 는 element bbox 와 일치 — viewBox 자동 확장 (Task edwardkim#860) 동작 정확. '비표준 비례' 가설 폐기.

새 ROOT CAUSE 후보: DX (character spacing) 처리.
- src/wmf/converter/svg/mod.rs:928~934 의 excess_dx 공식
- Korean wide char + WMF_DX=117 → dx=0 → SVG 가 natural width 만 사용
- 폰트 fallback 따라 변할 수 있음

PDF 의 WMF 영역은 raster — pdftohtml/pdftotext 텍스트 추출 불가.
정확 fix ratio 는 작업지시자 시각 비교 필요.

산출물:
- examples/extract_wmf.rs (WMF binary 추출 도구)
- WMF 디코드: 20869 records (SETMAPMODE/SETWINDOWORG/SETWINDOWEXT 각 1회,
  CREATEFONTINDIRECT 437회, EXTTEXTOUT 554회)
src/wmf/converter/svg/mod.rs:
- tspan relative dx + excess_dx 휴리스틱 → absolute x + byte-aware DX 합산
- WMF DX 배열이 MBCS byte index 임을 인식, unicode_width::s.width()
  로 wide char (Korean=2 byte) 마다 2 entry 합산
- 폰트 fallback metric 영향 제거 (절대 좌표)

v2 scope 재정의: WMF unit scale 정합 → WMF renderer 근본 개선
- 수행 계획서 v2 + 구현 계획서 v2 작성
- Stage 4~9: viewport / EXTTEXTOUT flags / 폰트 metric / 회귀 / PR

회귀: cargo test --release --all-targets — 1412 passed / 0 failed

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Stage 4~8 회귀 검증 baseline 자료:
- samples/hwp3-sample17/18/19.hwp + HWP5/HWPX 변환본
- pdf/hwp3-sample17/18/19-hwp5-2022.pdf (한컴 변환 권위 자료)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ROPIC ratio 정합

src/wmf/converter/svg/device_context.rs:
- Viewport struct 추가 (x, y, origin_x, origin_y, ext_explicitly_set)
- point_s_to_{absolute,relative}_point → logical_to_device_delta 로 ratio 계산 분기
  - ViewportExt 명시 호출 시: (logical - WindowOrg) × (ViewportExt/WindowExt) + ViewportOrg
  - ViewportExt 미호출 시: window.scale 기존 동작 유지 (Task edwardkim#860 호환)
- viewport_ext / viewport_origin mutator

src/wmf/converter/svg/mod.rs:
- set_viewport_ext / set_viewport_origin 의 not implemented 영역 구현
- viewport context 갱신

회귀: cargo test --release --all-targets — 1412 passed / 0 failed
- sample16 byte-identical (viewport 미호출)
- sample14/17/18 정상 SVG 생성

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…E/PDY)

src/wmf/converter/svg/mod.rs:
- ETO_PDY (0x2000): DX 배열을 (dx, dy) 쌍으로 해석, tspan 에 y 절대좌표 추가
  - entries_per_byte 분기 (1 or 2), Korean wide char 시 4 entries 차지
- ETO_OPAQUE (0x0002): rectangle 영역에 bk_color 배경 rect 를 text 앞에 push
- ETO_CLIPPED: 기존 shape-inside polygon 유지, record.rectangle borrow 충돌
  해결 위해 as_ref() 로 변경

회귀: cargo test --release --all-targets — 1412 passed / 0 failed
sample16: SVG byte-identical (ETO_PDY/OPAQUE 미설정 → fallback)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
src/wmf/converter/svg/device_context.rs:
- offset_window_origin / offset_viewport_origin mutator (origin delta 합산)
- scale_viewport_ext mutator (viewport.ext × num/denom)

src/wmf/converter/svg/mod.rs:
- META_OFFSETWINDOWORG, META_OFFSETVIEWPORTORG, META_SCALEVIEWPORTEXT 의
  not implemented 영역 구현

잔존 미구현 (sample16-like 다이어그램 미사용, follow-up):
- Region/Palette/Clip/Pixel ops, set_mapper_flags, set_layout 등

회귀: cargo test --release --all-targets — 1412 passed / 0 failed
sample16: PNG byte-identical (해당 records 미호출)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
src/wmf/converter/svg/util.rs:
- is_garbled_latin1_korean() 헬퍼: extended Latin (U+0080~U+00FF) 만 으로
  구성된 fallback_facename garbage 검출 → font-family chain 제외
- Korean facename 검출 시 sans/serif 별 한국어 fallback chain 정합:
  - sans: Apple SD Gothic Neo, Malgun Gothic, 맑은 고딕, Nanum Gothic,
          Noto Sans KR, Noto Sans CJK KR, sans-serif
  - serif: Batang, 바탕, Nanum Myeongjo, AppleMyungjo, Noto Serif KR,
           Noto Serif CJK KR, serif
- 한글/영문 명칭 모두 포함 → fontconfig 검색 호환성 향상

회귀: cargo test --release --all-targets — 1412 passed / 0 failed
sample16 PNG: byte-identical (macOS Apple SD Gothic Neo 동일 선택)
font-family chain garbled "±¼¸²Ã¼" 제거 확인

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
검증 결과:
- cargo test --release --all-targets: 1412 passed / 0 failed
- golden SVG snapshot (8 tests): 통과
- SVG export 회귀:
  - sample14 (Task edwardkim#860 fixture): 11 페이지 정상
  - sample16 (edwardkim#902 본 대상): 64 페이지 정상
  - sample17: 12 페이지 정상
  - sample18: 69 페이지 정상
  - sample19: pre-existing parser 실패 (WMF 무관, follow-up)
- rsvg-convert PNG 셀프 검증: sample16 page 18, sample18 page 17 정상

잔존 한계 (follow-up):
- WMF 굴림체 ↔ Apple SD Gothic Neo glyph metric 미세 차이
- 사용 빈도 낮은 WMF records (Region/Palette 등)
- sample19 parser 실패 (별도 issue)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
mydocs/report/task_m100_902_report.md (최종 보고서):
- Task edwardkim#902 v1 → v2 전환 요약
- Stage 3~8 처리 결과
- ROOT CAUSE (DX byte-aware indexing) 상세
- 회귀 검증 (1412 passed)
- 잔존 한계 (follow-up 후보)

mydocs/orders/20260515.md (오늘 할일):
- Task edwardkim#902 완료 상태 기록 (PR 생성 대기)
- 작업 방식 메모 (v2 자동 진행, rsvg-convert 셀프 검증)

다음 step: 작업지시자 명시 승인 후 gh pr create.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Cargo.toml:
- resvg 0.47 을 native target 영역에서 non-optional 화 (이전: native-skia
  feature gated)
- native-skia feature 에서 dep:resvg 제거

src/renderer/svg.rs:
- rasterize_wmf_svg_to_png(): usvg + resvg + tiny-skia 로 WMF SVG 를
  PNG raster 로 렌더링. fontdb 에 Nanum Gothic / Nanum Myeongjo 명시적
  매핑 → cross-platform 폰트 정합. 2x supersampling 으로 crispness.
- render_image_node + draw_image 의 WMF 경로: raster 우선 시도, 실패 시
  (WASM 등) SVG embed fallback
- WASM target 은 stub function (None 반환 → SVG fallback 자동)

회귀: cargo test --release --all-targets — 1412 passed / 0 failed
      cargo test --release --test svg_snapshot — 8 passed

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…8 구조 차이 분석

ROOT CAUSE 발견:
sample16 bin3 WMF = POLYPOLYGON 10,476개 + DIBSTRETCHBLT 177개 + flood
fill 168개 (4.7MB, 20869 records). sample18 bin3 WMF = 단순 폴리곤+텍스트
(211KB, 14896 records, POLYPOLYGON=0). sample18 의 정상 렌더링은 우리
renderer 의 약점 영역을 트리거 안 함이 본질.

src/wmf/converter/svg/mod.rs poly_polygon:
- 기존: 각 서브폴리곤을 별도 <polygon> 요소로 분리 → fill-rule hole 처리
  무력화, SVG 요소 수 폭증 (10476 × N points)
- 수정: 단일 <path> + M/L commands 합성 → WMF spec 의 fill-rule 정합
  (음영/그라데이션 영역 hole 처리)

examples/extract_wmf.rs + examples/wmf_record_summary.rs:
- WMF binary 분석 도구

회귀: cargo test --release --all-targets — 1412 passed / 0 failed
      cargo test --release --test svg_snapshot — 8 passed

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
새 모듈 src/wmf/converter/raster/:
- mod.rs: LO MPL 2.0 attribution + 모듈 entry
- state.rs: RasterState (device context), RasterObject (pen/brush/font/region)
- player.rs: RasterPlayer (Player trait 구현)

구현 범위 (Stage 12):
- state context 추적 (window/viewport/text_align/colors/fonts)
- DC stack (SAVEDC/RESTOREDC)
- SetWindowExt/Org, SetViewportExt/Org, Offset/Scale
- SetTextColor/Align, SetBkMode/Color, SetPolyFillMode, SetROP2
- MoveTo, header (Placeable bbox → extent)
- Drawing records: stub (Stage 13)
- Object table: stub (Stage 13)
- generate(): 빈 흰색 PNG (Stage 13)

Cargo.toml:
- fontdue 0.9 (pure Rust glyph rasterizer)
- tiny-skia 0.11 (명시화, resvg 의 transitive)

라이센스: LO emfio (MPL 2.0) 알고리즘 참조 시 file-level reciprocity 유지.
본 Rust 포팅은 rhwp MIT 라이센스 하에 작성.

회귀: cargo test --release --all-targets — 1412 passed / 0 failed

Stage 13+ 점진적 구현: polygon/text/bitmap/object table 등 raster 렌더링.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…+ object table)

src/wmf/converter/raster/player.rs:
- Drawing records (tiny-skia 기반):
  * line_to: PathBuilder + stroke_path
  * polyline: 다중 점 연결 + stroke
  * polygon: fill + stroke (poly_fill_mode 적용)
  * poly_polygon: LO DrawPolyPolygon 포팅 — 단일 path 다중 서브경로,
    fill-rule winding/alternate 정합
  * rectangle: fill + stroke

- Object table:
  * create_brush_indirect: LogBrush enum 분기 (Solid/Hatched/Null)
  * create_pen_indirect: Pen.style.style PS_NULL 검출
  * create_font_indirect: Font → FontInfo
  * select_object: 종류 별 selected_{pen,brush,font} 설정
  * delete_object: 제거 + selected 해제

- Helper:
  * build_stroke_paint / build_fill_paint: selected obj → tiny-skia Paint
  * logical_to_pixel: WMF logical → MM_ANISOTROPIC device → canvas pixel

src/wmf/converter/raster/mod.rs:
- LibreOffice git URL 을 GitHub mirror 로 변경 (접근성)

미구현 (Stage 14+):
- text rendering (fontdue + LO DrawText)
- bitmap records, arc/ellipse/pie/chord, flood fill, region ops

회귀: cargo test --release --all-targets — 1412 passed / 0 failed
RasterPlayer 별도 모듈 — 디스패치 통합은 Stage 16 에서 결정.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… 알고리즘)

src/wmf/converter/raster/text.rs (NEW):
- 시스템 폰트 cache (OnceLock<Font>) — Korean/Latin 별도
  * Korean: NanumGothic > Malgun > AppleSDGothicNeo > Noto CJK
  * Latin: Helvetica > Arial > DejaVuSans
  * macOS/Linux/Windows 경로 자동 탐색
- pick_font_for_grapheme: Unicode 영역 (Hangul/CJK) 검출 후 분기
- draw_text_with_dx: LO mtftools.cxx::DrawText 포팅
  * DX 배열은 MBCS byte index — wide char 마다 2 entries
  * grapheme 별 advance = sum(DX[byte..byte+width])
  * fontdue 로 glyph rasterize, alpha-blend blit
- blit_alpha: 알파 채널 기반 픽셀 합성 (src.rgb*a + dst.rgb*(1-a))

src/wmf/converter/raster/mod.rs:
- text module 등록

src/wmf/converter/raster/player.rs:
- ext_text_out 구현: selected_font → FontInfo → text 디코드 → draw_text_with_dx
- LO 의 W_META_EXTTEXTOUT 핸들러 알고리즘 정합

미구현 (follow-up):
- font.weight (bold), italic (synthetic slant)
- font.escapement / orientation (회전)
- 굴림체 정확 metric (시스템 폰트 의존)

회귀: cargo test --release --all-targets — 1412 passed / 0 failed

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…spatcher API

src/wmf/converter/raster/player.rs:
- ellipse: KAPPA 4-cubic-bezier 근사 ellipse path, fill + stroke
- round_rect: 단순화 rectangle 처리 (라운드 코너 follow-up)

src/renderer/svg.rs:
- rasterize_wmf_direct (pub(crate)): WMF binary → RasterPlayer → PNG
- rasterize_wmf_direct_pub (pub): examples/외부 도구용 wrapper

examples/wmf_raster_test.rs (NEW):
- RasterPlayer 시각 검증 CLI 도구

검증:
- cargo build --release — Finished
- sample16 WMF (4.7 MB, 20869 records) → RasterPlayer → 92KB PNG (2434×1648)

dispatcher 정책: 기존 SVG 경로 유지 (안정), RasterPlayer 는 opt-in API.
Bitmap records (DIBSTRETCHBLT 177개) 미구현 한계로 default 전환은 follow-up.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
회귀:
- cargo test --release --all-targets: 1412 passed / 0 failed
- golden SVG snapshot: 8 passed
- sample14/16/17/18 SVG export: 모두 정상

RasterPlayer 시각 검증:
- sample16 (4.7MB WMF, 20869 records) → 92KB PNG (2434×1648)
- sample18 (211KB WMF) → 518KB PNG (1600×1200)
- LO 외부 변환 참조 (140KB, 794×1123)

한계 (follow-up):
- Bitmap records (DIBSTRETCHBLT 미구현 — sample16 임베디드 비트맵 누락)
- font weight/italic, arc/pie/chord 미적용
- 굴림체 정확 metric 부재

dispatcher: SVG 경로 default 유지, RasterPlayer 는 opt-in API.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
jangster77 and others added 7 commits May 16, 2026 16:54
… RasterPlayer 우선 사용"

This reverts commit ac75962.
Stage 28~29 의 WASM RasterPlayer 가 shapes/text 미렌더링 (bitmap 만 동작).
wasm-opt 최적화가 RasterPlayer 의 일부 코드 경로 (fontdue rasterize, tiny-skia
path 등) 를 잘못 제거할 가능성 진단.

Cargo.toml:
- [package.metadata.wasm-pack.profile.release] wasm-opt = false 추가
- tiny-skia features 단순화 (default features 사용)

빌드:
- pkg/rhwp_bg.wasm 10.1 MB → 11.0 MB (wasm-opt 비활성화)

브라우저 강제 새로고침 후 rhwp-studio 의 WMF 렌더링 호전 여부 확인 필요.
호전 시: wasm-opt 가 원인 → wasm-opt 옵션 조정.
미호전 시: 다른 원인 (tiny-skia/fontdue WASM 동작) → 추가 조사.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…yer 완성)

mydocs/report/task_m100_902_report.md:
- Stage 28~31 추가 (WASM 호환 RasterPlayer + 디버그)
- Stage 31 의 결정적 발견: wasm-opt 가 RasterPlayer 코드 잘못 제거
- rhwp-studio 의 LO 독립 동작 확인 (임베디드 NanumGothic + tiny-skia)

총 41 commits (upstream/devel 72a6bbc 이후):
- Stage 1~2: v1 분석 + 계획
- Stage 3~9: v2 SVG renderer 근본 개선
- Stage 10~11: B2 raster + POLYPOLYGON fix
- Stage 12~17: LO emfio 포팅 baseline + LO opt-in
- Stage 18~23: 점진 포팅 (bitmap/bold/arc/clipping/escapement)
- Stage 24~27: WASM SVG path 시도 (font embed, inline, textLength)
- Stage 28~31: WASM RasterPlayer (이번 완성)
  * 28: cfg gate 제거 + 임베디드 NanumGothic
  * 29: WebCanvasRenderer 가 RasterPlayer 사용
  * 30: object_table lowest-free-slot (WMF spec)
  * 31: wasm-opt 비활성화 (decisive fix)

회귀: 1412 passed / 0 failed (Native)
WASM 빌드: pkg/rhwp_bg.wasm 11.0 MB

다음 step: 작업지시자 명시 승인 후 gh pr create.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
메인테이너 (edwardkim) PR edwardkim#918 review 피드백 반영:

1. RasterPlayer 를 native 전용으로 분리 (WASM 빌드 크기 보존):
   - src/wmf/converter/mod.rs: raster module 에 #[cfg(not(target_arch = "wasm32"))] 가드
   - Cargo.toml: tiny-skia + fontdue 를 [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 로 이동
   - src/renderer/svg.rs: rasterize_wmf_direct, rasterize_wmf_direct_pub 에 cfg 가드
   - src/renderer/web_canvas.rs: WASM RasterPlayer 사용 (Stage 29) 제거 → 기존 SVG 경로 복원

2. NanumGothic 임베드 제거 (외부 폰트 경로 선호):
   - ttfs/embedded/NanumGothic.ttf 삭제 (4.7 MB)
   - ttfs/embedded/LICENSE.md 삭제
   - src/wmf/converter/raster/text.rs: include_bytes! 제거, 시스템 폰트 검색만 사용
   - sample16 화면 visual quality 다소 저하 (사용자가 --font-path 로 NanumGothic.ttf
     경로 지정 시 native 환경 quality 복원)

3. @font-face base64 embed 제거 (Stage 24):
   - src/wmf/converter/svg/mod.rs: build_font_face_style 함수 제거
   - WASM SVG 경로 의 NanumGothic woff2 (340 KB) embed 제거
   - 브라우저 fontconfig fallback 사용 (rhwp-studio 기본 폰트)

4. inline SVG embed 제거 (Stage 25):
   - src/renderer/svg.rs: wmf_svg_to_inline 함수 제거
   - WASM dispatcher: convert_wmf_to_svg → image href data URL (기존 방식)

5. wasm-opt = false 제거:
   - Cargo.toml: [package.metadata.wasm-pack.profile.release] 섹션 삭제
   - RasterPlayer 가 WASM 에서 빠지면 wasm-opt 안전

6. resvg optional 복원:
   - Cargo.toml: resvg = { version = "0.47", optional = true }
   - native-skia feature 에 dep:resvg 추가
   - src/renderer/svg.rs: rasterize_wmf_svg_to_png 에 #[cfg(feature = "native-skia")] 가드

7. MPL 2.0 file-level 헤더 추가 (LO emfio 포팅 파일):
   - src/wmf/converter/raster/{state.rs,player.rs,text.rs}: MPL 2.0 헤더
   - mod.rs 는 이미 헤더 있음

8. examples/wmf_to_svg.rs 제거 (Stage 24 디버그용, 더이상 불필요)

WASM 빌드 크기:
- 이전 (Phase 2): 11 MB (NanumGothic 4.7 MB + tiny-skia + fontdue 임베드)
- 본 Phase 3: **4.54 MB** (목표 ~4.5 MB 충족)

native 회귀: cargo test --release --all-targets — 1413 passed / 0 failed
WASM 빌드: wasm-pack build --release — pkg/rhwp_bg.wasm 4.54 MB

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…base64 embed 복원

사용자 선택 — Phase 3 (메인테이너 정책) 적용 후 rhwp-studio 시각 회귀 (박스
안 글자 밀림) 보고. 메인테이너 정책의 trade-off 일부 완화.

src/wmf/converter/svg/mod.rs:
- build_font_face_style() 함수 복원 (이전 Phase 3 commit 에서 제거)
  * include_bytes!("../../../../web/fonts/NanumGothic-Regular.woff2") — 340 KB
  * base64 encode + @font-face CSS 생성 (OnceLock 캐시)
  * SIL OFL 1.1 라이센스, web/fonts/ 의 기존 폰트 (Git 포함)
- SVGPlayer::generate(): SVG 출력 시작에 <style>@font-face</style> 추가
- WMF 내부 텍스트가 NanumGothicEmbedded 폰트로 일관 렌더링

RasterPlayer (native 전용) 의 NanumGothic.ttf 임베드는 제거 유지 (Phase 3 의
메인테이너 요청 정합). 본 변경은 SVG 경로만 영향.

빌드:
- cargo test --release --all-targets — 1413 passed / 0 failed
- wasm-pack build — pkg/rhwp_bg.wasm 4.66 MB (이전 Phase 3 4.54 MB + 약 340 KB)

WASM 크기: 4.66 MB (목표 ~4.5 MB 보다 0.16 MB 초과)
trade-off: rhwp-studio 시각 quality (한국어 glyph 너비 정합) 회복

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Root cause: set_text_align 의 vertical bits 파싱 버그 — VTA_TOP=0x0000 의
AND 검사가 항상 true 라서 TA_BASELINE (0x0018) 인 모드도 VTA_TOP 으로 잘못
매핑되어 baseline 이 cell-top 보정 (~font_size × 0.8) 만큼 하강 → 박스 하단
라인 걸침.

수정:
1. svg/mod.rs::set_text_align — vertical bits (0x0018 mask) 값으로
   BASELINE → BOTTOM → TOP 우선순위 분기 (이전 enum.into_iter().find() 로직 폐기)
2. svg/mod.rs::ext_text_out — baseline y shift 정합:
   VTA_BASELINE = 0, VTA_TOP = +ascent, VTA_BOTTOM = -descent
   이전 `font.height < 0 => -font.height` 잘못된 보정 제거
3. renderer/svg.rs — WMF → SVG 결과를 nested <svg> inline 임베드 (sandboxed
   <image data:svg> 우회). id/url(#) prefix 로 충돌 회피 (wmf{N}_).
4. svg/mod.rs — 옵션 2 woff2 base64 임베드 제거 (build_font_face_style)
   — root cause fix 적용으로 우회책 불필요. WASM 크기 ~1.5 MB 감소.

메인테이너 8개 조건 모두 충족 (woff2 제거 포함).
WASM 실측: 4.33 MB (목표 4.5 MB 이하).

검증:
- rsvg-convert 페이지 18 박스 내부 텍스트 정합
- cargo test --lib: 1258 passed / 0 failed
- cargo clippy: clean

보고서: mydocs/working/task_m100_902_v2_stage33_a.md

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@jangster77

Copy link
Copy Markdown
Collaborator Author

#928

exam_kor.hwp

image image

별도 이슈로 처리 예정입니다.

@jangster77

Copy link
Copy Markdown
Collaborator Author

#933 로 exam_kor.hwp 문제 PR 하였습니다.

@jangster77

Copy link
Copy Markdown
Collaborator Author

#935
#936
이어서 진행하겠습니다.

@jangster77

Copy link
Copy Markdown
Collaborator Author

Task #902 v2 의 raster 포팅 (Stage 9~31) 이 #936 의 시각 회귀 해결과 인과 무관함이 #936 의 자동 진단으로 입증됨. 본 task #936 의 v2 fix (Stage 7.2/7.5/7.7/7.9) 만으로 사용자 보고 시각 회귀 (외곽 overflow + paragraph 흐름 + 다이어그램 fit) 모두 해결. PR 전면 재검토 결정.

@jangster77 jangster77 closed this May 16, 2026
edwardkim pushed a commit that referenced this pull request May 17, 2026
…loses #957)

`samples/hwp3-sample16.hwp` page 18 의 "나. 주요 과업내용" 후 본문 paragraphs (pi=395~401 "○ 통합모델...") 이 다음 페이지로 밀려 시각 누락. 한컴 viewer 는 같은 페이지 표시.

`RHWP_DEBUG_TAC_CURSOR` 추적 결과:

```
Shape pi=394 ci=1 y_in=767.3 y_out=1197.9 dy=430.6 ⚠️
FullPara pi=395 y_in=1197.9 (body 외 영역)
```

`src/renderer/layout.rs:3470-3475` (layout_shape_item 의 Bottom caption advance):
- pi=394 ci=1 picture 의 caption: `dir=Bottom width=0 paras=1 text=""` (빈 caption)
- 빈 caption 임에도 layout 이 `cap_bottom = cap_y + caption_h` 계산
- pic_y = 767.3 (has_prior_tac 로 갱신된 잘못된 para_start_y)
- image_bottom = 767.3 + 411.89 (pic_h) = 1179.19
- cap_y = 1179.19, caption_h = 18.7 (empty paragraph default line height)
- cap_bottom = 1197.89 → result_y +430.6 phantom advance
- 다음 paragraph (pi=395) 가 1197.89 부터 시작 → body 외 emit

빈 caption 시 result_y advance skip — `paragraphs[*].text` 가 모두 무의미 문자 + `controls.is_empty()` 시 advance 분기 미진입.

`RHWP_DEBUG_TAC_CURSOR` 환경변수 영구화 (paragraph item 별 y_offset 추적 진단 도구).

- cargo test --release --lib: 1288 passed, 0 failed, 2 ignored
- sample16 page 18: pi=395~401 본문 같은 페이지 정상 emit (한컴 viewer page 16 정합)
- hwp3-sample14 (Task #864 영역, non-empty caption "Cut&Paste 할 영역" + empty 다수): 정상
- hwp3-sample10/11/13, exam_kor/math: 회귀 없음

| 영역 | 영향 |
|------|------|
| Empty caption picture | result_y advance 제거 (회귀 fix) |
| Non-empty caption picture | 영향 없음 |
| Caption None | 영향 없음 |
| Caption Top direction | 영향 없음 |

- 원 issue #952 + PR #956 — Issue 1 (페이지 외곽선 paper/body) 해결
- archive/task936 — 본 영역 이전 fix 시도 history (미완)
- PR #918 — 본 영역 시도 history (close)
- 잔존 — 원 #952 의 Issue 3 (HWP5 시험지 page 1 문9 vertical) 별도 task

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
edwardkim pushed a commit that referenced this pull request May 17, 2026
… 박스 외부 텍스트 표시 해소 (closes #965, ports PR #918 Stage 33-A)

`samples/hwp3-sample16.hwp` page 18 (한컴 page 16) 의 WMF 다이어그램 (주전산센터 목표시스템 구성안) 내부 박스의 한글 텍스트 ("PE6450", "기록서버", "Windows 서버군", "Unix 서버군" 등) 가 박스 외부로 벗어남.

`src/wmf/converter/svg/mod.rs:2191-2197` 의 SetTextAlign vertical bits 파싱:

```rust
let align_vertical = [
    VerticalTextAlignmentMode::VTA_BOTTOM,
    VerticalTextAlignmentMode::VTA_TOP,    // VTA_TOP = 0x0000
]
.into_iter()
.find(|a| record.text_alignment_mode & (*a as u16) == *a as u16)
.unwrap_or(VerticalTextAlignmentMode::VTA_BASELINE);
```

`mode & VTA_TOP(=0x0000) == 0x0000` 가 **항상 true** → BASELINE/BOTTOM 인 mode 도 VTA_TOP 으로 잘못 매핑 → `ext_text_out` 에서 +ascent (~em × 0.8) shift → baseline 이 cell-top 보정만큼 아래로 → 박스 하단 라인 걸침.

WMF spec [MS-WMF] 2.1.2.18:
- TA_TOP = 0x0000 (default)
- TA_BOTTOM = 0x0008
- TA_BASELINE = 0x0018

`src/wmf/converter/svg/mod.rs` 3 영역 (~60 lines):

vertical bits (0x0018 mask) 값 기준 BASELINE → BOTTOM → TOP 우선순위 분기.

```rust
let v_bits = record.text_alignment_mode & 0x0018;
let align_vertical = if v_bits == 0x0018 {
    VerticalTextAlignmentMode::VTA_BASELINE
} else if v_bits == 0x0008 {
    VerticalTextAlignmentMode::VTA_BOTTOM
} else {
    VerticalTextAlignmentMode::VTA_TOP
};
```

```rust
match self.context_current.text_align_vertical {
    VerticalTextAlignmentMode::VTA_TOP => +ascent (em × 0.8)
    VerticalTextAlignmentMode::VTA_BOTTOM => -descent (em × 0.2)
    VerticalTextAlignmentMode::VTA_BASELINE => 0
    _ => 0,
}
```

기존 `font.height < 0 => -font.height` 잘못된 보정 제거.

META_TEXTOUT 동일 baseline 보정 (ext_text_out 와 일관성).

PR #918 (closed, 5082 additions) 의 Stage 33-A 핵심 height/baseline 보정만 단독 포팅. 제외:
- LibreOffice emfio 포팅, WASM RasterPlayer, nested SVG inline embed, woff2 base64 임베드 제거, DX byte-aware indexing, POLYPOLYGON fill-rule

PR #918 close 사유 (다양한 부작용) 회피 + root cause fix 만 도입. + text_out baseline 추가.

- cargo test --release --lib: 1288 passed, 0 failed
- sample16 page 18 WMF 박스 내부 한글 텍스트 정상 위치 ✓ 한컴 viewer 정합
- WMF sample (sample14 page 0~8, sample4 page 1) PNG diff <1% (정상화 방향, 회귀 없음)

| 영역 | 영향 |
|------|------|
| WMF BASELINE/BOTTOM 모드 텍스트 | 정상 위치 (회귀 fix) |
| WMF TOP 모드 텍스트 | 기존 동작 (영향 없음) |
| 비-WMF | 영향 없음 |
| WASM 환경 | 정합 개선 예상 (Canvas2D 가 동일 SVG 사용) |

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
edwardkim added a commit that referenced this pull request May 17, 2026
…gn vertical bits 파싱 정정

@jangster77 — Issue #965: sample16 page 18 WMF 다이어그램 박스 내부 한글 텍스트
박스 외부 벗어남.

Root cause (src/wmf/converter/svg/mod.rs:2208 set_text_align): mode & VTA_TOP(=0x0000)
== 0x0000 항상 true → BASELINE/BOTTOM mode 도 VTA_TOP 오매핑 → +ascent shift →
baseline 박스 하단 걸침. WMF [MS-WMF] 2.1.2.18 spec 정합.

본질 (svg/mod.rs 3 영역 ~60 lines):
- set_text_align (:2208): v_bits = mode & 0x0018 마스킹 + 우선순위 BASELINE→BOTTOM→TOP
- ext_text_out (:811): baseline y shift 정합 (VTA_BASELINE=0, BOTTOM=-em*0.2, TOP=+ascent)
  — font.height < 0 잘못된 보정 제거
- text_out (:1545): META_TEXTOUT 동일 보정 (PR #918 미포함, 본 PR 추가)

PR #918 supersede: PR #918 (CLOSED 5/16, +5082/-74 거대 PR, 다양한 부작용
LibreOffice emfio/WASM RasterPlayer 등) → Stage 33-A root cause ~60 lines 만
단독 포팅. feedback_pr_supersede_chain (a) + feedback_small_batch_release_strategy
권위 사례.

본 환경 충돌 수동 해결: orders/20260517.md (--ours + Task #965 작업 일지 갱신).
svg/mod.rs auto-merge — devel PR #860/#864 (5/16, EMF/WMF image 렌더) 변경
보존 + PR #966 정정 양립 확인.

자기 검증: cargo test --release --lib 1288 passed / clippy 통과 /
광범위 sweep 7 fixture / 169 페이지 / 169 same / 0 diff / WASM 4.4 MB 재빌드
시각 판정: 작업지시자 시각 검증 통과 (sample16 p18 WMF 박스 한글 텍스트 정상
+ WMF sample14/4 + devel PR #860/#864 EMF/WMF 회귀 부재)
CI: ✅ Build & Test + CodeQL

@jangster77 연속 5 PR (#956~#964) 완결 후 추가 #966 (#968 후속)
edwardkim added a commit that referenced this pull request May 17, 2026
- mydocs/pr/archives/pr_966_review.md (WMF SetTextAlign vertical bits 분석)
- mydocs/pr/archives/pr_966_report.md (옵션 A + PR #918 supersede)
- mydocs/orders/20260517.md PR #966 행 추가

핵심:
- mode & VTA_TOP(=0) == 0 항상-true 버그 root cause
- WMF [MS-WMF] 2.1.2.18 spec 정합 (v_bits 0x0018 mask)
- PR #918 (CLOSED, +5082 거대 PR) → root cause ~60 lines 단독 포팅
- svg/mod.rs auto-merge (devel PR #860/#864 EMF/WMF image 렌더 보존)
- sweep 169/169 same + 작업지시자 시각 판정 통과
- Issue #965 close, 추가 PR #968 후속
@edwardkim edwardkim mentioned this pull request May 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants