@weasel-js/paint 1.17.2 → 1.18.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.js CHANGED
@@ -15,215 +15,21 @@ function atKey(at) {
15
15
  return `s${at.subpath}`;
16
16
  }
17
17
 
18
- // src/resolvePaint.ts
19
- var INHERIT = "inherit";
20
- function resolveFill(fill, inherited = null) {
21
- if (fill === INHERIT) return inherited;
22
- return fill?.paint == null ? null : fill;
23
- }
24
- function resolveStroke(stroke, inherited = null) {
25
- if (stroke === INHERIT) return inherited;
26
- return stroke?.paint == null ? null : stroke;
27
- }
28
-
29
- // src/paintKey.ts
30
- function canonical(value) {
31
- if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
32
- if (typeof value !== "object" || value === null) return JSON.stringify(value) ?? "null";
33
- const fields = Object.keys(value).sort().filter((k) => value[k] !== void 0).map((k) => `${JSON.stringify(k)}:${canonical(value[k])}`);
34
- return `{${fields.join(",")}}`;
35
- }
36
- var structuralKeys = /* @__PURE__ */ new WeakMap();
37
- var strokeShapeKeys = /* @__PURE__ */ new WeakMap();
38
- function paintKey(paint) {
39
- if (paint == null) return "none";
40
- if (isSolidPaint(paint)) return `solid:${paint.color}:${paint.opacity ?? 1}`;
41
- let key = structuralKeys.get(paint);
42
- if (key === void 0) {
43
- key = canonical(paint);
44
- structuralKeys.set(paint, key);
45
- }
46
- return key;
47
- }
48
- function fillHasInk(fill) {
49
- return resolveFill(fill) !== null;
50
- }
51
- function strokeHasInk(stroke) {
52
- const painted = resolveStroke(stroke);
53
- if (painted === null) return false;
54
- const width = painted.width ?? 1;
55
- return (typeof width === "number" ? width : width.px) > 0;
56
- }
57
- function strokeKey(stroke) {
58
- if (!strokeHasInk(stroke)) return "none";
59
- let shape = strokeShapeKeys.get(stroke);
60
- if (shape === void 0) {
61
- const { paint: _paint, width, join, cap, miterLimit, align, dash, ...rest } = stroke;
62
- shape = canonical({
63
- ...rest,
64
- width: width ?? 1,
65
- join: join ?? "miter",
66
- cap: cap ?? "butt",
67
- miterLimit: miterLimit ?? 4,
68
- align: align ?? "center",
69
- dash: dash ?? []
70
- });
71
- strokeShapeKeys.set(stroke, shape);
72
- }
73
- return `${shape}|${paintKey(stroke.paint)}`;
74
- }
75
-
76
- // src/patternPaint.ts
77
- var TILE_KINDS = ["hatch", "crosshatch", "dots", "chunks"];
78
- var TILE_KIND_SET = new Set(TILE_KINDS);
79
- function isTileKind(kind) {
80
- return kind !== void 0 && TILE_KIND_SET.has(kind);
81
- }
82
- function isTilePaint(paint) {
83
- return isTileKind(paint.kind);
84
- }
85
- function isImagePatternPaint(paint) {
86
- return paint.kind === "image";
87
- }
88
- function isPatternPaint(paint) {
89
- return paint.kind === "texture" || paint.kind === "image" || isTileKind(paint.kind);
90
- }
91
- function patternPlacementOf(paint) {
92
- const { units, origin, transform, opacity } = paint;
93
- return {
94
- ...units !== void 0 ? { units } : {},
95
- ...origin !== void 0 ? { origin } : {},
96
- ...transform !== void 0 ? { transform } : {},
97
- ...opacity !== void 0 ? { opacity } : {}
98
- };
99
- }
100
- function tileSpecOf(paint) {
101
- const { units: _units, origin: _origin, transform: _transform, opacity: _opacity, ...spec } = paint;
102
- return spec;
103
- }
104
-
105
- // src/patternTransform.ts
106
- function composePatternTransform(parts) {
107
- const { rotation = 0, scaleX = 1, scaleY = 1, skewX = 0 } = parts;
108
- const cos = Math.cos(rotation);
109
- const sin = Math.sin(rotation);
110
- const shear = Math.tan(skewX) * scaleY;
111
- return [
112
- cos * scaleX,
113
- sin * scaleX,
114
- cos * shear - sin * scaleY,
115
- sin * shear + cos * scaleY
116
- ];
117
- }
118
- function decomposePatternTransform(t) {
119
- if (!t) return { rotation: 0, scaleX: 1, scaleY: 1, skewX: 0 };
120
- const [a, b, c, d] = t;
121
- const scaleX = Math.hypot(a, b);
122
- const rotation = scaleX === 0 ? 0 : Math.atan2(b, a);
123
- const cos = Math.cos(rotation);
124
- const sin = Math.sin(rotation);
125
- const shear = c * cos + d * sin;
126
- const scaleY = d * cos - c * sin;
127
- const skewX = scaleY === 0 ? 0 : Math.atan(shear / scaleY);
128
- return { rotation, scaleX, scaleY, skewX };
129
- }
130
-
131
- // src/vertexWidth.ts
132
- var usable = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0;
133
- function vertexHalfWidths(entry, fallback) {
134
- const half = fallback / 2;
135
- if (typeof entry === "number") return usable(entry) ? { left: entry / 2, right: entry / 2 } : { left: half, right: half };
136
- if (entry == null || typeof entry !== "object") return { left: half, right: half };
137
- return { left: usable(entry.left) ? entry.left : half, right: usable(entry.right) ? entry.right : half };
138
- }
139
- function vertexWidthFromSides(left, right) {
140
- return Math.abs(left - right) <= 1e-9 * Math.max(1, Math.abs(left), Math.abs(right)) ? left + right : { left, right };
141
- }
142
- function scaleVertexWidth(entry, k) {
143
- return typeof entry === "number" ? entry * k : { left: entry.left * k, right: entry.right * k };
144
- }
145
- function widestHalfWidth(width, vertexWidths) {
146
- let widest = width / 2;
147
- if (vertexWidths) {
148
- for (const entry of vertexWidths) {
149
- const { left, right } = vertexHalfWidths(entry, width);
150
- widest = Math.max(widest, left, right);
151
- }
152
- }
153
- return widest;
154
- }
155
-
156
- // src/paint.ts
157
- function resolveScreenLength(value, scale) {
158
- if (typeof value === "number") return value;
159
- if (!Number.isFinite(scale) || scale === 0) return value.px;
160
- return value.px / scale;
161
- }
162
- function alignedStrokeRect(rect, align, width = 1) {
163
- if (align === "center") return rect;
164
- const sign = align === "inner" ? -1 : 1;
165
- const d = sign * width / 2;
166
- const dx = rect.width < 0 ? -d : d;
167
- const dy = rect.height < 0 ? -d : d;
168
- return {
169
- x: rect.x - dx,
170
- y: rect.y - dy,
171
- width: rect.width + 2 * dx,
172
- height: rect.height + 2 * dy
173
- };
174
- }
175
- var STROKE_DASH_RATIOS = {
176
- dashed: [3, 2],
177
- dotted: [1, 2]
178
- };
179
- function dashWidth(width) {
180
- const w = typeof width === "object" ? width.px : width ?? 1;
181
- return Number.isFinite(w) && w > 0 ? w : 1;
182
- }
183
- function dashForStrokeStyle(style, width) {
184
- if (style !== "dashed" && style !== "dotted") return void 0;
185
- const w = dashWidth(width);
186
- return STROKE_DASH_RATIOS[style].map((r) => r * w);
187
- }
188
- var MAX_DASH_RUNS = 1e5;
189
- function dashPattern(dash, length) {
190
- if (dash === void 0 || dash.length === 0) return "solid";
191
- if (!dash.every(Number.isFinite)) return "non-finite";
192
- if (dash.some((v) => v < 0) || !dash.some((v) => v > 0)) return "solid";
193
- if (length !== void 0) {
194
- const period = dash.reduce((a, b) => a + b, 0);
195
- if (length / period * dash.length > MAX_DASH_RUNS) return "solid";
196
- }
197
- return dash;
198
- }
199
- function dashPhase(pattern, phase) {
200
- if (!Number.isFinite(phase) || phase === 0) return 0;
201
- const cycle = pattern.reduce((a, b) => a + b, 0) * (pattern.length % 2 + 1);
202
- if (!(cycle > 0)) return 0;
203
- const wrapped = phase % cycle;
204
- return wrapped < 0 ? wrapped + cycle : wrapped;
205
- }
206
- function scaledDash(stroke, k) {
207
- if (stroke.dash === void 0 && stroke.dashOffset === void 0) return stroke;
208
- return {
209
- ...stroke,
210
- ...stroke.dash ? { dash: stroke.dash.map((d) => d * k) } : {},
211
- ...stroke.dashOffset !== void 0 ? { dashOffset: stroke.dashOffset * k } : {}
212
- };
213
- }
214
- function strokeDashStyleOf(dash, width) {
215
- const pattern = dashPattern(dash);
216
- if (pattern === "solid") return "solid";
217
- if (pattern === "non-finite") return "custom";
218
- const w = dashWidth(width);
219
- for (const style of ["dashed", "dotted"]) {
220
- const preset = STROKE_DASH_RATIOS[style];
221
- if (pattern.length === preset.length && preset.every((r, i) => Math.abs(pattern[i] - r * w) <= w * 1e-3)) {
222
- return style;
223
- }
18
+ // src/hue.ts
19
+ function lerpHueDeg(h1, h2, t, method = "shorter") {
20
+ const a = wrapDeg(h1);
21
+ let dh = wrapDeg(h2) - a;
22
+ if (method === "shorter") {
23
+ if (dh > 180) dh -= 360;
24
+ else if (dh < -180) dh += 360;
25
+ } else if (dh > 0 && dh < 180) {
26
+ dh -= 360;
27
+ } else if (dh < 0 && dh > -180) {
28
+ dh += 360;
224
29
  }
225
- return "custom";
30
+ return wrapDeg(a + dh * t);
226
31
  }
32
+ var wrapDeg = (h) => (h % 360 + 360) % 360;
227
33
 
228
34
  // src/hex.ts
229
35
  var HEX = /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
@@ -242,6 +48,10 @@ function hexToRgbBytes(input) {
242
48
  if (!bytes) throw new Error(`not a hex color: "${input}"`);
243
49
  return [bytes[0], bytes[1], bytes[2]];
244
50
  }
51
+ function rgbaHex(rgba) {
52
+ if (rgba[3] === 0) return null;
53
+ return `#${byteHex(rgba[0])}${byteHex(rgba[1])}${byteHex(rgba[2])}`;
54
+ }
245
55
 
246
56
  // src/shortArc.ts
247
57
  var TWO_PI = Math.PI * 2;
@@ -443,15 +253,6 @@ function srgbToHsl(r, g, b) {
443
253
  return [(h * 60 % 360 + 360) % 360, s, l];
444
254
  }
445
255
  var ACHROMATIC_EPS = 1e-4;
446
- function lerpHueDeg(h1, h2, t, method) {
447
- let dh = ((h2 - h1) % 360 + 360) % 360;
448
- if (method === "shorter") {
449
- if (dh > 180) dh -= 360;
450
- } else if (dh > 0 && dh < 180) {
451
- dh -= 360;
452
- }
453
- return ((h1 + dh * t) % 360 + 360) % 360;
454
- }
455
256
  function lerpPolar(a, b, t, method, hueT) {
456
257
  const h1 = a[0] < ACHROMATIC_EPS ? b[1] : a[1];
457
258
  const h2 = b[0] < ACHROMATIC_EPS ? a[1] : b[1];
@@ -504,28 +305,6 @@ function interpolateSrgbSplit(from, to, t, hueT, space, hue) {
504
305
  }
505
306
  }
506
307
 
507
- // src/mix.ts
508
- function mixRgba(a, b, p, space = "oklch", hue = "shorter") {
509
- const alpha = a[3] * p + b[3] * (1 - p);
510
- if (alpha === 0) return [0, 0, 0, 0];
511
- const t = b[3] * (1 - p) / alpha;
512
- const [r, g, bl] = interpolateSrgbSplit([a[0], a[1], a[2]], [b[0], b[1], b[2]], t, 1 - p, space, hue);
513
- return [r, g, bl, alpha];
514
- }
515
- function mixOklab(a, b, p) {
516
- return mixRgba(a, b, p, "oklab");
517
- }
518
- function mixRgbaWeighted(members, space = "oklch", hue = "shorter") {
519
- if (members.length === 0) throw new Error("mixRgbaWeighted: no colors");
520
- let mixed = [...members[0].color];
521
- let total = members[0].weight;
522
- for (const { color, weight } of members.slice(1)) {
523
- total += weight;
524
- if (total > 0) mixed = mixRgba(color, mixed, weight / total, space, hue);
525
- }
526
- return mixed;
527
- }
528
-
529
308
  // src/color.ts
530
309
  function parseColor(input) {
531
310
  const cached = CACHE.get(input);
@@ -541,8 +320,7 @@ function parseColorUncached(input) {
541
320
  const s = input.trim();
542
321
  if (s.startsWith("#")) return parseHex(s);
543
322
  const lower = s.toLowerCase();
544
- const named = NAMED_COLORS[lower];
545
- if (named !== void 0) return named;
323
+ if (Object.hasOwn(NAMED_COLORS, lower)) return NAMED_COLORS[lower];
546
324
  if (lower.startsWith("rgb")) {
547
325
  const parsed = parseRgbFunction(lower);
548
326
  if (parsed) return parsed;
@@ -561,18 +339,8 @@ function parseColorUncached(input) {
561
339
  }
562
340
  throw new Error(`parseColor: unrecognized color "${input}"`);
563
341
  }
564
- var RESOLVE_COLOR_CACHE_CAP = 1024;
565
- var resolveColorCache = /* @__PURE__ */ new Map();
566
342
  function resolveColor(input) {
567
- const hit = resolveColorCache.get(input);
568
- if (hit) return hit;
569
- if (resolveColorCache.size >= RESOLVE_COLOR_CACHE_CAP) {
570
- resolveColorCache.clear();
571
- }
572
- const parsed = parseColor(input);
573
- const frozen = parsed;
574
- resolveColorCache.set(input, frozen);
575
- return frozen;
343
+ return parseColor(input);
576
344
  }
577
345
  function parseColorToRgba255(input) {
578
346
  const [r, g, b, a] = parseColor(input);
@@ -633,24 +401,19 @@ function splitChannels(body) {
633
401
  if (alpha !== null && alpha === "") return null;
634
402
  return { rgb: [parts[0], parts[1], parts[2]], alpha };
635
403
  }
636
- function parseChannel(tok, max) {
637
- if (tok.endsWith("%")) {
638
- const n2 = Number(tok.slice(0, -1));
639
- if (!Number.isFinite(n2)) return null;
640
- return clamp012(n2 / 100);
641
- }
404
+ var CSS_NUMBER = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/;
405
+ function cssNumber(tok) {
406
+ if (!CSS_NUMBER.test(tok)) return null;
642
407
  const n = Number(tok);
643
- if (!Number.isFinite(n)) return null;
644
- return clamp012(n / max);
408
+ return Number.isFinite(n) ? n : null;
409
+ }
410
+ function parseChannel(tok, max) {
411
+ const percent = tok.endsWith("%");
412
+ const n = cssNumber(percent ? tok.slice(0, -1) : tok);
413
+ return n === null ? null : clamp012(n / (percent ? 100 : max));
645
414
  }
646
415
  function parseAlpha(tok) {
647
- if (tok === null) return 1;
648
- if (tok.endsWith("%")) {
649
- const n2 = Number(tok.slice(0, -1));
650
- return Number.isFinite(n2) ? clamp012(n2 / 100) : 1;
651
- }
652
- const n = Number(tok);
653
- return Number.isFinite(n) ? clamp012(n) : 1;
416
+ return tok === null ? 1 : parseChannel(tok, 1);
654
417
  }
655
418
  function parseRgbFunction(s) {
656
419
  const body = unwrapFn(s, "rgba") ?? unwrapFn(s, "rgb");
@@ -660,8 +423,9 @@ function parseRgbFunction(s) {
660
423
  const r = parseChannel(channels.rgb[0], 255);
661
424
  const g = parseChannel(channels.rgb[1], 255);
662
425
  const b = parseChannel(channels.rgb[2], 255);
663
- if (r === null || g === null || b === null) return null;
664
- return [r, g, b, parseAlpha(channels.alpha)];
426
+ const a = parseAlpha(channels.alpha);
427
+ if (r === null || g === null || b === null || a === null) return null;
428
+ return [r, g, b, a];
665
429
  }
666
430
  function parseHslFunction(s) {
667
431
  const body = unwrapFn(s, "hsla") ?? unwrapFn(s, "hsl");
@@ -671,9 +435,10 @@ function parseHslFunction(s) {
671
435
  const h = parseHue(channels.rgb[0]);
672
436
  const sat = parsePercentOrNumber(channels.rgb[1]);
673
437
  const lit = parsePercentOrNumber(channels.rgb[2]);
674
- if (h === null || sat === null || lit === null) return null;
438
+ const a = parseAlpha(channels.alpha);
439
+ if (h === null || sat === null || lit === null || a === null) return null;
675
440
  const [r, g, b] = hslToSrgb(h, sat, lit);
676
- return [r, g, b, parseAlpha(channels.alpha)];
441
+ return [r, g, b, a];
677
442
  }
678
443
  function parseColorFunction(s) {
679
444
  const body = unwrapFn(s, "color");
@@ -682,9 +447,10 @@ function parseColorFunction(s) {
682
447
  const channels = splitChannels(body.slice(space[0].length));
683
448
  if (!channels) return null;
684
449
  const rgb = channels.rgb.map((tok) => parseChannel(tok, 1));
685
- if (rgb.some((c) => c === null)) return null;
450
+ const a = parseAlpha(channels.alpha);
451
+ if (rgb.some((c) => c === null) || a === null) return null;
686
452
  const [r, g, b] = space[1] === "srgb" ? rgb : rgb.map((c) => linearToSrgb(c));
687
- return [clamp012(r), clamp012(g), clamp012(b), parseAlpha(channels.alpha)];
453
+ return [clamp012(r), clamp012(g), clamp012(b), a];
688
454
  }
689
455
  function parseOkFunction(s) {
690
456
  const lch = unwrapFn(s, "oklch");
@@ -693,35 +459,23 @@ function parseOkFunction(s) {
693
459
  const channels = splitChannels(body);
694
460
  if (!channels) return null;
695
461
  const L = parsePercentOrNumber(channels.rgb[0]);
696
- const second = Number(channels.rgb[1]);
697
- if (L === null || !Number.isFinite(second)) return null;
698
- let A = second;
699
- let B = Number(channels.rgb[2]);
700
- if (lch !== null) {
701
- const h = parseHue(channels.rgb[2]);
702
- if (h === null) return null;
703
- [, A, B] = oklchToOklab(L, second, h * (Math.PI / 180));
704
- } else if (!Number.isFinite(B)) return null;
462
+ const second = cssNumber(channels.rgb[1]);
463
+ const third = lch !== null ? parseHue(channels.rgb[2]) : cssNumber(channels.rgb[2]);
464
+ const alpha = parseAlpha(channels.alpha);
465
+ if (L === null || second === null || third === null || alpha === null) return null;
466
+ const [, A, B] = lch !== null ? oklchToOklab(L, second, third * (Math.PI / 180)) : [L, second, third];
705
467
  const [r, g, b] = oklabToLinearSrgb(L, A, B).map((c) => clamp012(linearToSrgb(c)));
706
- return [r, g, b, parseAlpha(channels.alpha)];
468
+ return [r, g, b, alpha];
707
469
  }
708
470
  function parseHue(tok) {
709
- let n;
710
- if (tok.endsWith("deg")) n = Number(tok.slice(0, -3));
711
- else if (tok.endsWith("grad")) n = Number(tok.slice(0, -4)) * 0.9;
712
- else if (tok.endsWith("rad")) n = Number(tok.slice(0, -3)) * (180 / Math.PI);
713
- else if (tok.endsWith("turn")) n = Number(tok.slice(0, -4)) * 360;
714
- else n = Number(tok);
715
- if (!Number.isFinite(n)) return null;
716
- return (n % 360 + 360) % 360;
471
+ const unit = /(deg|grad|rad|turn)$/.exec(tok)?.[1];
472
+ const n = cssNumber(unit === void 0 ? tok : tok.slice(0, -unit.length));
473
+ if (n === null) return null;
474
+ const deg = unit === "grad" ? n * 0.9 : unit === "rad" ? n * (180 / Math.PI) : unit === "turn" ? n * 360 : n;
475
+ return (deg % 360 + 360) % 360;
717
476
  }
718
477
  function parsePercentOrNumber(tok) {
719
- if (tok.endsWith("%")) {
720
- const n2 = Number(tok.slice(0, -1));
721
- return Number.isFinite(n2) ? clamp012(n2 / 100) : null;
722
- }
723
- const n = Number(tok);
724
- return Number.isFinite(n) ? clamp012(n) : null;
478
+ return parseChannel(tok, 1);
725
479
  }
726
480
  function clamp012(n) {
727
481
  return n < 0 ? 0 : n > 1 ? 1 : n;
@@ -887,6 +641,251 @@ var NAMED_COLORS = (() => {
887
641
  return out;
888
642
  })();
889
643
 
644
+ // src/resolvePaint.ts
645
+ var INHERIT = "inherit";
646
+ function resolveFill(fill, inherited = null) {
647
+ if (fill === INHERIT) return inherited;
648
+ return fill?.paint == null ? null : fill;
649
+ }
650
+ function resolveStroke(stroke, inherited = null) {
651
+ if (stroke === INHERIT) return inherited;
652
+ return stroke?.paint == null ? null : stroke;
653
+ }
654
+
655
+ // src/paintKey.ts
656
+ function canonical(value) {
657
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
658
+ if (typeof value !== "object" || value === null) return JSON.stringify(value) ?? "null";
659
+ const fields = Object.keys(value).sort().filter((k) => value[k] !== void 0).map((k) => `${JSON.stringify(k)}:${canonical(value[k])}`);
660
+ return `{${fields.join(",")}}`;
661
+ }
662
+ var structuralKeys = /* @__PURE__ */ new WeakMap();
663
+ var strokeShapeKeys = /* @__PURE__ */ new WeakMap();
664
+ function paintKey(paint) {
665
+ if (paint == null) return "none";
666
+ if (isSolidPaint(paint)) return `solid:${solidColorKey(paint.color, paint.opacity ?? 1)}`;
667
+ let key = structuralKeys.get(paint);
668
+ if (key === void 0) {
669
+ key = canonical(paint);
670
+ structuralKeys.set(paint, key);
671
+ }
672
+ return key;
673
+ }
674
+ function solidColorKey(color, opacity) {
675
+ try {
676
+ const [r, g, b, a] = parseColor(color);
677
+ return `${r},${g},${b},${a * opacity}`;
678
+ } catch {
679
+ return `text:${color}:${opacity}`;
680
+ }
681
+ }
682
+ function fillHasInk(fill) {
683
+ return resolveFill(fill) !== null;
684
+ }
685
+ function strokeHasInk(stroke) {
686
+ const painted = resolveStroke(stroke);
687
+ if (painted === null) return false;
688
+ const width = painted.width ?? 1;
689
+ return (typeof width === "number" ? width : width.px) > 0;
690
+ }
691
+ function strokeKey(stroke) {
692
+ if (!strokeHasInk(stroke)) return "none";
693
+ let shape = strokeShapeKeys.get(stroke);
694
+ if (shape === void 0) {
695
+ const { paint: _paint, width, join, cap, miterLimit, align, dash, ...rest } = stroke;
696
+ shape = canonical({
697
+ ...rest,
698
+ width: width ?? 1,
699
+ join: join ?? "miter",
700
+ cap: cap ?? "butt",
701
+ miterLimit: miterLimit ?? 4,
702
+ align: align ?? "center",
703
+ dash: dash ?? []
704
+ });
705
+ strokeShapeKeys.set(stroke, shape);
706
+ }
707
+ return `${shape}|${paintKey(stroke.paint)}`;
708
+ }
709
+
710
+ // src/alignedStrokeRect.ts
711
+ function alignedStrokeRectHolds(rect, align, width = 1) {
712
+ return align !== "inner" || width < Math.min(Math.abs(rect.width), Math.abs(rect.height));
713
+ }
714
+ function alignedStrokeRect(rect, align, width = 1) {
715
+ if (align === "center") return rect;
716
+ const sign = align === "inner" ? -1 : 1;
717
+ const d = sign * width / 2;
718
+ const dx = rect.width < 0 ? -d : d;
719
+ const dy = rect.height < 0 ? -d : d;
720
+ return {
721
+ x: rect.x - dx,
722
+ y: rect.y - dy,
723
+ width: rect.width + 2 * dx,
724
+ height: rect.height + 2 * dy
725
+ };
726
+ }
727
+
728
+ // src/patternPaint.ts
729
+ var TILE_KINDS = ["hatch", "crosshatch", "dots", "chunks"];
730
+ var TILE_KIND_SET = new Set(TILE_KINDS);
731
+ function isTileKind(kind) {
732
+ return kind !== void 0 && TILE_KIND_SET.has(kind);
733
+ }
734
+ function isTilePaint(paint) {
735
+ return isTileKind(paint.kind);
736
+ }
737
+ function isImagePatternPaint(paint) {
738
+ return paint.kind === "image";
739
+ }
740
+ function isPatternPaint(paint) {
741
+ return paint.kind === "texture" || paint.kind === "image" || isTileKind(paint.kind);
742
+ }
743
+ function patternPlacementOf(paint) {
744
+ const { units, origin, transform, opacity } = paint;
745
+ return {
746
+ ...units !== void 0 ? { units } : {},
747
+ ...origin !== void 0 ? { origin } : {},
748
+ ...transform !== void 0 ? { transform } : {},
749
+ ...opacity !== void 0 ? { opacity } : {}
750
+ };
751
+ }
752
+ function tileSpecOf(paint) {
753
+ const { units: _units, origin: _origin, transform: _transform, opacity: _opacity, ...spec } = paint;
754
+ return spec;
755
+ }
756
+
757
+ // src/patternTransform.ts
758
+ function composePatternTransform(parts) {
759
+ const { rotation = 0, scaleX = 1, scaleY = 1, skewX = 0 } = parts;
760
+ const cos = Math.cos(rotation);
761
+ const sin = Math.sin(rotation);
762
+ const shear = Math.tan(skewX) * scaleY;
763
+ return [
764
+ cos * scaleX,
765
+ sin * scaleX,
766
+ cos * shear - sin * scaleY,
767
+ sin * shear + cos * scaleY
768
+ ];
769
+ }
770
+ function decomposePatternTransform(t) {
771
+ if (!t) return { rotation: 0, scaleX: 1, scaleY: 1, skewX: 0 };
772
+ const [a, b, c, d] = t;
773
+ const scaleX = Math.hypot(a, b);
774
+ const rotation = scaleX === 0 ? 0 : Math.atan2(b, a);
775
+ const cos = Math.cos(rotation);
776
+ const sin = Math.sin(rotation);
777
+ const shear = c * cos + d * sin;
778
+ const scaleY = d * cos - c * sin;
779
+ const skewX = scaleY === 0 ? 0 : Math.atan(shear / scaleY);
780
+ return { rotation, scaleX, scaleY, skewX };
781
+ }
782
+
783
+ // src/vertexWidth.ts
784
+ var usable = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0;
785
+ function vertexHalfWidths(entry, fallback) {
786
+ const half = fallback / 2;
787
+ if (typeof entry === "number") return usable(entry) ? { left: entry / 2, right: entry / 2 } : { left: half, right: half };
788
+ if (entry == null || typeof entry !== "object") return { left: half, right: half };
789
+ return { left: usable(entry.left) ? entry.left : half, right: usable(entry.right) ? entry.right : half };
790
+ }
791
+ function vertexWidthFromSides(left, right) {
792
+ return Math.abs(left - right) <= 1e-9 * Math.max(1, Math.abs(left), Math.abs(right)) ? left + right : { left, right };
793
+ }
794
+ function scaleVertexWidth(entry, k) {
795
+ return typeof entry === "number" ? entry * k : { left: entry.left * k, right: entry.right * k };
796
+ }
797
+ function widestHalfWidth(width, vertexWidths) {
798
+ let widest = width / 2;
799
+ if (vertexWidths) {
800
+ for (const entry of vertexWidths) {
801
+ const { left, right } = vertexHalfWidths(entry, width);
802
+ widest = Math.max(widest, left, right);
803
+ }
804
+ }
805
+ return widest;
806
+ }
807
+
808
+ // src/paint.ts
809
+ function resolveScreenLength(value, scale) {
810
+ if (typeof value === "number") return value;
811
+ if (!Number.isFinite(scale) || scale === 0) return value.px;
812
+ return value.px / scale;
813
+ }
814
+ var STROKE_DASH_RATIOS = {
815
+ dashed: [3, 2],
816
+ dotted: [1, 2]
817
+ };
818
+ function dashWidth(width) {
819
+ const w = typeof width === "object" ? width.px : width ?? 1;
820
+ return Number.isFinite(w) && w > 0 ? w : 1;
821
+ }
822
+ function dashForStrokeStyle(style, width) {
823
+ if (style !== "dashed" && style !== "dotted") return void 0;
824
+ const w = dashWidth(width);
825
+ return STROKE_DASH_RATIOS[style].map((r) => r * w);
826
+ }
827
+ var MAX_DASH_RUNS = 1e5;
828
+ function dashPattern(dash, length) {
829
+ if (dash === void 0 || dash.length === 0) return "solid";
830
+ if (!dash.every(Number.isFinite)) return "non-finite";
831
+ if (dash.some((v) => v < 0) || !dash.some((v) => v > 0)) return "solid";
832
+ if (length !== void 0) {
833
+ const period = dash.reduce((a, b) => a + b, 0);
834
+ if (length / period * dash.length > MAX_DASH_RUNS) return "solid";
835
+ }
836
+ return dash;
837
+ }
838
+ function dashPhase(pattern, phase) {
839
+ if (!Number.isFinite(phase) || phase === 0) return 0;
840
+ const cycle = pattern.reduce((a, b) => a + b, 0) * (pattern.length % 2 + 1);
841
+ if (!(cycle > 0)) return 0;
842
+ const wrapped = phase % cycle;
843
+ return wrapped < 0 ? wrapped + cycle : wrapped;
844
+ }
845
+ function scaledDash(stroke, k) {
846
+ if (stroke.dash === void 0 && stroke.dashOffset === void 0) return stroke;
847
+ return {
848
+ ...stroke,
849
+ ...stroke.dash ? { dash: stroke.dash.map((d) => d * k) } : {},
850
+ ...stroke.dashOffset !== void 0 ? { dashOffset: stroke.dashOffset * k } : {}
851
+ };
852
+ }
853
+ function strokeDashStyleOf(dash, width) {
854
+ const pattern = dashPattern(dash);
855
+ if (pattern === "solid") return "solid";
856
+ if (pattern === "non-finite") return "custom";
857
+ const w = dashWidth(width);
858
+ for (const style of ["dashed", "dotted"]) {
859
+ const preset = STROKE_DASH_RATIOS[style];
860
+ if (pattern.length === preset.length && preset.every((r, i) => Math.abs(pattern[i] - r * w) <= w * 1e-3)) {
861
+ return style;
862
+ }
863
+ }
864
+ return "custom";
865
+ }
866
+
867
+ // src/mix.ts
868
+ function mixRgba(a, b, p, space = "oklch", hue = "shorter") {
869
+ const alpha = a[3] * p + b[3] * (1 - p);
870
+ if (alpha === 0) return [0, 0, 0, 0];
871
+ const t = b[3] * (1 - p) / alpha;
872
+ const [r, g, bl] = interpolateSrgbSplit([a[0], a[1], a[2]], [b[0], b[1], b[2]], t, 1 - p, space, hue);
873
+ return [r, g, bl, alpha];
874
+ }
875
+ function mixOklab(a, b, p) {
876
+ return mixRgba(a, b, p, "oklab");
877
+ }
878
+ function mixRgbaWeighted(members, space = "oklch", hue = "shorter") {
879
+ if (members.length === 0) throw new Error("mixRgbaWeighted: no colors");
880
+ let mixed = [...members[0].color];
881
+ let total = members[0].weight;
882
+ for (const { color, weight } of members.slice(1)) {
883
+ total += weight;
884
+ if (total > 0) mixed = mixRgba(color, mixed, weight / total, space, hue);
885
+ }
886
+ return mixed;
887
+ }
888
+
890
889
  // src/contrast.ts
891
890
  function contrastLineColor(background, strength) {
892
891
  const rgb = parseHexBytes(background.trim());
@@ -931,7 +930,7 @@ function sampleResolvedStops(stops, t, space = "rgb") {
931
930
  if (stops.length === 0) return TRANSPARENT;
932
931
  const first = stops[0];
933
932
  const last = stops[stops.length - 1];
934
- if (t <= first.offset) return first.rgba;
933
+ if (!(t > first.offset)) return first.rgba;
935
934
  if (t >= last.offset) return last.rgba;
936
935
  for (let i = 0; i < stops.length - 1; i++) {
937
936
  const lo = stops[i];
@@ -947,7 +946,6 @@ function sampleResolvedStops(stops, t, space = "rgb") {
947
946
  }
948
947
  var TRANSPARENT = [0, 0, 0, 0];
949
948
  function sampleGradientStops(stops, t, space = "rgb") {
950
- if (stops.length === 0) return "rgba(0,0,0,0)";
951
949
  return rgbaToHex(sampleResolvedStops(resolveGradientStops(stops), t, space));
952
950
  }
953
951
  function withGradientKind(fill, kind) {
@@ -1028,8 +1026,7 @@ function solidOf(rgba) {
1028
1026
  return rgba[3] >= 1 ? { color } : { color, opacity: rgba[3] };
1029
1027
  }
1030
1028
  function solidRgba(paint) {
1031
- if (paint.kind !== void 0 && paint.kind !== "solid") return null;
1032
- return rgbaOf(paint.color, paint.opacity);
1029
+ return isSolidPaint(paint) ? rgbaOf(paint.color, paint.opacity) : null;
1033
1030
  }
1034
1031
  var lerp = (a, b, t) => a + (b - a) * t;
1035
1032
  function lerpPaint(a, b, t) {
@@ -1050,9 +1047,11 @@ function lerpPaint(a, b, t) {
1050
1047
  }
1051
1048
  return t < 0.5 ? a : b;
1052
1049
  }
1050
+ var byOffset = (stops) => [...stops].sort((p, q) => p.offset - q.offset);
1053
1051
  function lerpStops(a, b, t) {
1054
- return a.map((stop, i) => {
1055
- const other = b[i];
1052
+ const to = byOffset(b);
1053
+ return byOffset(a).map((stop, i) => {
1054
+ const other = to[i];
1056
1055
  return { offset: lerp(stop.offset, other.offset, t), color: mixStopColor(stop.color, other.color, t) };
1057
1056
  });
1058
1057
  }
@@ -1184,7 +1183,7 @@ var PREDEFINED_SPACES = /* @__PURE__ */ new Set(["srgb", "srgb-linear", "display
1184
1183
  function cssColorNotation(space, coords, alpha = 1) {
1185
1184
  if (coords.length !== 3) return null;
1186
1185
  const slash = alpha < 1 ? ` / ${alpha}` : "";
1187
- const percent = FUNCTIONAL_SPACES[space];
1186
+ const percent = Object.hasOwn(FUNCTIONAL_SPACES, space) ? FUNCTIONAL_SPACES[space] : void 0;
1188
1187
  if (percent) return `${space}(${coords.map((c, i) => c !== "none" && percent.includes(i) ? `${c}%` : c).join(" ")}${slash})`;
1189
1188
  if (PREDEFINED_SPACES.has(space)) return `color(${space} ${coords.join(" ")}${slash})`;
1190
1189
  return null;
@@ -1203,9 +1202,9 @@ function hexToColorLiteral(hex) {
1203
1202
  if (!bytes) return null;
1204
1203
  const [r, g, b, a] = bytes.map((v) => v / 255);
1205
1204
  const coords = [r, g, b];
1206
- return hex.length === 9 ? { space: "srgb", coords, alpha: a } : { space: "srgb", coords };
1205
+ return a < 1 ? { space: "srgb", coords, alpha: a } : { space: "srgb", coords };
1207
1206
  }
1208
1207
 
1209
- export { INHERIT, MAX_DASH_RUNS, STROKE_DASH_RATIOS, TILE_KINDS, alignedStrokeRect, colorLiteralToCss, colorLiteralToHex, composePatternTransform, contrastLineColor, contrastRatio, cssColorLiteral, cssColorNotation, dashForStrokeStyle, dashPattern, dashPhase, decomposePatternTransform, fillHasInk, gradientForBounds, gradientGeometry, hexToColorLiteral, hexToOklchDeg, hexToRgbBytes, hexToRgba, hslToSrgb, interpolateSrgb, isGradientPaint, isImagePatternPaint, isPatternPaint, isSolidPaint, isTileKind, isTilePaint, lerpColorArray, lerpFill, lerpOklab, lerpOklch, lerpPaint, lerpStroke, linearSrgbToOklab, linearToSrgb, luminanceContrast, mixOklab, mixRgba, mixRgbaWeighted, normalizeHex, oklabToLinearSrgb, oklabToOklch, oklabToSrgbU8, oklchDegToHex, oklchToOklab, paintKey, parseColor, parseColorToRgba255, parseHexBytes, patternPlacementOf, relativeLuminance, resolveColor, resolveFill, resolveGradientStops, resolvePaletteColor, resolvePaletteColors, resolveScreenLength, resolveStroke, rgbaToHex, sampleGradientStops, sampleResolvedStops, scaleVertexWidth, scaledDash, srgbFloatToOklab, srgbToHsl, srgbToLinear, srgbU8ToOklab, strokeDashStyleOf, strokeHasInk, strokeKey, strokeTrimKey, tileSpecOf, tryNormalizeHex, vertexHalfWidths, vertexWidthFromSides, wcagLevels, widestHalfWidth, withGradientKind };
1208
+ export { INHERIT, MAX_DASH_RUNS, STROKE_DASH_RATIOS, TILE_KINDS, alignedStrokeRect, alignedStrokeRectHolds, byteHex, colorLiteralToCss, colorLiteralToHex, composePatternTransform, contrastLineColor, contrastRatio, cssColorLiteral, cssColorNotation, dashForStrokeStyle, dashPattern, dashPhase, decomposePatternTransform, fillHasInk, gradientForBounds, gradientGeometry, hexToColorLiteral, hexToOklchDeg, hexToRgbBytes, hexToRgba, hslToSrgb, interpolateSrgb, isGradientPaint, isImagePatternPaint, isPatternPaint, isSolidPaint, isTileKind, isTilePaint, lerpColorArray, lerpFill, lerpHueDeg, lerpOklab, lerpOklch, lerpPaint, lerpStroke, linearSrgbToOklab, linearToSrgb, luminanceContrast, mixOklab, mixRgba, mixRgbaWeighted, normalizeHex, oklabToLinearSrgb, oklabToOklch, oklabToSrgbU8, oklchDegToHex, oklchToOklab, paintKey, parseColor, parseColorToRgba255, parseHexBytes, patternPlacementOf, relativeLuminance, resolveColor, resolveFill, resolveGradientStops, resolvePaletteColor, resolvePaletteColors, resolveScreenLength, resolveStroke, rgbaHex, rgbaToHex, sampleGradientStops, sampleResolvedStops, scaleVertexWidth, scaledDash, srgbFloatToOklab, srgbToHsl, srgbToLinear, srgbU8ToOklab, strokeDashStyleOf, strokeHasInk, strokeKey, strokeTrimKey, tileSpecOf, tryNormalizeHex, vertexHalfWidths, vertexWidthFromSides, wcagLevels, widestHalfWidth, withGradientKind };
1210
1209
  //# sourceMappingURL=index.js.map
1211
1210
  //# sourceMappingURL=index.js.map