@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.js CHANGED
@@ -1,3 +1,80 @@
1
+ // src/commandPaint.ts
2
+ function isSolidPaint(paint) {
3
+ return paint != null && (paint.kind === void 0 || paint.kind === "solid");
4
+ }
5
+
6
+ // src/paintKey.ts
7
+ function canonical(value) {
8
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
9
+ if (typeof value !== "object" || value === null) return JSON.stringify(value) ?? "null";
10
+ const fields = Object.keys(value).sort().filter((k) => value[k] !== void 0).map((k) => `${JSON.stringify(k)}:${canonical(value[k])}`);
11
+ return `{${fields.join(",")}}`;
12
+ }
13
+ var structuralKeys = /* @__PURE__ */ new WeakMap();
14
+ var strokeShapeKeys = /* @__PURE__ */ new WeakMap();
15
+ function paintKey(paint) {
16
+ if (paint == null) return "none";
17
+ if (isSolidPaint(paint)) return `solid:${paint.color}:${paint.opacity ?? 1}`;
18
+ let key = structuralKeys.get(paint);
19
+ if (key === void 0) {
20
+ key = canonical(paint);
21
+ structuralKeys.set(paint, key);
22
+ }
23
+ return key;
24
+ }
25
+ function fillHasInk(fill) {
26
+ return fill?.paint !== void 0;
27
+ }
28
+ function strokeHasInk(stroke) {
29
+ if (stroke?.paint === void 0) return false;
30
+ const width = stroke.width ?? 1;
31
+ return (typeof width === "number" ? width : width.px) > 0;
32
+ }
33
+ function strokeKey(stroke) {
34
+ if (!strokeHasInk(stroke)) return "none";
35
+ let shape = strokeShapeKeys.get(stroke);
36
+ if (shape === void 0) {
37
+ const { paint: _paint, width, join, cap, miterLimit, align, dash, ...rest } = stroke;
38
+ shape = canonical({
39
+ ...rest,
40
+ width: width ?? 1,
41
+ join: join ?? "miter",
42
+ cap: cap ?? "butt",
43
+ miterLimit: miterLimit ?? 4,
44
+ align: align ?? "center",
45
+ dash: dash ?? []
46
+ });
47
+ strokeShapeKeys.set(stroke, shape);
48
+ }
49
+ return `${shape}|${paintKey(stroke.paint)}`;
50
+ }
51
+
52
+ // src/patternPaint.ts
53
+ var TILE_KINDS = ["hatch", "crosshatch", "dots", "chunks"];
54
+ var TILE_KIND_SET = new Set(TILE_KINDS);
55
+ function isTileKind(kind) {
56
+ return kind !== void 0 && TILE_KIND_SET.has(kind);
57
+ }
58
+ function isTilePaint(paint) {
59
+ return isTileKind(paint.kind);
60
+ }
61
+ function isPatternPaint(paint) {
62
+ return paint.kind === "texture" || isTileKind(paint.kind);
63
+ }
64
+ function patternPlacementOf(paint) {
65
+ const { units, origin, transform, opacity } = paint;
66
+ return {
67
+ ...units !== void 0 ? { units } : {},
68
+ ...origin !== void 0 ? { origin } : {},
69
+ ...transform !== void 0 ? { transform } : {},
70
+ ...opacity !== void 0 ? { opacity } : {}
71
+ };
72
+ }
73
+ function tileSpecOf(paint) {
74
+ const { units: _units, origin: _origin, transform: _transform, opacity: _opacity, ...spec } = paint;
75
+ return spec;
76
+ }
77
+
1
78
  // src/patternTransform.ts
