@weasel-js/svg 1.6.0 → 1.7.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 +47 -0
- package/dist/index.d.ts +138 -12
- package/dist/index.js +273 -158
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -83,6 +83,53 @@ gradient — it just blends the stops in sRGB, which moves the midpoint rather
|
|
|
83
83
|
than losing the paint. A space this package does not know is dropped on import
|
|
84
84
|
rather than carried through.
|
|
85
85
|
|
|
86
|
+
## Scene nodes, both ways
|
|
87
|
+
|
|
88
|
+
`svgNodesToKitDrafts(parseSvg(text), nextId)` lowers a document to scene-node
|
|
89
|
+
drafts the kit's built-in path, text and image painters draw, and registers
|
|
90
|
+
the document's markers. Containers carry no opacity, so an element or group
|
|
91
|
+
`opacity` is multiplied into the paints of the leaves under it. `nextId` is
|
|
92
|
+
handed the node each id is for, and `options.leaf` maps each leaf's kit data
|
|
93
|
+
into data of your own shape, from the source node's metadata too.
|
|
94
|
+
|
|
95
|
+
`svgNodesFromKit(scene)` goes the other way: containers become groups, and
|
|
96
|
+
each leaf is written the way its painter draws it, with the pose baked into
|
|
97
|
+
the geometry and box-relative paints resolved into that box. Hand
|
|
98
|
+
`serializeSvg` the result. Options pick the roots, skip nodes (a hidden
|
|
99
|
+
layer), replace a leaf's lowering or decorate a group. The per-leaf pieces are
|
|
100
|
+
public too: `svgLeafFromKit`, `svgPaintFromKit`, `svgStrokeFromKit`,
|
|
101
|
+
`svgImageFromKit`.
|
|
102
|
+
|
|
103
|
+
## Stroke markers
|
|
104
|
+
|
|
105
|
+
A marker reference goes out as `marker-start` / `-mid` / `-end="url(#id)"` with
|
|
106
|
+
one `<marker>` def per distinct reference. The path keeps its full-length `d`:
|
|
107
|
+
baking the kit's inset in would have a re-import trim the line a second time.
|
|
108
|
+
The cost is that other renderers draw the line under a hollow head.
|
|
109
|
+
|
|
110
|
+
| Reference | Def id | What the def carries |
|
|
111
|
+
|---|---|---|
|
|
112
|
+
| `'arrow'` | `arrow` | `markerUnits="strokeWidth"` |
|
|
113
|
+
| `{ key: 'arrow', size: 3 }` | `arrow-s3` | `markerUnits="userSpaceOnUse"`, `wzl:key="arrow"`, `wzl:size="3"` |
|
|
114
|
+
| `{ key: 'arrow', size: { px: 6 } }` | `arrow-s6px` | the same, drawn at 6 user units, `wzl:size="6px"` |
|
|
115
|
+
|
|
116
|
+
Any entry with a nonzero inset adds `wzl:inset` in marker units, and the root
|
|
117
|
+
declares the `wzl` namespace whenever one of these attributes appears. `orient`
|
|
118
|
+
is `auto-start-reverse`, because the kit turns every start head around, or an
|
|
119
|
+
entry's fixed angle in degrees. An entry with its own `toSvg` writes its own
|
|
120
|
+
def and gets none of the `wzl` attributes (a sized reference to one warns). A
|
|
121
|
+
reference to a key nothing registered writes no attribute.
|
|
122
|
+
|
|
123
|
+
On import, `url(#id)` naming a registered key reads back as that key, so an
|
|
124
|
+
unsized built-in needs no def. A def with `wzl:key` and `wzl:size` reads back
|
|
125
|
+
as that sized reference, registering the def's geometry under the key if
|
|
126
|
+
nothing else has. Any other `<marker>` becomes an entry in
|
|
127
|
+
`ParseResult.markers`, keyed by its id plus a hash of what it draws, and
|
|
128
|
+
`svgNodesToKitDrafts` registers it. It is minted per reference rather than per
|
|
129
|
+
def, because a `userSpaceOnUse` size and an `orient="auto"` start both depend on
|
|
130
|
+
the referencing stroke. `wzl:inset` restores the inset; without it the inset is
|
|
131
|
+
0. A reference to a marker neither registered nor defined warns and is dropped.
|
|
132
|
+
|
|
86
133
|
## Raster images
|
|
87
134
|
|
|
88
135
|
`<image>` parses to an `SvgImageNode` holding the `href` verbatim — an
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Mat3 } from '@weasel-js/geom';
|
|
2
|
-
import { Path, FillStyle, ScreenLength, StrokeAlign, StyledRun, TextStyle, TextVerticalAlign, Stroke, MarkerEntry, TilePatternSpec, ImageNodeData, IngestCtx } from '@weasel-js/core';
|
|
2
|
+
import { Path, FillStyle, ScreenLength, StrokeAlign, MarkerRef, StyledRun, TextStyle, TextVerticalAlign, Stroke, MarkerEntry, TilePatternSpec, ImageNodeData, IngestCtx } from '@weasel-js/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Public types for `@weasel-js/svg`. The package exposes a flat,
|
|
@@ -99,10 +99,12 @@ interface SvgStroke {
|
|
|
99
99
|
* writes the stroke centered and says so through `onWarn`; the parser
|
|
100
100
|
* never sets it. */
|
|
101
101
|
align?: StrokeAlign;
|
|
102
|
-
/** `marker-start` / `marker-mid` / `marker-end
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
/** `marker-start` / `marker-mid` / `marker-end` as the kit's `MarkerRef`: the
|
|
103
|
+
* bare `url(#id)` key, or a key with the `size` the serializer writes into a
|
|
104
|
+
* def of its own. */
|
|
105
|
+
markerStart?: MarkerRef;
|
|
106
|
+
markerMid?: MarkerRef;
|
|
107
|
+
markerEnd?: MarkerRef;
|
|
106
108
|
}
|
|
107
109
|
/**
|
|
108
110
|
* Leaf node: a path geometry plus fill/stroke. All other v1 shapes
|
|
@@ -390,6 +392,104 @@ declare function tilePreviewSvg(spec: TilePatternSpec, background?: string): str
|
|
|
390
392
|
/** `tilePreviewSvg` packed as a `url(...)` value for CSS `background-image`. */
|
|
391
393
|
declare function tilePreviewCssUrl(spec: TilePatternSpec, background?: string): string;
|
|
392
394
|
|
|
395
|
+
/**
|
|
396
|
+
* Map between SVG `<linearGradient>` / `<radialGradient>` elements and
|
|
397
|
+
* weasel's `FillStyle` gradient variants. Definitions are indexed by id
|
|
398
|
+
* wherever in the document they appear, and looked up when a `fill` /
|
|
399
|
+
* `stroke` attribute references one as `url(#id)`. On serialize, we
|
|
400
|
+
* emit a fresh `<defs>` block with stable generated ids.
|
|
401
|
+
*/
|
|
402
|
+
|
|
403
|
+
/** `true` when SVG has a paint server for this kind, so the reference needs no
|
|
404
|
+
* fallback color and the root needs no foreign namespace. */
|
|
405
|
+
/** Whether SVG has its own paint server for paint kind `kind`: solids, the
|
|
406
|
+
* linear and radial gradients, and patterns. Any other kind is written in
|
|
407
|
+
* weasel's namespace with a fallback color, which other renderers paint flat. */
|
|
408
|
+
declare function nativeSvgKind(kind: string): boolean;
|
|
409
|
+
/** Whether a gradient interpolating through `space` blends the same in every
|
|
410
|
+
* SVG renderer — only sRGB, the default. A perceptual space still exports,
|
|
411
|
+
* but renderers other than weasel blend it in sRGB. */
|
|
412
|
+
declare function nativeSvgSpace(space: string | null | undefined): boolean;
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* The kit-to-SVG direction: scene leaves back to `SvgNode`s, the inverse of
|
|
416
|
+
* `svgNodesToKitDrafts`. Each leaf is written as the built-in painter that
|
|
417
|
+
* draws it would draw it — the pose baked into the geometry, box-relative
|
|
418
|
+
* paints resolved into that box — so `serializeSvg` over the result shows what
|
|
419
|
+
* the canvas shows.
|
|
420
|
+
*/
|
|
421
|
+
|
|
422
|
+
/** A leaf's box: where it is drawn, and the frame its box-relative paints
|
|
423
|
+
* resolve against. */
|
|
424
|
+
interface SvgKitPose {
|
|
425
|
+
x: number;
|
|
426
|
+
y: number;
|
|
427
|
+
width: number;
|
|
428
|
+
height: number;
|
|
429
|
+
rotation?: number;
|
|
430
|
+
}
|
|
431
|
+
/** The leaf data the kit's path, text and image painters read — what
|
|
432
|
+
* `svgNodesToKitDrafts` writes. */
|
|
433
|
+
interface SvgKitLeafData {
|
|
434
|
+
path?: Path;
|
|
435
|
+
fill?: FillStyle | null;
|
|
436
|
+
stroke?: Stroke | null;
|
|
437
|
+
text?: string;
|
|
438
|
+
style?: TextStyle;
|
|
439
|
+
runs?: readonly StyledRun[];
|
|
440
|
+
verticalAlign?: TextVerticalAlign;
|
|
441
|
+
image?: ImageNodeData['image'];
|
|
442
|
+
}
|
|
443
|
+
/** A kit paint as an `SvgPaint`. `null` is `fill="none"`; a box-relative
|
|
444
|
+
* gradient or pattern is resolved into `box`, since the geometry it paints
|
|
445
|
+
* is written already placed there. */
|
|
446
|
+
declare function svgPaintFromKit(fill: FillStyle | null, box: SvgKitPose): SvgPaint;
|
|
447
|
+
/** A kit `Stroke` as an `SvgStroke` — the inverse of `strokeDataFromSvg`.
|
|
448
|
+
* One with no paint or no width draws nothing and is written as no stroke. */
|
|
449
|
+
declare function svgStrokeFromKit(stroke: Stroke | null | undefined, box: SvgKitPose): SvgStroke | undefined;
|
|
450
|
+
/** Write a `kit:image` leaf as an `SvgImageNode` — the inverse of the image
|
|
451
|
+
* arm of `svgNodesToKitDrafts`. The pose is the image's box. */
|
|
452
|
+
declare function svgImageFromKit(image: ImageNodeData['image'], pose: SvgKitPose): SvgImageNode;
|
|
453
|
+
/**
|
|
454
|
+
* Write one leaf as the `SvgNode` its built-in painter draws — image, text or
|
|
455
|
+
* path, in the order the painters claim a node. `null` for data none of them
|
|
456
|
+
* draws.
|
|
457
|
+
*
|
|
458
|
+
* A path with no `fill` takes `kit:path`'s fallback: the default shape fill
|
|
459
|
+
* when it has no stroke, and no fill when it has one.
|
|
460
|
+
*/
|
|
461
|
+
declare function svgLeafFromKit(data: SvgKitLeafData, pose: SvgKitPose): SvgPathNode | SvgTextNode | SvgImageNode | null;
|
|
462
|
+
/** One node as {@link svgNodesFromKit} reads it. */
|
|
463
|
+
interface SvgKitTreeNode {
|
|
464
|
+
kind: string;
|
|
465
|
+
data: unknown;
|
|
466
|
+
pose: SvgKitPose;
|
|
467
|
+
}
|
|
468
|
+
/** The read side of a scene that {@link svgNodesFromKit} walks. A kit
|
|
469
|
+
* `Scene` is one as it stands. */
|
|
470
|
+
interface SvgKitTree<TId extends string = string> {
|
|
471
|
+
readonly roots: readonly TId[];
|
|
472
|
+
childrenOf(id: TId): readonly TId[];
|
|
473
|
+
get(id: TId): SvgKitTreeNode | undefined;
|
|
474
|
+
}
|
|
475
|
+
interface SvgNodesFromKitOptions<TId extends string = string> {
|
|
476
|
+
/** Walk these ids, in this order, instead of the tree's roots. */
|
|
477
|
+
roots?: readonly TId[];
|
|
478
|
+
/** `false` leaves a node out, with everything under it — a hidden layer. */
|
|
479
|
+
include?(id: TId): boolean;
|
|
480
|
+
/** Lower a leaf yourself; `null` leaves it out. Defaults to {@link svgLeafFromKit}. */
|
|
481
|
+
leaf?(id: TId, node: SvgKitTreeNode): SvgNode | null;
|
|
482
|
+
/** Decorate the group written for a container, e.g. with `meta`. */
|
|
483
|
+
group?(id: TId, group: SvgGroupNode): SvgGroupNode;
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Walk a scene's container tree into `SvgNode`s ready for `serializeSvg`:
|
|
487
|
+
* every `container` becomes a group of its children, in z-order, and every
|
|
488
|
+
* leaf goes through {@link svgLeafFromKit} unless `options.leaf` says
|
|
489
|
+
* otherwise.
|
|
490
|
+
*/
|
|
491
|
+
declare function svgNodesFromKit<TId extends string>(tree: SvgKitTree<TId>, options?: SvgNodesFromKitOptions<TId>): SvgNode[];
|
|
492
|
+
|
|
393
493
|
/** Axis-aligned box, in the coordinate space of the SVG being unpacked. Used
|
|
394
494
|
* for the bounds a draft occupies before it becomes a scene node. */
|
|
395
495
|
interface SvgDraftBounds {
|
|
@@ -406,7 +506,7 @@ type DraftPose = SvgDraftBounds & {
|
|
|
406
506
|
* order (a draft's `parentId` always names an earlier draft, or `null` for
|
|
407
507
|
* roots). Leaf `data` is kit-painter-native (see module doc).
|
|
408
508
|
*/
|
|
409
|
-
type SvgSceneDraft = {
|
|
509
|
+
type SvgSceneDraft<TData = Record<string, unknown>> = {
|
|
410
510
|
kind: 'container';
|
|
411
511
|
id: string;
|
|
412
512
|
parentId: string | null;
|
|
@@ -416,8 +516,29 @@ type SvgSceneDraft = {
|
|
|
416
516
|
id: string;
|
|
417
517
|
parentId: string | null;
|
|
418
518
|
pose: DraftPose;
|
|
419
|
-
data:
|
|
519
|
+
data: TData;
|
|
420
520
|
};
|
|
521
|
+
/** A leaf `SvgNode`: everything but a group. */
|
|
522
|
+
type SvgLeafNode = Exclude<SvgNode, SvgGroupNode>;
|
|
523
|
+
interface SvgNodesToKitDraftsOptions<TData> {
|
|
524
|
+
/** Lower a leaf yourself, from the kit data the default writes for it and
|
|
525
|
+
* the node it came from — for data shaped other than the kit painters', or
|
|
526
|
+
* metadata the document carries. `null` leaves it out. */
|
|
527
|
+
leaf?(id: string, draft: {
|
|
528
|
+
pose: DraftPose;
|
|
529
|
+
data: SvgKitLeafData;
|
|
530
|
+
}, source: SvgLeafNode): TData | null;
|
|
531
|
+
}
|
|
532
|
+
/** Lower an `SvgPaint` onto the `kit:path` painter's `data.fill` — a
|
|
533
|
+
* `FillStyle`, or `null` for SVG's `fill="none"` (the painter skips the
|
|
534
|
+
* fill rather than falling back to its default). A solid paint keeps its
|
|
535
|
+
* `fill-opacity`.
|
|
536
|
+
*
|
|
537
|
+
* A gradient rides through as the `FillStyle` it already is, normalized to
|
|
538
|
+
* the leaf's own box: `objectBoundingBox` gradients already are, and a
|
|
539
|
+
* `userSpaceOnUse` one is rebased so it survives the fit-clamp and the
|
|
540
|
+
* drop-point placement that move the geometry out from under it. */
|
|
541
|
+
declare function fillDataFromSvg(paint: SvgPaint | undefined, box: SvgDraftBounds): FillStyle | null | undefined;
|
|
421
542
|
/** Lower an `SvgStroke` onto the leaf's `data.stroke`.
|
|
422
543
|
*
|
|
423
544
|
* Everything the SVG carried — paint, width, cap, join, dash, miter limit,
|
|
@@ -426,16 +547,21 @@ type SvgSceneDraft = {
|
|
|
426
547
|
* `userSpaceOnUse` gradient survives the fit-clamp and the drop-point
|
|
427
548
|
* placement. */
|
|
428
549
|
declare function strokeDataFromSvg(stroke: SvgStroke | undefined, box: SvgDraftBounds): Stroke | undefined;
|
|
429
|
-
/** Write a `kit:image` leaf as an `SvgImageNode` — the inverse of the image
|
|
430
|
-
* arm of {@link svgNodesToKitDrafts}. The pose is the image's box. */
|
|
431
|
-
declare function svgImageFromKit(image: ImageNodeData['image'], pose: DraftPose): SvgImageNode;
|
|
432
550
|
/**
|
|
433
551
|
* Walk an `SvgNode[]` tree and emit a flat, parent-before-child list of
|
|
434
552
|
* {@link SvgSceneDraft}s. Each `<g>` becomes a container whose pose is the
|
|
435
553
|
* union AABB of its descendants (the kit `group` action's convention);
|
|
436
554
|
* path/text leaves carry kit-painter-native data. Empty groups are dropped.
|
|
555
|
+
*
|
|
556
|
+
* Handed a whole `ParseResult`, it also registers the document's markers
|
|
557
|
+
* (`parsed.markers`), without which the strokes naming them draw bare.
|
|
558
|
+
* Containers carry no opacity, so an element or group `opacity` is multiplied
|
|
559
|
+
* into the paints of every leaf under it.
|
|
560
|
+
*
|
|
561
|
+
* `nextId` is handed the node each id is for. `options.leaf` replaces a leaf's
|
|
562
|
+
* data; a group whose leaves it all leaves out is dropped like an empty one.
|
|
437
563
|
*/
|
|
438
|
-
declare function svgNodesToKitDrafts(
|
|
564
|
+
declare function svgNodesToKitDrafts<TData = Record<string, unknown>>(input: ParseResult | readonly SvgNode[], nextId: (source: SvgNode) => string, options?: SvgNodesToKitDraftsOptions<TData>): SvgSceneDraft<TData>[];
|
|
439
565
|
/**
|
|
440
566
|
* Parse each file and insert its node tree — one undoable `applyOps` batch
|
|
441
567
|
* per file. See the module doc for placement and wrapping policy. A file
|
|
@@ -444,4 +570,4 @@ declare function svgNodesToKitDrafts(nodes: readonly SvgNode[], nextId: () => st
|
|
|
444
570
|
*/
|
|
445
571
|
declare function unpackSvgFiles(files: File[], ctx: IngestCtx): Promise<void>;
|
|
446
572
|
|
|
447
|
-
export { IDENTITY_MATRIX, type Matrix, type NamespaceMeta, type NamespacedElement, type ParseOptions, type ParseResult, type SerializeOptions, type SvgDraftBounds, type SvgGroupNode, type SvgImageNode, type SvgNode, type SvgPaint, type SvgPathNode, type SvgSceneDraft, type SvgStroke, type SvgTextNode, parseSvg, serializeSvg, strokeDataFromSvg, svgImageFromKit, svgNodesToKitDrafts, tilePreviewCssUrl, tilePreviewSvg, unpackSvgFiles };
|
|
573
|
+
export { IDENTITY_MATRIX, type Matrix, type NamespaceMeta, type NamespacedElement, type ParseOptions, type ParseResult, type SerializeOptions, type SvgDraftBounds, type SvgGroupNode, type SvgImageNode, type SvgKitLeafData, type SvgKitPose, type SvgKitTree, type SvgKitTreeNode, type SvgLeafNode, type SvgNode, type SvgNodesFromKitOptions, type SvgNodesToKitDraftsOptions, type SvgPaint, type SvgPathNode, type SvgSceneDraft, type SvgStroke, type SvgTextNode, fillDataFromSvg, nativeSvgKind, nativeSvgSpace, parseSvg, serializeSvg, strokeDataFromSvg, svgImageFromKit, svgLeafFromKit, svgNodesFromKit, svgNodesToKitDrafts, svgPaintFromKit, svgStrokeFromKit, tilePreviewCssUrl, tilePreviewSvg, unpackSvgFiles };
|