@weasel-js/svg 1.4.4 → 1.5.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
@@ -40,6 +40,49 @@ answer, and the alternative is unrepresentable anyway (see above). It only
40
40
  shows up on foreign SVG that sets decoration on a group and cancels it on a
41
41
  child.
42
42
 
43
+ ## Paints SVG cannot express
44
+
45
+ SVG has `<linearGradient>`, `<radialGradient>` and `<pattern>` and nothing
46
+ else, so a conic gradient — or any paint kind a consumer registers — has no
47
+ element to serialize into. Both go out as a def in weasel's own namespace,
48
+ declared on the root as `xmlns:wzl="urn:weasel-js:svg"` only when a document
49
+ holds such a paint:
50
+
51
+ ```xml
52
+ <defs><wzl:conicGradient id="grad0" gradientUnits="objectBoundingBox"
53
+ cx="0.5" cy="0.5" angle="0.25"><stop offset="0" stop-color="#ff0000"/>
54
+ <stop offset="1" stop-color="#0000ff"/></wzl:conicGradient></defs>
55
+ <path fill="url(#grad0) #ff0000" d="…"/>
56
+ ```
57
+
58
+ The color after the reference is SVG's own paint fallback: this package reads
59
+ the def back and reproduces the paint exactly, every other renderer skips the
60
+ def it does not know and paints that flat color instead. A registered kind's
61
+ `toSvg` gets the same treatment, with the fallback taken from its `colorOf` —
62
+ or `none` when the kind has no single color, so an unresolvable reference
63
+ paints nothing rather than something arbitrary.
64
+
65
+ That cuts both ways on import: a `fill="url(#mesh1) #c04a3f"` from Inkscape,
66
+ whose mesh gradients this package does not model, imports as flat `#c04a3f`
67
+ rather than as a dropped fill.
68
+
69
+ ### A gradient's blend space
70
+
71
+ A gradient's `interpolate` — `'oklab'` or `'oklch'` — has no SVG spelling
72
+ either: `color-interpolation` carries `sRGB` and `linearRGB` only. It goes out
73
+ as `wzl:interpolate` on the gradient's *own* element, which stays
74
+ `<linearGradient>` or `<radialGradient>`:
75
+
76
+ ```xml
77
+ <linearGradient id="grad0" gradientUnits="objectBoundingBox"
78
+ x1="0" y1="0" x2="1" y2="0" wzl:interpolate="oklch">…</linearGradient>
79
+ ```
80
+
81
+ No fallback color rides along, because a foreign renderer still paints the
82
+ gradient — it just blends the stops in sRGB, which moves the midpoint rather
83
+ than losing the paint. A space this package does not know is dropped on import
84
+ rather than carried through.
85
+
43
86
  ## Raster images
44
87
 
45
88
  `<image>` parses to an `SvgImageNode` holding the `href` verbatim — an
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Path, FillStyle, StyledRun, TextStyle, Stroke, TilePatternSpec, IngestCtx } from '@weasel-js/core';
1
+ import { Path, FillStyle, StrokeAlign, StyledRun, TextStyle, TextVerticalAlign, Stroke, MarkerEntry, TilePatternSpec, IngestCtx } from '@weasel-js/core';
2
2
 
3
3
  /**
4
4
  * Public types for `@weasel-js/svg`. The package exposes a flat,
@@ -91,6 +91,10 @@ interface SvgStroke {
91
91
  * attribute may render with longer miters than the source SVG intended.
92
92
  */
93
93
  miterLimit?: number;
94
+ /** The kit stroke's `align`. SVG has no stroke alignment, so the serializer
95
+ * writes the stroke centered and says so through `onWarn`; the parser
96
+ * never sets it. */
97
+ align?: StrokeAlign;
94
98
  /** `marker-start` / `marker-mid` / `marker-end`, as the bare `url(#id)` key. */
95
99
  markerStart?: string;
96
100
  markerMid?: string;
@@ -126,20 +130,16 @@ interface SvgGroupNode {
126
130
  kind: 'group';
127
131
  children: SvgNode[];
128
132
  transform?: Matrix;
133
+ /** Clips the group's contents. Serializes to a `<clipPath>` def plus
134
+ * `clip-path="url(#id)"`, and lives in the same space as `children` —
135
+ * SVG applies the group's own `transform` to the clip as well.
136
+ * A `<clipPath>` of more than one shape parses back as no clip: SVG
137
+ * unions them and a `Path` holds one outline. */
138
+ clip?: Path;
129
139
  opacity?: number;
130
140
  /** Opaque per-element bag for declared namespaces. See `NamespaceMeta`. */
131
141
  meta?: NamespaceMeta;
132
142
  }
