@weasel-js/paint 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/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * A registered texture, named by the registry that holds it.
3
3
  *
4
- * The handle is here rather than beside the registry because `FillStyle`'s
4
+ * The handle is here rather than beside the registry because `Paint`'s
5
5
  * pattern variant names it, and paint is a leaf: the registry lives in the
6
6
  * renderer and imports this back.
7
7
  */
@@ -9,6 +9,124 @@ interface TextureHandle {
9
9
  readonly id: string;
10
10
  }
11
11
 
12
+ /**
13
+ * A pattern tile's linear map into paint space: `[a, b, c, d]`, the first
14
+ * four entries of SVG's `matrix(a b c d e f)`. A tile point `(u, v)` lands at
15
+ * `origin + (a·u + c·v, b·u + d·v)`. Translation is the paint's `origin`, so
16
+ * it has no slot here.
17
+ */
18
+ type PatternTransform = readonly [a: number, b: number, c: number, d: number];
19
+ /**
20
+ * A {@link PatternTransform} as the parts an editor shows. Angles are radians.
21
+ * Applied in the order scale, then skew, then rotation — rotation outermost,
22
+ * so turning a skewed tile turns it whole.
23
+ */
24
+ interface PatternTransformParts {
25
+ rotation: number;
26
+ scaleX: number;
27
+ scaleY: number;
28
+ /** Shear of the tile's y axis toward x, as SVG `skewX()`. */
29
+ skewX: number;
30
+ }
31
+ /** `R(rotation) · K(skewX) · S(scaleX, scaleY)`. Omitted parts are identity. */
32
+ declare function composePatternTransform(parts: Partial<PatternTransformParts>): PatternTransform;
33
+ /**
34
+ * Inverse of {@link composePatternTransform}. Any 2×2 matrix decomposes this
35
+ * way; a reflection comes back as a negative `scaleY`. `undefined` reads as the
36
+ * identity, which is what an absent `transform` means.
37
+ */
38
+ declare function decomposePatternTransform(t: PatternTransform | undefined): PatternTransformParts;
39
+
40
+ /**
41
+ * The pattern members of `Paint`: the built-in tiles, each its own kind, and
42
+ * `texture` for a tile the consumer rasterized itself.
43
+ */
44
+
45
+ /**
46
+ * Where a pattern's tile sits, shared by every pattern kind.
47
+ *
48
+ * `units` names the space the tile's origin and scale live in, not the space
49
+ * of any geometry (a pattern has none). Under `'bounds'` the tile anchors to
50
+ * the painted node's box, so the pattern travels with the node and a resize
51
+ * reveals more tiles rather than stretching them; `fillInPoseFrame` resolves
52
+ * that to a `'local'` paint with an explicit `origin` before the renderer
53
+ * sees it.
54
+ *
55
+ * `transform` rotates, scales or skews the tile about `origin` (SVG's
56
+ * `patternTransform`, less its translation). See `PatternTransform`.
57
+ */
58
+ interface PatternPlacement {
59
+ units?: PaintUnits;
60
+ origin?: {
61
+ x: number;
62
+ y: number;
63
+ };
64
+ transform?: PatternTransform;
65
+ opacity?: number;
66
+ }
67
+ /**
68
+ * A built-in tile, described as plain data: what a tile paint says about the
69
+ * tile itself, apart from where it sits. `resolvePatternSpec()` turns one into
70
+ * a `TextureHandle` at paint time, memoized so identical specs share a texture.
71
+ *
72
+ * `size` is the tile's edge length, and doubles as its extent in paint
73
+ * space — a bigger `size` rasterizes a bigger tile rather than magnifying
74
+ * a small one, which is why there is no separate scale field.
75
+ */
76
+ type TilePatternSpec = {
77
+ kind: 'hatch';
78
+ color: string;
79
+ size?: number;
80
+ lineWidth?: number;
81
+ } | {
82
+ kind: 'crosshatch';
83
+ color: string;
84
+ size?: number;
85
+ lineWidth?: number;
86
+ } | {
87
+ kind: 'dots';
88
+ color: string;
89
+ size?: number;
90
+ radius?: number;
91
+ } | {
92
+ kind: 'chunks';
93
+ color: string;
94
+ /** Omit for a transparent tile background. */
95
+ bg?: string;
96
+ size?: number;
97
+ density?: number;
98
+ chunkSize?: number;
99
+ seed?: number;
100
+ };
101
+ /** The built-in tile kinds. */
102
+ type TileKind = TilePatternSpec['kind'];
103
+ /** A paint of one of the built-in tiles. Plain data, so it survives
104
+ * serialization. */
105
+ type TilePaint = TilePatternSpec & PatternPlacement;
106
+ /**
107
+ * A paint of a tile the consumer built itself via `createTilePattern()`. The
108
+ * handle is a session-scoped registry key, so a paint carrying one cannot be
109
+ * persisted or exported; prefer a tile kind.
110
+ */
111
+ type TexturePaint = {
112
+ kind: 'texture';
113
+ texture: TextureHandle;
114
+ } & PatternPlacement;
115
+ /** Every paint that repeats a tile. */
116
+ type PatternPaint = TilePaint | TexturePaint;
117
+ /** The built-in tile kinds, in the order a picker offers them. */
118
+ declare const TILE_KINDS: readonly TileKind[];
119
+ /** Whether `kind` names a built-in tile. */
120
+ declare function isTileKind(kind: string | undefined): kind is TileKind;
121
+ /** Whether a paint is one of the built-in tiles. */
122
+ declare function isTilePaint(paint: Paint): paint is TilePaint;
123
+ /** Whether a paint repeats a tile: a built-in one or a consumer's texture. */
124
+ declare function isPatternPaint(paint: Paint): paint is PatternPaint;
125
+ /** A pattern paint's placement alone, holding only the fields it sets. */
126
+ declare function patternPlacementOf(paint: PatternPaint): PatternPlacement;
127
+ /** A tile paint's description of the tile alone, its placement left behind. */
128
+ declare function tileSpecOf(paint: TilePaint): TilePatternSpec;
129
+
12
130
  /**
13
131
  * Convert a 0..1 float sRGB triple to OKLab — the renderer's own color space,
14
132
  * and what a caller holding a `resolveColor` result has. Rounds into the u8
@@ -103,7 +221,6 @@ type ColorInterpolationSpace = 'oklch' | 'oklab' | 'hsl' | 'srgb' | 'srgb-linear
103
221
  /** Which way a polar space's hue goes between two endpoints, as CSS
104
222
  * `hue-interpolation-method`: the arc of at most 180°, or the other one. */
