@weasel-js/paint 1.9.3 → 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
@@ -77,7 +195,8 @@ interface OklchDeg {
77
195
  * is what lets a caller find a hue's ceiling by asking for far more than exists.
78
196
  */
79
197
  declare function oklchDegToHex(L: number, C: number, hueDeg: number): string;
80
- /** `#rrggbb` to OKLCh with the hue wrapped into 0..360. */
198
+ /** A hex color (`#rgb`, `#rgba`, `#rrggbb`, or `#rrggbbaa`, alpha ignored) to
199
+ * OKLCh with the hue wrapped into 0..360. Throws on anything else. */
81
200
  declare function hexToOklchDeg(hex: string): OklchDeg;
82
201
  /** sRGB-encoded channel (0..1) to linear light. Unclamped; odd-symmetric
83
202
  * below zero, as CSS `srgb-linear` extends it. */
@@ -102,7 +221,6 @@ type ColorInterpolationSpace = 'oklch' | 'oklab' | 'hsl' | 'srgb' | 'srgb-linear
102
221
  /** Which way a polar space's hue goes between two endpoints, as CSS
103
222
  * `hue-interpolation-method`: the arc of at most 180°, or the other one. */
104
223
  type HueInterpolation = 'shorter' | 'longer';
105
- type Rgb = readonly [number, number, number];
106
224
  /**
107
225
  * Interpolate between two sRGB colors (0..1 floats) in `space`, at `t` in 0..1.
108
226
  * Float precision throughout, with no byte round-trip. The result is sRGB;
@@ -113,98 +231,34 @@ type Rgb = readonly [number, number, number];
113
231
  * or no saturation (HSL) borrows the other endpoint's hue, so a ramp to white
114
232
  * or black holds its hue instead of sweeping from red.
115
233
  */
116
- declare function interpolateSrgb(from: Rgb, to: Rgb, t: number, space?: ColorInterpolationSpace, hue?: HueInterpolation): [number, number, number];
117
-
118
- /**
119
- * A pattern tile's linear map into paint space: `[a, b, c, d]`, the first
120
- * four entries of SVG's `matrix(a b c d e f)`. A tile point `(u, v)` lands at
121
- * `origin + (a·u + c·v, b·u + d·v)`. Translation is the paint's `origin`, so
122
- * it has no slot here.
123
- */
124
- type PatternTransform = readonly [a: number, b: number, c: number, d: number];
125
- /**
126
- * A {@link PatternTransform} as the parts an editor shows. Angles are radians.
127
- * Applied in the order scale, then skew, then rotation — rotation outermost,
128
- * so turning a skewed tile turns it whole.
129
- */
130
- interface PatternTransformParts {
131
- rotation: number;
132
- scaleX: number;
133
- scaleY: number;
134
- /** Shear of the tile's y axis toward x, as SVG `skewX()`. */
135
- skewX: number;
136
- }
137
- /** `R(rotation) · K(skewX) · S(scaleX, scaleY)`. Omitted parts are identity. */
138
- declare function composePatternTransform(parts: Partial<PatternTransformParts>): PatternTransform;
139
- /**
140
- * Inverse of {@link composePatternTransform}. Any 2×2 matrix decomposes this
141
- * way; a reflection comes back as a negative `scaleY`. `undefined` reads as the
142
- * identity, which is what an absent `transform` means.
143
- */
144
- declare function decomposePatternTransform(t: PatternTransform | undefined): PatternTransformParts;
234
+ declare function interpolateSrgb(from: readonly [number, number, number], to: readonly [number, number, number], t: number, space?: ColorInterpolationSpace, hue?: HueInterpolation): [number, number, number];
145
235
 
146
236
  /**
147
- * 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
148
238
  * paints these pixels," modeled on SVG's paint-server concept.
149
- *
150
- * - `FillStyle` is a tagged union: solid color, pattern, or gradient. Used
151
- * wherever a kit option previously took `fillStyle: string`.
152
- * - `Stroke` pairs a `FillStyle` with structural stroke parameters (width, dash,
153
- * line cap/join, alignment).
154
- * - These types are consumed by the GL renderer's DrawCommand path fills
155
- * and strokes.
156
- *
157
- * The 2D `applyPaint` / `applyStroke` / `renderFilledRegion` helpers that
158
- * formerly lived alongside these types were deleted with the 2D backend in
159
- * Step 10. `alignedStrokeRect` survives as a pure geometry helper used by
160
- * path tessellation and the selection overlay.
161
239
  */
162
240
 
163
241
  /**
164
242
  * Color/texture strategy for fills (and, via `Stroke.paint`, strokes).
165
243
  *
166
- * `fill` is optional and defaults to `'solid'` — `{ color: '#abc' }` is
167
- * equivalent to `{ fill: 'solid', color: '#abc' }`. Pattern paints must set
168
- * `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.
169
247
  *
170
- * The `'pattern'` variant's payload is either a `TilePatternSpec` — plain
171
- * data naming one of the built-in tiles, which survives serialization — or a
172
- * `TextureHandle` for a tile the consumer built itself via
173
- * `createTilePattern()`. A handle is a session-scoped registry key, so a
174
- * 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`.
175
250
  *
176
251
  * `interpolate` on a gradient names the space its stops blend through, and
177
- * defaults to `'rgb'` — the channel-wise sRGB lerp every other vector format
178
- * means by a gradient. `'oklab'` spaces the blend perceptually; `'oklch'`
252
+ * defaults to `'rgb'`. `'oklab'` spaces the blend perceptually; `'oklch'`
179
253
  * additionally travels around the hue wheel, so red to blue stays saturated
180
254
  * instead of passing through a muddy purple. Alpha is linear in every space.
181
- *
182
- * `units` on a pattern names the space the tile's origin and scale live in,
183
- * not the space of any geometry (a pattern has none). Under `'bounds'` the
184
- * tile anchors to the painted node's box, so the pattern travels with the
185
- * node and a resize reveals more tiles rather than stretching them;
186
- * `fillInPoseFrame` resolves that to a `'local'` paint with an explicit
187
- * `origin` before the renderer sees it.
188
- *
189
- * `transform` on a pattern rotates, scales or skews the tile about `origin`
190
- * (SVG's `patternTransform`, less its translation). See `PatternTransform`.
191
255
  */
192
- type FillStyle = {
193
- fill?: 'solid';
256
+ type Paint = {
257
+ kind?: 'solid';
194
258
  color: string;
195
259
  opacity?: number;
196
- } | {
197
- fill: 'pattern';
198
- pattern: TextureHandle | TilePatternSpec;
199
- units?: GradientUnits;
200
- origin?: {
201
- x: number;
202
- y: number;
203
- };
204
- transform?: PatternTransform;
205
- opacity?: number;
206
- } | {
207
- fill: 'linear-gradient';
260
+ } | PatternPaint | {
261
+ kind: 'linear-gradient';
208
262
  from: {
209
263
  x: number;
210
264
  y: number;
@@ -214,105 +268,71 @@ type FillStyle = {
214
268
  y: number;
215
269
  };
216
270
  stops: GradStop[];
217
- units?: GradientUnits;
271
+ units?: PaintUnits;
218
272
  interpolate?: ColorSpace;
219
273
  opacity?: number;
220
274
  } | {
221
- fill: 'radial-gradient';
275
+ kind: 'radial-gradient';
222
276
  center: {
223
277
  x: number;
224
278
  y: number;
225
279
  };
226
280
  radius: number;
227
281
  stops: GradStop[];
228
- units?: GradientUnits;
282
+ units?: PaintUnits;
229
283
  interpolate?: ColorSpace;
230
284
  opacity?: number;
231
285
  } | {
232
- fill: 'conic-gradient';
286
+ kind: 'conic-gradient';
233
287
  center: {
234
288
  x: number;
235
289
  y: number;
236
290
  };
237
291
  angle: number;
238
292
  stops: GradStop[];
239
- units?: GradientUnits;
293
+ units?: PaintUnits;
240
294
  interpolate?: ColorSpace;
241
295
  opacity?: number;
242
296
  };
243
297
  /**
244
298
  * Which coordinate space a gradient's geometry (`from`/`to`, `center`,
245
299
  * `radius`) is expressed in. SVG's `gradientUnits`, plus an option for
246
- * paints that are themselves viewport furniture.
300
+ * paints that are themselves viewport furniture. Defaults to `'screen'`.
247
301
  *
248
302
  * - `'bounds'`: fractions of the painted node's bounding box, `0..1` on each
249
- * axis — SVG `objectBoundingBox`. Resolved by the node painter, before the
250
- * renderer sees it, so the paint follows the node through moves, resizes
251
- * 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.
252
305
  * - `'local'`: the coordinate space the geometry was handed to the renderer
253
- * in — the enclosing group's frame. For draw commands a consumer builds
254
- * itself, where "the coordinates I just wrote" is the useful frame.
306
+ * in — the enclosing group's frame.
255
307
  * - `'world'`: scene coordinates. The paint stays put under pan and zoom
256
308
  * while the geometry moves through it. SVG `userSpaceOnUse`. Requires the
257
309
  * renderer to have been handed a view matrix; falls back to `'screen'`
258
310
  * when it has not.
259
311
  * - `'screen'`: CSS pixels of the drawing surface. For overlays and
260
312
  * viewport-fixed washes that should not move with the content at all.
261
- *
262
- * Defaults to `'screen'`, which is the behavior every gradient had before
263
- * this field existed.
264
313
  */
265
- type GradientUnits = 'bounds' | 'local' | 'world' | 'screen';
266
- /**
267
- * A built-in tile, described as plain data. The serializable half of the
268
- * pattern paint: `resolvePatternSpec()` turns one into a `TextureHandle`
269
- * at paint time, memoized so identical specs share a texture.
270
- *
271
- * `size` is the tile's edge length, and doubles as its extent in paint
272
- * space — a bigger `size` rasterizes a bigger tile rather than magnifying
273
- * a small one, which is why there is no separate scale field.
274
- */
275
- interface TilePatternSpec {
276
- tile: 'hatch' | 'crosshatch' | 'dots' | 'chunks';
277
- color: string;
278
- /** `chunks` only — omit for a transparent tile background. */
279
- bg?: string;
280
- size?: number;
281
- /** `hatch` / `crosshatch`. */
282
- lineWidth?: number;
283
- /** `dots`. */
284
- radius?: number;
285
- /** `chunks`. */
286
- density?: number;
287
- chunkSize?: number;
288
- seed?: number;
289
- }
314
+ type PaintUnits = 'bounds' | 'local' | 'world' | 'screen';
290
315
  /** A single color stop within a gradient. `offset` is in 0..1. */
291
316
  interface GradStop {
292
317
  offset: number;
293
318
  color: string;
294
319
  }
295
- /** 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
296
321
  * edits, and what the gradient-specific helpers accept. */
297
- type GradientFill = Extract<FillStyle, {
298
- fill: 'linear-gradient' | 'radial-gradient' | 'conic-gradient';
322
+ type GradientPaint = Extract<Paint, {
323
+ kind: 'linear-gradient' | 'radial-gradient' | 'conic-gradient';
299
324
  }>;
300
- /** `GradientFill['fill']` — the three gradient discriminants on their own. */
301
- type GradientKind = GradientFill['fill'];
325
+ /** `GradientPaint['kind']` — the three gradient discriminants on their own. */
326
+ type GradientKind = GradientPaint['kind'];
302
327
  /**
303
328
  * Where a stroke sits relative to the geometric edge it strokes.
304
329
  *
305
- * - `'center'` (default): canvas-native — half the stroke width sits inside
306
- * the geometry, half outside.
307
- * - `'inner'`: the entire stroke lies inside the geometry. The outer edge of
308
- * the stroke coincides with the geometric edge.
309
- * - `'outer'`: the entire stroke lies outside the geometry. The inner edge
310
- * 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.
311
334
  *
312
- * Mirrors the (proposed) SVG `stroke-alignment` property. Honoring `inner`
313
- * or `outer` is the renderer's responsibility — for axis-aligned rects, the
314
- * kit shifts coordinates by `width / 2`. For arbitrary paths, renderers
315
- * typically use a stencil mask of the stroked path against the geometry.
335
+ * Mirrors the (proposed) SVG `stroke-alignment` property.
316
336
  */
317
337
  type StrokeAlign = 'center' | 'inner' | 'outer';
318
338
  /**
@@ -350,15 +370,23 @@ type ScreenLength = number | {
350
370
  * rather than becoming `Infinity`.
351
371
  */
352
372
  declare function resolveScreenLength(value: ScreenLength, scale: number): number;
353
- /** 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. */
354
383
  interface Stroke {
355
384
  /**
356
385
  * Omitted, the stroke paints nothing — the same "no paint" a `fill: null`
357
- * says for a node's fill. Documents written before mid-2026 can hold a
358
- * stroke whose other fields were seeded without one, and the type is
359
- * 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.
360
388
  */
361
- paint?: FillStyle;
389
+ paint?: Paint;
362
390
  /** World units, or `{ px }` for screen pixels — resolved against the
363
391
  * accumulated transform at draw time, so it holds its on-screen thickness
364
392
  * as the view zooms, in every direction even when one axis is zoomed more
@@ -400,14 +428,13 @@ interface Stroke {
400
428
  * the tessellator interpolates half-widths along each segment to produce
401
429
  * a tapered ribbon. Entries are world units, even on a `{ px }` stroke.
402
430
  * `width` is used as the fallback for any anchor whose entry is missing or
403
- * non-finite. Pressure-driven pencil strokes use
404
- * this; pair with `pressureToWidth` to derive widths from stylus input.
431
+ * non-finite. Pair with `pressureToWidth` to derive widths from stylus
432
+ * input.
405
433
  *
406
434
  * Joins between adjacent segments whose widths differ by more than
407
435
  * `varyingWidthJoinThreshold` (default 1.5×) are forced to bevel
408
436
  * regardless of the `join` setting — miter math is unstable when widths
409
- * vary across the corner; smooth round joins with mismatched widths
410
- * are a future enhancement.
437
+ * vary across the corner.
411
438
  */
412
439
  vertexWidths?: number[];
413
440
  /**
@@ -420,13 +447,9 @@ interface Stroke {
420
447
  }
421
448
  /**
422
449
  * Inflate (positive) or deflate (negative) a rect to honor `align` when
423
- * stroking it. Returns the rect to pass to a stroked-rect renderer. `width`
424
- * is the stroke width (defaults to 1 to match canvas).
425
- *
426
- * Pure geometry helper — no rendering side effects. Used by path
427
- * tessellation and the selection overlay to produce a rect whose
428
- * center-aligned stroke visually coincides with the requested
429
- * 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).
430
453
  */
431
454
  declare function alignedStrokeRect(rect: {
432
455
  x: number;
@@ -483,6 +506,82 @@ interface Region {
483
506
  shape: 'rectangle' | 'circle';
484
507
  }
485
508
 
509
+ /**
510
+ * The paint a draw command takes: `Paint` and `Stroke` narrowed to what a
511
+ * renderer can draw as it stands. A node's own paint may name its box
512
+ * (`units: 'bounds'`) or a tile by kind; a command has neither a node nor a
513
+ * rasterizer, so both are resolved before one is built.
514
+ */
515
+
516
+ /** The spaces a draw command's paint can name. `'bounds'` is not one: it means
517
+ * a node's box, and a command has no node. */
518
+ type CommandPaintUnits = Exclude<PaintUnits, 'bounds'>;
519
+ /** `F` with `'bounds'` units ruled out, where `F` is a paint that has units. */
520
+ type InCommandUnits<F> = F extends {
521
+ units?: PaintUnits;
522
+ } ? Omit<F, 'units'> & {
523
+ units?: CommandPaintUnits;
524
+ } : F;
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';
536
+ }>;
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}. */
545
+ type CommandStroke = Omit<Stroke, 'paint'> & {
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;
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;
584
+
486
585
  /**
487
586
  * A line color that reads against `background` — a grid, a rule, a guide
488
587
  * drawn over a surface whose color the document chooses, where theme tokens
@@ -495,6 +594,135 @@ interface Region {
495
594
  */
496
595
  declare function contrastLineColor(background: string, strength: number): string;
497
596
 
597
+ /**
598
+ * The kit's one hex color reader: `#rgb`, `#rgba`, `#rrggbb`, or `#rrggbbaa`,
599
+ * in any case, as four 0..255 channels with alpha last (255 when the color
600
+ * names none). `null` for anything else, a missing `#` included.
601
+ */
602
+ declare function parseHexBytes(input: string): [number, number, number, number] | null;
603
+ /** A hex color's red, green, and blue as 0..255, its alpha dropped. Throws on anything {@link parseHexBytes} does not read. */
604
+ declare function hexToRgbBytes(input: string): [number, number, number];
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
+
498
726
  /**
499
727
  * A color given directly. `coords` are in the order CSS Color 4 uses for
500
728
  * `space` — `srgb` as 0..1 r, g, b; `oklab` as L, a, b; `oklch` as L, C and a
@@ -557,7 +785,15 @@ declare function resolvePaletteColors(palette: Palette, name: string, external?:
557
785
  /** `#rrggbb`, or `#rrggbbaa` below full opacity. `null` for a space the kit
558
786
  * cannot convert, or fewer than three coordinates. */
559
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;
560
796
  /** `#rrggbb` or `#rrggbbaa` as an `srgb` literal; `null` for anything else. */
561
797
  declare function hexToColorLiteral(hex: string): ColorLiteral | null;
562
798
 
563
- export { type ColorFn, type ColorFnContext, type ColorInterpolationSpace, type ColorLiteral, type ColorRef, type ColorSeqFn, type ColorSource, type ColorSpace, 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 Region, STROKE_DASH_RATIOS, type ScreenLength, type Stroke, type StrokeAlign, type StrokeDashStyle, type TextureHandle, type TilePatternSpec, alignedStrokeRect, colorLiteralToHex, composePatternTransform, contrastLineColor, dashForStrokeStyle, decomposePatternTransform, hexToColorLiteral, hexToOklchDeg, hslToSrgb, interpolateSrgb, lerpColorArray, lerpOklab, lerpOklch, linearSrgbToOklab, linearToSrgb, mixOklab, oklabToLinearSrgb, oklabToOklch, oklabToSrgbU8, oklchDegToHex, oklchToOklab, 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 };