@weasel-js/svg 1.9.3 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -20,16 +20,13 @@ import { /* … */ } from '@weasel-js/svg';
20
20
 
21
21
  ## Text fidelity
22
22
 
23
- Two things worth knowing before you rely on a text round-trip.
24
-
25
23
  **The format is exactly as expressive as the runs model.** A run can turn
26
24
  `underline` on but not off, so `<g text-decoration="underline"><text
27
25
  text-decoration="none">` has no representation on the way in — and neither
28
26
  does "this word is not underlined inside an underlined node" on the way out.
29
- That is not loss in the serializer; it is the model, faithfully reproduced. A
30
- future "un-underline this word" feature needs a model change, not a
31
- serializer change. (Contrast `letterSpacing`, where the model *was* more
32
- expressive than the serializer — that was a real data-loss bug, since fixed.)
27
+ That is not loss in the serializer; it is the model. An
28
+ "un-underline this word" feature needs a model change, not a
29
+ serializer change.
33
30
 
34
31
  **Decoration is treated as inherited.** Real CSS says `text-decoration` is
35
32
  not inherited and cannot be cancelled by a descendant; a browser shown
@@ -151,8 +148,13 @@ query against the same environment.
151
148
  `@supports` asks whether this parser honors a declaration, not whether a browser
152
149
  parses it: `(fill: red)` holds, `(display: grid)` and `(clip-path: url(#c))` do
153
150
  not, because nothing reads them from a stylesheet. `@import` is not fetched, and
154
- each one is reported in `warnings`. Rules inside `@layer`, `@container` and
155
- other at-rules are skipped.
151
+ each one is reported in `warnings`. Rules inside other at-rules are skipped.
152
+
153
+ `@container` size queries are answered against the root `<svg>`, the one box a
154
+ parse knows the size of: the same viewport `@media` sees. They apply to its
155
+ descendants when the root declares `container-type: size` or `inline-size`
156
+ (or the `container` shorthand), by name when the query gives one. A style
157
+ query never holds.
156
158
 
157
159
  ## Raster images
158
160
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Mat3 } from '@weasel-js/geom';
2
- import { Path, FillStyle, ScreenLength, StrokeAlign, MarkerRef, StyledRun, TextStyle, TextVerticalAlign, Stroke, MarkerEntry, RenderNeeds, TilePatternSpec, ImageNodeData, IngestCtx } from '@weasel-js/core';
2
+ import { Path, Paint, ScreenLength, StrokeAlign, MarkerRef, StyledRun, TextStyle, TextVerticalAlign, Fill, Stroke, PoseLinear, MarkerEntry, RenderNeeds, TilePatternSpec, ImageNodeData, IngestCtx } from '@weasel-js/core';
3
3
 
4
4
  /**
5
5
  * Media Queries 4 evaluation against a fixed environment.
@@ -41,39 +41,25 @@ declare const DEFAULT_MEDIA_ENVIRONMENT: SvgMediaEnvironment;
41
41
  declare function evaluateMediaQuery(list: string, env: SvgMediaEnvironment): boolean;
42
42
 
43
43
  /**
44
- * Public types for `@weasel-js/svg`. The package exposes a flat,
45
- * discriminated-union node model (`SvgNode`) that mirrors the SVG element
46
- * tree but uses weasel-native leaf data (`Path`, `FillStyle`) for geometry and
47
- * paint.
48
- *
49
- * Parsing collapses every `transform="..."` onto its descendants' geometry,
50
- * so the `SvgNode` tree returned from `parseSvg` never has a non-identity
51
- * group transform. Consumers may still construct groups with explicit
52
- * transforms before serializing, in which case the serializer emits a
53
- * single `matrix(a b c d e f)` on the `<g>`.
44
+ * Public types for `@weasel-js/svg`: a node model (`SvgNode`) that mirrors
45
+ * the SVG element tree but uses weasel-native leaf data (`Path`, `Paint`)
46
+ * for geometry and paint.
54
47
  */
55
48
 
56
49
  /**
57
- * Opaque pass-through bag for namespaced XML content.
58
- *
59
- * weasel-svg does not interpret the contents — it only ensures that any
60
- * attributes or child elements in a *declared* XML namespace round-trip
61
- * losslessly through parse → serialize. Consumers (e.g. an app-specific
62
- * bridge layer) hang their domain semantics off this structure.
50
+ * Opaque pass-through bag for namespaced XML content. The package does not
51
+ * interpret it; attributes and child elements in a *declared* XML namespace
52
+ * round-trip losslessly through parse → serialize.
63
53
  *
64
- * Keyed by namespace prefix (the prefix is a write-time choice; the URI
65
- * is the canonical identifier and is supplied via `ParseOptions.namespaces`
66
- * / `SerializeOptions.namespaces`).
54
+ * Keyed by namespace prefix. The URI is the canonical identifier and is
55
+ * supplied via `ParseOptions.namespaces` / `SerializeOptions.namespaces`.
67
56
  */