105
223
  type HueInterpolation = 'shorter' | 'longer';
106
- type Rgb = readonly [number, number, number];
107
224
  /**
108
225
  * Interpolate between two sRGB colors (0..1 floats) in `space`, at `t` in 0..1.
109
226
  * Float precision throughout, with no byte round-trip. The result is sRGB;
@@ -114,98 +231,34 @@ type Rgb = readonly [number, number, number];
114
231
  * or no saturation (HSL) borrows the other endpoint's hue, so a ramp to white
115
232
  * or black holds its hue instead of sweeping from red.
116
233
  */
117
- declare function interpolateSrgb(from: Rgb, to: Rgb, t: number, space?: ColorInterpolationSpace, hue?: HueInterpolation): [number, number, number];
234
+ declare function interpolateSrgb(from: readonly [number, number, number], to: readonly [number, number, number], t: number, space?: ColorInterpolationSpace, hue?: HueInterpolation): [number, number, number];
118
235
 
119
236
  /**
120
- * A pattern tile's linear map into paint space: `[a, b, c, d]`, the first
121
- * four entries of SVG's `matrix(a b c d e f)`. A tile point `(u, v)` lands at
122
- * `origin + (a·u + c·v, b·u + d·v)`. Translation is the paint's `origin`, so
123
- * it has no slot here.
124
- */
125
- type PatternTransform = readonly [a: number, b: number, c: number, d: number];
126
- /**
127
- * A {@link PatternTransform} as the parts an editor shows. Angles are radians.
128
- * Applied in the order scale, then skew, then rotation — rotation outermost,
129
- * so turning a skewed tile turns it whole.
130
- */
131
- interface PatternTransformParts {
132
- rotation: number;
133
- scaleX: number;
134
- scaleY: number;
135
- /** Shear of the tile's y axis toward x, as SVG `skewX()`. */
136
- skewX: number;
137
- }
138
- /** `R(rotation) · K(skewX) · S(scaleX, scaleY)`. Omitted parts are identity. */
139
- declare function composePatternTransform(parts: Partial<PatternTransformParts>): PatternTransform;
140
- /**
141
- * Inverse of {@link composePatternTransform}. Any 2×2 matrix decomposes this
142
- * way; a reflection comes back as a negative `scaleY`. `undefined` reads as the
143
- * identity, which is what an absent `transform` means.
144
- */
145
- declare function decomposePatternTransform(t: PatternTransform | undefined): PatternTransformParts;
146
-
147
- /**
148
- * FillStyle and Stroke types — the unified shape for "what color or texture
237
+ * Paint and Stroke types — the unified shape for "what color or texture
149
238
  * paints these pixels," modeled on SVG's paint-server concept.
150
- *
151
- * - `FillStyle` is a tagged union: solid color, pattern, or gradient. Used
152
- * wherever a kit option previously took `fillStyle: string`.
153
- * - `Stroke` pairs a `FillStyle` with structural stroke parameters (width, dash,
154
- * line cap/join, alignment).
155
- * - These types are consumed by the GL renderer's DrawCommand path fills
156
- * and strokes.
157
- *
158
- * The 2D `applyPaint` / `applyStroke` / `renderFilledRegion` helpers that
159
- * formerly lived alongside these types were deleted with the 2D backend in
160
- * Step 10. `alignedStrokeRect` survives as a pure geometry helper used by
161
- * path tessellation and the selection overlay.
162
239
  */