133
- /**
134
- * Width given to an external `<text>` that carries no `data-weasel-width`:
135
- * large enough that line wrapping never fires, since SVG text flows from a
136
- * baseline and the source says nothing about a box.
137
- *
138
- * It is a wrap sentinel, not a measurement. Anything that treats a text
139
- * node's width as geometry — a union AABB, a fit-clamp — has to recognize it
140
- * and substitute an estimate, or one text node swamps the whole file.
141
- */
142
- declare const UNBOUNDED_TEXT_WIDTH = 99999;
143
143
  /**
144
144
  * Text leaf node. Mirrors the kit's `TextPose` shape — a bounding box plus
145
145
  * text content, optional rich-text `runs` (per-range styling), and an
@@ -150,9 +150,12 @@ declare const UNBOUNDED_TEXT_WIDTH = 99999;
150
150
  * `dominant-baseline="text-before-edge"` so `y` is the top of the text
151
151
  * box, plus `data-weasel-width` / `data-weasel-height` so weasel's
152
152
  * explicit box dimensions round-trip losslessly. External SVG text
153
- * (lacking the data-* attrs) imports with estimated dimensions —
154
- * {@link UNBOUNDED_TEXT_WIDTH} and
153
+ * (lacking the data-* attrs) imports with the width the kit's text layout
154
+ * measures — an em-based estimate when no registered font can — and
155
155
  * `height = fontSize * lineHeight * (lineCount || 1)`.
156
+ *
157
+ * `x` here is always the box's left edge. In the SVG it is the
158
+ * `text-anchor` point, and the parser and serializer convert between them.
156
159
  */
157
160
  interface SvgTextNode {
158
161
  kind: 'text';
@@ -166,6 +169,10 @@ interface SvgTextNode {
166
169
  runs?: StyledRun[];
167
170
  /** Node-wide typography. Defaults applied at render time via `resolveTextStyle`. */
168
171
  style?: TextStyle;
172
+ /** Where the laid-out text sits inside the box — `kit:text`'s
173
+ * `data.verticalAlign`. SVG text has no box to align in, so this rides in
174
+ * `data-weasel-vertical-align` and other readers draw the text top-aligned. */
175
+ verticalAlign?: TextVerticalAlign;
169
176
  /**
170
177
  * Node-wide glyph paint, the `FillStyle` / `Stroke` a kit text node holds
171
178
  * in `data.fill` / `data.stroke`. Not `SvgPaint`, which is the path
@@ -193,6 +200,12 @@ interface SvgTextNode {
193
200
  * a reference and only resolves when something downstream loads it.
194
201
  *
195
202
  * SVG's `preserveAspectRatio` is not modeled; the box is taken literally.
203
+ *
204
+ * With a `source` rect or a flip, the element is written as a `<g
205
+ * data-weasel-image>` holding a nested `<svg>` viewport at the box, whose
206
+ * `viewBox` is the source window over a unit-square `<image>` — plain SVG 1.1,
207
+ * which any reader crops and mirrors the same way, and which `parseSvg` reads
208
+ * back as one image node.
196
209
  */
197
210
  interface SvgImageNode {
198
211
  kind: 'image';
@@ -201,6 +214,20 @@ interface SvgImageNode {
201
214
  y: number;
202
215
  width: number;
203
216
  height: number;
217
+ /** The part of the bitmap drawn into the box, as fractions of the bitmap's
218
+ * width and height from its top-left. Omitted draws the whole bitmap.
219
+ * Fractions rather than bitmap pixels because this package never decodes
220
+ * the image, so it cannot know its size. */
221
+ source?: {
222
+ x: number;
223
+ y: number;
224
+ width: number;
225
+ height: number;
226
+ };
227
+ /** Mirror the drawn region within the box, as `ImageDrawCommand` does. The
228
+ * box does not move. */
229
+ flipX?: boolean;
230
+ flipY?: boolean;
204
231
  /** Element-level opacity (`opacity="..."`), 0..1. */
205
232
  opacity?: number;
206
233
  /** Element-level rotation in **radians**, pivoting around the unrotated
@@ -247,6 +274,13 @@ interface ParseResult {
247
274
  height?: number;
248
275
  /** Text content of the first `<title>` child of `<svg>`, when present. */
249
276
  title?: string;
277
+ /**
278
+ * An entry for each document `<marker>` a stroke references under a key the
279
+ * marker registry does not know, keyed as the strokes in `nodes` now name
280
+ * them. Nothing draws them until they are registered (`registerMarker`),
281
+ * which `unpackSvgFiles` does.
282
+ */
283
+ markers?: MarkerEntry[];
250
284
  }
251
285
  /** Options for {@link serializeSvg}. */
252
286
  interface SerializeOptions {
@@ -261,9 +295,11 @@ interface SerializeOptions {
261
295
  height: number;
262
296
  };
263
297
  /**
264
- * Called for paint that can't be expressed in SVG and is dropped — a
265
- * conic gradient, or a pattern carrying a `TextureHandle` instead of a
266
- * `TilePatternSpec`. Without this the loss is silent.
298
+ * Called for what the document cannot carry the way weasel draws it: a
299
+ * paint SVG cannot express (a pattern carrying a `TextureHandle` instead of
300
+ * a `TilePatternSpec`), a stroke aligned off its edge, text that wraps or
301
+ * sits lower than the top of its box. Each message is said once per call.
302
+ * Without this the loss is silent.
267
303
  */
268
304
  onWarn?: (message: string) => void;
269
305
  /** Emit `width="..."` on the root `<svg>`. */
@@ -401,4 +437,4 @@ declare function svgNodesToKitDrafts(nodes: readonly SvgNode[], nextId: () => st
401
437
  */
402
438
  declare function unpackSvgFiles(files: File[], ctx: IngestCtx): Promise<void>;
403
439
 
404
- 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, UNBOUNDED_TEXT_WIDTH, parseSvg, serializeSvg, strokeDataFromSvg, svgNodesToKitDrafts, tilePreviewCssUrl, tilePreviewSvg, unpackSvgFiles };
440
+ 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, svgNodesToKitDrafts, tilePreviewCssUrl, tilePreviewSvg, unpackSvgFiles };