Skip to content

Task #195 — OLE/Chart 네이티브 렌더링 (EMF + OOXML 차트 + HWPX + 네이티브 이미지)#221

Merged
edwardkim merged 18 commits into
edwardkim:develfrom
planet6897:task195-ooxml-chart-bitmap
Apr 20, 2026
Merged

Task #195 — OLE/Chart 네이티브 렌더링 (EMF + OOXML 차트 + HWPX + 네이티브 이미지)#221
edwardkim merged 18 commits into
edwardkim:develfrom
planet6897:task195-ooxml-chart-bitmap

Conversation

@planet6897

Copy link
Copy Markdown
Contributor

요약

OLE 개체를 단순 placeholder로 표시하던 문제를 해결하여 차트·임베딩 이미지·EMF 메타파일을 네이티브 SVG로 렌더링한다. HWP 바이너리 / HWPX 양쪽 포맷을 지원.

주요 변경

모델

  • ShapeObject::Chart(ChartShape), ShapeObject::Ole(OleShape) 추가 (src/model/shape.rs)

파서

  • OLE CFB 컨테이너 (src/parser/ole_container.rs)
    • \x02OlePres000 → EMF 프리뷰
    • OOXMLChartContents → OOXML 차트 XML
    • \x01Ole10Native → 네이티브 이미지(BMP/PNG/JPEG/GIF)
    • EMF/네이티브 부재 시 OlePres000의 DIB → 표준 BMP 재포장 fallback
  • HWPX 섹션 (src/parser/hwpx/section.rs)
    • hp:switch / <hp:case required-namespace=".../ooxmlchart"> / <hp:chart chartIDRef> dispatch
    • Chart/chart*.xml zip 엔트리를 BinDataContent로 주입 (id=60000+N, ext=ooxml_chart)
  • HWP 바이너리 (src/parser/control/shape.rs): CHART_DATA / SHAPE_COMPONENT_OLE 분기

OOXML 차트 (src/ooxml_chart/)

  • c:barChart + c:lineChart 콤보 인식 (시리즈별 타입 보존)
  • c:valAx axPos="l|r" → 이중 Y축 자동 매핑
  • a:srgbClr / a:schemeClr(accent1~6) 색상, c:formatCode 천 단위 콤마
  • 렌더러: nice-number 눈금, 적응형 라벨 여백, 라인 외곽선+원 마커, 범례 선·사각형 구분

EMF 네이티브 변환기 (src/emf/)

  • EMR_HEADER / 객체·상태 / 드로잉 / 텍스트·비트맵 레코드 파서
  • IR → SVG 컨버터 (convert_to_svg)
  • 단위 테스트 749줄

렌더러 (src/renderer/)

  • OLE arm 폴백: OOXML 차트 → EMF → 네이티브 이미지 → placeholder
  • HWPX용 find_bin_data sparse-ID fallback
  • 차트 XML은 base64 data URI + <image xlink:href+href> 이중 속성으로 삽입

검증

sample.zip

│ 파일 │ 포맷 │ Before │ After │
│ 차트1.hwp │ HWP │ 단일 파란 바 │ 녹색 바 + 파란 라인 + 이중축 + 콤마 │
│ 차트.hwpx │ HWPX │ 공백 페이지 │ 차트1.hwp와 동일 렌더 │
│ bitmap.hwp │ HWP (BMP) │ "OLE 개체" placeholder │ 손글씨 드로잉 정상 렌더 │

  • cargo test --lib ooxml_chart 10/10
  • cargo test --lib ole_container 4/4
  • cargo test --lib emf 전체 통과

범위 밖

  • 차트 겹침 positioning (Square-wrap 다중 OLE 자동 스태킹) — 레이아웃 엔진 별도 작업
  • 3D / 영역 / 산점도 차트
  • 테마 파일 기반 scheme 색상 (현재 Office 2016 기본값)

