@weasel-js/svg 0.5.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/LICENSE +21 -0
- package/README.md +23 -0
- package/dist/index.d.ts +307 -0
- package/dist/index.js +1384 -0
- package/dist/index.js.map +1 -0
- package/package.json +46 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 orochi235
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# @weasel-js/svg
|
|
2
|
+
|
|
3
|
+
SVG import/export for @weasel-js/core: parse SVG strings into weasel-native shapes and serialize them back.
|
|
4
|
+
|
|
5
|
+
Part of [weasel](https://github.com/orochi235/weasel), a domain-agnostic 2D
|
|
6
|
+
scene-graph canvas kit for React. See the
|
|
7
|
+
[API reference](https://orochi235.github.io/weasel/api/).
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install @weasel-js/svg
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { /* … */ } from '@weasel-js/svg';
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## License
|
|
22
|
+
|
|
23
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { Path, FillStyle, StyledRun, TextStyle, IngestCtx } from '@weasel-js/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Public types for `@weasel-js/svg`. The package exposes a flat,
|
|
5
|
+
* discriminated-union node model (`SvgNode`) that mirrors the SVG element
|
|
6
|
+
* tree but uses weasel-native leaf data (`Path`, `FillStyle`) for geometry and
|
|
7
|
+
* paint.
|
|
8
|
+
*
|
|
9
|
+
* Parsing collapses every `transform="..."` onto its descendants' geometry,
|
|
10
|
+
* so the `SvgNode` tree returned from `parseSvg` never has a non-identity
|
|
11
|
+
* group transform. Consumers may still construct groups with explicit
|
|
12
|
+
* transforms before serializing, in which case the serializer emits a
|
|
13
|
+
* single `matrix(a b c d e f)` on the `<g>`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Opaque pass-through bag for namespaced XML content.
|
|
18
|
+
*
|
|
19
|
+
* weasel-svg does not interpret the contents — it only ensures that any
|
|
20
|
+
* attributes or child elements in a *declared* XML namespace round-trip
|
|
21
|
+
* losslessly through parse → serialize. Consumers (e.g. an app-specific
|
|
22
|
+
* bridge layer) hang their domain semantics off this structure.
|
|
23
|
+
*
|
|
24
|
+
* Keyed by namespace prefix (the prefix is a write-time choice; the URI
|
|
25
|
+
* is the canonical identifier and is supplied via `ParseOptions.namespaces`
|
|
26
|
+
* / `SerializeOptions.namespaces`).
|
|
27
|
+
*/
|
|
28
|
+
interface NamespaceMeta {
|
|
29
|
+
[prefix: string]: {
|
|
30
|
+
/** Local-name → string-value map for attributes in this namespace. */
|
|
31
|
+
attrs?: Record<string, string>;
|
|
32
|
+
/**
|
|
33
|
+
* Child elements in this namespace, keyed by local name. Each entry
|
|
34
|
+
* is an array because a namespace can host multiple sibling elements
|
|
35
|
+
* with the same tag (e.g. `<wd:layer/><wd:layer/>`).
|
|
36
|
+
*/
|
|
37
|
+
elements?: Record<string, NamespacedElement[]>;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** Opaque structured representation of a namespaced element. */
|
|
41
|
+
interface NamespacedElement {
|
|
42
|
+
/** Attribute local-name → string-value. */
|
|
43
|
+
attrs: Record<string, string>;
|
|
44
|
+
/** Text content, when the element contains only text (no child elements). */
|
|
45
|
+
text?: string;
|
|
46
|
+
/** Nested namespaced children, keyed by local name (recursive). */
|
|
47
|
+
children?: Record<string, NamespacedElement[]>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 2x3 affine matrix in column-major form (SVG's `matrix(a b c d e f)`
|
|
51
|
+
* order). Maps `[x', y'] = [a*x + c*y + e, b*x + d*y + f]`.
|
|
52
|
+
*/
|
|
53
|
+
type Matrix = readonly [number, number, number, number, number, number];
|
|
54
|
+
/** Identity matrix — useful as a default in tests / constructors. */
|
|
55
|
+
declare const IDENTITY_MATRIX: Matrix;
|
|
56
|
+
/**
|
|
57
|
+
* FillStyle description in the SVG sense — either explicit `none`, a solid
|
|
58
|
+
* color, or a reference to a weasel-native gradient `FillStyle`. Solid colors
|
|
59
|
+
* are normalized to `#rrggbb` strings; opacity is carried separately so
|
|
60
|
+
* `fill-opacity` and `stroke-opacity` round-trip cleanly.
|
|
61
|
+
*/
|
|
62
|
+
type SvgPaint = {
|
|
63
|
+
kind: 'none';
|
|
64
|
+
} | {
|
|
65
|
+
kind: 'solid';
|
|
66
|
+
color: string;
|
|
67
|
+
opacity?: number;
|
|
68
|
+
} | {
|
|
69
|
+
kind: 'gradient';
|
|
70
|
+
paint: FillStyle;
|
|
71
|
+
};
|
|
72
|
+
/** Stroke description: a paint plus structural line parameters. */
|
|
73
|
+
interface SvgStroke {
|
|
74
|
+
paint: SvgPaint;
|
|
75
|
+
width: number;
|
|
76
|
+
opacity?: number;
|
|
77
|
+
/** `stroke-linecap`. Default per SVG spec is `'butt'`. */
|
|
78
|
+
cap?: 'butt' | 'round' | 'square';
|
|
79
|
+
/** `stroke-linejoin`. Default per SVG spec is `'miter'`. SVG's `arcs` / `miter-clip` map to `'miter'` with a warning. */
|
|
80
|
+
join?: 'miter' | 'round' | 'bevel';
|
|
81
|
+
/** `stroke-dasharray` as a flat number array. Odd-length inputs are doubled per SVG spec. */
|
|
82
|
+
dash?: number[];
|
|
83
|
+
/**
|
|
84
|
+
* `stroke-miterlimit`. SVG's default is 4. Weasel's renderer defaults to
|
|
85
|
+
* 10 (Canvas2D) when unset, so parsed strokes without an explicit
|
|
86
|
+
* attribute may render with longer miters than the source SVG intended.
|
|
87
|
+
*/
|
|
88
|
+
miterLimit?: number;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Leaf node: a path geometry plus fill/stroke. All other v1 shapes
|
|
92
|
+
* (`<rect>`, `<ellipse>`, etc.) are lowered to this representation on
|
|
93
|
+
* parse — `<rect>` uses weasel's `RectPath` fast-path subtype when
|
|
94
|
+
* possible, everything else becomes a `PolygonPath`.
|
|
95
|
+
*/
|
|
96
|
+
interface SvgPathNode {
|
|
97
|
+
kind: 'path';
|
|
98
|
+
path: Path;
|
|
99
|
+
fill: SvgPaint;
|
|
100
|
+
stroke?: SvgStroke;
|
|
101
|
+
/** Element-level opacity (`opacity="..."`), 0..1. */
|
|
102
|
+
opacity?: number;
|
|
103
|
+
/** Element-level rotation in **radians**, pivoting around the AABB
|
|
104
|
+
* center of the underlying `path` geometry. Emitted as SVG
|
|
105
|
+
* `transform="rotate(angleDegrees cx cy)"`. */
|
|
106
|
+
rotation?: number;
|
|
107
|
+
/** Opaque per-element bag for declared namespaces. See `NamespaceMeta`. */
|
|
108
|
+
meta?: NamespaceMeta;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Group node: an SVG `<g>`. On parse, `transform` is always omitted (any
|
|
112
|
+
* `transform` attribute is collapsed onto descendants' geometry). On
|
|
113
|
+
* serialize, a non-identity `transform` is emitted as
|
|
114
|
+
* `matrix(a b c d e f)`.
|
|
115
|
+
*/
|
|
116
|
+
interface SvgGroupNode {
|
|
117
|
+
kind: 'group';
|
|
118
|
+
children: SvgNode[];
|
|
119
|
+
transform?: Matrix;
|
|
120
|
+
opacity?: number;
|
|
121
|
+
/** Opaque per-element bag for declared namespaces. See `NamespaceMeta`. */
|
|
122
|
+
meta?: NamespaceMeta;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Text leaf node. Mirrors the kit's `TextPose` shape — a bounding box plus
|
|
126
|
+
* text content, optional rich-text `runs` (per-range styling), and an
|
|
127
|
+
* optional `TextStyle` for the node-wide defaults.
|
|
128
|
+
*
|
|
129
|
+
* Note on SVG text geometry: native `<text>` flows from a baseline; it
|
|
130
|
+
* doesn't have width/height attributes. The serializer emits
|
|
131
|
+
* `dominant-baseline="text-before-edge"` so `y` is the top of the text
|
|
132
|
+
* box, plus `data-weasel-width` / `data-weasel-height` so weasel's
|
|
133
|
+
* explicit box dimensions round-trip losslessly. External SVG text
|
|
134
|
+
* (lacking the data-* attrs) imports with estimated dimensions —
|
|
135
|
+
* `width = 99999` (effectively unbounded so wrap doesn't fire) and
|
|
136
|
+
* `height = fontSize * lineHeight * (lineCount || 1)`.
|
|
137
|
+
*/
|
|
138
|
+
interface SvgTextNode {
|
|
139
|
+
kind: 'text';
|
|
140
|
+
x: number;
|
|
141
|
+
y: number;
|
|
142
|
+
width: number;
|
|
143
|
+
height: number;
|
|
144
|
+
/** Plain text. When `runs` is set, `runsToPlainText(runs)` must equal `text`. */
|
|
145
|
+
text: string;
|
|
146
|
+
/** Optional per-range styling — same shape as `TextPose.runs`. */
|
|
147
|
+
runs?: StyledRun[];
|
|
148
|
+
/** Node-wide style. Defaults applied at render time via `resolveTextStyle`. */
|
|
149
|
+
style?: TextStyle;
|
|
150
|
+
/** Element-level opacity (`opacity="..."`), 0..1. */
|
|
151
|
+
opacity?: number;
|
|
152
|
+
/** Element-level rotation in **radians**, pivoting around the unrotated
|
|
153
|
+
* AABB center `(x + width/2, y + height/2)`. Emitted as SVG
|
|
154
|
+
* `transform="rotate(angleDegrees cx cy)"`. */
|
|
155
|
+
rotation?: number;
|
|
156
|
+
/** Opaque per-element bag for declared namespaces. See `NamespaceMeta`. */
|
|
157
|
+
meta?: NamespaceMeta;
|
|
158
|
+
}
|
|
159
|
+
/** Discriminated-union node — the leaf of the public tree. */
|
|
160
|
+
type SvgNode = SvgPathNode | SvgGroupNode | SvgTextNode;
|
|
161
|
+
/** Options for {@link parseSvg}. */
|
|
162
|
+
interface ParseOptions {
|
|
163
|
+
/**
|
|
164
|
+
* Map of prefix → URI for XML namespaces the caller wants surfaced.
|
|
165
|
+
* Attributes / child elements in any declared namespace are collected
|
|
166
|
+
* into `SvgNode.meta` (per element) or `ParseResult.documentMeta` (root).
|
|
167
|
+
* Undeclared namespaces are preserved in the DOM but not promoted into
|
|
168
|
+
* the structured `meta` bag; they are silently dropped at serialize time.
|
|
169
|
+
*/
|
|
170
|
+
namespaces?: Record<string, string>;
|
|
171
|
+
}
|
|
172
|
+
/** Output of {@link parseSvg}. */
|
|
173
|
+
interface ParseResult {
|
|
174
|
+
nodes: SvgNode[];
|
|
175
|
+
/** Non-fatal notices (unsupported elements, unrecognized attributes). */
|
|
176
|
+
warnings: string[];
|
|
177
|
+
/**
|
|
178
|
+
* Opaque per-document bag for declared namespaces. Holds root-level
|
|
179
|
+
* namespaced attributes (`documentMeta.<prefix>.attrs`) and namespaced
|
|
180
|
+
* root children (`documentMeta.<prefix>.elements`).
|
|
181
|
+
*/
|
|
182
|
+
documentMeta?: NamespaceMeta;
|
|
183
|
+
/** Root `viewBox` attribute, parsed from `"x y width height"`. */
|
|
184
|
+
viewBox?: {
|
|
185
|
+
x: number;
|
|
186
|
+
y: number;
|
|
187
|
+
width: number;
|
|
188
|
+
height: number;
|
|
189
|
+
};
|
|
190
|
+
/** Root `width` attribute, parsed as a unitless number when present. */
|
|
191
|
+
width?: number;
|
|
192
|
+
/** Root `height` attribute, parsed as a unitless number when present. */
|
|
193
|
+
height?: number;
|
|
194
|
+
/** Text content of the first `<title>` child of `<svg>`, when present. */
|
|
195
|
+
title?: string;
|
|
196
|
+
}
|
|
197
|
+
/** Options for {@link serializeSvg}. */
|
|
198
|
+
interface SerializeOptions {
|
|
199
|
+
/**
|
|
200
|
+
* Override the root `viewBox`. When omitted, the serializer computes
|
|
201
|
+
* a tight bounding box from the supplied nodes.
|
|
202
|
+
*/
|
|
203
|
+
viewBox?: {
|
|
204
|
+
x: number;
|
|
205
|
+
y: number;
|
|
206
|
+
width: number;
|
|
207
|
+
height: number;
|
|
208
|
+
};
|
|
209
|
+
/** Emit `width="..."` on the root `<svg>`. */
|
|
210
|
+
width?: number;
|
|
211
|
+
/** Emit `height="..."` on the root `<svg>`. */
|
|
212
|
+
height?: number;
|
|
213
|
+
/**
|
|
214
|
+
* Emit `<title>...</title>` as the first child of the root `<svg>`. The
|
|
215
|
+
* value is XML-escaped on output. Empty strings are skipped.
|
|
216
|
+
*/
|
|
217
|
+
title?: string;
|
|
218
|
+
/**
|
|
219
|
+
* Map of prefix → URI for XML namespaces to declare on the root
|
|
220
|
+
* `<svg>`. The serializer reads `documentMeta[prefix]` and
|
|
221
|
+
* `node.meta[prefix]` to write the actual attributes and elements.
|
|
222
|
+
*/
|
|
223
|
+
namespaces?: Record<string, string>;
|
|
224
|
+
/**
|
|
225
|
+
* Opaque per-document namespaced extras. Each `<prefix>.attrs` becomes
|
|
226
|
+
* `<prefix>:<name>="..."` attributes on the root `<svg>`. Each
|
|
227
|
+
* `<prefix>.elements[localName]` becomes `<prefix>:<localName>...>`
|
|
228
|
+
* children placed immediately before any geometry.
|
|
229
|
+
*/
|
|
230
|
+
documentMeta?: NamespaceMeta;
|
|
231
|
+
/** Pretty-print with newlines + indentation. Default `false`. */
|
|
232
|
+
pretty?: boolean;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Parse an SVG document string into a `SvgNode[]` tree. Walks the SVG
|
|
237
|
+
* DOM (via the platform's `DOMParser` — jsdom in tests, the browser at
|
|
238
|
+
* runtime) and lowers each supported element to weasel-native shapes.
|
|
239
|
+
*
|
|
240
|
+
* Transforms are pushed onto a matrix stack and collapsed into leaf
|
|
241
|
+
* geometry at the moment a leaf is created, so the output tree never
|
|
242
|
+
* carries `transform` data — even on `<g>` nodes.
|
|
243
|
+
*/
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Public entry point: parse an SVG document string. Errors during DOM
|
|
247
|
+
* parsing produce a `ParseResult` with an empty `nodes` array and a
|
|
248
|
+
* warning describing the issue, rather than throwing.
|
|
249
|
+
*/
|
|
250
|
+
declare function parseSvg(svg: string, opts?: ParseOptions): ParseResult;
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Serialize a `SvgNode[]` tree to an SVG document string. Every leaf is
|
|
254
|
+
* emitted as a `<path>` (even shapes that started as `<rect>` etc.) —
|
|
255
|
+
* lossless geometry round-trip beats round-tripping the element-kind
|
|
256
|
+
* label. Gradient paints are gathered into a single `<defs>` block.
|
|
257
|
+
*/
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Walk the tree and produce an SVG document string. The root `<svg>`'s
|
|
261
|
+
* `viewBox` is taken from `opts.viewBox` if supplied, otherwise computed
|
|
262
|
+
* as a tight bounding box around every leaf.
|
|
263
|
+
*/
|
|
264
|
+
declare function serializeSvg(nodes: SvgNode[], opts?: SerializeOptions): string;
|
|
265
|
+
|
|
266
|
+
interface SvgDraftBounds {
|
|
267
|
+
x: number;
|
|
268
|
+
y: number;
|
|
269
|
+
width: number;
|
|
270
|
+
height: number;
|
|
271
|
+
}
|
|
272
|
+
type DraftPose = SvgDraftBounds & {
|
|
273
|
+
rotation?: number;
|
|
274
|
+
};
|
|
275
|
+
/**
|
|
276
|
+
* One node the unpack wants the scene to materialize, in parent-before-child
|
|
277
|
+
* order (a draft's `parentId` always names an earlier draft, or `null` for
|
|
278
|
+
* roots). Leaf `data` is kit-painter-native (see module doc).
|
|
279
|
+
*/
|
|
280
|
+
type SvgSceneDraft = {
|
|
281
|
+
kind: 'container';
|
|
282
|
+
id: string;
|
|
283
|
+
parentId: string | null;
|
|
284
|
+
pose: DraftPose;
|
|
285
|
+
} | {
|
|
286
|
+
kind: 'leaf';
|
|
287
|
+
id: string;
|
|
288
|
+
parentId: string | null;
|
|
289
|
+
pose: DraftPose;
|
|
290
|
+
data: Record<string, unknown>;
|
|
291
|
+
};
|
|
292
|
+
/**
|
|
293
|
+
* Walk an `SvgNode[]` tree and emit a flat, parent-before-child list of
|
|
294
|
+
* {@link SvgSceneDraft}s. Each `<g>` becomes a container whose pose is the
|
|
295
|
+
* union AABB of its descendants (the kit `group` action's convention);
|
|
296
|
+
* path/text leaves carry kit-painter-native data. Empty groups are dropped.
|
|
297
|
+
*/
|
|
298
|
+
declare function svgNodesToKitDrafts(nodes: readonly SvgNode[], nextId: () => string): SvgSceneDraft[];
|
|
299
|
+
/**
|
|
300
|
+
* Parse each file and insert its node tree — one undoable `applyOps` batch
|
|
301
|
+
* per file. See the module doc for placement and wrapping policy. A file
|
|
302
|
+
* that fails to parse (or parses to nothing) is skipped with a
|
|
303
|
+
* `console.warn`; the rest proceed.
|
|
304
|
+
*/
|
|
305
|
+
declare function unpackSvgFiles(files: File[], ctx: IngestCtx): Promise<void>;
|
|
306
|
+
|
|
307
|
+
export { IDENTITY_MATRIX, type Matrix, type NamespaceMeta, type NamespacedElement, type ParseOptions, type ParseResult, type SerializeOptions, type SvgDraftBounds, type SvgGroupNode, type SvgNode, type SvgPaint, type SvgPathNode, type SvgSceneDraft, type SvgStroke, type SvgTextNode, parseSvg, serializeSvg, svgNodesToKitDrafts, unpackSvgFiles };
|