68
57
  interface NamespaceMeta {
69
58
  [prefix: string]: {
70
59
  /** Local-name → string-value map for attributes in this namespace. */
71
60
  attrs?: Record<string, string>;
72
- /**
73
- * Child elements in this namespace, keyed by local name. Each entry
74
- * is an array because a namespace can host multiple sibling elements
75
- * with the same tag (e.g. `<wd:layer/><wd:layer/>`).
76
- */
61
+ /** Child elements in this namespace, keyed by local name. An array
62
+ * because siblings can share a tag. */
77
63
  elements?: Record<string, NamespacedElement[]>;
78
64
  };
79
65
  }
@@ -88,21 +74,17 @@ interface NamespacedElement {
88
74
  }
89
75
  /**
90
76
  * 2x3 affine matrix in SVG's `matrix(a b c d e f)` order, mapping
91
- * `[x', y'] = [a*x + c*y + e, b*x + d*y + f]`.
92
- *
93
- * The same order the kernel's affine tier uses, so this is that type under
94
- * the name the SVG spec gives it — not a parallel one. An `SvgGroupNode`'s
95
- * transform and a geom `Mat3` are interchangeable without a conversion.
77
+ * `[x', y'] = [a*x + c*y + e, b*x + d*y + f]`. It is geom's `Mat3`, so the
78
+ * two are interchangeable without a conversion.
96
79
  */
97
80
  type Matrix = Mat3;
98
81
 
99
- /** Identity matrix — useful as a default in tests / constructors. */
82
+ /** The identity matrix. */
100
83
  declare const IDENTITY_MATRIX: Matrix;
101
84
  /**
102
- * FillStyle description in the SVG sense — either explicit `none`, a solid
103
- * color, or a reference to a weasel-native gradient `FillStyle`. Solid colors
104
- * are normalized to `#rrggbb` strings; opacity is carried separately so
105
- * `fill-opacity` and `stroke-opacity` round-trip cleanly.
85
+ * A paint in the SVG sense: explicit `none`, a solid color, or a weasel-native
86
+ * gradient `Paint`. Solid colors are normalized to `#rrggbb` strings, with
87
+ * opacity carried separately.
106
88
  */
