Skip to content

feat: embed images as base64 data URIs in SVG/HTML export#335

Merged
edwardkim merged 2 commits intoedwardkim:develfrom
oksure:contrib/embed-image-base64
Apr 26, 2026
Merged

feat: embed images as base64 data URIs in SVG/HTML export#335
edwardkim merged 2 commits intoedwardkim:develfrom
oksure:contrib/embed-image-base64

Conversation

@oksure
Copy link
Copy Markdown
Contributor

@oksure oksure commented Apr 25, 2026

변경 요약

SVG/HTML 내보내기에서 이미지를 base64 data URI로 임베딩합니다.

기존에는 draw_image 트레이트 메서드가 회색 placeholder (<rect> / <div>)만 출력했습니다. 이제 render_pictureweb_canvas와 동일하게 이미지를 MIME 타입 감지 + base64 인코딩하여 data URI로 삽입합니다.

  • SVG: <image href="data:image/...;base64,..."/>
  • HTML: <img src="data:image/...;base64,..."/>
  • WMF 파일은 기존 convert_wmf_to_svg로 SVG 변환 후 임베딩
  • detect_image_mime_typepub(crate)로 변경하여 html.rs에서 재사용

검증

samples/hwp-img-001.hwp로 SVG 내보내기 확인:

  • 4개 이미지 (JPEG, PNG, BMP) 정상 임베딩
  • data URI 형식 확인: data:image/jpeg;base64,..., data:image/png;base64,..., data:image/bmp;base64,...

테스트

  • cargo test 통과 (937 tests, 0 failures)
  • cargo clippy -- -D warnings 통과
  • 관련 샘플 파일로 SVG 내보내기 확인 (hwp-img-001.hwp)
  • 웹(WASM) 렌더링 확인 (해당 없음 — web_canvas는 이미 구현됨)

Copilot AI review requested due to automatic review settings April 25, 2026 13:15
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

SVG/HTML 내보내기에서 이미지 데이터를 base64 data URI로 임베딩하여, 기존의 placeholder(회색 박스) 대신 실제 이미지가 포함된 결과물을 생성하려는 PR입니다.

Changes:

  • SvgRenderer::draw_image에서 MIME 감지 + base64 인코딩을 통해 <image href="data:...">로 임베딩
  • HtmlRenderer::draw_image에서 <img src="data:...">로 임베딩 및 WMF→SVG 변환 경로 추가
  • detect_image_mime_typepub(crate)로 변경하여 HTML에서 재사용

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/renderer/svg.rs SVG 이미지 렌더링용 data URI 생성 로직 추가, MIME 감지 함수 가시성 확장
src/renderer/html.rs HTML 이미지 렌더링용 data URI 생성 로직 추가, SVG 모듈의 MIME/WMF 변환 유틸 재사용
Comments suppressed due to low confidence (1)

src/renderer/svg.rs:2105

  • detect_image_mime_type is now reused by the HTML renderer, but it recognizes fewer formats than the WASM web_canvas implementation (e.g., WEBP via RIFF/WEBP signature and ICO via 00 00 01 00). As a result, those images would be exported with data:application/octet-stream;base64,... and won’t render in HTML/SVG. Consider extending this function to detect at least WEBP and ICO to match web_canvas behavior.
pub(crate) fn detect_image_mime_type(data: &[u8]) -> &'static str {
    if data.len() >= 8 {
        // PNG: 89 50 4E 47 0D 0A 1A 0A
        if data.starts_with(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]) {
            return "image/png";
        }
        // JPEG: FF D8 FF
        if data.starts_with(&[0xFF, 0xD8, 0xFF]) {
            return "image/jpeg";
        }
        // GIF: GIF87a or GIF89a
        if data.starts_with(b"GIF87a") || data.starts_with(b"GIF89a") {
            return "image/gif";
        }
        // BMP: BM
        if data.starts_with(&[0x42, 0x4D]) {
            return "image/bmp";
        }
        // WMF: Placeable (D7 CD C6 9A) 또는 Standard (01 00 09 00)
        if data.starts_with(&[0xD7, 0xCD, 0xC6, 0x9A]) || data.starts_with(&[0x01, 0x00, 0x09, 0x00]) {
            return "image/x-wmf";
        }
        // TIFF: II or MM
        if data.starts_with(&[0x49, 0x49, 0x2A, 0x00]) || data.starts_with(&[0x4D, 0x4D, 0x00, 0x2A]) {
            return "image/tiff";
        }
    }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/renderer/html.rs
