Skip to content

Accept SVG as an image, and keep it vector - #72

Merged
marcosqlbi merged 1 commit into
mainfrom
feature/svg-support
Aug 25, 2026
Merged

Accept SVG as an image, and keep it vector#72
marcosqlbi merged 1 commit into
mainfrom
feature/svg-support

Conversation

@marcosqlbi

Copy link
Copy Markdown
Collaborator

SVG now works everywhere an image works: clipboard paste, Explorer drag-and-drop, File → Import, and .wimport recipes. It is stored as its markup and redrawn at whatever size it is shown at, rather than rasterized once on arrival.

Why a dependency

WPF has no SVG decoder, so supporting SVG at all meant taking a rendering library — including the option of rasterizing on arrival, which needs one just the same. That made the choice about which library, not whether to have one.

SharpVectors.Wpf produces a DrawingGroup, which is the point on a canvas whose zoom is unbounded. It is BSD-3, managed-only, and ships no native binary, so it stays out of the signing step and out of the per-architecture question the installer would otherwise have to answer. Direct2D was the tempting alternative — Vortice is already referenced and it would have cost nothing — but it implements a restricted SVG subset with no <text> element, which is most of what a DAX SVG measure emits. Decision 21 records this.

What carried and what needed work

Adding .svg to ImportCatalog is what carries it into drag-and-drop and .wimport, since both already route through it. Paste needed the work. SVG arrives either under one of five clipboard format names or, more often here, as markup that was simply copied as text, so the shape of the content decides. The root element has to be svg — matching on "contains <svg" would make a page of HTML with a picture inside it paste as an image. Copying an SVG container publishes the markup and a bitmap together, so an editor receives the source and a slide receives the picture.

BoardImageCodec becomes the one place an asset's bytes turn into something drawable, and BoardSurface caches ImageSource rather than BitmapSource.

Two details worth stating

The renderer is given ExternalResourcesAccessModes.Ignore. Its default fetches whatever the markup names, which would let a pasted or dropped file turn opening a board into an outbound request.

A vector is grown to a legible edge on arrival, because icons are authored at 16 or 24 units and would otherwise land as a speck on a 2400-wide row. Enlarging costs a bitmap its sharpness and a vector nothing.

Compatibility

Assets were already stored as opaque bytes carrying a content type, so the board format did not move — it is still version 5. The decoder is chosen from the bytes rather than the stored content type, so boards written before this change are unaffected. A board holding an SVG opened in 1.0.3 draws the missing-image placeholder for it rather than failing to open.

.svg is deliberately not registered as a file type by the installer. Whiteboard opens SVG; it does not take it from the browser.

Verification

Beyond the smoke tests, the real code paths were exercised against a throwaway harness:

  • Shapes, <text>, gradients, namespace prefixes, viewBox-only, and a doctype prologue all render.
  • The author's declared canvas is preserved. EnsureViewboxSize was needed for this: without it a label centred in a 300×60 canvas arrived cropped to its glyphs at 147×26 and stretched.
  • An <image href="http://…"> is ignored, with no fetch.
  • A board with an SVG asset round-trips through .wboard byte-for-byte and renders identically at 1024×512 — vector, not a 200×100 bitmap enlarged.
  • docs/samples/contoso-workshop.wimport, which now includes an SVG container, resolves with zero missing files.

🤖 Generated with Claude Code

WPF has no SVG decoder, so this takes SharpVectors.Wpf. It produces a
DrawingGroup rather than pixels, which is the point on a canvas whose zoom is
unbounded: an SVG container is redrawn at whatever size it is shown at instead
of being rasterized once on arrival. Decision 21 records why that library and
not Direct2D, which is already referenced through Vortice but implements an SVG
subset with no <text> element - most of what a DAX SVG measure emits.

Adding .svg to the import catalog is what carries it into drag-and-drop and
.wimport, since both already route through it. Clipboard paste needed the work:
SVG arrives either under one of five format names or, more often here, as markup
that was simply copied as text, so the shape of the content decides. The root
element has to be svg, otherwise a page of HTML with a picture inside it would
paste as an image. Copying an SVG container puts the markup and a bitmap out
together, so an editor gets the source and a slide gets the picture.

Two details worth stating. The renderer is given
ExternalResourcesAccessModes.Ignore - its default fetches whatever the markup
names, which would let a dropped file turn opening a board into an outbound
request. And a vector is grown to a legible edge on arrival, because icons are
authored at 24 units and would otherwise land as a speck on a 2400-wide row;
enlarging costs a bitmap its sharpness and a vector nothing.

Assets were already stored as opaque bytes, so the board format did not move. A
board holding an SVG opened in 1.0.3 draws the missing-image placeholder rather
than failing to open.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@marcosqlbi
marcosqlbi merged commit 3e1feb1 into main Aug 25, 2026
4 checks passed
@marcosqlbi
marcosqlbi deleted the feature/svg-support branch August 25, 2026 15:42
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.

1 participant