@weasel-js/svg 1.9.4 → 1.10.1

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
@@ -4,7 +4,7 @@ SVG import/export for @weasel-js/core: parse SVG strings into weasel-native shap
4
4
 
5
5
  Part of [weasel](https://github.com/orochi235/weasel), a domain-agnostic 2D
6
6
  scene-graph canvas kit for React. See the
7
- [API reference](https://orochi235.github.io/weasel/api/).
7
+ [API reference](https://orochi235.github.io/weasel/#pkg/svg).
8
8
 
9
9
  ## Install
10
10
 
@@ -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
- import { Mat3 } from '@weasel-js/geom';
2
- import { Path, FillStyle, ScreenLength, StrokeAlign, MarkerRef, StyledRun, TextStyle, TextVerticalAlign, Stroke, PoseLinear, MarkerEntry, RenderNeeds, TilePatternSpec, ImageNodeData, IngestCtx } from '@weasel-js/core';
1
+ import { Affine } from '@weasel-js/geom';
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 `Affine`, so the
78
+ * two are interchangeable without a conversion.
96
79
  */
97
- type Matrix = Mat3;
80
+ type Matrix = Affine;
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;
@@ -240,22 +221,24 @@ interface SvgTextNode {
240
221
  * With one, the element is written with its whole frame as a
241
222
  * `transform="matrix(...)"`. */
242
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;
243
228
  /** Opaque per-element bag for declared namespaces. See `NamespaceMeta`. */
244
229
  meta?: NamespaceMeta;
245
230
  }
246
231
  /**
247
232
  * Raster-image leaf — an SVG `<image>`. `href` is the element's `href` (or
248
233
  * legacy `xlink:href`) verbatim: an external URL or a `data:` URI. The
249
- * package neither fetches nor decodes it, so an external URL round-trips as
250
- * a reference and only resolves when something downstream loads it.
234
+ * package neither fetches nor decodes it.
251
235
  *
252
236
  * SVG's `preserveAspectRatio` is not modeled; the box is taken literally.
253
237
  *
254
238
  * With a `source` rect or a flip, the element is written as a `<g
255
239
  * data-weasel-image>` holding a nested `<svg>` viewport at the box, whose
256
240
  * `viewBox` is the source window over a unit-square `<image>` — plain SVG 1.1,
257
- * which any reader crops and mirrors the same way, and which `parseSvg` reads
258
- * back as one image node.
241
+ * which `parseSvg` reads back as one image node.
259
242
  */
260
243
  interface SvgImageNode {
261
244
  kind: 'image';
@@ -289,19 +272,22 @@ interface SvgImageNode {
289
272
  * With one, the element is written with its whole frame as a
290
273
  * `transform="matrix(...)"`. */
291
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;
292
279
  /** Opaque per-element bag for declared namespaces. See `NamespaceMeta`. */
293
280
  meta?: NamespaceMeta;
294
281
  }
295
- /** Discriminated-union node — the leaf of the public tree. */
282
+ /** Any node of a parsed SVG tree. */
296
283
  type SvgNode = SvgPathNode | SvgGroupNode | SvgTextNode | SvgImageNode;
297
284
  /** Options for {@link parseSvg}. */
298
285
  interface ParseOptions {
299
286
  /**
300
287
  * Map of prefix → URI for XML namespaces the caller wants surfaced.
301
- * Attributes / child elements in any declared namespace are collected
288
+ * Attributes and child elements in a declared namespace are collected
302
289
  * into `SvgNode.meta` (per element) or `ParseResult.documentMeta` (root).
303
- * Undeclared namespaces are preserved in the DOM but not promoted into
304
- * the structured `meta` bag; they are silently dropped at serialize time.
290
+ * Content in an undeclared namespace is dropped.
305
291
  */
306
292
  namespaces?: Record<string, string>;
307
293
  /**
@@ -312,17 +298,19 @@ interface ParseOptions {
312
298
  * those defaults.
313
299
  */
314
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;
315
307
  }
316
308
  /** Output of {@link parseSvg}. */
317
309
  interface ParseResult {
318
310
  nodes: SvgNode[];
319
311
  /** Non-fatal notices (unsupported elements, unrecognized attributes). */
320
312
  warnings: string[];
321
- /**
322
- * Opaque per-document bag for declared namespaces. Holds root-level
323
- * namespaced attributes (`documentMeta.<prefix>.attrs`) and namespaced
324
- * root children (`documentMeta.<prefix>.elements`).
325
- */
313
+ /** The root's namespaced attributes and children, for declared namespaces. */
326
314
  documentMeta?: NamespaceMeta;
327
315
  /** Root `viewBox` attribute, parsed from `"x y width height"`. */
328
316
  viewBox?: {
@@ -347,10 +335,7 @@ interface ParseResult {
347
335
  }
348
336
  /** Options for {@link serializeSvg}. */
349
337
  interface SerializeOptions {
350
- /**
351
- * Override the root `viewBox`. When omitted, the serializer computes
352
- * a tight bounding box from the supplied nodes.
353
- */
338
+ /** The root `viewBox`. Default: a tight bounding box around the nodes. */
354
339
  viewBox?: {
355
340
  x: number;
356
341
  y: number;
@@ -369,25 +354,23 @@ interface SerializeOptions {
369
354
  width?: number;
370
355
  /** Emit `height="..."` on the root `<svg>`. */
371
356
  height?: number;
372
- /**
373
- * Emit `<title>...</title>` as the first child of the root `<svg>`. The
374
- * value is XML-escaped on output. Empty strings are skipped.
375
- */
357
+ /** Emit `<title>` as the first child of the root `<svg>`. Empty strings are skipped. */
376
358
  title?: string;
377
359
  /**
378
- * Map of prefix → URI for XML namespaces to declare on the root
379
- * `<svg>`. The serializer reads `documentMeta[prefix]` and
380
- * `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.
381
362
  */
382
363
  namespaces?: Record<string, string>;
383
364
  /**
384
- * Opaque per-document namespaced extras. Each `<prefix>.attrs` becomes
385
- * `<prefix>:<name>="..."` attributes on the root `<svg>`. Each
386
- * `<prefix>.elements[localName]` becomes `<prefix>:<localName>...>`
387
- * 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.
388
367
  */
389
368
  documentMeta?: NamespaceMeta;
390
- /** 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
+ */
391
374
  pretty?: boolean;
392
375
  }
393
376
 
@@ -410,7 +393,7 @@ declare function parseSvg(svg: string, opts?: ParseOptions): ParseResult;
410
393
 
411
394
  /**
412
395
  * Map between SVG `<linearGradient>` / `<radialGradient>` elements and
413
- * weasel's `FillStyle` gradient variants. Definitions are indexed by id
396
+ * weasel's `Paint` gradient variants. Definitions are indexed by id
414
397
  * wherever in the document they appear, and looked up when a `fill` /
415
398
  * `stroke` attribute references one as `url(#id)`. On serialize, we
416
399
  * emit a fresh `<defs>` block with stable generated ids.
@@ -428,9 +411,13 @@ declare function nativeSvgKind(kind: string): boolean;
428
411
  declare function nativeSvgSpace(space: string | null | undefined): boolean;
429
412
 
430
413
  /**
431
- * Walk the tree and produce an SVG document string. The root `<svg>`'s
432
- * `viewBox` is taken from `opts.viewBox` if supplied, otherwise computed
433
- * as a tight bounding box around every leaf.
414
+ * Serialize a `SvgNode[]` tree to an SVG document string. Every path leaf is
415
+ * emitted as a `<path>`, even shapes that started as `<rect>` etc.
416
+ */
417
+
418
+ /**
419
+ * Produce an SVG document string. The root `viewBox` is `opts.viewBox` if
420
+ * supplied, otherwise a tight bounding box around every leaf.
434
421
  */
435
422
  declare function serializeSvg(nodes: SvgNode[], opts?: SerializeOptions): string;
436
423
 
@@ -452,27 +439,17 @@ declare function svgNeeds(nodes: readonly SvgNode[]): RenderNeeds;
452
439
  declare function warmSvg(nodes: readonly SvgNode[]): Promise<void>;
453
440
 
454
441
  /**
455
- * Map between SVG `<pattern>` elements and weasel's `pattern` `FillStyle`.
456
- * The counterpart to `gradients.ts` for the other paint-server kind.
442
+ * Map between SVG `<pattern>` elements and weasel's pattern paints.
457
443
  *
458
- * The tile's shapes come from `tileGeometry` in core, so the vector form a
459
- * viewer sees is generated from the same description that rasterizes the GL
460
- * texture. Alongside them we write the spec itself onto a `data-weasel-tile`
461
- * attribute: any SVG consumer renders the shapes, and weasel reads the
462
- * attribute back to recover the exact spec rather than reverse-engineering
463
- * it from geometry.
464
- *
465
- * A pattern whose payload is a `TextureHandle` has no vector form — the
466
- * handle is a session-scoped registry key with no tile description behind
467
- * it — and exports as nothing, with a warning. Same treatment conic
468
- * gradients get.
444
+ * The tile's shapes are written for any SVG consumer to render, and the spec
445
+ * itself goes on a `data-weasel-tile` attribute, which weasel reads back
446
+ * rather than reverse-engineering it from geometry. A `texture` paint has no
447
+ * vector form and exports as nothing, with a warning.
469
448
  */
470
449
 
471
450
  /**
472
- * A single tile as a standalone `<svg>` document, sized to its own extent.
473
- * For UI that needs to show a tile outside a document — a picker swatch,
474
- * set as a repeating CSS `background-image` via a data URI. Shares the shape
475
- * mapper with `patternXml`, so a preview can't drift from what paints.
451
+ * A single tile as a standalone `<svg>` document, sized to its own extent,
452
+ * for UI that shows a tile outside a document, such as a picker swatch.
476
453
  */
477
454
  declare function tilePreviewSvg(spec: TilePatternSpec, background?: string): string;
478
455
  /** `tilePreviewSvg` packed as a `url(...)` value for CSS `background-image`. */
@@ -511,14 +488,15 @@ interface SvgKitPose {
511
488
  width: number;
512
489
  height: number;
513
490
  rotation?: number;
514
- /** Read for text and image leaves only; a path leaf's is not written. */
491
+ /** Carried onto a text or image node. A path node has nowhere to hold it,
492
+ * so a path leaf's is baked into its geometry with its rotation. */
515
493
  transform?: PoseLinear;
516
494
  }
517
495
  /** The leaf data the kit's path, text and image painters read — what
518
496
  * `svgNodesToKitDrafts` writes. */
519
497
  interface SvgKitLeafData {
520
498
  path?: Path;
521
- fill?: FillStyle | null;
499
+ fill?: Fill | null;
522
500
  stroke?: Stroke | null;
523
501
  text?: string;
524
502
  style?: TextStyle;
@@ -529,7 +507,7 @@ interface SvgKitLeafData {
529
507
  /** A kit paint as an `SvgPaint`. `null` is `fill="none"`; a box-relative
530
508
  * gradient or pattern is resolved into `box`, since the geometry it paints
531
509
  * is written already placed there. */
532
- declare function svgPaintFromKit(fill: FillStyle | null, box: SvgKitPose): SvgPaint;
510
+ declare function svgPaintFromKit(fill: Paint | null, box: SvgKitPose): SvgPaint;
533
511
  /** A kit `Stroke` as an `SvgStroke` — the inverse of `strokeDataFromSvg`.
534
512
  * One with no paint or no width draws nothing and is written as no stroke. */
535
513
  declare function svgStrokeFromKit(stroke: Stroke | null | undefined, box: SvgKitPose): SvgStroke | undefined;
@@ -542,7 +520,9 @@ declare function svgImageFromKit(image: ImageNodeData['image'], pose: SvgKitPose
542
520
  * draws.
543
521
  *
544
522
  * A path with no `fill` takes `kit:path`'s fallback: the default shape fill
545
- * when it has no stroke, and no fill when it has one.
523
+ * when it has no stroke, and no fill when it has one. Under a pose `transform`
524
+ * its stroke width takes the uniform part of the stretch, as `parseSvg` reads
525
+ * a stroke under a skewed element.
546
526
  */
547
527
  declare function svgLeafFromKit(data: SvgKitLeafData, pose: SvgKitPose): SvgPathNode | SvgTextNode | SvgImageNode | null;
548
528
  /** One node as {@link svgNodesFromKit} reads it. */
@@ -550,6 +530,9 @@ interface SvgKitTreeNode {
550
530
  kind: string;
551
531
  data: unknown;
552
532
  pose: SvgKitPose;
533
+ /** A container's copies, as the kit's `ContainerNode.copiesFromPose`
534
+ * draws them: each is written as a transformed group of its children. */
535
+ copiesFromPose?(pose: SvgKitPose, node: never): readonly Matrix[] | null;
553
536
  }
554
537
  /** The read side of a scene that {@link svgNodesFromKit} walks. A kit
555
538
  * `Scene` is one as it stands. */
@@ -585,26 +568,37 @@ interface SvgDraftBounds {
585
568
  width: number;
586
569
  height: number;
587
570
  }
588
- type DraftPose = SvgDraftBounds & {
571
+ /** Where a draft node sits: its box, and the rotation or linear part its SVG transform left it with. */
572
+ type SvgDraftPose = SvgDraftBounds & {
589
573
  rotation?: number;
590
574
  transform?: PoseLinear;
591
575
  };
576
+
592
577
  /**
593
578
  * One node the unpack wants the scene to materialize, in parent-before-child
594
579
  * order (a draft's `parentId` always names an earlier draft, or `null` for
595
- * roots). Leaf `data` is kit-painter-native (see module doc).
580
+ * roots). `hidden` is the source node's, for the caller to map onto whatever
581
+ * it hides a node with.
582
+ *
583
+ * A container's `data` is empty unless its `<g>` is clipped. A scene container
584
+ * clips to what its painter draws, so a clipped one holds the clip as an
585
+ * unpainted path, `{ path, fill: null }`, and its `pose` is the clip's box
586
+ * rather than its children's.
596
587
  */
597
588
  type SvgSceneDraft<TData = Record<string, unknown>> = {
598
589
  kind: 'container';
599
590
  id: string;
600
591
  parentId: string | null;
601
- pose: DraftPose;
592
+ pose: SvgDraftPose;
593
+ data: SvgKitLeafData;
594
+ hidden?: true;
602
595
  } | {
603
596
  kind: 'leaf';
604
597
  id: string;
605
598
  parentId: string | null;
606
- pose: DraftPose;
599
+ pose: SvgDraftPose;
607
600
  data: TData;
601
+ hidden?: true;
608
602
  };
609
603
  /** A leaf `SvgNode`: everything but a group. */
610
604
  type SvgLeafNode = Exclude<SvgNode, SvgGroupNode>;
@@ -614,33 +608,27 @@ interface SvgNodesToKitDraftsOptions<TData> {
614
608
  * the node it came from — for data shaped other than the kit painters', or
615
609
  * metadata the document carries. `null` leaves it out. */
616
610
  leaf?(id: string, draft: {
617
- pose: DraftPose;
611
+ pose: SvgDraftPose;
618
612
  data: SvgKitLeafData;
619
613
  }, source: SvgLeafNode): TData | null;
620
614
  }
621
- /** Lower an `SvgPaint` onto the `kit:path` painter's `data.fill` — a
622
- * `FillStyle`, or `null` for SVG's `fill="none"` (the painter skips the
623
- * fill rather than falling back to its default). A solid paint keeps its
624
- * `fill-opacity`.
615
+ /** Lower an `SvgPaint` onto the paint of the `kit:path` painter's
616
+ * `data.fill` — a `Paint`, or `null` for SVG's `fill="none"` (the painter
617
+ * skips the fill rather than falling back to its default).
625
618
  *
626
- * A gradient rides through as the `FillStyle` it already is, normalized to
627
- * the leaf's own box: `objectBoundingBox` gradients already are, and a
628
- * `userSpaceOnUse` one is rebased so it survives the fit-clamp and the
629
- * drop-point placement that move the geometry out from under it. */
630
- declare function fillDataFromSvg(paint: SvgPaint | undefined, box: SvgDraftBounds): FillStyle | null | undefined;
631
- /** Lower an `SvgStroke` onto the leaf's `data.stroke`.
632
- *
633
- * Everything the SVG carried — paint, width, cap, join, dash, miter limit,
634
- * opacity — lands on the one `Stroke` `data.stroke` takes. The paint is
635
- * normalized to the leaf's own box the same way a fill is, so a
636
- * `userSpaceOnUse` gradient survives the fit-clamp and the drop-point
637
- * placement. */
619
+ * A `userSpaceOnUse` gradient is rebased onto the leaf's own box, so it
620
+ * survives the fit-clamp and the drop-point placement that move the
621
+ * geometry out from under it. */
622
+ declare function fillDataFromSvg(paint: SvgPaint | undefined, box: SvgDraftBounds): Paint | null | undefined;
623
+ /** Lower an `SvgStroke` onto the leaf's `data.stroke`, or `undefined` for an
624
+ * unpainted one. The paint is rebased onto the leaf's own box the same way
625
+ * a fill is. */
638
626
  declare function strokeDataFromSvg(stroke: SvgStroke | undefined, box: SvgDraftBounds): Stroke | undefined;
639
627
  /**
640
628
  * Walk an `SvgNode[]` tree and emit a flat, parent-before-child list of
641
629
  * {@link SvgSceneDraft}s. Each `<g>` becomes a container whose pose is the
642
- * union AABB of its descendants (the kit `group` action's convention);
643
- * path/text leaves carry kit-painter-native data. Empty groups are dropped.
630
+ * union AABB of its descendants, or its clip's box when it has one; leaves
631
+ * carry kit-painter-native data. Empty groups are dropped.
644
632
  *
645
633
  * Handed a whole `ParseResult`, it also registers the document's markers
646
634
  * (`parsed.markers`), without which the strokes naming them draw bare.
@@ -652,11 +640,15 @@ declare function strokeDataFromSvg(stroke: SvgStroke | undefined, box: SvgDraftB
652
640
  */
653
641
  declare function svgNodesToKitDrafts<TData = Record<string, unknown>>(input: ParseResult | readonly SvgNode[], nextId: (source: SvgNode) => string, options?: SvgNodesToKitDraftsOptions<TData>): SvgSceneDraft<TData>[];
654
642
  /**
655
- * Parse each file and insert its node tree — one undoable `applyOps` batch
656
- * per file. See the module doc for placement and wrapping policy. A file
657
- * that fails to parse (or parses to nothing) is skipped with a
658
- * `console.warn`; the rest proceed.
643
+ * Parse each file and insert its node tree, one undo step per file. Pass it
644
+ * as `<SceneCanvas ingestion={{ svg: { unpack: unpackSvgFiles } }}>`.
645
+ *
646
+ * Each file is fit-clamped to 90% of the visible viewport and centered on the
647
+ * drop point (or the viewport center), with later files cascading by a fixed
648
+ * offset. A multi-root file is wrapped in one container so it arrives as a
649
+ * single selectable unit. A file that fails to parse, or parses to nothing,
650
+ * is skipped with a `console.warn`.
659
651
  */
660
652
  declare function unpackSvgFiles(files: File[], ctx: IngestCtx): Promise<void>;
661
653
 
662
- 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 };
654
+ 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 };