@weasel-js/svg 1.5.0 → 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,6 +130,12 @@ 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;
@@ -159,6 +169,10 @@ interface SvgTextNode {
159
169
  runs?: StyledRun[];
160
170
  /** Node-wide typography. Defaults applied at render time via `resolveTextStyle`. */
161
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;
162
176
  /**
163
177
  * Node-wide glyph paint, the `FillStyle` / `Stroke` a kit text node holds
164
178
  * in `data.fill` / `data.stroke`. Not `SvgPaint`, which is the path
@@ -186,6 +200,12 @@ interface SvgTextNode {
186
200
  * a reference and only resolves when something downstream loads it.
187
201
  *
188
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.
189
209
  */
190
210
  interface SvgImageNode {
191
211
  kind: 'image';
@@ -194,6 +214,20 @@ interface SvgImageNode {
194
214
  y: number;
195
215
  width: number;
196
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;
197
231
  /** Element-level opacity (`opacity="..."`), 0..1. */
198
232
  opacity?: number;
199
233
  /** Element-level rotation in **radians**, pivoting around the unrotated
@@ -240,6 +274,13 @@ interface ParseResult {
240
274
  height?: number;
241
275
  /** Text content of the first `<title>` child of `<svg>`, when present. */
242
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[];
243
284
  }
244
285
  /** Options for {@link serializeSvg}. */
245
286
  interface SerializeOptions {
@@ -254,9 +295,11 @@ interface SerializeOptions {
254
295
  height: number;
255
296
  };
256
297
  /**
257
- * Called for paint that can't be expressed in SVG and is dropped — a
258
- * conic gradient, or a pattern carrying a `TextureHandle` instead of a
259
- * `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.
260
303
  */
261
304
  onWarn?: (message: string) => void;
262
305
  /** Emit `width="..."` on the root `<svg>`. */