fix(pptx): harden serialize output — media dedupe, DEFLATE, SVG fallback#94
Merged
Merged
Conversation
serializeDeck round-trips produced invalid / oversized packages:
- Media de-duplication: shared images (icons, logos, backgrounds) were
copied once PER reference under slidewise_preserved_N_ names — a 1.5 MB
deck ballooned to ~6 MB with one image written 9×. Copies are now keyed
on the immutable source path and written once; every referencing rel
points at that single copy (new PreservedPartRegistry / preserveSourcePart,
threaded through injectIntoSlide, copyPartDependencies, injectSlideBg).
- Compression: the package shipped with JSZip's default STORE (0%), so
multi-MB slide XML went out raw. generateAsync now uses DEFLATE, matching
the source archive. With dedupe: ~6 MB → ~1.1 MB on the repro deck.
- SVG raster fallback (1.19.1 follow-ups):
- F1: the last-resort transparent PNG had a bad IDAT CRC (decodes in
lenient readers, rejected by strict PNG/OOXML validators). Replaced
with a CRC-correct 1×1 transparent PNG.
- F2: added an optional `rasterizeSvg` hook to SerializeOptions so the
headless Node/SSR path can emit a faithful raster (host injects e.g.
@resvg/resvg-js) instead of a blank transparent PNG. Resolution order:
host hook → browser canvas → transparent fallback. Non-PNG/throwing
hook output is ignored.
Tests: media-dedup.test.ts (dedupe + DEFLATE) and svg-fallback.test.ts
(F1 CRC + F2 hook); both verified to fail pre-fix. Full suite 165 passing;
repo typecheck clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TL;DR
After a small edit, saving a deck ballooned it from 1.5 MB to ~6 MB and produced images that break outside PowerPoint. This hardens the serializer so a round-trip stays valid and compact — dedupe shared media, DEFLATE the package, and emit a valid SVG raster fallback (with an optional
rasterizeSvghook so our headless pipeline can rasterize faithfully).Summary
slidewise_preserved_N_names; one image was written 9×. Copies are now keyed on the immutable source path and written once, with every referencing rel pointing at that single copy. NewPreservedPartRegistry/preserveSourcePart, threaded throughinjectIntoSlide,copyPartDependencies, andinjectSlideBg.generateAsyncshipped with JSZip's defaultSTORE(0%), so multi-MB slide XML went out raw. NowDEFLATE(level 6), matching the source archive. Combined with dedupe: ~6 MB → ~1.1 MB on the repro deck.rasterizeSvghook toSerializeOptions(exportedSvgRasterizer). On Node/SSR (no canvas) the SVG raster fallback degraded to a blank transparent PNG; a host can now inject e.g.@resvg/resvg-js. Order: host hook → browser canvas → transparent fallback; non-PNG / throwing output is ignored.Review focus
SerializeOptions.rasterizeSvg+SvgRasterizertype (optional, backward-compatible). Chose a host-injected hook over a hard@resvg/resvg-jsdependency because the library is isomorphic and a native dep would break the browser bundle.relativeTarget; the dependency walk now runs once per source part across the whole serialize (reg.depsWalked).DEFLATEon already-compressed media (PNG/JPEG) is a negligible-CPU no-op; the win is on XML.Test plan
media-dedup.test.ts— shared image written once + both rels resolve it; package is DEFLATE-compressed (both fail pre-fix)svg-fallback.test.ts— F1 emits CRC-correct PNG on headless path; F2 uses host rasterizer; bogus/throwing rasterizer ignored (all fail pre-fix)