107
89
  type SvgPaint = {
108
90
  kind: 'none';
@@ -112,14 +94,13 @@ type SvgPaint = {
112
94
  opacity?: number;
113
95
  } | {
114
96
  kind: 'gradient';
115
- paint: FillStyle;
97
+ paint: Paint;
116
98
  };
117
99
  /** Stroke description: a paint plus structural line parameters. */
118
100
  interface SvgStroke {
119
101
  paint: SvgPaint;
120
102
  /** World units, or `{ px }` for a width that holds its rendered thickness
121
- * however the document is scaled — SVG's `vector-effect="non-scaling-stroke"`.
122
- * Literally the kit's own unit, not a second spelling of it. */
103
+ * however the document is scaled — SVG's `vector-effect="non-scaling-stroke"`. */
123
104
  width: ScreenLength;
124
105
  opacity?: number;
125
106
  /** `stroke-linecap`. Default per SVG spec is `'butt'`. */
@@ -146,10 +127,8 @@ interface SvgStroke {
146
127
  markerEnd?: MarkerRef;
147
128
  }
148
129
  /**
149
- * Leaf node: a path geometry plus fill/stroke. All other v1 shapes
150
- * (`<rect>`, `<ellipse>`, etc.) are lowered to this representation on
151
- * parse — `<rect>` uses weasel's `RectPath` fast-path subtype when
152
- * possible, everything else becomes a `PolygonPath`.
130
+ * Leaf node: a path geometry plus fill/stroke. Other shapes (`<rect>`,
131
+ * `<ellipse>`, etc.) are lowered to this on parse.
153
132
  */
154
133
  interface SvgPathNode {
155
134
  kind: 'path';
@@ -162,14 +141,17 @@ interface SvgPathNode {
162
141
  * center of the underlying `path` geometry. Emitted as SVG
163
142
  * `transform="rotate(angleDegrees cx cy)"`. */
164
143
  rotation?: number;
144
+ /** Kept by `ParseOptions.keepHidden` from content SVG does not render:
145
+ * `display="none"` here, or a leaf under `visibility="hidden"`. Written
146
+ * back as `display="none"`. */
147
+ hidden?: boolean;
165
148
  /** Opaque per-element bag for declared namespaces. See `NamespaceMeta`. */
166
149
  meta?: NamespaceMeta;
167
150
  }
168
151
  /**
169
- * Group node: an SVG `<g>`. On parse, `transform` is always omitted (any
170
- * `transform` attribute is collapsed onto descendants' geometry). On
171
- * serialize, a non-identity `transform` is emitted as
172
- * `matrix(a b c d e f)`.
152
+ * Group node: an SVG `<g>`. Parsing collapses any `transform` attribute onto
153
+ * the descendants' geometry, so a parsed group never has one. On serialize, a
154
+ * non-identity `transform` is emitted as `matrix(a b c d e f)`.
173
155
  */
174
156
  interface SvgGroupNode {
175
157
  kind: 'group';
@@ -182,21 +164,21 @@ interface SvgGroupNode {
182
164
  * unions them and a `Path` holds one outline. */
183
165
  clip?: Path;
184
166
  opacity?: number;
167
+ /** Kept by `ParseOptions.keepHidden` from content SVG does not render:
168
+ * `display="none"` here, or a leaf under `visibility="hidden"`. Written
169
+ * back as `display="none"`. */
170
+ hidden?: boolean;
185
171
  /** Opaque per-element bag for declared namespaces. See `NamespaceMeta`. */
186
172
  meta?: NamespaceMeta;
187
173
  }
188
174
  /**
189
- * Text leaf node. Mirrors the kit's `TextPose` shape — a bounding box plus
190
- * text content, optional rich-text `runs` (per-range styling), and an
191
- * optional `TextStyle` for the node-wide defaults.
175
+ * Text leaf node: a bounding box plus text content, optional per-range
176
+ * `runs`, and an optional `TextStyle` for the node-wide defaults.
192
177
  *
193
- * Note on SVG text geometry: native `<text>` flows from a baseline; it
194
- * doesn't have width/height attributes. The serializer emits
195
- * `dominant-baseline="text-before-edge"` so `y` is the top of the text
196
- * box, plus `data-weasel-width` / `data-weasel-height` so weasel's
197
- * explicit box dimensions round-trip losslessly. External SVG text
198
- * (lacking the data-* attrs) imports with the width the kit's text layout
199
- * measures — an em-based estimate when no registered font can — and
178
+ * Native `<text>` has no width or height, so the serializer writes the box
179
+ * as `data-weasel-width` / `data-weasel-height`. External SVG text, lacking
180
+ * them, imports with the width the kit's text layout measures — an em-based
181
+ * estimate when no registered font can — and
200
182
  * `height = fontSize * lineHeight * (lineCount || 1)`.
201
183
  *
202
184
  * `x` here is always the box's left edge. In the SVG it is the
@@ -210,24 +192,23 @@ interface SvgTextNode {
210
192
  height: number;
211
193
  /** Plain text. When `runs` is set, `runsToPlainText(runs)` must equal `text`. */
212
194
  text: string;
213
- /** Optional per-range styling — same shape as `TextPose.runs`. */
195
+ /** Per-range styling. */
214
196
  runs?: StyledRun[];
215
- /** Node-wide typography. Defaults applied at render time via `resolveTextStyle`. */
197
+ /** Node-wide typography. */
216
198
  style?: TextStyle;
217
199
  /** Where the laid-out text sits inside the box — `kit:text`'s
218
200
  * `data.verticalAlign`. SVG text has no box to align in, so this rides in
219
201
  * `data-weasel-vertical-align` and other readers draw the text top-aligned. */
220
202
  verticalAlign?: TextVerticalAlign;
221
203
  /**
222
- * Node-wide glyph paint, the `FillStyle` / `Stroke` a kit text node holds
223
- * in `data.fill` / `data.stroke`. Not `SvgPaint`, which is the path
224
- * nodes' shape — a text paint reaches the renderer through the runs, and
225
- * `StyledRun.fill` / `.stroke` override these per range.
204
+ * Node-wide glyph paint, as the kit's `Fill` / `Stroke` rather than
205
+ * the path nodes' `SvgPaint`. `StyledRun.fill` / `.stroke` override these
206
+ * per range.
226
207
  *
227
208
  * `null` is SVG's `fill="none"`: outline-only text, painted by its stroke
228
209
  * alone. Absent takes the default black.
229
210
  */
230
- fill?: FillStyle | null;
211
+ fill?: Fill | null;
231
212
  stroke?: Stroke;
232
213
  /** Element-level opacity (`opacity="..."`), 0..1. */
233
214
  opacity?: number;
@@ -235,22 +216,29 @@ interface SvgTextNode {
235
216
  * AABB center `(x + width/2, y + height/2)`. Emitted as SVG
236
217
  * `transform="rotate(angleDegrees cx cy)"`. */
237
218
  rotation?: number;
219
+ /** The skew or per-axis scale the box, `rotation`, and flips cannot hold,
220
+ * applied about the box center inside the rotation; see `PoseLinear`.
221
+ * With one, the element is written with its whole frame as a
222
+ * `transform="matrix(...)"`. */
223
+ transform?: PoseLinear;
224
+ /** Kept by `ParseOptions.keepHidden` from content SVG does not render:
225
+ * `display="none"` here, or a leaf under `visibility="hidden"`. Written
226
+ * back as `display="none"`. */
227
+ hidden?: boolean;
238
228
  /** Opaque per-element bag for declared namespaces. See `NamespaceMeta`. */
239
229
  meta?: NamespaceMeta;
240
230
  }
241
231
  /**
242
232
  * Raster-image leaf — an SVG `<image>`. `href` is the element's `href` (or
243
233
  * legacy `xlink:href`) verbatim: an external URL or a `data:` URI. The
244
- * package neither fetches nor decodes it, so an external URL round-trips as
245
- * a reference and only resolves when something downstream loads it.
234
+ * package neither fetches nor decodes it.
246
235
  *
247
236
  * SVG's `preserveAspectRatio` is not modeled; the box is taken literally.
248
237
  *
249
238
  * With a `source` rect or a flip, the element is written as a `<g
250
239
  * data-weasel-image>` holding a nested `<svg>` viewport at the box, whose
251
240
  * `viewBox` is the source window over a unit-square `<image>` — plain SVG 1.1,
252
- * which any reader crops and mirrors the same way, and which `parseSvg` reads
253
- * back as one image node.
241
+ * which `parseSvg` reads back as one image node.
254
242
  */
255
243
  interface SvgImageNode {
256
244
  kind: 'image';
@@ -279,19 +267,27 @@ interface SvgImageNode {
279
267
  * AABB center `(x + width/2, y + height/2)`. Emitted as SVG
280
268
  * `transform="rotate(angleDegrees cx cy)"`. */
281
269
  rotation?: number;
270
+ /** The skew or per-axis scale the box, `rotation`, and flips cannot hold,
271
+ * applied about the box center inside the rotation; see `PoseLinear`.
272
+ * With one, the element is written with its whole frame as a
273
+ * `transform="matrix(...)"`. */
274
+ transform?: PoseLinear;
275
+ /** Kept by `ParseOptions.keepHidden` from content SVG does not render:
276
+ * `display="none"` here, or a leaf under `visibility="hidden"`. Written
277
+ * back as `display="none"`. */
278
+ hidden?: boolean;
282
279
  /** Opaque per-element bag for declared namespaces. See `NamespaceMeta`. */
283
280
  meta?: NamespaceMeta;
284
281
  }
285
- /** Discriminated-union node — the leaf of the public tree. */
282
+ /** Any node of a parsed SVG tree. */
286
283
  type SvgNode = SvgPathNode | SvgGroupNode | SvgTextNode | SvgImageNode;
287
284
  /** Options for {@link parseSvg}. */
288
285
  interface ParseOptions {
289
286
  /**
290
287
  * Map of prefix → URI for XML namespaces the caller wants surfaced.
291
- * Attributes / child elements in any declared namespace are collected
288
+ * Attributes and child elements in a declared namespace are collected
292
289
  * into `SvgNode.meta` (per element) or `ParseResult.documentMeta` (root).
293
- * Undeclared namespaces are preserved in the DOM but not promoted into
294
- * the structured `meta` bag; they are silently dropped at serialize time.
290
+ * Content in an undeclared namespace is dropped.
295
291
  */
296
292
  namespaces?: Record<string, string>;
297
293
  /**
@@ -302,17 +298,19 @@ interface ParseOptions {
302
298
  * those defaults.
303
299
  */
304
300
  media?: Partial<SvgMediaEnvironment>;
301
+ /**
302
+ * Keep `display="none"` and `visibility="hidden"` content, each node marked
303
+ * `hidden`, so a file's hidden layers survive the import. By default it is
304
+ * left out, as a renderer leaves it out.
305
+ */
306
+ keepHidden?: boolean;
305
307
  }
306
308
  /** Output of {@link parseSvg}. */
307
309
  interface ParseResult {
308
310
  nodes: SvgNode[];
309
311
  /** Non-fatal notices (unsupported elements, unrecognized attributes). */
310
312
  warnings: string[];
311
- /**
312
- * Opaque per-document bag for declared namespaces. Holds root-level
313
- * namespaced attributes (`documentMeta.<prefix>.attrs`) and namespaced
314
- * root children (`documentMeta.<prefix>.elements`).
315
- */
313
+ /** The root's namespaced attributes and children, for declared namespaces. */
316
314
  documentMeta?: NamespaceMeta;
317
315
  /** Root `viewBox` attribute, parsed from `"x y width height"`. */
318
316
  viewBox?: {
@@ -321,9 +319,9 @@ interface ParseResult {
321
319
  width: number;
322
320
  height: number;
323
321
  };
324
- /** Root `width` attribute, parsed as a unitless number when present. */
322
+ /** Root `width` attribute in user units when present, an absolute CSS unit converted at 96 per inch. */
325
323
  width?: number;
326
- /** Root `height` attribute, parsed as a unitless number when present. */
324
+ /** Root `height` attribute in user units when present, converted as `width` is. */
327
325
  height?: number;
328
326
  /** Text content of the first `<title>` child of `<svg>`, when present. */
329
327
  title?: string;
@@ -337,10 +335,7 @@ interface ParseResult {
337
335
  }
338
336
  /** Options for {@link serializeSvg}. */
339
337
  interface SerializeOptions {
340
- /**
341
- * Override the root `viewBox`. When omitted, the serializer computes
342
- * a tight bounding box from the supplied nodes.
343
- */
338
+ /** The root `viewBox`. Default: a tight bounding box around the nodes. */
344
339
  viewBox?: {
345
340
  x: number;
346
341
  y: number;
@@ -359,25 +354,23 @@ interface SerializeOptions {
359
354
  width?: number;
360
355
  /** Emit `height="..."` on the root `<svg>`. */
361
356
  height?: number;
362
- /**
363
- * Emit `<title>...</title>` as the first child of the root `<svg>`. The
364
- * value is XML-escaped on output. Empty strings are skipped.
365
- */
357
+ /** Emit `<title>` as the first child of the root `<svg>`. Empty strings are skipped. */
366
358
  title?: string;
367
359
  /**
368
- * Map of prefix → URI for XML namespaces to declare on the root
369
- * `<svg>`. The serializer reads `documentMeta[prefix]` and
370
- * `node.meta[prefix]` to write the actual attributes and elements.
360
+ * Map of prefix → URI for XML namespaces to declare on the root `<svg>`.
361
+ * Only `documentMeta` and `node.meta` entries under a declared prefix are written.
371
362
  */
372
363
  namespaces?: Record<string, string>;
373
364
  /**
374
- * Opaque per-document namespaced extras. Each `<prefix>.attrs` becomes
375
- * `<prefix>:<name>="..."` attributes on the root `<svg>`. Each
376
- * `<prefix>.elements[localName]` becomes `<prefix>:<localName>...>`
377
- * children placed immediately before any geometry.
365
+ * Per-document namespaced extras: `attrs` go on the root `<svg>`, and
366
+ * `elements` become children placed immediately before any geometry.
378
367
  */
379
368
  documentMeta?: NamespaceMeta;
380
- /** Pretty-print with newlines + indentation. Default `false`. */
369
+ /**
370
+ * Put each element on its own line, indented two spaces per level. A
371
+ * `<text>` element, and any element holding character data, stays on one
372
+ * line, since whitespace inside it is content.
373
+ */
381
374
  pretty?: boolean;
382
375
  }
383
376
 
@@ -400,7 +393,7 @@ declare function parseSvg(svg: string, opts?: ParseOptions): ParseResult;
400
393
 
401
394
  /**
402
395
  * Map between SVG `<linearGradient>` / `<radialGradient>` elements and
403
- * weasel's `FillStyle` gradient variants. Definitions are indexed by id
396
+ * weasel's `Paint` gradient variants. Definitions are indexed by id
404
397
  * wherever in the document they appear, and looked up when a `fill` /
405
398
  * `stroke` attribute references one as `url(#id)`. On serialize, we
406
399
  * emit a fresh `<defs>` block with stable generated ids.
@@ -418,9 +411,8 @@ declare function nativeSvgKind(kind: string): boolean;
418
411
  declare function nativeSvgSpace(space: string | null | undefined): boolean;
419
412
 
420
413
  /**
421
- * Walk the tree and produce an SVG document string. The root `<svg>`'s
422
- * `viewBox` is taken from `opts.viewBox` if supplied, otherwise computed
423
- * as a tight bounding box around every leaf.
414
+ * Produce an SVG document string. The root `viewBox` is `opts.viewBox` if
415
+ * supplied, otherwise a tight bounding box around every leaf.
424
416
  */
425
417
  declare function serializeSvg(nodes: SvgNode[], opts?: SerializeOptions): string;
426
418
 
@@ -442,27 +434,17 @@ declare function svgNeeds(nodes: readonly SvgNode[]): RenderNeeds;
442
434
  declare function warmSvg(nodes: readonly SvgNode[]): Promise<void>;
443
435
 
444
436
  /**
445
- * Map between SVG `<pattern>` elements and weasel's `pattern` `FillStyle`.
446
- * The counterpart to `gradients.ts` for the other paint-server kind.
447
- *
448
- * The tile's shapes come from `tileGeometry` in core, so the vector form a
449
- * viewer sees is generated from the same description that rasterizes the GL
450
- * texture. Alongside them we write the spec itself onto a `data-weasel-tile`
451
- * attribute: any SVG consumer renders the shapes, and weasel reads the
452
- * attribute back to recover the exact spec rather than reverse-engineering
453
- * it from geometry.
437
+ * Map between SVG `<pattern>` elements and weasel's pattern paints.
454
438
  *
455
- * A pattern whose payload is a `TextureHandle` has no vector form — the
456
- * handle is a session-scoped registry key with no tile description behind
457
- * it — and exports as nothing, with a warning. Same treatment conic
458
- * gradients get.
439
+ * The tile's shapes are written for any SVG consumer to render, and the spec
440
+ * itself goes on a `data-weasel-tile` attribute, which weasel reads back
441
+ * rather than reverse-engineering it from geometry. A `texture` paint has no
442
+ * vector form and exports as nothing, with a warning.
459
443
  */
460
444
 
461
445
  /**
462
- * A single tile as a standalone `<svg>` document, sized to its own extent.
463
- * For UI that needs to show a tile outside a document — a picker swatch,
464
- * set as a repeating CSS `background-image` via a data URI. Shares the shape
465
- * mapper with `patternXml`, so a preview can't drift from what paints.
446
+ * A single tile as a standalone `<svg>` document, sized to its own extent,
447
+ * for UI that shows a tile outside a document, such as a picker swatch.
466
448
  */
467
449
  declare function tilePreviewSvg(spec: TilePatternSpec, background?: string): string;
468
450
  /** `tilePreviewSvg` packed as a `url(...)` value for CSS `background-image`. */
@@ -473,6 +455,7 @@ declare function tilePreviewCssUrl(spec: TilePatternSpec, background?: string):
473
455
  * honor the declaration ({@link isDeclarationHonored}), not whether a browser
474
456
  * could parse it — the answer decides whether the guarded rules apply here.
475
457
  */
458
+ /** Options for evaluating a `@supports` condition. */
476
459
  interface SupportsOptions {
477
460
  /** Answers `selector(…)`; without it every selector test is false. */
478
461
  readonly selector?: (selector: string) => boolean;
@@ -500,12 +483,15 @@ interface SvgKitPose {
500
483
  width: number;
501
484
  height: number;
502
485
  rotation?: number;
486
+ /** Carried onto a text or image node. A path node has nowhere to hold it,
487
+ * so a path leaf's is baked into its geometry with its rotation. */
488
+ transform?: PoseLinear;
503
489
  }
504
490
  /** The leaf data the kit's path, text and image painters read — what
505
491
  * `svgNodesToKitDrafts` writes. */
506
492
  interface SvgKitLeafData {
507
493
  path?: Path;
508
- fill?: FillStyle | null;
494
+ fill?: Fill | null;
509
495
  stroke?: Stroke | null;
510
496
  text?: string;
511
497
  style?: TextStyle;
@@ -516,7 +502,7 @@ interface SvgKitLeafData {
516
502
  /** A kit paint as an `SvgPaint`. `null` is `fill="none"`; a box-relative
517
503
  * gradient or pattern is resolved into `box`, since the geometry it paints
518
504
  * is written already placed there. */
519
- declare function svgPaintFromKit(fill: FillStyle | null, box: SvgKitPose): SvgPaint;
505
+ declare function svgPaintFromKit(fill: Paint | null, box: SvgKitPose): SvgPaint;
520
506
  /** A kit `Stroke` as an `SvgStroke` — the inverse of `strokeDataFromSvg`.
521
507
  * One with no paint or no width draws nothing and is written as no stroke. */
522
508
  declare function svgStrokeFromKit(stroke: Stroke | null | undefined, box: SvgKitPose): SvgStroke | undefined;
@@ -529,7 +515,9 @@ declare function svgImageFromKit(image: ImageNodeData['image'], pose: SvgKitPose
529
515
  * draws.
530
516
  *
531
517
  * A path with no `fill` takes `kit:path`'s fallback: the default shape fill
532
- * when it has no stroke, and no fill when it has one.
518
+ * when it has no stroke, and no fill when it has one. Under a pose `transform`
519
+ * its stroke width takes the uniform part of the stretch, as `parseSvg` reads
520
+ * a stroke under a skewed element.
533
521
  */
534
522
  declare function svgLeafFromKit(data: SvgKitLeafData, pose: SvgKitPose): SvgPathNode | SvgTextNode | SvgImageNode | null;
535
523
  /** One node as {@link svgNodesFromKit} reads it. */
@@ -572,25 +560,37 @@ interface SvgDraftBounds {
572
560
  width: number;
573
561
  height: number;
574
562
  }
575
- type DraftPose = SvgDraftBounds & {
563
+ /** Where a draft node sits: its box, and the rotation or linear part its SVG transform left it with. */
564
+ type SvgDraftPose = SvgDraftBounds & {
576
565
  rotation?: number;
566
+ transform?: PoseLinear;
577
567
  };
568
+
578
569
  /**
579
570
  * One node the unpack wants the scene to materialize, in parent-before-child
580
571
  * order (a draft's `parentId` always names an earlier draft, or `null` for
581
- * roots). Leaf `data` is kit-painter-native (see module doc).
572
+ * roots). `hidden` is the source node's, for the caller to map onto whatever
573
+ * it hides a node with.
574
+ *
575
+ * A container's `data` is empty unless its `<g>` is clipped. A scene container
576
+ * clips to what its painter draws, so a clipped one holds the clip as an
577
+ * unpainted path, `{ path, fill: null }`, and its `pose` is the clip's box
578
+ * rather than its children's.
582
579
  */
583
580
  type SvgSceneDraft<TData = Record<string, unknown>> = {
584
581
  kind: 'container';
585
582
  id: string;
586
583
  parentId: string | null;
587
- pose: DraftPose;
584
+ pose: SvgDraftPose;
585
+ data: SvgKitLeafData;
586
+ hidden?: true;
588
587
  } | {
589
588
  kind: 'leaf';
590
589
  id: string;
591
590
  parentId: string | null;
592
- pose: DraftPose;
591
+ pose: SvgDraftPose;
593
592
  data: TData;
593
+ hidden?: true;
594
594
  };
595
595
  /** A leaf `SvgNode`: everything but a group. */
596
596
  type SvgLeafNode = Exclude<SvgNode, SvgGroupNode>;
@@ -600,33 +600,27 @@ interface SvgNodesToKitDraftsOptions<TData> {
600
600
  * the node it came from — for data shaped other than the kit painters', or
601
601
  * metadata the document carries. `null` leaves it out. */
602
602
  leaf?(id: string, draft: {
603
- pose: DraftPose;
603
+ pose: SvgDraftPose;
604
604
  data: SvgKitLeafData;
605
605
  }, source: SvgLeafNode): TData | null;
606
606
  }
607
- /** Lower an `SvgPaint` onto the `kit:path` painter's `data.fill` — a
608
- * `FillStyle`, or `null` for SVG's `fill="none"` (the painter skips the
609
- * fill rather than falling back to its default). A solid paint keeps its
610
- * `fill-opacity`.
607
+ /** Lower an `SvgPaint` onto the paint of the `kit:path` painter's
608
+ * `data.fill` — a `Paint`, or `null` for SVG's `fill="none"` (the painter
609
+ * skips the fill rather than falling back to its default).
611
610
  *
612
- * A gradient rides through as the `FillStyle` it already is, normalized to
613
- * the leaf's own box: `objectBoundingBox` gradients already are, and a
614
- * `userSpaceOnUse` one is rebased so it survives the fit-clamp and the
615
- * drop-point placement that move the geometry out from under it. */
616
- declare function fillDataFromSvg(paint: SvgPaint | undefined, box: SvgDraftBounds): FillStyle | null | undefined;
617
- /** Lower an `SvgStroke` onto the leaf's `data.stroke`.
618
- *
619
- * Everything the SVG carried — paint, width, cap, join, dash, miter limit,
620
- * opacity — lands on the one `Stroke` `data.stroke` takes. The paint is
621
- * normalized to the leaf's own box the same way a fill is, so a
622
- * `userSpaceOnUse` gradient survives the fit-clamp and the drop-point
623
- * placement. */
611
+ * A `userSpaceOnUse` gradient is rebased onto the leaf's own box, so it
612
+ * survives the fit-clamp and the drop-point placement that move the
613
+ * geometry out from under it. */
614
+ declare function fillDataFromSvg(paint: SvgPaint | undefined, box: SvgDraftBounds): Paint | null | undefined;
615
+ /** Lower an `SvgStroke` onto the leaf's `data.stroke`, or `undefined` for an
616
+ * unpainted one. The paint is rebased onto the leaf's own box the same way
617
+ * a fill is. */
624
618
  declare function strokeDataFromSvg(stroke: SvgStroke | undefined, box: SvgDraftBounds): Stroke | undefined;
625
619
  /**
626
620
  * Walk an `SvgNode[]` tree and emit a flat, parent-before-child list of
627
621
  * {@link SvgSceneDraft}s. Each `<g>` becomes a container whose pose is the
628
- * union AABB of its descendants (the kit `group` action's convention);
629
- * path/text leaves carry kit-painter-native data. Empty groups are dropped.
622
+ * union AABB of its descendants, or its clip's box when it has one; leaves
623
+ * carry kit-painter-native data. Empty groups are dropped.
630
624
  *
631
625
  * Handed a whole `ParseResult`, it also registers the document's markers
632
626
  * (`parsed.markers`), without which the strokes naming them draw bare.
@@ -638,11 +632,15 @@ declare function strokeDataFromSvg(stroke: SvgStroke | undefined, box: SvgDraftB
638
632
  */
639
633
  declare function svgNodesToKitDrafts<TData = Record<string, unknown>>(input: ParseResult | readonly SvgNode[], nextId: (source: SvgNode) => string, options?: SvgNodesToKitDraftsOptions<TData>): SvgSceneDraft<TData>[];
640
634
  /**
641
- * Parse each file and insert its node tree — one undoable `applyOps` batch
642
- * per file. See the module doc for placement and wrapping policy. A file
643
- * that fails to parse (or parses to nothing) is skipped with a
644
- * `console.warn`; the rest proceed.
635
+ * Parse each file and insert its node tree, one undo step per file. Pass it
636
+ * as `<SceneCanvas ingestion={{ svg: { unpack: unpackSvgFiles } }}>`.
637
+ *
638
+ * Each file is fit-clamped to 90% of the visible viewport and centered on the
639
+ * drop point (or the viewport center), with later files cascading by a fixed
640
+ * offset. A multi-root file is wrapped in one container so it arrives as a
641
+ * single selectable unit. A file that fails to parse, or parses to nothing,
642
+ * is skipped with a `console.warn`.
645
643
  */
646
644
  declare function unpackSvgFiles(files: File[], ctx: IngestCtx): Promise<void>;
647
645
 
648
- export { DEFAULT_MEDIA_ENVIRONMENT, IDENTITY_MATRIX, type Matrix, type NamespaceMeta, type NamespacedElement, type ParseOptions, type ParseResult, type SerializeOptions, type SupportsOptions, type SvgDraftBounds, type SvgGroupNode, type SvgImageNode, type SvgKitLeafData, type SvgKitPose, type SvgKitTree, type SvgKitTreeNode, type SvgLeafNode, type SvgMediaEnvironment, type SvgNode, type SvgNodesFromKitOptions, type SvgNodesToKitDraftsOptions, type SvgPaint, type SvgPathNode, type SvgSceneDraft, type SvgStroke, type SvgTextNode, evaluateMediaQuery, evaluateSupports, fillDataFromSvg, nativeSvgKind, nativeSvgSpace, parseSvg, serializeSvg, strokeDataFromSvg, svgImageFromKit, svgLeafFromKit, svgNeeds, svgNodesFromKit, svgNodesToKitDrafts, svgPaintFromKit, svgStrokeFromKit, tilePreviewCssUrl, tilePreviewSvg, unpackSvgFiles, warmSvg };
646
+ export { DEFAULT_MEDIA_ENVIRONMENT, IDENTITY_MATRIX, type Matrix, type NamespaceMeta, type NamespacedElement, type ParseOptions, type ParseResult, type SerializeOptions, type SupportsOptions, type SvgDraftBounds, type SvgDraftPose, type SvgGroupNode, type SvgImageNode, type SvgKitLeafData, type SvgKitPose, type SvgKitTree, type SvgKitTreeNode, type SvgLeafNode, type SvgMediaEnvironment, type SvgNode, type SvgNodesFromKitOptions, type SvgNodesToKitDraftsOptions, type SvgPaint, type SvgPathNode, type SvgSceneDraft, type SvgStroke, type SvgTextNode, evaluateMediaQuery, evaluateSupports, fillDataFromSvg, nativeSvgKind, nativeSvgSpace, parseSvg, serializeSvg, strokeDataFromSvg, svgImageFromKit, svgLeafFromKit, svgNeeds, svgNodesFromKit, svgNodesToKitDrafts, svgPaintFromKit, svgStrokeFromKit, tilePreviewCssUrl, tilePreviewSvg, unpackSvgFiles, warmSvg };