@weasel-js/svg 1.9.4 → 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, PoseLinear, 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;
@@ -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,8 @@ 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
+ * Produce an SVG document string. The root `viewBox` is `opts.viewBox` if
415
+ * supplied, otherwise a tight bounding box around every leaf.
434
416
  */
435
417
  declare function serializeSvg(nodes: SvgNode[], opts?: SerializeOptions): string;
436
418
 
@@ -452,27 +434,17 @@ declare function svgNeeds(nodes: readonly SvgNode[]): RenderNeeds;
452
434
  declare function warmSvg(nodes: readonly SvgNode[]): Promise<void>;
453
435
 
454
436
  /**
455
- * Map between SVG `<pattern>` elements and weasel's `pattern` `FillStyle`.
456
- * The counterpart to `gradients.ts` for the other paint-server kind.
457
- *
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.
437
+ * Map between SVG `<pattern>` elements and weasel's pattern paints.
464
438
  *
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.
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.
469
443
  */
470
444
 
471
445
  /**
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.
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.
476
448
  */
477
449
  declare function tilePreviewSvg(spec: TilePatternSpec, background?: string): string;
478
450
  /** `tilePreviewSvg` packed as a `url(...)` value for CSS `background-image`. */
@@ -511,14 +483,15 @@ interface SvgKitPose {
511
483
  width: number;
512
484
  height: number;
513
485
  rotation?: number;
514
- /** Read for text and image leaves only; a path leaf's is not written. */
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. */
515
488
  transform?: PoseLinear;
516
489
  }
517
490
  /** The leaf data the kit's path, text and image painters read — what
518
491
  * `svgNodesToKitDrafts` writes. */
519
492
  interface SvgKitLeafData {
520
493
  path?: Path;
521
- fill?: FillStyle | null;
494
+ fill?: Fill | null;
522
495
  stroke?: Stroke | null;
523
496
  text?: string;
524
497
  style?: TextStyle;
@@ -529,7 +502,7 @@ interface SvgKitLeafData {
529
502
  /** A kit paint as an `SvgPaint`. `null` is `fill="none"`; a box-relative
530
503
  * gradient or pattern is resolved into `box`, since the geometry it paints
531
504
  * is written already placed there. */
532
- declare function svgPaintFromKit(fill: FillStyle | null, box: SvgKitPose): SvgPaint;
505
+ declare function svgPaintFromKit(fill: Paint | null, box: SvgKitPose): SvgPaint;
533
506
  /** A kit `Stroke` as an `SvgStroke` — the inverse of `strokeDataFromSvg`.
534
507
  * One with no paint or no width draws nothing and is written as no stroke. */
535
508
  declare function svgStrokeFromKit(stroke: Stroke | null | undefined, box: SvgKitPose): SvgStroke | undefined;
@@ -542,7 +515,9 @@ declare function svgImageFromKit(image: ImageNodeData['image'], pose: SvgKitPose
542
515
  * draws.
543
516
  *
544
517
  * 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.
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.
546
521
  */
547
522
  declare function svgLeafFromKit(data: SvgKitLeafData, pose: SvgKitPose): SvgPathNode | SvgTextNode | SvgImageNode | null;
548
523
  /** One node as {@link svgNodesFromKit} reads it. */
@@ -585,26 +560,37 @@ interface SvgDraftBounds {
585
560
  width: number;
586
561
  height: number;
587
562
  }
588
- 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 & {
589
565
  rotation?: number;
590
566
  transform?: PoseLinear;
591
567
  };
568
+
592
569
  /**
593
570
  * One node the unpack wants the scene to materialize, in parent-before-child
594
571
  * order (a draft's `parentId` always names an earlier draft, or `null` for
595
- * 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.
596
579
  */
597
580
  type SvgSceneDraft<TData = Record<string, unknown>> = {
598
581
  kind: 'container';
599
582
  id: string;
600
583
  parentId: string | null;
601
- pose: DraftPose;
584
+ pose: SvgDraftPose;
585
+ data: SvgKitLeafData;
586
+ hidden?: true;
602
587
  } | {
603
588
  kind: 'leaf';
604
589
  id: string;
605
590
  parentId: string | null;
606
- pose: DraftPose;
591
+ pose: SvgDraftPose;
607
592
  data: TData;
593
+ hidden?: true;
608
594
  };
609
595
  /** A leaf `SvgNode`: everything but a group. */
610
596
  type SvgLeafNode = Exclude<SvgNode, SvgGroupNode>;
@@ -614,33 +600,27 @@ interface SvgNodesToKitDraftsOptions<TData> {
614
600
  * the node it came from — for data shaped other than the kit painters', or
615
601
  * metadata the document carries. `null` leaves it out. */
616
602
  leaf?(id: string, draft: {
617
- pose: DraftPose;
603
+ pose: SvgDraftPose;
618
604
  data: SvgKitLeafData;
619
605
  }, source: SvgLeafNode): TData | null;
620
606
  }
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`.
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).
625
610
  *
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. */
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. */
638
618
  declare function strokeDataFromSvg(stroke: SvgStroke | undefined, box: SvgDraftBounds): Stroke | undefined;
639
619
  /**
640
620
  * Walk an `SvgNode[]` tree and emit a flat, parent-before-child list of
641
621
  * {@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.
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.
644
624
  *
645
625
  * Handed a whole `ParseResult`, it also registers the document's markers
646
626
  * (`parsed.markers`), without which the strokes naming them draw bare.
@@ -652,11 +632,15 @@ declare function strokeDataFromSvg(stroke: SvgStroke | undefined, box: SvgDraftB
652
632
  */
653
633
  declare function svgNodesToKitDrafts<TData = Record<string, unknown>>(input: ParseResult | readonly SvgNode[], nextId: (source: SvgNode) => string, options?: SvgNodesToKitDraftsOptions<TData>): SvgSceneDraft<TData>[];
654
634
  /**
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.
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`.
659
643
  */
660
644
  declare function unpackSvgFiles(files: File[], ctx: IngestCtx): Promise<void>;
661
645
 
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 };
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 };