Repository navigation
Conversation
ikashapov
force-pushed
the
issue-150-wmf-svg
branch
6 times, most recently
from
September 12, 2026 09:48
7dbf620 to
9057063
Compare
ikashapov
force-pushed
the
issue-150-wmf-svg
branch
2 times, most recently
from
September 18, 2026 02:49
87eca00 to
ecfa239
Compare
Both formats were recognised as image parts and dropped — the space reserved, nothing drawn, because Skia's built-in decoder reads neither. WMF (issue nerdy-pro#150, [MS-WMF]): a new `render::wmf` mirrors `render::emf` — handle the single-embedded-bitmap case, decline what needs GDI replay. The optional placeable header is sniffed and skipped, the META_HEADER validated, and the record list scanned for `META_DIBSTRETCHBLT`, `META_DIBBITBLT` (each with the spec's own discriminator for their DIB-less second forms), `META_STRETCHDIB` and `META_SETDIBTODEV`. The payload is the same DeviceIndependentBitmap object EMF carries (§2.2.2.9), so the DIB decoding moved out of `emf.rs` into a shared `render::dib` — and grew what clip-art metafiles actually contain: the palette-indexed 1/4/8-bpp depths, the 12-byte BitmapCoreHeader, and the BI_JPEG/BI_PNG compressions, whose payload is one embedded stream handed to Skia's ordinary decoder. Both metafile paths gain all of it. The placeable magic also joins `ImageFormat::detect_by_magic`. SVG: Word embeds SVG as an `asvg:svgBlip` extension beside a rasterized PNG fallback the main blip points at — kept, per [MS-ODRAWXML] "Pictures", *for backward compatibility* with consumers that cannot draw SVG. The extension now parses (`Blip.svg_embed`), survives header/footer rel remapping, and outranks the fallback at selection — the reading LibreOffice 24.2 chose too. Producers that point the main blip straight at an `image/svg+xml` part (pandoc without a rasterizer) work the same way. Rendering is by resvg with default features off (pure Rust, ~40 crates): deliberately not skia-safe's own `svg` feature, which no prebuilt Skia binary pairs with `embed-freetype` — enabling it would turn every cold build into a from-source Skia build. The raster is produced at the painter's existing per-placement downsample target, so an SVG is crisp at exactly `--image-dpi` and the size-keyed bitmap cache keeps differently-sized placements apart. `<text>` inside an SVG is not rendered — the `text` feature would pull a second font stack beside Skia's; recorded in `render::svg`'s module doc. Fixtures (scripts/make_issue150_fixtures.py, red-first): a hand-built placeable WMF wrapping a 2×2 four-colour DIB, and an SVG document whose fallback PNG is blue while the svgBlip's SVG is red — the drawn colour says which part was picked — plus a no-fallback direct-SVG blip. `tests/wmf_svg_images.rs` pins parse → media → selection → an /Image XObject in the converted PDF; pixel-exact decode assertions live in the `render::dib`/`render::wmf`/`render::svg` unit tests. Closes nerdy-pro#150
ikashapov
force-pushed
the
issue-150-wmf-svg
branch
from
September 22, 2026 09:39
ecfa239 to
6e5aa99
Compare
ikashapov
added a commit
to ikashapov/dxpdf
that referenced
this pull request
Oct 14, 2026
One squashed commit carrying the tree of integration/2026-10-14, which was previously a chain of ten merges ending at ac5787e (kept locally as archive/integration-2026-10-14-merges). Base: upstream main 3a5c7ca. Integrated, each at the head of its open upstream pull request: - nerdy-pro#252 fix-layout-defects-228-232 (a40d5ce) — issues nerdy-pro#228–nerdy-pro#232: bookmark-only paragraphs, nested-table width, list instances over one abstract numbering, table width resolution (pct and tcW autofit), column balancing and separators - nerdy-pro#183 issue-150-wmf-svg (6e5aa99) — WMF/EMF and SVG images - nerdy-pro#184 issue-151-pgborders-docgrid (27204b3) — page borders, document grid - nerdy-pro#186 issue-153-shaped-clusters (65739f6) — shaped text clusters - nerdy-pro#187 issue-154-tracked-changes (6982b34) — tracked changes and comments - nerdy-pro#185 issue-155-smartart-charts (3683223) — SmartArt and charts - nerdy-pro#256 issue-253-del-instr-text (0656a52) — w:delInstrText tolerated - nerdy-pro#257 issue-250-image-hyperlinks (cb5ce2b) — a:hlinkClick on pictures - nerdy-pro#258 issue-188-font-absent-cost (9a63bf0) — no deep catalogue tier before the substitution table, no theme script-font preload - nerdy-pro#259 issue-158-auto-hyphenation (9bfbae2) — w:autoHyphenation Beyond the union of those branches, the integration itself changed: - README keeps one row per feature after the merges; the matrix tally is recomputed from the rows. - nerdy-pro#253's tests assert that a deleted field code is never drawn and never run text, because nerdy-pro#154 keeps w:del runs instead of dropping them at parse. - nerdy-pro#250's push_link_over sits after nerdy-pro#151's resolve_line_metrics; nerdy-pro#158's HyphenationCtx.measure and rejoin_hyphenated take &dyn SplitMeasure (nerdy-pro#153). Verified on this exact tree: cargo fmt, clippy and doc clean; cargo test 2977 passed / 0 failed (skipping the capi library freshness guards); --no-default-features and --features capi build; the twelve-document harness passes 12/12.
This branch has not been deployed
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.
Decode WMF and SVG images instead of detecting and skipping them
Closes #150. Based on
main(b2c0174, v0.5.1); one commit, branchissue-150-wmf-svg. Independent of the other open branches.What renders now
Both formats used to be recognised as image parts and dropped — the space
reserved, nothing drawn, because Skia's built-in decoder reads neither.
WMF —
render::wmf, built torender::emf's patternThe issue named
emf.rsas the model to follow, documented limitsincluded, and that is what this is: handle the single-embedded-bitmap
case, decline anything that needs full GDI record replay. Per [MS-WMF]:
0x9AC6CDD7key and skipped — standalone.wmffiles carry it,clipboard/RTF metafiles don't, so both spellings parse;
META_HEADER(§2.3.2.2) is validated on its two fixed fields(
HeaderSize= 9 words, version 0x0100/0x0300);META_DIBSTRETCHBLTandMETA_DIBBITBLT— matched on their low byte, as §2.1.1.1 requires, andeach guarded by the spec's own discriminator for their DIB-less second
forms (
RecordSize == (RecordFunction >> 8) + 3) — plusMETA_STRETCHDIBandMETA_SETDIBTODEV. SRCCOPY /DIB_RGB_COLORSonly, like the EMF twin.
The payload is the same
DeviceIndependentBitmapobject EMF carries —[MS-EMF] defines its bitmap records by reference to the [MS-WMF] objects —
so the DIB decoding moved out of
emf.rsinto a sharedrender::dib,and grew what clip-art metafiles actually contain:
biClrUsedshortening, an out-of-table index painting black rather than sinking
the bitmap);
stream handed to Skia's ordinary decoder;
§2.2.2.9) that WMF records use, next to EMF's separate-slices shape.
Both metafile paths gain all of it. RLE and 16-bpp decline — drawing
nothing beats drawing garbage. The placeable magic also joins
ImageFormat::detect_by_magic, so an extension-less WMF part classifies.SVG —
svgBlipparsed, the fallback outranked, resvg rasterizingWord embeds SVG as an
asvg:svgBlipextension(
{96DAC541-7B7A-43D3-8B79-37D633B846F1}, [MS-ODRAWXML]) beside arasterized PNG the main blip points at — kept, in the spec's own words,
"for backward compatibility" with consumers that cannot draw SVG. This
renderer now can, so:
Blip.svg_embed), tolerant of theother extensions Word writes into the same
extLst;id must follow its part's namespace);
chose too. Producers that point the main blip straight at an
image/svg+xmlpart (pandoc without a rasterizer at hand) take thesame decode path.
Why resvg and why a raster. Skia has its own SVG module, but
skia-safe'ssvgfeature forfeits the prebuilt-binary cache on everyplatform this project builds on — no published binary pairs
svgwithembed-freetype, and none carries it without a GPU backend orwebpintow — turning every cold build into a from-source Skia build.
resvgwith default features off is pure Rust (~40 crates, seconds to build,
MIT/Apache). The raster is produced at the painter's existing
per-placement downsample target, so an SVG comes out crisp at exactly
--image-dpiand the size-keyed bitmap cache keeps differently-sizedplacements apart; alpha stays premultiplied end to end. The one recorded
limit:
<text>inside an SVG is not rendered — thetextfeature wouldpull a second font stack (
fontdb/harfrust) beside Skia's, andOffice-inserted SVGs are overwhelmingly icons and diagrams.
Tests and fixtures
test-files/wmf-image.docx+test-files/svg-image.docx(new, byscripts/make_issue150_fixtures.py, written red-first). The WMF wrapsa hand-built placeable metafile around a 2×2 four-colour DIB. The SVG
fixture makes the selection observable: the fallback PNG is blue, the
svgBlip's SVG is red — which part was picked is the drawn colour — and
a second picture carries a no-fallback direct-SVG blip.
tests/wmf_svg_images.rspins the pipeline: the extension in themodel, both parts' formats in the media table, both draw commands
resolving to the SVG parts, and a converted PDF that embeds an
/ImageXObject where it used to embed none.equality pins both transcriptions), the DIB-less-form discriminator,
placeable/bare parity, non-SRCCOPY declines; the shared DIB decoder's
palette packing (nibble and bit order pinned on asymmetric patterns),
core header, BI_PNG passthrough, undersized-table behaviour, RLE
decline; resvg rasterization at the display target, intrinsic size,
transparency, malformed-input decline; and the EMF suite re-based onto
the shared decoder unchanged in behaviour.
the SVG row ❌ → ✅, and EMF's row records its widened DIB support.
cargo fmt --check,clippy --all-targets -- -D warnings,RUSTDOCFLAGS="-D warnings" cargo doc --no-depsand the full 45-targettest suite pass (macOS, pinned 1.95.0). New dependency:
resvg 0.48,default features off,
raster-images+svgzonly — no C code, nobuild-script downloads, MSRV 1.85 against the repo's pinned 1.95.