163
240
 
164
241
  /**
165
242
  * Color/texture strategy for fills (and, via `Stroke.paint`, strokes).
166
243
  *
167
- * `fill` is optional and defaults to `'solid'` — `{ color: '#abc' }` is
168
- * equivalent to `{ fill: 'solid', color: '#abc' }`. Pattern paints must set
169
- * `fill: 'pattern'` explicitly.
244
+ * `kind` is optional and defaults to `'solid'` — `{ color: '#abc' }` is
245
+ * equivalent to `{ kind: 'solid', color: '#abc' }`. Every other paint names
246
+ * its kind.
170
247
  *
171
- * The `'pattern'` variant's payload is either a `TilePatternSpec` — plain
172
- * data naming one of the built-in tiles, which survives serialization — or a
173
- * `TextureHandle` for a tile the consumer built itself via
174
- * `createTilePattern()`. A handle is a session-scoped registry key, so a
175
- * paint carrying one cannot be persisted or exported; prefer the spec.
248
+ * A pattern is one of the built-in tile kinds or a `texture`; see
249
+ * `PatternPaint`.
176
250
  *
177
251
  * `interpolate` on a gradient names the space its stops blend through, and
178
- * defaults to `'rgb'` — the channel-wise sRGB lerp every other vector format
179
- * means by a gradient. `'oklab'` spaces the blend perceptually; `'oklch'`
252
+ * defaults to `'rgb'`. `'oklab'` spaces the blend perceptually; `'oklch'`
180
253
  * additionally travels around the hue wheel, so red to blue stays saturated
181
254
  * instead of passing through a muddy purple. Alpha is linear in every space.
182
- *
183
- * `units` on a pattern names the space the tile's origin and scale live in,
184
- * not the space of any geometry (a pattern has none). Under `'bounds'` the
185
- * tile anchors to the painted node's box, so the pattern travels with the
186
- * node and a resize reveals more tiles rather than stretching them;
187
- * `fillInPoseFrame` resolves that to a `'local'` paint with an explicit
188
- * `origin` before the renderer sees it.
189
- *
190
- * `transform` on a pattern rotates, scales or skews the tile about `origin`
191
- * (SVG's `patternTransform`, less its translation). See `PatternTransform`.
192
255
  */