2
79
  function composePatternTransform(parts) {
3
80
  const { rotation = 0, scaleX = 1, scaleY = 1, skewX = 0 } = parts;
@@ -66,6 +143,21 @@ function strokeDashStyleOf(dash, width) {
66
143
  return "custom";
67
144
  }
68
145
 
146
+ // src/hex.ts
147
+ var HEX = /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
148
+ function parseHexBytes(input) {
149
+ if (!HEX.test(input)) return null;
150
+ const digits = input.slice(1);
151
+ const long = digits.length <= 4 ? digits.replace(/./g, "$&$&") : digits;
152
+ const byte = (i) => Number.parseInt(long.slice(i, i + 2), 16);
153
+ return [byte(0), byte(2), byte(4), long.length === 8 ? byte(6) : 255];
154
+ }
155
+ function hexToRgbBytes(input) {
156
+ const bytes = parseHexBytes(input);
157
+ if (!bytes) throw new Error(`not a hex color: "${input}"`);
158
+ return [bytes[0], bytes[1], bytes[2]];
159
+ }
160
+
69
161
  // src/colorSpaces.ts
70
162
  var SRGB_TO_LINEAR = (() => {
71
163
  const lut = new Float64Array(256);
@@ -229,8 +321,7 @@ function oklchDegToHex(L, C, hueDeg) {
229
321
  return `#${hex2(r)}${hex2(g)}${hex2(bl)}`;
230
322
  }
231
323
  function hexToOklchDeg(hex) {
232
- const n = Number.parseInt(hex.slice(1), 16);
233
- const [L, A, B] = srgbU8ToOklab(n >> 16 & 255, n >> 8 & 255, n & 255);
324
+ const [L, A, B] = srgbU8ToOklab(...hexToRgbBytes(hex));
234
325
  const [l, c, h] = oklabToOklch(L, A, B);
235
326
  return { L: l, C: c, H: (h * 180 / Math.PI % 360 + 360) % 360 };
236
327
  }
@@ -333,16 +424,8 @@ function interpolateSrgb(from, to, t, space = "oklch", hue = "shorter") {
333
424
 
334
425
  // src/contrast.ts
335
426
  var hex22 = (v) => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2, "0");
336
- function readHex(color) {
337
- const m = /^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.exec(color.trim());
338
- if (!m) return null;
339
- let h = m[1];
340
- if (h.length === 3) h = h.replace(/./g, (c) => c + c);
341
- const n = Number.parseInt(h.slice(0, 6), 16);
342
- return [n >> 16 & 255, n >> 8 & 255, n & 255];
343
- }
344
427
  function contrastLineColor(background, strength) {
345
- const rgb = readHex(background);
428
+ const rgb = parseHexBytes(background.trim());
346
429
  if (!rgb) return `rgba(0, 0, 0, ${strength})`;
347
430
  const [L, A, B] = srgbU8ToOklab(rgb[0], rgb[1], rgb[2]);
348
431
  const next = L > 0.5 ? Math.max(0, L - strength) : Math.min(1, L + strength);
@@ -350,6 +433,420 @@ function contrastLineColor(background, strength) {
350
433
  return `#${hex22(r)}${hex22(g)}${hex22(b)}`;
351
434
  }
352
435
 
436
+ // src/color.ts
437
+ function parseColor(input) {
438
+ const cached = CACHE.get(input);
439
+ if (cached !== void 0) return cached;
440
+ const result = parseColorUncached(input);
441
+ if (CACHE.size >= CACHE_MAX) CACHE.clear();
442
+ CACHE.set(input, result);
443
+ return result;
444
+ }
445
+ var CACHE = /* @__PURE__ */ new Map();
446
+ var CACHE_MAX = 1024;
447
+ function parseColorUncached(input) {
448
+ const s = input.trim();
449
+ if (s.startsWith("#")) return parseHex(s);
450
+ const lower = s.toLowerCase();
451
+ const named = NAMED_COLORS[lower];
452
+ if (named !== void 0) return named;
453
+ if (lower.startsWith("rgb")) {
454
+ const parsed = parseRgbFunction(lower);
455
+ if (parsed) return parsed;
456
+ }
457
+ if (lower.startsWith("hsl")) {
458
+ const parsed = parseHslFunction(lower);
459
+ if (parsed) return parsed;
460
+ }
461
+ throw new Error(`parseColor: unrecognized color "${input}"`);
462
+ }
463
+ var RESOLVE_COLOR_CACHE_CAP = 1024;
464
+ var resolveColorCache = /* @__PURE__ */ new Map();
465
+ function resolveColor(input) {
466
+ const hit = resolveColorCache.get(input);
467
+ if (hit) return hit;
468
+ if (resolveColorCache.size >= RESOLVE_COLOR_CACHE_CAP) {
469
+ resolveColorCache.clear();
470
+ }
471
+ const parsed = parseColor(input);
472
+ const frozen = parsed;
473
+ resolveColorCache.set(input, frozen);
474
+ return frozen;
475
+ }
476
+ function parseColorToRgba255(input) {
477
+ const [r, g, b, a] = parseColor(input);
478
+ return [
479
+ Math.round(r * 255),
480
+ Math.round(g * 255),
481
+ Math.round(b * 255),
482
+ Math.round(a * 255)
483
+ ];
484
+ }
485
+ function normalizeHex(input) {
486
+ const trimmed = input.trim();
487
+ const hashed = trimmed.startsWith("#") ? trimmed : `#${trimmed}`;
488
+ const bytes = parseHexBytes(hashed);
489
+ if (!bytes) throw new Error(`normalizeHex: invalid hex "${input}"`);
490
+ const digits = bytes.map((n) => n.toString(16).padStart(2, "0")).join("");
491
+ const namesAlpha = hashed.length === 5 || hashed.length === 9;
492
+ return `#${namesAlpha ? digits : digits.slice(0, 6)}`;
493
+ }
494
+ function hexToRgba(input) {
495
+ return parseHex(normalizeHex(input));
496
+ }
497
+ function rgbaToHex(rgba) {
498
+ const [r, g, b] = rgba;
499
+ const a = rgba.length === 4 ? rgba[3] : 1;
500
+ const f = (n) => Math.max(0, Math.min(255, Math.round(n * 255))).toString(16).padStart(2, "0");
501
+ const base = `#${f(r)}${f(g)}${f(b)}`;
502
+ return a >= 1 ? base : `${base}${f(a)}`;
503
+ }
504
+ function parseHex(s) {
505
+ const bytes = parseHexBytes(s);
506
+ if (!bytes) throw new Error(`parseColor: invalid hex "${s}"`);
507
+ return [bytes[0] / 255, bytes[1] / 255, bytes[2] / 255, bytes[3] / 255];
508
+ }
509
+ function unwrapFn(s, name) {
510
+ if (!s.startsWith(name + "(") || !s.endsWith(")")) return null;
511
+ return s.slice(name.length + 1, -1).trim();
512
+ }
513
+ function splitChannels(body) {
514
+ if (body.includes(",")) {
515
+ const parts2 = body.split(",").map((p) => p.trim());
516
+ if (parts2.length !== 3 && parts2.length !== 4) return null;
517
+ return {
518
+ rgb: [parts2[0], parts2[1], parts2[2]],
519
+ alpha: parts2.length === 4 ? parts2[3] : null
520
+ };
521
+ }
522
+ const slash = body.indexOf("/");
523
+ const head = (slash === -1 ? body : body.slice(0, slash)).trim();
524
+ const alpha = slash === -1 ? null : body.slice(slash + 1).trim();
525
+ const parts = head.split(/\s+/);
526
+ if (parts.length !== 3) return null;
527
+ if (alpha !== null && alpha === "") return null;
528
+ return { rgb: [parts[0], parts[1], parts[2]], alpha };
529
+ }
530
+ function parseChannel(tok, max) {
531
+ if (tok.endsWith("%")) {
532
+ const n2 = Number(tok.slice(0, -1));
533
+ if (!Number.isFinite(n2)) return null;
534
+ return clamp012(n2 / 100);
535
+ }
536
+ const n = Number(tok);
537
+ if (!Number.isFinite(n)) return null;
538
+ return clamp012(n / max);
539
+ }
540
+ function parseAlpha(tok) {
541
+ if (tok === null) return 1;
542
+ if (tok.endsWith("%")) {
543
+ const n2 = Number(tok.slice(0, -1));
544
+ return Number.isFinite(n2) ? clamp012(n2 / 100) : 1;
545
+ }
546
+ const n = Number(tok);
547
+ return Number.isFinite(n) ? clamp012(n) : 1;
548
+ }
549
+ function parseRgbFunction(s) {
550
+ const body = unwrapFn(s, "rgba") ?? unwrapFn(s, "rgb");
551
+ if (body === null) return null;
552
+ const channels = splitChannels(body);
553
+ if (!channels) return null;
554
+ const r = parseChannel(channels.rgb[0], 255);
555
+ const g = parseChannel(channels.rgb[1], 255);
556
+ const b = parseChannel(channels.rgb[2], 255);
557
+ if (r === null || g === null || b === null) return null;
558
+ return [r, g, b, parseAlpha(channels.alpha)];
559
+ }
560
+ function parseHslFunction(s) {
561
+ const body = unwrapFn(s, "hsla") ?? unwrapFn(s, "hsl");
562
+ if (body === null) return null;
563
+ const channels = splitChannels(body);
564
+ if (!channels) return null;
565
+ const h = parseHue(channels.rgb[0]);
566
+ const sat = parsePercentOrNumber(channels.rgb[1]);
567
+ const lit = parsePercentOrNumber(channels.rgb[2]);
568
+ if (h === null || sat === null || lit === null) return null;
569
+ const [r, g, b] = hslToSrgb(h, sat, lit);
570
+ return [r, g, b, parseAlpha(channels.alpha)];
571
+ }
572
+ function parseHue(tok) {
573
+ let n;
574
+ if (tok.endsWith("deg")) n = Number(tok.slice(0, -3));
575
+ else if (tok.endsWith("grad")) n = Number(tok.slice(0, -4)) * 0.9;
576
+ else if (tok.endsWith("rad")) n = Number(tok.slice(0, -3)) * (180 / Math.PI);
577
+ else if (tok.endsWith("turn")) n = Number(tok.slice(0, -4)) * 360;
578
+ else n = Number(tok);
579
+ if (!Number.isFinite(n)) return null;
580
+ return (n % 360 + 360) % 360;
581
+ }
582
+ function parsePercentOrNumber(tok) {
583
+ if (tok.endsWith("%")) {
584
+ const n2 = Number(tok.slice(0, -1));
585
+ return Number.isFinite(n2) ? clamp012(n2 / 100) : null;
586
+ }
587
+ const n = Number(tok);
588
+ return Number.isFinite(n) ? clamp012(n) : null;
589
+ }
590
+ function clamp012(n) {
591
+ return n < 0 ? 0 : n > 1 ? 1 : n;
592
+ }
593
+ var NAMED_COLORS = (() => {
594
+ const hex = {
595
+ aliceblue: 15792383,
596
+ antiquewhite: 16444375,
597
+ aqua: 65535,
598
+ aquamarine: 8388564,
599
+ azure: 15794175,
600
+ beige: 16119260,
601
+ bisque: 16770244,
602
+ black: 0,
603
+ blanchedalmond: 16772045,
604
+ blue: 255,
605
+ blueviolet: 9055202,
606
+ brown: 10824234,
607
+ burlywood: 14596231,
608
+ cadetblue: 6266528,
609
+ chartreuse: 8388352,
610
+ chocolate: 13789470,
611
+ coral: 16744272,
612
+ cornflowerblue: 6591981,
613
+ cornsilk: 16775388,
614
+ crimson: 14423100,
615
+ cyan: 65535,
616
+ darkblue: 139,
617
+ darkcyan: 35723,
618
+ darkgoldenrod: 12092939,
619
+ darkgray: 11119017,
620
+ darkgreen: 25600,
621
+ darkgrey: 11119017,
622
+ darkkhaki: 12433259,
623
+ darkmagenta: 9109643,
624
+ darkolivegreen: 5597999,
625
+ darkorange: 16747520,
626
+ darkorchid: 10040012,
627
+ darkred: 9109504,
628
+ darksalmon: 15308410,
629
+ darkseagreen: 9419919,
630
+ darkslateblue: 4734347,
631
+ darkslategray: 3100495,
632
+ darkslategrey: 3100495,
633
+ darkturquoise: 52945,
634
+ darkviolet: 9699539,
635
+ deeppink: 16716947,
636
+ deepskyblue: 49151,
637
+ dimgray: 6908265,
638
+ dimgrey: 6908265,
639
+ dodgerblue: 2003199,
640
+ firebrick: 11674146,
641
+ floralwhite: 16775920,
642
+ forestgreen: 2263842,
643
+ fuchsia: 16711935,
644
+ gainsboro: 14474460,
645
+ ghostwhite: 16316671,
646
+ gold: 16766720,
647
+ goldenrod: 14329120,
648
+ gray: 8421504,
649
+ green: 32768,
650
+ greenyellow: 11403055,
651
+ grey: 8421504,
652
+ honeydew: 15794160,
653
+ hotpink: 16738740,
654
+ indianred: 13458524,
655
+ indigo: 4915330,
656
+ ivory: 16777200,
657
+ khaki: 15787660,
658
+ lavender: 15132410,
659
+ lavenderblush: 16773365,
660
+ lawngreen: 8190976,
661
+ lemonchiffon: 16775885,
662
+ lightblue: 11393254,
663
+ lightcoral: 15761536,
664
+ lightcyan: 14745599,
665
+ lightgoldenrodyellow: 16448210,
666
+ lightgray: 13882323,
667
+ lightgreen: 9498256,
668
+ lightgrey: 13882323,
669
+ lightpink: 16758465,
670
+ lightsalmon: 16752762,
671
+ lightseagreen: 2142890,
672
+ lightskyblue: 8900346,
673
+ lightslategray: 7833753,
674
+ lightslategrey: 7833753,
675
+ lightsteelblue: 11584734,
676
+ lightyellow: 16777184,
677
+ lime: 65280,
678
+ limegreen: 3329330,
679
+ linen: 16445670,
680
+ magenta: 16711935,
681
+ maroon: 8388608,
682
+ mediumaquamarine: 6737322,
683
+ mediumblue: 205,
684
+ mediumorchid: 12211667,
685
+ mediumpurple: 9662683,
686
+ mediumseagreen: 3978097,
687
+ mediumslateblue: 8087790,
688
+ mediumspringgreen: 64154,
689
+ mediumturquoise: 4772300,
690
+ mediumvioletred: 13047173,
691
+ midnightblue: 1644912,
692
+ mintcream: 16121850,
693
+ mistyrose: 16770273,
694
+ moccasin: 16770229,
695
+ navajowhite: 16768685,
696
+ navy: 128,
697
+ oldlace: 16643558,
698
+ olive: 8421376,
699
+ olivedrab: 7048739,
700
+ orange: 16753920,
701
+ orangered: 16729344,
702
+ orchid: 14315734,
703
+ palegoldenrod: 15657130,
704
+ palegreen: 10025880,
705
+ paleturquoise: 11529966,
706
+ palevioletred: 14381203,
707
+ papayawhip: 16773077,
708
+ peachpuff: 16767673,
709
+ peru: 13468991,
710
+ pink: 16761035,
711
+ plum: 14524637,
712
+ powderblue: 11591910,
713
+ purple: 8388736,
714
+ rebeccapurple: 6697881,
715
+ red: 16711680,
716
+ rosybrown: 12357519,
717
+ royalblue: 4286945,
718
+ saddlebrown: 9127187,
719
+ salmon: 16416882,
720
+ sandybrown: 16032864,
721
+ seagreen: 3050327,
722
+ seashell: 16774638,
723
+ sienna: 10506797,
724
+ silver: 12632256,
725
+ skyblue: 8900331,
726
+ slateblue: 6970061,
727
+ slategray: 7372944,
728
+ slategrey: 7372944,
729
+ snow: 16775930,
730
+ springgreen: 65407,
731
+ steelblue: 4620980,
732
+ tan: 13808780,
733
+ teal: 32896,
734
+ thistle: 14204888,
735
+ tomato: 16737095,
736
+ turquoise: 4251856,
737
+ violet: 15631086,
738
+ wheat: 16113331,
739
+ white: 16777215,
740
+ whitesmoke: 16119285,
741
+ yellow: 16776960,
742
+ yellowgreen: 10145074
743
+ };
744
+ const out = {
745
+ transparent: [0, 0, 0, 0]
746
+ };
747
+ for (const name in hex) {
748
+ const n = hex[name];
749
+ out[name] = [(n >> 16 & 255) / 255, (n >> 8 & 255) / 255, (n & 255) / 255, 1];
750
+ }
751
+ return out;
752
+ })();
753
+
754
+ // src/gradient.ts
755
+ function isGradientPaint(fill) {
756
+ if (fill == null) return false;
757
+ return fill.kind === "linear-gradient" || fill.kind === "radial-gradient" || fill.kind === "conic-gradient";
758
+ }
759
+ function resolveGradientStops(stops) {
760
+ return [...stops].sort((a, b) => a.offset - b.offset).map((s) => ({ offset: s.offset, rgba: resolveColor(s.color) }));
761
+ }
762
+ function sampleResolvedStops(stops, t, space = "rgb") {
763
+ if (stops.length === 0) return TRANSPARENT;
764
+ const first = stops[0];
765
+ const last = stops[stops.length - 1];
766
+ if (t <= first.offset) return first.rgba;
767
+ if (t >= last.offset) return last.rgba;
768
+ for (let i = 0; i < stops.length - 1; i++) {
769
+ const lo = stops[i];
770
+ const hi = stops[i + 1];
771
+ if (t < lo.offset || t > hi.offset) continue;
772
+ const span = hi.offset - lo.offset;
773
+ if (span <= 0) return hi.rgba;
774
+ const f = (t - lo.offset) / span;
775
+ const [r, g, b, a] = lerpColorArray(lo.rgba, hi.rgba, f, space);
776
+ return [r, g, b, a];
777
+ }
778
+ return last.rgba;
779
+ }
780
+ var TRANSPARENT = [0, 0, 0, 0];
781
+ function sampleGradientStops(stops, t, space = "rgb") {
782
+ if (stops.length === 0) return "rgba(0,0,0,0)";
783
+ return rgbaToHex(sampleResolvedStops(resolveGradientStops(stops), t, space));
784
+ }
785
+ function withGradientKind(fill, kind) {
786
+ if (fill.kind === kind) return fill;
787
+ const common = {
788
+ stops: fill.stops,
789
+ units: fill.units,
790
+ interpolate: fill.interpolate,
791
+ opacity: fill.opacity
792
+ };
793
+ const { center, radius, angle } = gradientGeometry(fill);
794
+ if (kind === "linear-gradient") {
795
+ const dx = Math.cos(angle) * radius;
796
+ const dy = Math.sin(angle) * radius;
797
+ return {
798
+ kind: "linear-gradient",
799
+ from: { x: center.x - dx, y: center.y - dy },
800
+ to: { x: center.x + dx, y: center.y + dy },
801
+ ...common
802
+ };
803
+ }
804
+ if (kind === "radial-gradient") {
805
+ return { kind: "radial-gradient", center, radius, ...common };
806
+ }
807
+ return { kind: "conic-gradient", center, angle, ...common };
808
+ }
809
+ function gradientGeometry(fill) {
810
+ if (fill.kind === "linear-gradient") {
811
+ const dx = fill.to.x - fill.from.x;
812
+ const dy = fill.to.y - fill.from.y;
813
+ return {
814
+ center: { x: (fill.from.x + fill.to.x) / 2, y: (fill.from.y + fill.to.y) / 2 },
815
+ radius: Math.hypot(dx, dy) / 2,
816
+ angle: Math.atan2(dy, dx)
817
+ };
818
+ }
819
+ if (fill.kind === "radial-gradient") {
820
+ return { center: fill.center, radius: fill.radius, angle: 0 };
821
+ }
822
+ return { center: fill.center, radius: conicArmRadius(fill.units), angle: fill.angle };
823
+ }
824
+ function conicArmRadius(units) {
825
+ return units === "bounds" ? 0.5 : 50;
826
+ }
827
+ function gradientForBounds(kind, bounds, stops, units) {
828
+ const center = { x: bounds.x + bounds.width / 2, y: bounds.y + bounds.height / 2 };
829
+ if (kind === "linear-gradient") {
830
+ return {
831
+ kind: "linear-gradient",
832
+ from: { x: bounds.x, y: center.y },
833
+ to: { x: bounds.x + bounds.width, y: center.y },
834
+ stops,
835
+ units
836
+ };
837
+ }
838
+ if (kind === "radial-gradient") {
839
+ return {
840
+ kind: "radial-gradient",
841
+ center,
842
+ radius: Math.max(bounds.width, bounds.height) / 2,
843
+ stops,
844
+ units
845
+ };
846
+ }
847
+ return { kind: "conic-gradient", center, angle: 0, stops, units };
848
+ }
849
+
353
850
  // src/palette.ts
354
851
  function nth(items, n) {
355
852
  if (!Number.isInteger(n) || n < 0) return void 0;
@@ -414,13 +911,26 @@ function colorLiteralToHex(color) {
414
911
  const a = color.alpha ?? 1;
415
912
  return `#${rgb.map(hex23).join("")}${a < 1 ? hex23(a * 255) : ""}`;
416
913
  }
914
+ function colorLiteralToCss(color) {
915
+ if (color.coords.length < 3) return null;
916
+ const hex = colorLiteralToHex(color);
917
+ if (hex) return hex;
918
+ const a = color.alpha ?? 1;
919
+ return `color(${color.space} ${color.coords.join(" ")}${a < 1 ? ` / ${a}` : ""})`;
920
+ }
921
+ function cssColorLiteral(css) {
922
+ const [r, g, b, a] = parseColor(css);
923
+ const coords = [r, g, b];
924
+ return a < 1 ? { space: "srgb", coords, alpha: a } : { space: "srgb", coords };
925
+ }
417
926
  function hexToColorLiteral(hex) {
418
- if (!/^#(?:[0-9a-f]{6}|[0-9a-f]{8})$/i.test(hex)) return null;
419
- const byte = (i) => Number.parseInt(hex.slice(i, i + 2), 16) / 255;
420
- const coords = [byte(1), byte(3), byte(5)];
421
- return hex.length === 9 ? { space: "srgb", coords, alpha: byte(7) } : { space: "srgb", coords };
927
+ const bytes = hex.length === 7 || hex.length === 9 ? parseHexBytes(hex) : null;
928
+ if (!bytes) return null;
929
+ const [r, g, b, a] = bytes.map((v) => v / 255);
930
+ const coords = [r, g, b];
931
+ return hex.length === 9 ? { space: "srgb", coords, alpha: a } : { space: "srgb", coords };
422
932
  }
423
933
 
424
- export { STROKE_DASH_RATIOS, 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 };
934
+ export { STROKE_DASH_RATIOS, TILE_KINDS, 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 };
425
935
  //# sourceMappingURL=index.js.map
426
936
  //# sourceMappingURL=index.js.map