planet6897 and others added 18 commits April 19, 2026 12:40
- 수행계획서/구현계획서 (차트+OLE 프리뷰, 5단계 분할)
- CHART_DATA 구조/ChartShape IR 문서
- SHAPE_COMPONENT_OLE 구조/OleShape IR 문서
- BinData 스트림 압축 실측 기록 (1.hwp BIN0001/BIN0002 비표준 매직)
- 단계1 완료보고서
- ShapeObject enum에 Chart(Box<ChartShape>), Ole(Box<OleShape>) 추가
- ChartType/LegendPosition/Axis/Legend/DataSeries/OlePreview 등 타입 신규
- raw_chart_data/raw_tag_data 필드로 라운드트립 보존
- 8개 매치 사이트(렌더러/직렬화/object_ops/dump/tests) Chart/Ole arm 추가
- 렌더링은 placeholder Rectangle (단계 4에서 교체)
- cargo test: 875 passed 0 failed
- HWPTAG_SHAPE_COMPONENT_OLE를 shape_tag_id로 수집
- HWPTAG_CHART_DATA 존재 시 차트 우선 분류
- parse_ole_shape: extent/flags/drawing_aspect/bin_data_id 파싱 + raw 보존
- 미지 태그 Rectangle 폴백 경로 유지 (이제 차트/OLE는 여기로 안 옴)
- dump: 하드코딩 "도형" → shape_name() 사용
- 1.hwp 2개 OLE 컨트롤 정상 분류 확인
- 단위 테스트 3건 추가 (총 878 passed)
- Chart: 연한 파란 (#E8F0FE) + #4A90E2 대시 테두리
- Ole: 연한 회색 (#F0F0F0) + #909090 대시 테두리
- drawing의 fill/stroke가 이미 있으면 유지 (is_none일 때만 오버라이드)
- 1.hwp export-svg 확인: 2개 OLE placeholder 정상 렌더
- 기존 samples/ 회귀 테스트 (draw-group/aift/biz_plan): 크래시 없음
- 전체 테스트 878+13 passed
- 오늘할일(20260419) edwardkim#195 완료 항목 추가, 후속 이슈 2건 제안
- 최종 보고서 작성

자체 제작 samples/chart-basic.hwp은 한컴오피스 authoring 환경
부재로 분리 이슈로 이월.
- RenderNodeType::Placeholder variant 신규
- svg.rs draw_rect_with_gradient: stroke_dash 누락 수정 (기존 버그)
- Chart: '차트 (Chart)' 라벨, 연한 파란 배경
- Ole:  'OLE 개체 (BinData #NNN)' 라벨, 연한 회색 배경
- 중앙 정렬 텍스트, 폰트 크기는 박스 크기 기준 clamp(12, 28)
- 1.hwp 페이지 3/4 육안 검증: 라벨 정상 렌더
- cargo test: 878 passed 0 failed
작업지시자 요청: placeholder를 넘어 실제 OLE 데이터 렌더링까지 확장
- 단계 6: BinData 스트림 해제 인프라 (zlib raw deflate + DocInfo 플래그)
- 단계 7: 내부 CFB 파싱 (OlePres000/OOXMLChartContents/Contents)
- 단계 8: OOXML 차트 네이티브 SVG 렌더 + EMF 폴백

1.hwp 실측 기반:
- BIN0001.OLE: 30KB → 해제 후 384KB CFB
- 내부: EMF 프리뷰 + OOXML barChart XML 모두 존재
- 1차 경로는 OOXML(벡터 품질, 순수 Rust), 폴백은 EMF 파일 참조
단계 6 — BinData 해제 인프라:
- load_bin_data_content: Storage(OLE) 타입도 로드 (기존은 Embedding만)
- OLE 해제 후 4-byte size prefix 자동 스킵하여 CFB 매직부터 노출

단계 7 — 내부 CFB 파싱:
- src/parser/ole_container.rs 신규
- cfb crate로 중첩 OLE 컨테이너 파싱
- \x02OlePres000 / OOXMLChartContents / Contents 스트림 추출
- OLE Presentation Stream 헤더 스킵 후 EMF 바이트 반환

단계 8 — OOXML 차트 렌더:
- src/ooxml_chart/ 신규 모듈 (parser + renderer)
- quick-xml으로 DrawingML 파싱 → OoxmlChart 데이터 모델
- 지원: barChart(col/bar) / lineChart / pieChart
- RenderNodeType::RawSvg variant 신규 (사전 생성 SVG 조각 삽입)
- shape_layout.rs Ole arm: OOXML 성공 시 실제 차트, 실패 시 placeholder 폴백
- parse_ole_shape의 bin_data_id 오프셋 수정 (1.hwp 실측 기반)

1.hwp 검증:
- 페이지 3: 월별 기부 금액/건수 막대 차트 (2 시리즈 × 12)
- 페이지 4: 시간대별 기부 건수 막대 차트
- 이전 placeholder 텍스트 대신 실제 데이터 기반 벡터 차트

cargo test: 890 passed 0 failed (기존 878 + 신규 12)
## 변경 사항

### ooxml_chart (콤보 차트 + 이중 Y축)
- OoxmlSeries에 series_type/axis_group/axis_ids/format_code 필드 추가
- 파서: barChart/lineChart 공존 인식, valAx axPos로 primary/secondary 축 매핑
- 시리즈 색상 추출: spPr의 srgbClr (직접 RGB) + schemeClr (accent1~6 매핑)
- 숫자 포맷: c:formatCode 파싱, 천 단위 콤마 지원
- 렌더러: 콤보 렌더, 이중 Y축, 라인 데이터 포인트 마커, nice-number 눈금

### HWPX 차트 파싱 (신규)
- parse_hwpx: Chart/chartN.xml을 BinDataContent에 주입 (id=60000+N, extension=ooxml_chart)
- section.rs: hp:switch/hp:case/hp:chart 엘리먼트 핸들러 추가
- hp:switch 내부 OOXML 차트 우선, 없으면 hp:ole fallback
- 공통 shape 속성(sz, pos, outMargin) 파싱 헬퍼 추가

### 렌더러 연결
- find_bin_data: 1-indexed 순번 실패 시 id 필드로 직접 검색 fallback
- shape_layout Ole arm: extension=ooxml_chart면 CFB 파싱 건너뛰고 직접 렌더
## 변경 사항

### ole_container.rs
- NativeImageKind enum (Bmp/Png/Jpeg/Gif) 추가, mime() 유틸 제공
- OleContainer.native_image: Option<(NativeImageKind, Vec<u8>)> 필드 추가
- 파싱 경로:
  1. \x01Ole10Native 스트림 인식: 4바이트 LE 길이 프리픽스 이후 매직 바이트로 포맷 판별
  2. EMF/OOXMLChart/Ole10Native 모두 부재 시 OlePres000에서 DIB 추출 폴백
     BITMAPINFOHEADER(biSize=40) 스캔 → 14바이트 BMP FILEHEADER 합성 → 표준 BMP 재포장
- detect_native_image(), extract_dib_as_bmp() 유틸 추가

### shape_layout.rs (Ole arm)
- OLE 렌더 폴백 순서 확장: OOXML 차트 → EMF → native_image → placeholder
- native_image 렌더: base64 data URI를 xlink:href + href 이중 속성으로 <image> 요소 생성

## 동기
bitmap.hwp처럼 OLE 컨테이너에 BMP만 임베딩된 경우 EMF 프리뷰 부재로
'OLE 개체 (BinData #1)' placeholder만 표시되던 문제 해결. 손글씨 드로잉,
스캔 이미지, OLE-embedded 스크린샷 등이 실제 이미지로 렌더링됨.

## 검증
- bitmap.hwp → 손글씨 선 그림 정상 렌더링
- 차트1.hwp 회귀 없음 (2차트 정상)
- cargo test --lib ole_container 통과 (4/4)
@edwardkim edwardkim merged commit d862c0e into edwardkim:devel Apr 20, 2026
edwardkim added a commit that referenced this pull request Apr 20, 2026
PR #181 (by @seunghan91) 가 2026-04-17 기준으로 작성된 후 다음 두 PR 이 devel 에 머지되어 SVG 출력이 변경됨:

- #213 (cherry-picked by @jskang): HWPX char offsets interleaved 보정
- #221 (by @planet6897): OLE/Chart/EMF 네이티브 렌더링 + HWPX 차트 파서

두 fix 가 form-002 렌더링에 반영되어 원본 golden 과 diff 발생.
`UPDATE_GOLDEN=1 cargo test --test svg_snapshot` 로 재생성 후 결정성 확인 완료.

Verification:
- cargo test --test svg_snapshot → 2 passed (form_002_page_0 + render_is_deterministic_within_process)
- 재실행 back-to-back 결정성 유지

Refs #181 · #173

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
edwardkim added a commit that referenced this pull request Apr 20, 2026
…est 실패 수정)

PR #221 Stage 3에서 추가된 parse_ole_shape 의 doc comment 가 언어 지시자 없는 \`\`\` 블록으로 되어있어, rustdoc 이 Rust 코드로 해석 시도 → \"01 00 00 00\" + 화살표(←, U+2190) 등이 Rust 토큰으로 파싱 실패.

수정: \`\`\` → \`\`\`text 로 변경하여 doctest 대상에서 제외.

검증:
- cargo test --doc: 컴파일 에러 0, 0 passed / 0 failed
- cargo test --lib: 935 passed, 0 failed (기존 그린 유지)

CI 실패 로그: run #24669204138 (2026-04-20T13:30)
  error: unknown start of token: \\u{2190}
  --> src/parser/control/shape.rs:295:32

Refs #221 #195

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
edwardkim added a commit that referenced this pull request Apr 20, 2026
CI가 cargo clippy -- -D warnings 로 돌아 warning 을 error 처리. PR #221 신규 코드에서 3건 위반:

1. src/parser/hwpx/section.rs:2939 — unused-parens
   ole.bin_data_id = (60000u32 + chart_num as u32);  →  괄호 제거
2. src/emf/converter/device_context.rs:76 — doc-lazy-continuation
   doc 리스트 아래 설명 단락 앞에 빈 주석줄 추가
3. src/ooxml_chart/renderer.rs:55 — manual-is-multiple-of
   (len - i) % 3 == 0  →  (len - i).is_multiple_of(3)

검증:
- cargo clippy -- -D warnings: 0 errors, 0 warnings
- cargo test --lib: 935 passed

CI 실패 로그: run #24670427894

Refs #221

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
edwardkim added a commit that referenced this pull request Apr 20, 2026
2026-04-20~21 사이클 정리:

PR 처리 9건:
- admin merge 5: #209 #214 #215 #221 #224
- cherry-pick + close 2: #213 (+중복 #210 close), #181 (+golden 재생성)
- dependabot close 2: #211 #212 (devel 수동 bump + target-branch=devel 설정)
- 보류 1: #165 skia (별도 사이클)

이슈 close 7: #173 #195 #202 #205 #207 #210 #222
신규 이슈 등록 1: #204 (표 Undo/Redo)

Chrome Web Store / Edge Add-ons v0.2.1 심사 통과 (2026-04-21).

local/task205 폐기: PR #209+#214 가 중복 처리하여 잔여 고유 기여 분리 곤란.

기여자 7명 감사 기록.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
edwardkim added a commit that referenced this pull request Apr 21, 2026
한글판 README.md 와 영문판 README_EN.md 의 섹션 구조·내용을 1:1 일치시킴.

주요 변경:
- Roadmap + Milestones 섹션을 상단(로드맵/이정표 위치) 으로 이동
- v0.5.0 ~ v0.7.x 이정표에 v0.2.1 사이클 전체 반영 (Chrome/Edge 심사 통과, PR #213/#215/#221/#169/#209/#214/#224/#181)
- rhwp-firefox (v0.1.1 AMO 준비) + rhwp-safari (v0.2.1) 섹션 추가
- 기여자 감사 목록 확장: @seo-rii, @planet6897, @yl-star7 추가 (9명)
- Features: OLE/Chart/EMF native rendering 항목 추가 (#221)
- Project Structure: src/emf/, src/ooxml_chart/, rhwp-firefox/, rhwp-shared/ 반영
- Trademark 섹션 신규 (한글판 동일 위치)
- AI 페어 프로그래밍 섹션: Git Workflow / Task Workflow / Debugging Protocol / Documentation Rules 완전 반영
- 로드맵 링크를 mydocs/eng/report/rhwp-milestone.md 로 보정
- 테스트 수치 783 → 935

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
edwardkim added a commit that referenced this pull request Apr 22, 2026
한글판 README.md 와 영문판 README_EN.md 의 섹션 구조·내용을 1:1 일치시킴.

주요 변경:
- Roadmap + Milestones 섹션을 상단(로드맵/이정표 위치) 으로 이동
- v0.5.0 ~ v0.7.x 이정표에 v0.2.1 사이클 전체 반영 (Chrome/Edge 심사 통과, PR #213/#215/#221/#169/#209/#214/#224/#181)
- rhwp-firefox (v0.1.1 AMO 준비) + rhwp-safari (v0.2.1) 섹션 추가
- 기여자 감사 목록 확장: @seo-rii, @planet6897, @yl-star7 추가 (9명)
- Features: OLE/Chart/EMF native rendering 항목 추가 (#221)
- Project Structure: src/emf/, src/ooxml_chart/, rhwp-firefox/, rhwp-shared/ 반영
- Trademark 섹션 신규 (한글판 동일 위치)
- AI 페어 프로그래밍 섹션: Git Workflow / Task Workflow / Debugging Protocol / Documentation Rules 완전 반영
- 로드맵 링크를 mydocs/eng/report/rhwp-milestone.md 로 보정
- 테스트 수치 783 → 935

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@planet6897 planet6897 deleted the task195-ooxml-chart-bitmap branch April 22, 2026 07:48
edwardkim added a commit that referenced this pull request Apr 23, 2026
…ot 반영)

피드백 mydocs/feedback/manual_currency_audit.md 결정 반영. 최근 사이클의 주요 변경을
매뉴얼에 동기화하고, PR 처리 절차 매뉴얼을 신설.

[A] publish_guide.md
- 버전 예시 0.7.0 → 0.7.3 (현재 릴리즈 기준)
- 커맨드 예시 갱신 (git tag v0.7.3 등)
- 테스트 수치 783 → 941
- 신규 섹션 "브라우저 확장 버전 정책 (라이브러리와 이원화)" — v0.2.x 정책 명문화

[B] chrome_edge_extension_build_deploy.md → 통합 확장 매뉴얼로 확장
- 제목: "브라우저 확장 빌드 및 배포 매뉴얼 (Chrome/Edge/Firefox/Safari)"
- 빌드 크기 실측치 갱신 (WASM 3.9MB · 전체 23MB)
- 테스트 페이지에 06-security.html 추가
- 4.4 절 Firefox (AMO 제출 절차) + 4.5 절 Safari (macOS 전용 빌드) 신규

[C] browser_extension_dev_guide.md
- 제목: Safari/Chrome/Edge → Safari/Chrome/Edge/Firefox
- Service Worker vs Background Scripts 표에 Firefox 열 추가
- 9절 신규 "chrome.* vs browser.* 네임스페이스 차이"
- 10절 신규 "rhwp-shared/ 공통 모듈 + 심볼릭 링크 + dereference 빌드 패턴" (PR #214)
- 11절 신규 "Chrome onDeterminingFilename vs Firefox onCreated+onChanged"

[D] hyper_waterfall.md
- 783+ 테스트 → 941+ 테스트
- "1인 개발" → "1인 메인테이너 + Claude Code AI + 외부 기여자 9명 (v0.2.1 기준)"
- "v0.6.0 릴리즈" → "v0.5.0 공개 릴리즈 (뼈대 완성 지점)"

[E] e2e_verification_guide.md
- 신규 섹션 "SVG 회귀 검증 (Rust 유닛 테스트 기반)" — PR #181 하네스
- UPDATE_GOLDEN=1 사용법, 결정성 재확인 절차
- 경고: 렌더 영향 PR 머지 후 golden 재생성 필수 (PR #221 / #251 2회 재현)
- 신규 섹션 "향후 작업 — 한컴 PDF 기준 Visual Diff 하네스" — 이슈 #253

[F] onboarding_guide.md
- E2E 모드 설명 환경 일반화 (Windows Chrome → 로컬/컨테이너 헤드리스 · 호스트 Chrome/Chromium)

[G.2] 신규 mydocs/manual/pr_review_workflow.md
- PR 리뷰 · 통합 워크플로우 표준화
- 리뷰 문서 2종 (review + impl) 작성 절차
- 로컬 사전 검증 (빌드/테스트/Clippy/doctest/svg_snapshot)
- Admin merge 후 필수 후속 (이슈 close · 감사 코멘트 · devel sync · golden 재생성 · archives 이동)
- 재작업 요청 패턴 (PR #234#251 사례)
- 예외 케이스 (dependabot · 오래된 base · 대형 PR)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
edwardkim added a commit that referenced this pull request Apr 23, 2026
…ot 반영)

피드백 mydocs/feedback/manual_currency_audit.md 결정 반영. 최근 사이클의 주요 변경을
매뉴얼에 동기화하고, PR 처리 절차 매뉴얼을 신설.

[A] publish_guide.md
- 버전 예시 0.7.0 → 0.7.3 (현재 릴리즈 기준)
- 커맨드 예시 갱신 (git tag v0.7.3 등)
- 테스트 수치 783 → 941
- 신규 섹션 "브라우저 확장 버전 정책 (라이브러리와 이원화)" — v0.2.x 정책 명문화

[B] chrome_edge_extension_build_deploy.md → 통합 확장 매뉴얼로 확장
- 제목: "브라우저 확장 빌드 및 배포 매뉴얼 (Chrome/Edge/Firefox/Safari)"
- 빌드 크기 실측치 갱신 (WASM 3.9MB · 전체 23MB)
- 테스트 페이지에 06-security.html 추가
- 4.4 절 Firefox (AMO 제출 절차) + 4.5 절 Safari (macOS 전용 빌드) 신규

[C] browser_extension_dev_guide.md
- 제목: Safari/Chrome/Edge → Safari/Chrome/Edge/Firefox
- Service Worker vs Background Scripts 표에 Firefox 열 추가
- 9절 신규 "chrome.* vs browser.* 네임스페이스 차이"
- 10절 신규 "rhwp-shared/ 공통 모듈 + 심볼릭 링크 + dereference 빌드 패턴" (PR #214)
- 11절 신규 "Chrome onDeterminingFilename vs Firefox onCreated+onChanged"

[D] hyper_waterfall.md
- 783+ 테스트 → 941+ 테스트
- "1인 개발" → "1인 메인테이너 + Claude Code AI + 외부 기여자 9명 (v0.2.1 기준)"
- "v0.6.0 릴리즈" → "v0.5.0 공개 릴리즈 (뼈대 완성 지점)"

[E] e2e_verification_guide.md
- 신규 섹션 "SVG 회귀 검증 (Rust 유닛 테스트 기반)" — PR #181 하네스
- UPDATE_GOLDEN=1 사용법, 결정성 재확인 절차
- 경고: 렌더 영향 PR 머지 후 golden 재생성 필수 (PR #221 / #251 2회 재현)
- 신규 섹션 "향후 작업 — 한컴 PDF 기준 Visual Diff 하네스" — 이슈 #253

[F] onboarding_guide.md
- E2E 모드 설명 환경 일반화 (Windows Chrome → 로컬/컨테이너 헤드리스 · 호스트 Chrome/Chromium)

[G.2] 신규 mydocs/manual/pr_review_workflow.md
- PR 리뷰 · 통합 워크플로우 표준화
- 리뷰 문서 2종 (review + impl) 작성 절차
- 로컬 사전 검증 (빌드/테스트/Clippy/doctest/svg_snapshot)
- Admin merge 후 필수 후속 (이슈 close · 감사 코멘트 · devel sync · golden 재생성 · archives 이동)
- 재작업 요청 패턴 (PR #234#251 사례)
- 예외 케이스 (dependabot · 오래된 base · 대형 PR)

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