193
- type FillStyle = {
194
- fill?: 'solid';
256
+ type Paint = {
257
+ kind?: 'solid';
195
258
  color: string;
196
259
  opacity?: number;
197
- } | {
198
- fill: 'pattern';
199
- pattern: TextureHandle | TilePatternSpec;
200
- units?: GradientUnits;
201
- origin?: {
202
- x: number;
203
- y: number;
204
- };
205
- transform?: PatternTransform;
206
- opacity?: number;
207
- } | {
208
- fill: 'linear-gradient';
260
+ } | PatternPaint | {
261
+ kind: 'linear-gradient';
209
262
  from: {
210
263
  x: number;
211
264
  y: number;
@@ -215,105 +268,71 @@ type FillStyle = {
215
268
  y: number;
216
269
  };
217
270
  stops: GradStop[];
218
- units?: GradientUnits;
271
+ units?: PaintUnits;
219
272
  interpolate?: ColorSpace;
220
273
  opacity?: number;
221
274
  } | {
222
- fill: 'radial-gradient';
275
+ kind: 'radial-gradient';
223
276
  center: {
224
277
  x: number;
225
278
  y: number;
226
279
  };
227
280
  radius: number;
228
281
  stops: GradStop[];
229
- units?: GradientUnits;
282
+ units?: PaintUnits;
230
283
  interpolate?: ColorSpace;
231
284
  opacity?: number;
232
285
  } | {
233
- fill: 'conic-gradient';
286
+ kind: 'conic-gradient';
234
287
  center: {
235
288
  x: number;
236
289
  y: number;
237
290
  };
238
291
  angle: number;
239
292
  stops: GradStop[];
240
- units?: GradientUnits;
293
+ units?: PaintUnits;
241
294
  interpolate?: ColorSpace;
242
295
  opacity?: number;
243
296
  };
244
297
  /**
245
298
  * Which coordinate space a gradient's geometry (`from`/`to`, `center`,
246
299
  * `radius`) is expressed in. SVG's `gradientUnits`, plus an option for
247
- * paints that are themselves viewport furniture.
300
+ * paints that are themselves viewport furniture. Defaults to `'screen'`.
248
301
  *
249
302
  * - `'bounds'`: fractions of the painted node's bounding box, `0..1` on each
250
- * axis — SVG `objectBoundingBox`. Resolved by the node painter, before the
251
- * renderer sees it, so the paint follows the node through moves, resizes
252
- * and rotation. What a gradient on a scene node wants.
303
+ * axis — SVG `objectBoundingBox`. The paint follows the node through moves,
304
+ * resizes and rotation.
253
305
  * - `'local'`: the coordinate space the geometry was handed to the renderer
254
- * in — the enclosing group's frame. For draw commands a consumer builds
255
- * itself, where "the coordinates I just wrote" is the useful frame.
306
+ * in — the enclosing group's frame.
256
307
  * - `'world'`: scene coordinates. The paint stays put under pan and zoom
257
308
  * while the geometry moves through it. SVG `userSpaceOnUse`. Requires the
258
309
  * renderer to have been handed a view matrix; falls back to `'screen'`
259
310
  * when it has not.
260
311
  * - `'screen'`: CSS pixels of the drawing surface. For overlays and
261
312
  * viewport-fixed washes that should not move with the content at all.
262
- *
263
- * Defaults to `'screen'`, which is the behavior every gradient had before
264
- * this field existed.
265
- */
266
- type GradientUnits = 'bounds' | 'local' | 'world' | 'screen';
267
- /**
268
- * A built-in tile, described as plain data. The serializable half of the
269
- * pattern paint: `resolvePatternSpec()` turns one into a `TextureHandle`
270
- * at paint time, memoized so identical specs share a texture.
271
- *
272
- * `size` is the tile's edge length, and doubles as its extent in paint
273
- * space — a bigger `size` rasterizes a bigger tile rather than magnifying
274
- * a small one, which is why there is no separate scale field.
275
313
  */
276
- interface TilePatternSpec {
277
- tile: 'hatch' | 'crosshatch' | 'dots' | 'chunks';
278
- color: string;
279
- /** `chunks` only — omit for a transparent tile background. */
280
- bg?: string;
281
- size?: number;
282
- /** `hatch` / `crosshatch`. */
283
- lineWidth?: number;
284
- /** `dots`. */
285
- radius?: number;
286
- /** `chunks`. */
287
- density?: number;
288
- chunkSize?: number;
289
- seed?: number;
290
- }
314
+ type PaintUnits = 'bounds' | 'local' | 'world' | 'screen';
291
315
  /** A single color stop within a gradient. `offset` is in 0..1. */
292
316
  interface GradStop {
293
317
  offset: number;
294
318
  color: string;
295
319
  }
296
- /** The gradient members of `FillStyle`, as one type. What a gradient editor
320
+ /** The gradient members of `Paint`, as one type. What a gradient editor
297
321
  * edits, and what the gradient-specific helpers accept. */
298
- type GradientFill = Extract<FillStyle, {
299
- fill: 'linear-gradient' | 'radial-gradient' | 'conic-gradient';
322
+ type GradientPaint = Extract<Paint, {
323
+ kind: 'linear-gradient' | 'radial-gradient' | 'conic-gradient';
300
324
  }>;
301
- /** `GradientFill['fill']` — the three gradient discriminants on their own. */
302
- type GradientKind = GradientFill['fill'];
325
+ /** `GradientPaint['kind']` — the three gradient discriminants on their own. */
326
+ type GradientKind = GradientPaint['kind'];
303
327
  /**
304
328
  * Where a stroke sits relative to the geometric edge it strokes.
305
329
  *
306
- * - `'center'` (default): canvas-native — half the stroke width sits inside
307
- * the geometry, half outside.
308
- * - `'inner'`: the entire stroke lies inside the geometry. The outer edge of
309
- * the stroke coincides with the geometric edge.
310
- * - `'outer'`: the entire stroke lies outside the geometry. The inner edge
311
- * of the stroke coincides with the geometric edge.
330
+ * - `'center'` (default): half the stroke width sits inside the geometry,
331
+ * half outside.
332
+ * - `'inner'`: the entire stroke lies inside the geometry.
333
+ * - `'outer'`: the entire stroke lies outside the geometry.
312
334
  *
313
- * Mirrors the (proposed) SVG `stroke-alignment` property. Honoring `inner`
314
- * or `outer` is the renderer's responsibility — for axis-aligned rects, the
315
- * kit shifts coordinates by `width / 2`. For arbitrary paths, renderers
316
- * typically use a stencil mask of the stroked path against the geometry.
335
+ * Mirrors the (proposed) SVG `stroke-alignment` property.
317
336
  */
318
337
  type StrokeAlign = 'center' | 'inner' | 'outer';
319
338
  /**
@@ -351,15 +370,23 @@ type ScreenLength = number | {
351
370
  * rather than becoming `Infinity`.
352
371
  */
353
372
  declare function resolveScreenLength(value: ScreenLength, scale: number): number;
354
- /** Stroke style: a FillStyle plus structural line parameters. */
373
+ /**
374
+ * Fill style: the paint laid inside an outline. `Stroke`'s sibling, and the
375
+ * same shape: a record holding a `paint`, with room for what belongs to the
376
+ * fill rather than to the paint.
377
+ */
378
+ interface Fill {
379
+ /** Omitted, the fill paints nothing, as a stroke with no paint does. */
380
+ paint?: Paint;
381
+ }
382
+ /** Stroke style: a Paint plus structural line parameters. */
355
383
  interface Stroke {
356
384
  /**
357
385
  * Omitted, the stroke paints nothing — the same "no paint" a `fill: null`
358
- * says for a node's fill. Documents written before mid-2026 can hold a
359
- * stroke whose other fields were seeded without one, and the type is
360
- * optional so every reader has to answer for that rather than throw on it.
386
+ * says for a node's fill. Stored documents can hold a stroke without one,
387
+ * so every reader has to answer for that rather than throw on it.
361
388
  */
362
- paint?: FillStyle;
389
+ paint?: Paint;
363
390
  /** World units, or `{ px }` for screen pixels — resolved against the
364
391
  * accumulated transform at draw time, so it holds its on-screen thickness
365
392
  * as the view zooms, in every direction even when one axis is zoomed more
@@ -401,14 +428,13 @@ interface Stroke {
401
428
  * the tessellator interpolates half-widths along each segment to produce
402
429
  * a tapered ribbon. Entries are world units, even on a `{ px }` stroke.
403
430
  * `width` is used as the fallback for any anchor whose entry is missing or
404
- * non-finite. Pressure-driven pencil strokes use
405
- * this; pair with `pressureToWidth` to derive widths from stylus input.
431
+ * non-finite. Pair with `pressureToWidth` to derive widths from stylus
432
+ * input.
406
433
  *
407
434
  * Joins between adjacent segments whose widths differ by more than
408
435
  * `varyingWidthJoinThreshold` (default 1.5×) are forced to bevel
409
436
  * regardless of the `join` setting — miter math is unstable when widths
410
- * vary across the corner; smooth round joins with mismatched widths
411
- * are a future enhancement.
437
+ * vary across the corner.
412
438
  */
413
439
  vertexWidths?: number[];
414
440
  /**
@@ -421,13 +447,9 @@ interface Stroke {
421
447
  }
422
448
  /**
423
449
  * Inflate (positive) or deflate (negative) a rect to honor `align` when
424
- * stroking it. Returns the rect to pass to a stroked-rect renderer. `width`
425
- * is the stroke width (defaults to 1 to match canvas).
426
- *
427
- * Pure geometry helper — no rendering side effects. Used by path
428
- * tessellation and the selection overlay to produce a rect whose
429
- * center-aligned stroke visually coincides with the requested
430
- * inner/outer-aligned stroke of the original rect.
450
+ * stroking it: the returned rect's center-aligned stroke coincides with the
451
+ * requested inner/outer-aligned stroke of the original. `width` is the
452
+ * stroke width (defaults to 1 to match canvas).
431
453
  */
432
454
  declare function alignedStrokeRect(rect: {
433
455
  x: number;
@@ -485,39 +507,80 @@ interface Region {
485
507
  }
486
508
 
487
509
  /**
488
- * The paint a draw command takes: `FillStyle` and `Stroke` narrowed to what a
510
+ * The paint a draw command takes: `Paint` and `Stroke` narrowed to what a
489
511
  * renderer can draw as it stands. A node's own paint may name its box
490
- * (`units: 'bounds'`) or a tile by spec; a command has neither a node nor a
512
+ * (`units: 'bounds'`) or a tile by kind; a command has neither a node nor a
491
513
  * rasterizer, so both are resolved before one is built.
492
514
  */
493
515
 
494
516
  /** The spaces a draw command's paint can name. `'bounds'` is not one: it means
495
517
  * a node's box, and a command has no node. */
496
- type CommandPaintUnits = Exclude<GradientUnits, 'bounds'>;
518
+ type CommandPaintUnits = Exclude<PaintUnits, 'bounds'>;
519
+ /** `F` with `'bounds'` units ruled out, where `F` is a paint that has units. */
497
520
  type InCommandUnits<F> = F extends {
498
- units?: GradientUnits;
521
+ units?: PaintUnits;
499
522
  } ? Omit<F, 'units'> & {
500
523
  units?: CommandPaintUnits;
501
524
  } : F;
502
- type WithPatternHandle<F> = F extends {
503
- fill: 'pattern';
504
- } ? Omit<F, 'pattern'> & {
505
- pattern: TextureHandle;
506
- } : F;
507
- /** A `FillStyle` placed in a frame a command can name. Its pattern may still
508
- * be a tile spec. */
509
- type PosedFill = InCommandUnits<FillStyle>;
510
- /** A `FillStyle` a renderer can draw as it stands: no paint is in `'bounds'`
511
- * units, and a pattern names a registered texture rather than a tile spec. */
512
- type CommandFill = WithPatternHandle<PosedFill>;
513
- /** The solid member of `FillStyle`: a color and an opacity. */
514
- type SolidFill = Extract<FillStyle, {
515
- color: string;
525
+ /** A `Paint` placed in a frame a command can name. Its pattern may still
526
+ * be a tile kind. */
527
+ type PosedPaint = InCommandUnits<Paint>;
528
+ /** A `Paint` a renderer can draw as it stands: no paint is in `'bounds'`
529
+ * units, and a pattern is a registered `texture` rather than a tile kind. */
530
+ type CommandPaint = Exclude<PosedPaint, {
531
+ kind: TileKind;
532
+ }>;
533
+ /** The solid member of `Paint`: a color and an opacity. */
534
+ type SolidPaint = Extract<Paint, {
535
+ kind?: 'solid';
516
536
  }>;
517
- /** A `Stroke` whose paint is a {@link CommandFill}. */
537
+ /** Whether a paint is the solid member. A tile paint carries a `color` too, so
538
+ * the presence of one does not answer this. */
539
+ declare function isSolidPaint(paint: Paint | null | undefined): paint is SolidPaint;
540
+ /** A `Fill` whose paint is a {@link CommandPaint}. */
541
+ interface CommandFill extends Omit<Fill, 'paint'> {
542
+ paint?: CommandPaint;
543
+ }
544
+ /** A `Stroke` whose paint is a {@link CommandPaint}. */
518
545
  type CommandStroke = Omit<Stroke, 'paint'> & {
519
- paint?: CommandFill;
546
+ paint?: CommandPaint;
547
+ };
548
+
549
+ /**
550
+ * Paint identity as a string: paints with equal keys draw the same, however
551
+ * they were built. Paint is plain data, so the comparison is structural.
552
+ */
553
+
554
+ /**
555
+ * A key two paints share only if they draw the same. No paint at all is
556
+ * `'none'`. Two solids that draw the same always share one. A gradient or
557
+ * pattern keys by its fields as written, so one that spells out a default
558
+ * (`opacity: 1`, `units`) keys apart from one that omits it: that splits a
559
+ * group, where merging two that differ would draw one with the other's paint.
560
+ *
561
+ * A gradient or pattern is read once: changed in place after its first use,
562
+ * it keeps the key it had. A solid is read on every call.
563
+ */
564
+ declare function paintKey(paint: Paint | null | undefined): string;
565
+ /** Whether a fill puts ink down: it does not when it is absent or holds no paint. */
566
+ declare function fillHasInk(fill: Fill | null | undefined): fill is Fill & {
567
+ paint: Paint;
520
568
  };
569
+ /** Whether a stroke puts ink down: it does not when it is absent, holds no
570
+ * paint, or has no width. */
571
+ declare function strokeHasInk(stroke: Stroke | null | undefined): stroke is Stroke & {
572
+ paint: Paint;
573
+ };
574
+ /**
575
+ * A key two strokes share only if they draw the same ribbon over the same
576
+ * geometry. Every stroke that puts no ink down is `'none'`. A `{ px }` width
577
+ * keys apart from the equal world-unit number, which is a different ribbon.
578
+ *
579
+ * Everything but the paint is read once: a stroke whose width, dash, or other
580
+ * shape is changed in place keeps the key it had. Its paint is keyed on every
581
+ * call, as {@link paintKey} keys it.
582
+ */
583
+ declare function strokeKey(stroke: Stroke | null | undefined): string;
521
584
 
522
585
  /**
523
586
  * A line color that reads against `background` — a grid, a rule, a guide
@@ -540,6 +603,126 @@ declare function parseHexBytes(input: string): [number, number, number, number]
540
603
  /** A hex color's red, green, and blue as 0..255, its alpha dropped. Throws on anything {@link parseHexBytes} does not read. */
541
604
  declare function hexToRgbBytes(input: string): [number, number, number];
542
605
 
606
+ /**
607
+ * Parse a CSS color string into [r, g, b, a] with 0..1 components.
608
+ *
609
+ * Supported forms:
610
+ * - Hex: `#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa` (any case)
611
+ * - `rgb()` / `rgba()` with integer, float, or percent R/G/B; number or
612
+ * percent alpha; commas or modern space-separated `R G B / A` syntax
613
+ * - `hsl()` / `hsla()` with hue as a unitless number (deg) or with `deg`,
614
+ * `rad`, `grad`, `turn` units; saturation/lightness as percent (or
615
+ * unitless in the modern syntax); same alpha rules and separator rules
616
+ * as `rgb()`
617
+ * - The 148 CSS named colors plus `transparent`
618
+ *
619
+ * Not supported: `oklch()`, `lab()`, `color()`, `currentColor`, system
620
+ * colors. Throws on anything unrecognized.
621
+ *
622
+ * Results are cached by input-string identity. Callers MUST treat the
623
+ * returned tuple as read-only — mutation would corrupt the cache.
624
+ */
625
+ declare function parseColor(input: string): [number, number, number, number];
626
+ /** Cached `parseColor`. Use this from renderer hot paths (per-frame draw
627
+ * callbacks, gradient ramp builds). The cache is capped at ~1024 entries and
628
+ * cleared wholesale on overflow. */
629
+ declare function resolveColor(input: string): readonly [number, number, number, number];
630
+ /** Same as `parseColor` but returns integer 0..255 components. */
631
+ declare function parseColorToRgba255(input: string): [number, number, number, number];
632
+ /**
633
+ * Canonicalize a hex color string to lowercase `#rrggbb` or `#rrggbbaa`.
634
+ *
635
+ * Accepts: `#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`, with or without the
636
+ * leading `#`, any case. Throws on other inputs.
637
+ */
638
+ declare function normalizeHex(input: string): string;
639
+ /**
640
+ * Parse a hex color into [r, g, b, a] with 0..1 components. Accepts the same
641
+ * forms as `normalizeHex`, including a missing leading `#`.
642
+ */
643
+ declare function hexToRgba(input: string): [number, number, number, number];
644
+ /**
645
+ * Format [r, g, b, a] (0..1 components) as a hex string. Emits `#rrggbb` when
646
+ * alpha is exactly 1 (or omitted), otherwise `#rrggbbaa`.
647
+ */
648
+ declare function rgbaToHex(rgba: readonly [number, number, number, number] | readonly [number, number, number]): string;
649
+
650
+ /**
651
+ * Gradient helpers shared by renderers, editors and exporters: reading a
652
+ * color out of a stop list, and moving a gradient between its three kinds.
653
+ */
654
+
655
+ /** A stop with its color already parsed to 0..1 RGBA. */
656
+ interface ResolvedGradientStop {
657
+ offset: number;
658
+ rgba: readonly [number, number, number, number];
659
+ }
660
+ /** Narrow a paint to the gradient members of `Paint`. */
661
+ declare function isGradientPaint(fill: Paint | null | undefined): fill is GradientPaint;
662
+ /**
663
+ * Sort a stop list by offset and parse each color to 0..1 RGBA, so a caller
664
+ * sampling many positions parses each color once. Colors go through
665
+ * `resolveColor`, so any CSS color the kit understands is a legal stop, not
666
+ * just hex.
667
+ */
668
+ declare function resolveGradientStops(stops: readonly GradStop[]): ResolvedGradientStop[];
669
+ /**
670
+ * RGBA (0..1) at position `t` along resolved stops: flat extension past
671
+ * either end, interpolation between neighbors in `space`, alpha included.
672
+ * The single definition of what a stop list means; a second implementation
673
+ * is a gradient that paints differently from its own swatch.
674
+ *
675
+ * `space` is a gradient's `interpolate`. Alpha is linear in every space —
676
+ * only the color channels travel through OKLab or OKLCh. Returns transparent
677
+ * black for an empty list.
678
+ */
679
+ declare function sampleResolvedStops(stops: readonly ResolvedGradientStop[], t: number, space?: ColorSpace): readonly [number, number, number, number];
680
+ /**
681
+ * Color at position `t` (0..1) along a stop list, as a hex string — the same
682
+ * sampling the GL ramp texture bakes, so an editor track and the painted
683
+ * gradient cannot disagree.
684
+ *
685
+ * Returns transparent black for an empty list.
686
+ */
687
+ declare function sampleGradientStops(stops: readonly GradStop[], t: number, space?: ColorSpace): string;
688
+ /**
689
+ * Retarget a gradient to a different kind, preserving stops, `units` and
690
+ * `opacity`. Linear carries a segment; radial and conic carry a center.
691
+ * Converting between them keeps the center fixed and reads the radius /
692
+ * angle off the segment.
693
+ *
694
+ * Lossy in both directions: a radial gradient stores no angle, so a round
695
+ * trip through one leaves the segment horizontal; a conic stores no radius,
696
+ * so a round trip through one resets the segment's length. An editor that
697
+ * wants a kind switch to be undoable should keep the original paint rather
698
+ * than converting back.
699
+ */
700
+ declare function withGradientKind(fill: GradientPaint, kind: GradientKind): GradientPaint;
701
+ /**
702
+ * Center / radius / angle for any gradient kind. For a linear gradient the
703
+ * center is the segment midpoint, the radius is half its length, and the
704
+ * angle is its direction.
705
+ */
706
+ declare function gradientGeometry(fill: GradientPaint): {
707
+ center: {
708
+ x: number;
709
+ y: number;
710
+ };
711
+ radius: number;
712
+ angle: number;
713
+ };
714
+ /**
715
+ * A gradient spanning a box left edge to right edge through its vertical
716
+ * center (linear) or filling it (radial / conic) — a starting geometry when
717
+ * only the shape's bounds are known.
718
+ */
719
+ declare function gradientForBounds(kind: GradientKind, bounds: {
720
+ x: number;
721
+ y: number;
722
+ width: number;
723
+ height: number;
724
+ }, stops: GradStop[], units?: GradientPaint['units']): GradientPaint;
725
+
543
726
  /**
544
727
  * A color given directly. `coords` are in the order CSS Color 4 uses for
545
728
  * `space` — `srgb` as 0..1 r, g, b; `oklab` as L, a, b; `oklch` as L, C and a
@@ -602,7 +785,15 @@ declare function resolvePaletteColors(palette: Palette, name: string, external?:
602
785
  /** `#rrggbb`, or `#rrggbbaa` below full opacity. `null` for a space the kit
603
786
  * cannot convert, or fewer than three coordinates. */
604
787
  declare function colorLiteralToHex(color: ColorLiteral): string | null;
788
+ /**
789
+ * A literal as a CSS color: hex where `colorLiteralToHex` has one, else
790
+ * `color(<space> …)`, which is the form CSS gives every predefined space.
791
+ * `null` for fewer than three coordinates.
792
+ */
793
+ declare function colorLiteralToCss(color: ColorLiteral): string | null;
794
+ /** Any color string `parseColor` reads, as an `srgb` literal. Throws, as it does, on one it cannot. */
795
+ declare function cssColorLiteral(css: string): ColorLiteral;
605
796
  /** `#rrggbb` or `#rrggbbaa` as an `srgb` literal; `null` for anything else. */
606
797
  declare function hexToColorLiteral(hex: string): ColorLiteral | null;
607
798
 
608
- export { type ColorFn, type ColorFnContext, type ColorInterpolationSpace, type ColorLiteral, type ColorRef, type ColorSeqFn, type ColorSource, type ColorSpace, type CommandFill, type CommandPaintUnits, type CommandStroke, type ExternalColors, type FillStyle, type GradStop, type GradientFill, type GradientKind, type GradientUnits, type HueInterpolation, type KitMarkerKey, type MarkerKey, type MarkerRef, type OklchDeg, type Palette, type PaletteEntry, type PatternTransform, type PatternTransformParts, type PosedFill, type Region, STROKE_DASH_RATIOS, type ScreenLength, type SolidFill, type Stroke, type StrokeAlign, type StrokeDashStyle, type TextureHandle, type TilePatternSpec, alignedStrokeRect, colorLiteralToHex, composePatternTransform, contrastLineColor, dashForStrokeStyle, decomposePatternTransform, hexToColorLiteral, hexToOklchDeg, hexToRgbBytes, hslToSrgb, interpolateSrgb, lerpColorArray, lerpOklab, lerpOklch, linearSrgbToOklab, linearToSrgb, mixOklab, oklabToLinearSrgb, oklabToOklch, oklabToSrgbU8, oklchDegToHex, oklchToOklab, parseHexBytes, resolvePaletteColor, resolvePaletteColors, resolveScreenLength, srgbFloatToOklab, srgbToHsl, srgbToLinear, srgbU8ToOklab, strokeDashStyleOf };
799
+ export { type ColorFn, type ColorFnContext, type ColorInterpolationSpace, type ColorLiteral, type ColorRef, type ColorSeqFn, type ColorSource, type ColorSpace, type CommandFill, type CommandPaint, type CommandPaintUnits, type CommandStroke, type ExternalColors, type Fill, type GradStop, type GradientKind, type GradientPaint, type HueInterpolation, type InCommandUnits, type KitMarkerKey, type MarkerKey, type MarkerRef, type OklchDeg, type Paint, type PaintUnits, type Palette, type PaletteEntry, type PatternPaint, type PatternPlacement, type PatternTransform, type PatternTransformParts, type PosedPaint, type Region, type ResolvedGradientStop, STROKE_DASH_RATIOS, type ScreenLength, type SolidPaint, type Stroke, type StrokeAlign, type StrokeDashStyle, TILE_KINDS, type TextureHandle, type TexturePaint, type TileKind, type TilePaint, type TilePatternSpec, alignedStrokeRect, colorLiteralToCss, colorLiteralToHex, composePatternTransform, contrastLineColor, cssColorLiteral, dashForStrokeStyle, decomposePatternTransform, fillHasInk, gradientForBounds, gradientGeometry, hexToColorLiteral, hexToOklchDeg, hexToRgbBytes, hexToRgba, hslToSrgb, interpolateSrgb, isGradientPaint, isPatternPaint, isSolidPaint, isTileKind, isTilePaint, lerpColorArray, lerpOklab, lerpOklch, linearSrgbToOklab, linearToSrgb, mixOklab, normalizeHex, oklabToLinearSrgb, oklabToOklch, oklabToSrgbU8, oklchDegToHex, oklchToOklab, paintKey, parseColor, parseColorToRgba255, parseHexBytes, patternPlacementOf, resolveColor, resolveGradientStops, resolvePaletteColor, resolvePaletteColors, resolveScreenLength, rgbaToHex, sampleGradientStops, sampleResolvedStops, srgbFloatToOklab, srgbToHsl, srgbToLinear, srgbU8ToOklab, strokeDashStyleOf, strokeHasInk, strokeKey, tileSpecOf, withGradientKind };