Skip to content

feat: add svg as a DocumentFormat read into the drawing variant - #614

Merged
Mearman merged 1 commit into
mainfrom
feat/svg-read
Aug 17, 2026
Merged

feat: add svg as a DocumentFormat read into the drawing variant#614
Mearman merged 1 commit into
mainfrom
feat/svg-read

Conversation

@Mearman

@Mearman Mearman commented Aug 17, 2026

Copy link
Copy Markdown
Member

Closes #82

svg joins the twelve DocumentFormat members as a drawing-variant format, mapping the six shape primitives (rect/circle/ellipse/line/polyline/polygon/path) onto a one-page drawing ContentDocument — the same mapping odf.js performs for ODF's draw: primitives, per the issue's framing.

Read/write adapter family (src/svg/)

  • text.ts — byte↔text boundary, rejecting malformed UTF-8 (same contract as csv/markdown).
  • read.ts — the six primitives onto vectors; transform lists composed as one 2×3 affine; CSS lengths and the viewBox map resolved into page points (1 user unit = 1px = 0.75pt exactly). Root size falls back to the CSS 300×150px default under svg/default-size-assumed; aspect mismatch reports svg/preserve-aspect-ratio-stretched.
  • write.ts — one SVG shape element per vector; the writer emits a 1pt-per-unit root so read-back is the identity map and round trips are exact.
  • path.ts — the full path-data grammar: M/L/H/V/C/Z overlap with the schema segments exactly; S/Q/T convert exactly (a quadratic elevates to an exact cubic; T reflects the previous quadratic's own control); A is the one bounded approximation — exact F.6.5 endpoint-to-centre parameterisation with the F.6.5.6 radii correction, then kappa cubics at ≤90° per segment.
  • transform.ts — transform-list parse/compose plus frame-representability classification: an axis-aligned map (any scale, mirrors included) folds into the frame, a similarity rotation records rotationDeg about the frame's centre, a shear or rotation-composed non-uniform scale narrows to path. The affine itself is exact in every case.
  • units.ts, paint.ts — CSS length units/root fallback chain; fill/stroke presentation attributes and dash styles.
  • diagnostics.tsSVG_DIAGNOSTIC_CODES, the scope-limit vocabulary. Text, images, use references, gradients, CSS style blocks, and opacity are named diagnostics on onSvgDiagnostic, never silent drops — the same contract as markdown's construct-mapping vocabulary. A plain vector SVG reads silently.

Composition wiring (parallels the csv commit 75180a1)

  • 111 routable pairs. svgToOdg/odgToSvg bridge svg to its drawing sibling — same-variant direct copy, PDF-bypassing, lossless for the six primitives. svgToPdf/pdfToSvg run through the existing drawing layout engine with zero new layout code; pdfToSvg reuses pdfToOdg's near-1:1 vector recovery, and recovered text boxes sit outside the vector-only writer scope, reported per shape.
  • svgPdfCodec/odgSvgCodec as z.codec() pairs; registry codec writes page 0 by default.
  • SvgBytesSchema (well-formed UTF-8 + a real <svg tag); metadata reads root <title> as metadata.title, setDocumentMetadata rejects svg in both directions (<title> is svg's whole metadata surface).
  • Writing svg takes exactly one page: SvgMultiPageNotSpecifiedError names the page count until { page } selects one (drawing pages are anonymous, where csv's sheets are named).
  • svg→csv and svg→markdown honestly produce empty output — no text in the read's scope, no vector vocabulary in the target — pinned in the round-trip matrix, which now re-runs with { page: 0 } the way the csv sweep re-runs with { sheet }.

Full quality bar green: lint, typecheck, build, 2238 unit tests (76 new across the five svg suites), 3 worker tests, 66 smoke tests.

Generated by Claude Code

svg joins the twelve DocumentFormat members as a drawing-variant format:
reading maps the six shape primitives (rect/circle/ellipse/line/polyline/
polygon/path) onto a one-page drawing ContentDocument, writing emits one
shape element per vector. Transforms compose into a single 2x3 affine
classified by frame representability (axis-aligned maps fold into the
frame, a similarity rotation records rotationDeg about the frame's
centre, shears and rotation-composed non-uniform scales narrow to a
path); CSS lengths and the viewBox map resolve into page points at
1 user unit = 1px = 0.75pt exactly.

The adapter family lives in src/svg/: text.ts (byte<->text boundary
rejecting malformed UTF-8), read.ts and write.ts, path.ts (the full
path-data grammar -- M/L/H/V/C/S/Z plus Q/T/A and the relative forms;
S/Q/T convert exactly, A is the one bounded approximation at <=90deg
per kappa cubic after the exact endpoint-to-centre parameterisation),
transform.ts (transform-list parse/compose and classification), units.ts
(CSS length units and the root fallback chain), paint.ts (fill/stroke
presentation attributes and dash styles), and diagnostics.ts (the
SVG_DIAGNOSTIC_CODES scope-limit vocabulary: text, images, use,
gradients, CSS, and opacity are named diagnostics on onSvgDiagnostic,
never silent drops).

The composition engine now routes 111 (source, target) pairs: svg
bridges to its drawing sibling odg (svgToOdg/odgToSvg -- same-variant,
PDF-bypassing, lossless for the six primitives) and reaches PDF through
the drawing layout engine odg already feeds (svgToPdf/pdfToSvg, where
recovered text boxes sit outside the vector-only writer scope and are
reported per shape). Writing svg takes exactly one page:
SvgMultiPageNotSpecifiedError names the page count until { page }
selects one. svgPdfCodec and odgSvgCodec expose the same pairs as
z.codec() forms.

SvgBytesSchema validates well-formed UTF-8 plus a real <svg tag; svg
reads its root <title> as metadata.title and setDocumentMetadata
rejects svg in both directions, since <title> is svg's whole metadata
surface. svg->csv and svg->markdown honestly produce empty output (no
text in the read's scope, no vector vocabulary in the target) -- pinned
in the round-trip matrix, which now selects a page when a multi-page
intermediate meets the one-drawing target.
@github-code-quality

Copy link
Copy Markdown

Code Coverage Overview

Languages: TypeScript

TypeScript / unit

The overall coverage in commit 3efdc70 in the feat/svg-read branch remains at 91%, unchanged from commit 75180a1 in the main branch.

Show a code coverage summary of the most impacted files.
File main 75180a1 feat/svg-read 3efdc70 +/-
src/convert/codec.ts 89% 83% -6%
src/model/bytes.ts 94% 90% -4%
src/convert/convert.ts 96% 93% -3%
src/svg/read.ts 0% 89% +89%
src/svg/transform.ts 0% 93% +93%
src/svg/paint.ts 0% 95% +95%
src/svg/path.ts 0% 97% +97%
src/svg/units.ts 0% 97% +97%
src/svg/write.ts 0% 100% +100%
src/svg/text.ts 0% 100% +100%

@Mearman
Mearman merged commit 09740d8 into main Aug 17, 2026
12 checks passed
@Mearman
Mearman deleted the feat/svg-read branch August 17, 2026 19:11
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 2.2.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add SVG read into the drawing variant

1 participant