Comment on lines +394 to 410
fn draw_image(&mut self, data: &[u8], x: f64, y: f64, w: f64, h: f64) {
let mime_type = detect_image_mime_type(data);
let (render_data, render_mime): (std::borrow::Cow<[u8]>, &str) = if mime_type == "image/x-wmf" {
match convert_wmf_to_svg(data) {
Some(svg_bytes) => (std::borrow::Cow::Owned(svg_bytes), "image/svg+xml"),
None => (std::borrow::Cow::Borrowed(data), mime_type),
}
} else {
(std::borrow::Cow::Borrowed(data), mime_type)
};
let base64_data = base64::engine::general_purpose::STANDARD.encode(&*render_data);
let data_uri = format!("data:{};base64,{}", render_mime, base64_data);
self.output.push_str(&format!(
"<div class=\"hwp-image\" style=\"position:absolute;left:{}px;top:{}px;width:{}px;height:{}px;background:#eee;\"></div>\n",
x, y, w, h,
"<img class=\"hwp-image\" src=\"{}\" style=\"position:absolute;left:{}px;top:{}px;width:{}px;height:{}px;\" />\n",
data_uri, x, y, w, h,
));
}
Comment thread src/renderer/html.rs
Comment on lines +394 to 410
fn draw_image(&mut self, data: &[u8], x: f64, y: f64, w: f64, h: f64) {
let mime_type = detect_image_mime_type(data);
let (render_data, render_mime): (std::borrow::Cow<[u8]>, &str) = if mime_type == "image/x-wmf" {
match convert_wmf_to_svg(data) {
Some(svg_bytes) => (std::borrow::Cow::Owned(svg_bytes), "image/svg+xml"),
None => (std::borrow::Cow::Borrowed(data), mime_type),
}
} else {
(std::borrow::Cow::Borrowed(data), mime_type)
};
let base64_data = base64::engine::general_purpose::STANDARD.encode(&*render_data);
let data_uri = format!("data:{};base64,{}", render_mime, base64_data);
self.output.push_str(&format!(
"<div class=\"hwp-image\" style=\"position:absolute;left:{}px;top:{}px;width:{}px;height:{}px;background:#eee;\"></div>\n",
x, y, w, h,
"<img class=\"hwp-image\" src=\"{}\" style=\"position:absolute;left:{}px;top:{}px;width:{}px;height:{}px;\" />\n",
data_uri, x, y, w, h,
));
}
oksure added 2 commits April 26, 2026 12:03
Previously draw_image rendered a grey placeholder rect (SVG) or empty
div (HTML). Now it detects the image MIME type, converts WMF to SVG
when needed, and encodes the image data as a base64 data URI.

This matches the existing behavior in render_picture and web_canvas,
bringing the SVG/HTML export backends to parity.

- SVG: <image href="data:image/...;base64,..."/>
- HTML: <img src="data:image/...;base64,..."/>
- Made detect_image_mime_type pub(crate) for reuse in html.rs
- WMF conversion reused from existing convert_wmf_to_svg

Verified with samples/hwp-img-001.hwp: 4 images (JPEG, PNG, BMP)
correctly embedded in SVG output.
Address Copilot review feedback:

1. render_node's RenderNodeType::Image branch now calls draw_image
   when image data is present, instead of always emitting a grey
   placeholder div. Falls back to placeholder when data is None.

2. Add 3 unit tests for draw_image: PNG, JPEG, and unknown format
   headers, asserting correct data URI MIME types in output.
@edwardkim edwardkim changed the base branch from main to devel April 26, 2026 03:13
@edwardkim edwardkim force-pushed the contrib/embed-image-base64 branch from 37a0850 to b882104 Compare April 26, 2026 03:13
@edwardkim
Copy link
Copy Markdown
Owner

@oksure 님, 두 번째 PR 감사합니다 🙏

검증

  • cargo test --lib: 1000 passed (997 → +3 신규 테스트)
  • svg_snapshot 6/6, issue_301, clippy clean, wasm32 clean
  • samples/hwp-img-001.hwp 시각 검증: JPEG/PNG 모두 base64 data URI 임베딩 확인

처리

PR #334 와 동일 패턴으로 cherry-pick + author 보존 + force-push + base devel 로 변경 완료. CI 통과 직후 admin merge 진행하겠습니다.

render_picture / web_canvas 와의 정합 + Copilot 리뷰 반영으로 추가하신 3 unit tests 까지, 일관된 품질로 두 번째 기여해주셔서 감사합니다.

Copy link
Copy Markdown
Owner

@edwardkim edwardkim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

검증 완료 (1000 lib + 6/6 svg_snapshot + clippy + wasm32 + CI SUCCESS + 시각 검증). admin merge.

@edwardkim edwardkim merged commit 1bac451 into edwardkim:devel Apr 26, 2026
11 checks passed
edwardkim added a commit that referenced this pull request Apr 26, 2026
@oksure 의 두 번째 PR. cherry-pick + author 보존 + force-push +
admin merge (commit 1bac451).

draw_image (svg/html) 의 placeholder → base64 data URI 임베딩.
render_picture / web_canvas 와의 backend 정합.

검증: 1000 lib (997 → +3 신규) + svg_snapshot 6/6 + clippy + wasm32 +
hwp-img-001.hwp 시각 검증 (JPEG/PNG data URI 임베딩 확인).

Co-Authored-By: Hyunwoo Park <oksure@gmail.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
edwardkim added a commit that referenced this pull request Apr 26, 2026
라이브러리 버전 동기화 (Cargo.toml / rhwp-vscode / npm/editor /
rhwp-studio): 0.7.3 → 0.7.6

브라우저 확장 (rhwp-firefox): 0.2.1 → 0.2.2 (AMO 재제출용)
- manifest strict_min_version 142 + viewer 번들 보안 sanitize 반영

본 사이클 외부 기여 PR:
- #268/#334 (@oksure): replaceOne API
- #279/#282 (@seanshin): 목차 리더 + 페이지번호 정렬
- #324/#327 (@planet6897): form-002 인너 표 페이지 분할
- #335 (@oksure): SVG/HTML draw_image base64 임베딩
- #338/#339 (@postmelee): Firefox AMO 워닝 해결
- #340/#341 (@planet6897): typeset 경로 정합
- #342/#343 (@planet6897): Task #321~#332 통합 + 회귀 해소

rhwp-firefox/README.md 에 v0.2.2 변경 이력 + 기여자 감사 섹션 추가
(@postmelee, @seanshin 인정).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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 동일 내용 영문 반영
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.

3 participants