gradiente 2.0.1 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { converter, formatRgb, parse as parse$1 } from "culori";
1
+ import { converter, fixupHueDecreasing, fixupHueIncreasing, fixupHueLonger, fixupHueShorter, formatRgb, interpolate, parse as parse$1 } from "culori";
2
2
  //#region src/utils/math/base.ts
3
3
  function roundTo(value, digits) {
4
4
  const factor = 10 ** digits;
@@ -30,6 +30,26 @@ function fromPercent(value) {
30
30
  function isAngleUnit(unit) {
31
31
  return unit === "deg" || unit === "rad" || unit === "turn" || unit === "grad";
32
32
  }
33
+ function isAngle(value) {
34
+ try {
35
+ return typeof angleValueFromString(value) === "number";
36
+ } catch (e) {
37
+ return false;
38
+ }
39
+ }
40
+ function angleValueFromString(value) {
41
+ const match = value.match(/^([+-]?(?:\d+\.?\d*|\.\d+))(deg|rad|turn|grad)$/);
42
+ if (match === null) throw new Error(`Invalid angle value: "${value}"`);
43
+ if (!isAngleUnit(match[2])) throw new Error(`Unsupported angle unit: "${match[2]}"`);
44
+ if (!Number.isFinite(+match[1])) throw new SyntaxError(`Invalid angle value: "${match[1]}"`);
45
+ const angleValue = Number(match[1]);
46
+ switch (match[2]) {
47
+ case "deg": return degToRad(angleValue);
48
+ case "rad": return angleValue;
49
+ case "turn": return turnToRad(angleValue);
50
+ case "grad": return gradToRad(angleValue);
51
+ }
52
+ }
33
53
  function degToRad(value) {
34
54
  return value * Math.PI / 180;
35
55
  }
@@ -45,21 +65,9 @@ function gradToRad(value) {
45
65
  function normalizeAngleDeg(value, digits = 3) {
46
66
  return roundTo((value % 360 + 360) % 360, digits);
47
67
  }
48
- function normalizeAngleRad(value) {
68
+ function normalizeAngleRad(value, digits = 6) {
49
69
  const tau = Math.PI * 2;
50
- return (value % tau + tau) % tau;
51
- }
52
- function toAngleRad(value, unit) {
53
- switch (unit) {
54
- case "deg": return degToRad(value);
55
- case "rad": return value;
56
- case "turn": return turnToRad(value);
57
- case "grad": return gradToRad(value);
58
- default: throw new Error(`Unsupported angle unit: ${String(unit)}`);
59
- }
60
- }
61
- function normalizeAngle(value, unit, digits = 6) {
62
- return roundTo(normalizeAngleRad(toAngleRad(value, unit)), digits);
70
+ return roundTo((value % tau + tau) % tau, digits);
63
71
  }
64
72
  //#endregion
65
73
  //#region src/dsl/types.ts
@@ -700,6 +708,44 @@ function splitTopLevelByWhitespace(value) {
700
708
  return result;
701
709
  }
702
710
  //#endregion
711
+ //#region src/gradients/helpers.ts
712
+ const GRADIENT_COLOR_SPACE = [
713
+ "oklab",
714
+ "lch",
715
+ "oklch",
716
+ "hsl",
717
+ "hwb",
718
+ "lab",
719
+ "srgb",
720
+ "srgb-linear",
721
+ "xyz",
722
+ "display-p3",
723
+ "a98-rgb",
724
+ "prophoto-rgb",
725
+ "rec2020"
726
+ ];
727
+ const GRADIENT_HUE_INTERPOLATIONS = [
728
+ "shorter",
729
+ "longer",
730
+ "increasing",
731
+ "decreasing"
732
+ ];
733
+ const GRADIENT_POLAR_COLOR_SPACES = [
734
+ "hsl",
735
+ "hwb",
736
+ "lch",
737
+ "oklch"
738
+ ];
739
+ function isGradientHueInterpolation(value) {
740
+ return GRADIENT_HUE_INTERPOLATIONS.includes(value);
741
+ }
742
+ function isGradientColorSpace(value) {
743
+ return GRADIENT_COLOR_SPACE.includes(value);
744
+ }
745
+ function isGradientPolarColorSpace(value) {
746
+ return GRADIENT_POLAR_COLOR_SPACES.includes(value);
747
+ }
748
+ //#endregion
703
749
  //#region src/gradients/GradientBase.ts
704
750
  var GradientBase = class {
705
751
  _isRepeating;
@@ -725,8 +771,8 @@ var GradientBase = class {
725
771
  return {
726
772
  type: this.type,
727
773
  isRepeating: this.isRepeating,
728
- config: this.config,
729
- stops: this.stops
774
+ config: this._cloneConfig(this.config),
775
+ stops: this._cloneStops(this.stops)
730
776
  };
731
777
  }
732
778
  addStop(stop) {
@@ -799,12 +845,10 @@ var GradientBase = class {
799
845
  }
800
846
  }
801
847
  _cloneStops(stops) {
802
- return stops.map((stop) => ({ ...stop }));
848
+ return structuredClone(stops);
803
849
  }
804
850
  _cloneConfig(value) {
805
- if (typeof value !== "object" || value === null) return value;
806
- if (Array.isArray(value)) return [...value];
807
- return { ...value };
851
+ return structuredClone(value);
808
852
  }
809
853
  _buildSerializedStopTokens() {
810
854
  const result = [];
@@ -962,66 +1006,72 @@ var GradientBase = class {
962
1006
  var LinearGradient = class LinearGradient extends GradientBase {
963
1007
  type = "linear-gradient";
964
1008
  constructor(config) {
1009
+ config.config.angle = normalizeAngleRad(config.config.angle);
1010
+ if (config.config.interpolation) config.config.interpolation = LinearGradient._normalizeConfigInterpolation(config.config.interpolation);
965
1011
  super(config);
966
1012
  }
967
1013
  static normalizeConfig(value) {
968
- if (typeof value === "string") {
969
- const tokens = value.trim().toLowerCase().split(/\s+/).filter(Boolean);
970
- if (tokens.length === 0) throw new SyntaxError("Linear gradient angle keyword cannot be empty");
971
- if (tokens[0] !== "to") throw new SyntaxError("Linear gradient keyword direction must start with \"to\"");
972
- const directions = tokens.slice(1);
973
- if (directions.length === 0 || directions.length > 2) throw new SyntaxError("Linear gradient keyword direction must contain one or two direction tokens");
974
- const allowed = new Set([
975
- "top",
976
- "right",
977
- "bottom",
978
- "left"
979
- ]);
980
- for (const direction of directions) if (!allowed.has(direction)) throw new SyntaxError(`Invalid linear gradient direction token: "${direction}"`);
981
- if (new Set(directions).size !== directions.length) throw new SyntaxError("Linear gradient keyword direction cannot contain duplicate tokens");
982
- const hasTop = directions.includes("top");
983
- const hasRight = directions.includes("right");
984
- const hasBottom = directions.includes("bottom");
985
- const hasLeft = directions.includes("left");
986
- if (hasTop && hasBottom || hasLeft && hasRight) throw new SyntaxError("Linear gradient keyword direction contains conflicting tokens");
987
- if (hasTop && hasLeft) return { angle: degToRad(315) };
988
- else if (hasTop && hasRight) return { angle: degToRad(45) };
989
- else if (hasBottom && hasLeft) return { angle: degToRad(225) };
990
- else if (hasBottom && hasRight) return { angle: degToRad(135) };
991
- else if (hasTop) return { angle: degToRad(0) };
992
- else if (hasRight) return { angle: degToRad(90) };
993
- else if (hasBottom) return { angle: degToRad(180) };
994
- else if (hasLeft) return { angle: degToRad(270) };
995
- throw new SyntaxError(`Unsupported linear gradient keyword direction: "${value}"`);
1014
+ const tokenizedValue = LinearGradient._tokenizeConfigInput(value);
1015
+ const seen = /* @__PURE__ */ new Set();
1016
+ for (const token of tokenizedValue) {
1017
+ if (seen.has(token.type)) throw new SyntaxError(`Duplicate linear gradient config token: "${token.type}"`);
1018
+ seen.add(token.type);
996
1019
  }
997
- switch (value.unit) {
998
- case "deg": return { angle: normalizeAngleRad(degToRad(value.value)) };
999
- case "rad": return { angle: normalizeAngleRad(value.value) };
1000
- case "turn": return { angle: normalizeAngleRad(turnToRad(value.value)) };
1001
- case "grad": return { angle: normalizeAngleRad(gradToRad(value.value)) };
1002
- default: throw new SyntaxError(`Unsupported angle unit: "${value.unit}"`);
1020
+ const angleToken = tokenizedValue.find((token) => token.type === "angle");
1021
+ const colorSpaceToken = tokenizedValue.find((token) => token.type === "colorSpace");
1022
+ const hueToken = tokenizedValue.find((token) => token.type === "hue");
1023
+ const assembledConfig = {
1024
+ angle: 3.141593,
1025
+ interpolation: {
1026
+ hue: "shorter",
1027
+ colorSpace: "oklab"
1028
+ }
1029
+ };
1030
+ if (angleToken) {
1031
+ const value = angleToken.value;
1032
+ if (value.startsWith("to ")) {
1033
+ const tokens = value.trim().toLowerCase().split(/\s+/).filter(Boolean);
1034
+ if (tokens.length === 0) throw new SyntaxError("Linear gradient angle keyword cannot be empty");
1035
+ if (tokens[0] !== "to") throw new SyntaxError("Linear gradient keyword direction must start with \"to\"");
1036
+ const directions = tokens.slice(1);
1037
+ if (directions.length === 0 || directions.length > 2) throw new SyntaxError("Linear gradient keyword direction must contain one or two direction tokens");
1038
+ const allowed = new Set([
1039
+ "top",
1040
+ "right",
1041
+ "bottom",
1042
+ "left"
1043
+ ]);
1044
+ for (const direction of directions) if (!allowed.has(direction)) throw new SyntaxError(`Invalid linear gradient direction token: "${direction}"`);
1045
+ if (new Set(directions).size !== directions.length) throw new SyntaxError("Linear gradient keyword direction cannot contain duplicate tokens");
1046
+ const hasTop = directions.includes("top");
1047
+ const hasRight = directions.includes("right");
1048
+ const hasBottom = directions.includes("bottom");
1049
+ const hasLeft = directions.includes("left");
1050
+ if (hasTop && hasBottom || hasLeft && hasRight) throw new SyntaxError("Linear gradient keyword direction contains conflicting tokens");
1051
+ if (hasTop && hasLeft) assembledConfig.angle = degToRad(315);
1052
+ else if (hasTop && hasRight) assembledConfig.angle = degToRad(45);
1053
+ else if (hasBottom && hasLeft) assembledConfig.angle = degToRad(225);
1054
+ else if (hasBottom && hasRight) assembledConfig.angle = degToRad(135);
1055
+ else if (hasTop) assembledConfig.angle = degToRad(0);
1056
+ else if (hasRight) assembledConfig.angle = degToRad(90);
1057
+ else if (hasBottom) assembledConfig.angle = degToRad(180);
1058
+ else if (hasLeft) assembledConfig.angle = degToRad(270);
1059
+ else throw new SyntaxError(`Unsupported linear gradient keyword direction: "${value}"`);
1060
+ } else assembledConfig.angle = normalizeAngleRad(angleValueFromString(value));
1003
1061
  }
1062
+ if (colorSpaceToken) assembledConfig.interpolation.colorSpace = colorSpaceToken.value;
1063
+ if (hueToken) assembledConfig.interpolation.hue = hueToken.value;
1064
+ return assembledConfig;
1004
1065
  }
1005
1066
  static fromString(input) {
1006
1067
  return LinearGradient.fromAbi(parseStringToAbi(input));
1007
1068
  }
1008
1069
  static fromAbi(abi) {
1009
- let config = { angle: 0 };
1070
+ let config = { angle: 3.141592 };
1010
1071
  if (abi.inputs[0].type === "config") {
1011
1072
  const inputValue = abi.inputs[0].value.trim().toLowerCase();
1012
1073
  if (inputValue.length === 0) throw new SyntaxError("Linear gradient config cannot be empty");
1013
- if (inputValue.startsWith("to ")) config = LinearGradient.normalizeConfig(inputValue);
1014
- else {
1015
- const match = inputValue.match(/^([+-]?(?:\d+\.?\d*|\.\d+))(deg|rad|turn|grad)$/i);
1016
- if (match === null) throw new SyntaxError(`Invalid linear gradient angle: "${inputValue}"`);
1017
- const rawValue = Number(match[1]);
1018
- const unit = match[2].toLowerCase();
1019
- if (!Number.isFinite(rawValue)) throw new SyntaxError(`Invalid linear gradient angle value: "${inputValue}"`);
1020
- config = LinearGradient.normalizeConfig({
1021
- value: rawValue,
1022
- unit
1023
- });
1024
- }
1074
+ config = LinearGradient.normalizeConfig(inputValue);
1025
1075
  }
1026
1076
  const inputsWithoutConfig = abi.inputs[0]?.type === "config" ? abi.inputs.slice(1) : abi.inputs;
1027
1077
  const stops = LinearGradient._normalizeAbiInputsToStops(inputsWithoutConfig);
@@ -1035,9 +1085,96 @@ var LinearGradient = class LinearGradient extends GradientBase {
1035
1085
  return new LinearGradient(this.toJSON());
1036
1086
  }
1037
1087
  toString() {
1038
- return `${this.isRepeating ? `repeating-${this.type}` : this.type}(${[this.config.angle === 0 ? "" : `${roundTo(radToDeg(this.config.angle), 3)}deg`, ...this._serializeStopsCompact()].filter(Boolean).join(", ")})`;
1088
+ return `${this.isRepeating ? `repeating-${this.type}` : this.type}(${[this._parseConfigToString(this.config), ...this._serializeStopsCompact()].filter(Boolean).join(", ")})`;
1039
1089
  }
1040
1090
  _validateConfig(_) {}
1091
+ static _normalizeConfigInterpolation(value) {
1092
+ const { colorSpace, hue } = value;
1093
+ if (hue === void 0) return { colorSpace };
1094
+ if (!isGradientPolarColorSpace(colorSpace)) return { colorSpace };
1095
+ return {
1096
+ colorSpace,
1097
+ hue
1098
+ };
1099
+ }
1100
+ _parseConfigToString(config) {
1101
+ const { angle, interpolation } = config;
1102
+ const configParts = [];
1103
+ const angleString = this._parseAngleToString(angle);
1104
+ if (angleString.length > 0) configParts.push(angleString);
1105
+ if (interpolation !== void 0) configParts.push(this._parseInterpolationToString(interpolation));
1106
+ return configParts.join(" ");
1107
+ }
1108
+ _parseAngleToString(angle) {
1109
+ const angleDeg = normalizeAngleDeg(radToDeg(angle), 3);
1110
+ switch (angleDeg) {
1111
+ case 0: return "to top";
1112
+ case 45: return "to top right";
1113
+ case 90: return "to right";
1114
+ case 135: return "to bottom right";
1115
+ case 180: return "";
1116
+ case 225: return "to bottom left";
1117
+ case 270: return "to left";
1118
+ case 315: return "to top left";
1119
+ default: return `${angleDeg}deg`;
1120
+ }
1121
+ }
1122
+ _parseInterpolationToString(interpolation) {
1123
+ const { colorSpace, hue } = interpolation;
1124
+ if (hue === void 0) return `in ${colorSpace}`;
1125
+ return `in ${colorSpace} ${hue} hue`;
1126
+ }
1127
+ static _tokenizeConfigInput(value) {
1128
+ const inputValue = value.trim().toLowerCase();
1129
+ if (inputValue.length === 0) throw new SyntaxError("Linear gradient config cannot be empty");
1130
+ const parts = inputValue.split(/\s+/);
1131
+ const tokens = [];
1132
+ for (let index = 0; index < parts.length; index += 1) {
1133
+ const part = parts[index];
1134
+ if (part === "in") {
1135
+ const colorSpace = parts[index + 1];
1136
+ if (colorSpace === void 0 || !isGradientColorSpace(colorSpace)) throw new SyntaxError(`Expected color space after "in"`);
1137
+ tokens.push({
1138
+ type: "colorSpace",
1139
+ value: colorSpace
1140
+ });
1141
+ index += 1;
1142
+ continue;
1143
+ }
1144
+ if (isAngle(part)) {
1145
+ tokens.push({
1146
+ type: "angle",
1147
+ value: part
1148
+ });
1149
+ continue;
1150
+ }
1151
+ if (part === "to") {
1152
+ const directionParts = [];
1153
+ const firstDirection = parts[index + 1];
1154
+ const secondDirection = parts[index + 2];
1155
+ if (firstDirection !== void 0) directionParts.push(firstDirection);
1156
+ if (secondDirection === "left" || secondDirection === "right") directionParts.push(secondDirection);
1157
+ const direction = `to ${directionParts.join(" ")}`;
1158
+ tokens.push({
1159
+ type: "angle",
1160
+ value: direction
1161
+ });
1162
+ index += directionParts.length;
1163
+ continue;
1164
+ }
1165
+ if (isGradientHueInterpolation(part)) {
1166
+ if (parts[index + 1] !== "hue") throw new SyntaxError(`Expected "hue" after "${part}"`);
1167
+ tokens.push({
1168
+ type: "hue",
1169
+ value: part
1170
+ });
1171
+ index += 1;
1172
+ continue;
1173
+ }
1174
+ throw new SyntaxError(`Unknown linear gradient config token: "${part}"`);
1175
+ }
1176
+ return tokens;
1177
+ }
1041
1178
  };
1042
1179
  //#endregion
1043
1180
  //#region src/gradients/RadialGradient.ts
@@ -1332,14 +1469,163 @@ var ModuleTransformerConicGradientToCss = class {
1332
1469
  }
1333
1470
  };
1334
1471
  //#endregion
1472
+ //#region src/gradient-transformer/modules/helpers/expand-repeating-stops.ts
1473
+ function positiveModulo(value, modulo) {
1474
+ return (value % modulo + modulo) % modulo;
1475
+ }
1476
+ function sampleColorAtPosition(stops, position) {
1477
+ const colorStops = stops.filter((stop) => stop.type === "color-stop" && stop.position != null).sort((a, b) => a.position - b.position);
1478
+ if (colorStops.length === 0) throw new Error("Cannot sample color from empty stops.");
1479
+ if (position <= colorStops[0].position) return colorStops[0].value;
1480
+ const lastStop = colorStops[colorStops.length - 1];
1481
+ if (position >= lastStop.position) return lastStop.value;
1482
+ for (let index = 0; index < colorStops.length - 1; index += 1) {
1483
+ const current = colorStops[index];
1484
+ const next = colorStops[index + 1];
1485
+ if (position >= current.position && position <= next.position) {
1486
+ const range = next.position - current.position || 1;
1487
+ const localT = (position - current.position) / range;
1488
+ return formatRgb(interpolate([current.value, next.value], "rgb")(localT));
1489
+ }
1490
+ }
1491
+ return lastStop.value;
1492
+ }
1493
+ function sampleRepeatingColorAtPosition(stops, position, firstPosition, period) {
1494
+ return sampleColorAtPosition(stops, firstPosition + positiveModulo(position - firstPosition, period));
1495
+ }
1496
+ function expandRepeatingStops(stops) {
1497
+ const colorStops = stops.filter((stop) => stop.type === "color-stop" && stop.position != null).sort((a, b) => a.position - b.position);
1498
+ if (colorStops.length < 2) return colorStops;
1499
+ const firstPosition = colorStops[0].position;
1500
+ const period = colorStops[colorStops.length - 1].position - firstPosition;
1501
+ if (period <= 0) return colorStops;
1502
+ const result = [];
1503
+ let order = 0;
1504
+ result.push({
1505
+ type: "color-stop",
1506
+ value: sampleRepeatingColorAtPosition(colorStops, 0, firstPosition, period),
1507
+ position: 0,
1508
+ _order: order
1509
+ });
1510
+ order += 1;
1511
+ const startRepeat = Math.floor((0 - firstPosition) / period) - 1;
1512
+ const endRepeat = Math.ceil((1 - firstPosition) / period) + 1;
1513
+ for (let repeatIndex = startRepeat; repeatIndex <= endRepeat; repeatIndex += 1) {
1514
+ const offset = repeatIndex * period;
1515
+ for (const stop of colorStops) {
1516
+ const position = stop.position + offset;
1517
+ if (position <= 0 || position >= 1) continue;
1518
+ result.push({
1519
+ ...stop,
1520
+ position,
1521
+ _order: order
1522
+ });
1523
+ order += 1;
1524
+ }
1525
+ }
1526
+ result.push({
1527
+ type: "color-stop",
1528
+ value: sampleRepeatingColorAtPosition(colorStops, 1, firstPosition, period),
1529
+ position: 1,
1530
+ _order: order
1531
+ });
1532
+ return result.sort((a, b) => {
1533
+ if (a.position === b.position) return a._order - b._order;
1534
+ return a.position - b.position;
1535
+ }).map(({ _order, ...stop }) => stop);
1536
+ }
1537
+ //#endregion
1538
+ //#region src/gradient-transformer/modules/helpers/resolve-renderable-linear-gradient-stops.ts
1539
+ function getHueFixup(hue) {
1540
+ switch (hue) {
1541
+ case "longer": return fixupHueLonger;
1542
+ case "increasing": return fixupHueIncreasing;
1543
+ case "decreasing": return fixupHueDecreasing;
1544
+ default: return fixupHueShorter;
1545
+ }
1546
+ }
1547
+ function colorSpaceToCuloriMode(colorSpace) {
1548
+ switch (colorSpace) {
1549
+ case "a98-rgb": return "a98";
1550
+ case "display-p3": return "p3";
1551
+ case "prophoto-rgb": return "prophoto";
1552
+ case "xyz": return "xyz65";
1553
+ case "srgb":
1554
+ case "srgb-linear": return "rgb";
1555
+ default: return colorSpace;
1556
+ }
1557
+ }
1558
+ function createCuloriInterpolationOverrides(interpolation) {
1559
+ if (interpolation.hue === void 0) return;
1560
+ return { h: { fixup: getHueFixup(interpolation.hue) } };
1561
+ }
1562
+ function getColorStopsWithPositions(stops) {
1563
+ const colorStops = stops.filter((stop) => stop.type === "color-stop");
1564
+ if (colorStops.length === 0) return [];
1565
+ if (colorStops.length === 1) return [{
1566
+ ...colorStops[0],
1567
+ position: colorStops[0].position ?? 0
1568
+ }];
1569
+ return colorStops.map((stop, index) => {
1570
+ if (stop.position != null) return stop;
1571
+ if (index === 0) return {
1572
+ ...stop,
1573
+ position: 0
1574
+ };
1575
+ if (index === colorStops.length - 1) return {
1576
+ ...stop,
1577
+ position: 1
1578
+ };
1579
+ return {
1580
+ ...stop,
1581
+ position: index / (colorStops.length - 1)
1582
+ };
1583
+ });
1584
+ }
1585
+ function formatColorForCanvas(input) {
1586
+ const color = toRgb$4(input);
1587
+ if (!color) throw new Error("Failed to convert interpolated color to rgb.");
1588
+ return formatRgb(color);
1589
+ }
1590
+ const DEFAULT_SAMPLE_COUNT = 64;
1591
+ const toRgb$4 = converter("rgb");
1592
+ function resolveRenderableLinearGradientStops(gradient, sampleCount = DEFAULT_SAMPLE_COUNT) {
1593
+ const colorStops = getColorStopsWithPositions(gradient.stops);
1594
+ const interpolation = gradient.config.interpolation;
1595
+ if (colorStops.length < 2) return colorStops;
1596
+ if (interpolation === void 0) return gradient.isRepeating ? expandRepeatingStops(colorStops) : colorStops;
1597
+ const sampledStops = [];
1598
+ const mode = colorSpaceToCuloriMode(interpolation.colorSpace);
1599
+ const overrides = createCuloriInterpolationOverrides(interpolation);
1600
+ for (let index = 0; index < colorStops.length - 1; index += 1) {
1601
+ const current = colorStops[index];
1602
+ const next = colorStops[index + 1];
1603
+ const startPosition = current.position;
1604
+ const endPosition = next.position;
1605
+ const colorInterpolator = interpolate([current.value, next.value], mode, overrides);
1606
+ for (let sampleIndex = 0; sampleIndex <= sampleCount; sampleIndex += 1) {
1607
+ if (index > 0 && sampleIndex === 0) continue;
1608
+ const localT = sampleIndex / sampleCount;
1609
+ const position = startPosition + (endPosition - startPosition) * localT;
1610
+ const color = colorInterpolator(localT);
1611
+ sampledStops.push({
1612
+ type: "color-stop",
1613
+ value: formatColorForCanvas(color),
1614
+ position
1615
+ });
1616
+ }
1617
+ }
1618
+ return gradient.isRepeating ? expandRepeatingStops(sampledStops) : sampledStops;
1619
+ }
1620
+ //#endregion
1335
1621
  //#region src/gradient-transformer/modules/canvas/ModuleTransformerLinearGradientToCanvas.ts
1336
- const toRgb$2 = converter("rgb");
1622
+ const toRgb$3 = converter("rgb");
1337
1623
  function toCanvasColor$1(input) {
1338
- const color = toRgb$2(input);
1624
+ const color = toRgb$3(input);
1339
1625
  if (!color) throw new Error(`Failed to convert color: ${input}`);
1340
1626
  return formatRgb(color);
1341
1627
  }
1342
- function getStopRange$1(stops) {
1628
+ function getStopRange$2(stops) {
1343
1629
  const colorStops = stops.filter((stop) => stop.type === "color-stop" && stop.position != null);
1344
1630
  if (!colorStops.length) return {
1345
1631
  min: 0,
@@ -1352,7 +1638,7 @@ function getStopRange$1(stops) {
1352
1638
  stops: colorStops
1353
1639
  };
1354
1640
  }
1355
- function normalizeStops$1(stops, min, max) {
1641
+ function normalizeStops$2(stops, min, max) {
1356
1642
  const range = max - min || 1;
1357
1643
  return stops.filter((stop) => stop.type === "color-stop" && stop.position != null).map((stop) => ({
1358
1644
  ...stop,
@@ -1360,38 +1646,37 @@ function normalizeStops$1(stops, min, max) {
1360
1646
  }));
1361
1647
  }
1362
1648
  var ModuleTransformerLinearGradientToCanvas = class {
1363
- target = "canvas";
1649
+ target = "canvas-2d";
1364
1650
  gradientType = "linear-gradient";
1365
1651
  to(input) {
1366
1652
  const gradient = input;
1367
1653
  return { draw: (ctx, width, height) => {
1368
1654
  const angle = gradient.config.angle;
1369
- const cx = width / 2;
1370
- const cy = height / 2;
1371
- const half = Math.max(width, height) / 2;
1372
- const dx = Math.sin(angle) * half;
1373
- const dy = Math.cos(angle) * half;
1374
- const x0 = cx - dx;
1375
- const y0 = cy - dy;
1376
- const x1 = cx + dx;
1377
- const y1 = cy + dy;
1378
- const { min, max, stops } = getStopRange$1(gradient.stops);
1379
- let startX = x0;
1380
- let startY = y0;
1381
- let endX = x1;
1382
- let endY = y1;
1655
+ const dirX = Math.sin(angle);
1656
+ const dirY = -Math.cos(angle);
1657
+ const centerX = width / 2;
1658
+ const centerY = height / 2;
1659
+ const lineLength = Math.abs(width * dirX) + Math.abs(height * dirY);
1660
+ let startX = centerX - dirX * lineLength / 2;
1661
+ let startY = centerY - dirY * lineLength / 2;
1662
+ let endX = centerX + dirX * lineLength / 2;
1663
+ let endY = centerY + dirY * lineLength / 2;
1664
+ const { min, max, stops } = getStopRange$2(resolveRenderableLinearGradientStops(gradient));
1383
1665
  let normalizedStops = stops;
1384
1666
  if (min < 0 || max > 1) {
1385
- const vx = x1 - x0;
1386
- const vy = y1 - y0;
1387
- startX = x0 + vx * min;
1388
- startY = y0 + vy * min;
1389
- endX = x0 + vx * max;
1390
- endY = y0 + vy * max;
1391
- normalizedStops = normalizeStops$1(stops, min, max);
1667
+ const vx = endX - startX;
1668
+ const vy = endY - startY;
1669
+ const baseStartX = startX;
1670
+ const baseStartY = startY;
1671
+ startX = baseStartX + vx * min;
1672
+ startY = baseStartY + vy * min;
1673
+ endX = baseStartX + vx * max;
1674
+ endY = baseStartY + vy * max;
1675
+ normalizedStops = normalizeStops$2(stops, min, max);
1392
1676
  }
1393
1677
  const canvasGradient = ctx.createLinearGradient(startX, startY, endX, endY);
1394
1678
  for (const stop of normalizedStops) canvasGradient.addColorStop(stop.position, toCanvasColor$1(stop.value));
1679
+ ctx.clearRect(0, 0, width, height);
1395
1680
  ctx.fillStyle = canvasGradient;
1396
1681
  ctx.fillRect(0, 0, width, height);
1397
1682
  } };
@@ -1399,13 +1684,13 @@ var ModuleTransformerLinearGradientToCanvas = class {
1399
1684
  };
1400
1685
  //#endregion
1401
1686
  //#region src/gradient-transformer/modules/canvas/ModuleTransformerRadialGradientToCanvas.ts
1402
- const toRgb$1 = converter("rgb");
1687
+ const toRgb$2 = converter("rgb");
1403
1688
  function toCanvasColor(input) {
1404
- const color = toRgb$1(input);
1689
+ const color = toRgb$2(input);
1405
1690
  if (!color) throw new Error(`Failed to convert color: ${input}`);
1406
1691
  return formatRgb(color);
1407
1692
  }
1408
- function getStopRange(stops) {
1693
+ function getStopRange$1(stops) {
1409
1694
  const colorStops = stops.filter((stop) => stop.type === "color-stop" && stop.position != null);
1410
1695
  if (!colorStops.length) return {
1411
1696
  min: 0,
@@ -1418,7 +1703,7 @@ function getStopRange(stops) {
1418
1703
  stops: colorStops
1419
1704
  };
1420
1705
  }
1421
- function normalizeStops(stops, min, max) {
1706
+ function normalizeStops$1(stops, min, max) {
1422
1707
  const range = max - min || 1;
1423
1708
  return stops.map((stop) => ({
1424
1709
  ...stop,
@@ -1426,7 +1711,7 @@ function normalizeStops(stops, min, max) {
1426
1711
  }));
1427
1712
  }
1428
1713
  var ModuleTransformerRadialGradientToCanvas = class {
1429
- target = "canvas";
1714
+ target = "canvas-2d";
1430
1715
  gradientType = "radial-gradient";
1431
1716
  to(input) {
1432
1717
  const gradient = input;
@@ -1441,17 +1726,17 @@ var ModuleTransformerRadialGradientToCanvas = class {
1441
1726
  const dx = Math.max(x, width - x);
1442
1727
  const dy = Math.max(y, height - y);
1443
1728
  const radius = Math.sqrt(dx * dx + dy * dy);
1444
- const { min, max, stops } = getStopRange(gradient.stops);
1729
+ const { min, max, stops } = getStopRange$1(gradient.stops);
1445
1730
  let innerRadius = 0;
1446
1731
  let outerRadius = radius;
1447
1732
  let normalizedStops = stops;
1448
1733
  if (min >= 0 && (min < 0 || max > 1)) {
1449
1734
  innerRadius = radius * min;
1450
1735
  outerRadius = radius * max;
1451
- normalizedStops = normalizeStops(stops, min, max);
1736
+ normalizedStops = normalizeStops$1(stops, min, max);
1452
1737
  } else if (max > 1) {
1453
1738
  outerRadius = radius * max;
1454
- normalizedStops = normalizeStops(stops, min, max);
1739
+ normalizedStops = normalizeStops$1(stops, min, max);
1455
1740
  }
1456
1741
  const g = ctx.createRadialGradient(x, y, innerRadius, x, y, outerRadius);
1457
1742
  for (const stop of normalizedStops) g.addColorStop(stop.position, toCanvasColor(stop.value));
@@ -1467,9 +1752,9 @@ var ModuleTransformerRadialGradientToCanvas = class {
1467
1752
  };
1468
1753
  //#endregion
1469
1754
  //#region src/gradient-transformer/modules/canvas/ModuleTransformerConicGradientToCanvas.ts
1470
- const toRgb = converter("rgb");
1755
+ const toRgb$1 = converter("rgb");
1471
1756
  var ModuleTransformerConicGradientToCanvas = class {
1472
- target = "canvas";
1757
+ target = "canvas-2d";
1473
1758
  gradientType = "conic-gradient";
1474
1759
  to(input) {
1475
1760
  const gradient = input;
@@ -1566,7 +1851,7 @@ var ModuleTransformerConicGradientToCanvas = class {
1566
1851
  };
1567
1852
  }
1568
1853
  _parseColor(input) {
1569
- const color = toRgb(input);
1854
+ const color = toRgb$1(input);
1570
1855
  if (!color) throw new Error(`Failed to convert color: ${input}`);
1571
1856
  return {
1572
1857
  r: Math.round((color.r ?? 0) * 255),
@@ -1580,6 +1865,243 @@ var ModuleTransformerConicGradientToCanvas = class {
1580
1865
  }
1581
1866
  };
1582
1867
  //#endregion
1868
+ //#region src/gradient-transformer/modules/webgl/ModuleTransformerLinearGradientToWebgl.ts
1869
+ const toRgb = converter("rgb");
1870
+ const MAX_STOPS = 128;
1871
+ function toWebGLColor(input) {
1872
+ const color = toRgb(input);
1873
+ if (!color) throw new Error(`Failed to convert color: ${input}`);
1874
+ return [
1875
+ color.r ?? 0,
1876
+ color.g ?? 0,
1877
+ color.b ?? 0,
1878
+ color.alpha ?? 1
1879
+ ];
1880
+ }
1881
+ function getStopRange(stops) {
1882
+ const colorStops = stops.filter((stop) => stop.type === "color-stop" && stop.position != null);
1883
+ if (!colorStops.length) return {
1884
+ min: 0,
1885
+ max: 1,
1886
+ stops: []
1887
+ };
1888
+ return {
1889
+ min: Math.min(...colorStops.map((stop) => stop.position)),
1890
+ max: Math.max(...colorStops.map((stop) => stop.position)),
1891
+ stops: colorStops
1892
+ };
1893
+ }
1894
+ function normalizeStops(stops, min, max) {
1895
+ const range = max - min || 1;
1896
+ return stops.filter((stop) => stop.type === "color-stop" && stop.position != null).map((stop) => ({
1897
+ ...stop,
1898
+ position: (stop.position - min) / range
1899
+ }));
1900
+ }
1901
+ function createShader(gl, type, source) {
1902
+ const shader = gl.createShader(type);
1903
+ if (!shader) throw new Error("Failed to create WebGL shader.");
1904
+ gl.shaderSource(shader, source);
1905
+ gl.compileShader(shader);
1906
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
1907
+ const error = gl.getShaderInfoLog(shader);
1908
+ gl.deleteShader(shader);
1909
+ throw new Error(`WebGL shader compile error: ${error}`);
1910
+ }
1911
+ return shader;
1912
+ }
1913
+ function createProgram(gl, vertexSource, fragmentSource) {
1914
+ const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexSource);
1915
+ const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource);
1916
+ const program = gl.createProgram();
1917
+ if (!program) throw new Error("Failed to create WebGL program.");
1918
+ gl.attachShader(program, vertexShader);
1919
+ gl.attachShader(program, fragmentShader);
1920
+ gl.linkProgram(program);
1921
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
1922
+ const error = gl.getProgramInfoLog(program);
1923
+ gl.deleteProgram(program);
1924
+ throw new Error(`WebGL program link error: ${error}`);
1925
+ }
1926
+ return program;
1927
+ }
1928
+ function getColorStopCount(stops) {
1929
+ return stops.filter((stop) => stop.type === "color-stop").length;
1930
+ }
1931
+ function getWebGLSampleCount(gradient, maxStops) {
1932
+ const colorStopCount = getColorStopCount(gradient.stops);
1933
+ const segmentCount = Math.max(1, colorStopCount - 1);
1934
+ return Math.max(2, Math.floor((maxStops - 1) / segmentCount));
1935
+ }
1936
+ function getColorAtPosition(stops, position) {
1937
+ const colorStops = stops.filter((stop) => stop.type === "color-stop" && stop.position != null).sort((a, b) => a.position - b.position);
1938
+ if (colorStops.length === 0) throw new Error("Cannot sample color from empty gradient stops.");
1939
+ if (position <= colorStops[0].position) return colorStops[0].value;
1940
+ const lastStop = colorStops[colorStops.length - 1];
1941
+ if (position >= lastStop.position) return lastStop.value;
1942
+ for (let index = 0; index < colorStops.length - 1; index += 1) {
1943
+ const current = colorStops[index];
1944
+ const next = colorStops[index + 1];
1945
+ if (position >= current.position && position <= next.position) {
1946
+ const range = next.position - current.position || 1;
1947
+ const localT = (position - current.position) / range;
1948
+ return formatRgb(interpolate([current.value, next.value], "rgb")(localT));
1949
+ }
1950
+ }
1951
+ return lastStop.value;
1952
+ }
1953
+ function fitStopsToWebGLLimit(stops, maxStops) {
1954
+ const colorStops = stops.filter((stop) => stop.type === "color-stop" && stop.position != null).sort((a, b) => a.position - b.position);
1955
+ if (colorStops.length <= maxStops) return colorStops;
1956
+ const sampledStops = [];
1957
+ for (let index = 0; index < maxStops; index += 1) {
1958
+ const position = index / (maxStops - 1);
1959
+ sampledStops.push({
1960
+ type: "color-stop",
1961
+ value: getColorAtPosition(colorStops, position),
1962
+ position
1963
+ });
1964
+ }
1965
+ return sampledStops;
1966
+ }
1967
+ var ModuleTransformerLinearGradientToCanvasWebGL = class {
1968
+ target = "canvas-webgl";
1969
+ gradientType = "linear-gradient";
1970
+ to(input) {
1971
+ const gradient = input;
1972
+ return { draw: (canvas, width, height) => {
1973
+ const gl = canvas.getContext("webgl");
1974
+ if (!gl) throw new Error("WebGL is not supported.");
1975
+ canvas.width = width;
1976
+ canvas.height = height;
1977
+ gl.viewport(0, 0, width, height);
1978
+ const program = createProgram(gl, `
1979
+ attribute vec2 a_position;
1980
+ varying vec2 v_uv;
1981
+
1982
+ void main() {
1983
+ v_uv = a_position * 0.5 + 0.5;
1984
+ gl_Position = vec4(a_position, 0.0, 1.0);
1985
+ }
1986
+ `, `
1987
+ precision mediump float;
1988
+
1989
+ varying vec2 v_uv;
1990
+
1991
+ uniform vec2 u_start;
1992
+ uniform vec2 u_end;
1993
+ uniform int u_stopCount;
1994
+ uniform float u_positions[${MAX_STOPS}];
1995
+ uniform vec4 u_colors[${MAX_STOPS}];
1996
+
1997
+ vec4 getGradientColor(float t) {
1998
+ vec4 result = u_colors[0];
1999
+
2000
+ for (int i = 0; i < ${MAX_STOPS - 1}; i++) {
2001
+ if (i >= u_stopCount - 1) {
2002
+ break;
2003
+ }
2004
+
2005
+ float currentPosition = u_positions[i];
2006
+ float nextPosition = u_positions[i + 1];
2007
+
2008
+ if (t <= currentPosition) {
2009
+ return u_colors[i];
2010
+ }
2011
+
2012
+ if (t >= currentPosition && t <= nextPosition) {
2013
+ float localT = (t - currentPosition) / max(nextPosition - currentPosition, 0.00001);
2014
+ return mix(u_colors[i], u_colors[i + 1], localT);
2015
+ }
2016
+
2017
+ result = u_colors[i + 1];
2018
+ }
2019
+
2020
+ return result;
2021
+ }
2022
+
2023
+ void main() {
2024
+ vec2 axis = u_end - u_start;
2025
+ vec2 point = v_uv;
2026
+
2027
+ float axisLengthSquared = dot(axis, axis);
2028
+ float t = dot(point - u_start, axis) / axisLengthSquared;
2029
+
2030
+ t = clamp(t, 0.0, 1.0);
2031
+
2032
+ gl_FragColor = getGradientColor(t);
2033
+ }
2034
+ `);
2035
+ gl.useProgram(program);
2036
+ const positionBuffer = gl.createBuffer();
2037
+ gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
2038
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
2039
+ -1,
2040
+ -1,
2041
+ 1,
2042
+ -1,
2043
+ -1,
2044
+ 1,
2045
+ -1,
2046
+ 1,
2047
+ 1,
2048
+ -1,
2049
+ 1,
2050
+ 1
2051
+ ]), gl.STATIC_DRAW);
2052
+ const positionLocation = gl.getAttribLocation(program, "a_position");
2053
+ gl.enableVertexAttribArray(positionLocation);
2054
+ gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
2055
+ const angle = gradient.config.angle;
2056
+ const dirX = Math.sin(angle);
2057
+ const dirY = -Math.cos(angle);
2058
+ const centerX = width / 2;
2059
+ const centerY = height / 2;
2060
+ const lineLength = Math.abs(width * dirX) + Math.abs(height * dirY);
2061
+ let startX = centerX - dirX * lineLength / 2;
2062
+ let startY = centerY - dirY * lineLength / 2;
2063
+ let endX = centerX + dirX * lineLength / 2;
2064
+ let endY = centerY + dirY * lineLength / 2;
2065
+ const { min, max, stops } = getStopRange(resolveRenderableLinearGradientStops(gradient, getWebGLSampleCount(gradient, MAX_STOPS)));
2066
+ let normalizedStops = stops;
2067
+ if (min < 0 || max > 1) {
2068
+ const vx = endX - startX;
2069
+ const vy = endY - startY;
2070
+ const baseStartX = startX;
2071
+ const baseStartY = startY;
2072
+ startX = baseStartX + vx * min;
2073
+ startY = baseStartY + vy * min;
2074
+ endX = baseStartX + vx * max;
2075
+ endY = baseStartY + vy * max;
2076
+ normalizedStops = normalizeStops(stops, min, max);
2077
+ }
2078
+ const startU = startX / width;
2079
+ const startV = 1 - startY / height;
2080
+ const endU = endX / width;
2081
+ const endV = 1 - endY / height;
2082
+ const limitedStops = fitStopsToWebGLLimit(normalizedStops, MAX_STOPS);
2083
+ const positions = new Float32Array(MAX_STOPS);
2084
+ const colors = new Float32Array(MAX_STOPS * 4);
2085
+ limitedStops.forEach((stop, index) => {
2086
+ const color = toWebGLColor(stop.value);
2087
+ positions[index] = stop.position;
2088
+ colors[index * 4 + 0] = color[0];
2089
+ colors[index * 4 + 1] = color[1];
2090
+ colors[index * 4 + 2] = color[2];
2091
+ colors[index * 4 + 3] = color[3];
2092
+ });
2093
+ gl.uniform2f(gl.getUniformLocation(program, "u_start"), startU, startV);
2094
+ gl.uniform2f(gl.getUniformLocation(program, "u_end"), endU, endV);
2095
+ gl.uniform1i(gl.getUniformLocation(program, "u_stopCount"), limitedStops.length);
2096
+ gl.uniform1fv(gl.getUniformLocation(program, "u_positions"), positions);
2097
+ gl.uniform4fv(gl.getUniformLocation(program, "u_colors"), colors);
2098
+ gl.clearColor(0, 0, 0, 0);
2099
+ gl.clear(gl.COLOR_BUFFER_BIT);
2100
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
2101
+ } };
2102
+ }
2103
+ };
2104
+ //#endregion
1583
2105
  //#region src/gradient-transformer/GradientTransformer.ts
1584
2106
  var GradientTransformer = class {
1585
2107
  static _modules = /* @__PURE__ */ new Map();
@@ -1616,6 +2138,7 @@ var GradientTransformer = class {
1616
2138
  this.add(new ModuleTransformerLinearGradientToCanvas());
1617
2139
  this.add(new ModuleTransformerRadialGradientToCanvas());
1618
2140
  this.add(new ModuleTransformerConicGradientToCanvas());
2141
+ this.add(new ModuleTransformerLinearGradientToCanvasWebGL());
1619
2142
  }
1620
2143
  static _getKey(target, gradientType) {
1621
2144
  return `${target}:${gradientType}`;
@@ -1677,4 +2200,4 @@ function transformFrom(target, gradientType, input) {
1677
2200
  return GradientTransformer.from(target, gradientType, input);
1678
2201
  }
1679
2202
  //#endregion
1680
- export { ConicGradient, GradientBase, GradientFactory, GradientTransformer, LinearGradient, ModuleTransformerConicGradientToCanvas, ModuleTransformerConicGradientToCss, ModuleTransformerLinearGradientToCanvas, ModuleTransformerLinearGradientToCss, ModuleTransformerRadialGradientToCanvas, ModuleTransformerRadialGradientToCss, PatternTokenKind, RadialGradient, ceilTo, clamp, degToRad, floorTo, format, fromPercent, gradToRad, isAngleUnit, isColorHint, isColorStop, isConfig, isGradient, isPatternSyntaxValid, isPatternValid, isValid, matchExpression, normalizeAngle, normalizeAngleDeg, normalizeAngleRad, parse, parseStringToAbi, radToDeg, roundTo, splitTopLevelByWhitespace, toAngleRad, toPercent, tokenizePattern, transformFrom, transformTo, truncTo, turnToRad, validate, validatePattern, validatePatternSemantic, validatePatternStructure, validatePatternSyntax };
2203
+ export { ConicGradient, GRADIENT_COLOR_SPACE, GRADIENT_HUE_INTERPOLATIONS, GRADIENT_POLAR_COLOR_SPACES, GradientBase, GradientFactory, GradientTransformer, LinearGradient, ModuleTransformerConicGradientToCanvas, ModuleTransformerConicGradientToCss, ModuleTransformerLinearGradientToCanvas, ModuleTransformerLinearGradientToCanvasWebGL, ModuleTransformerLinearGradientToCss, ModuleTransformerRadialGradientToCanvas, ModuleTransformerRadialGradientToCss, PatternTokenKind, RadialGradient, angleValueFromString, ceilTo, clamp, degToRad, floorTo, format, fromPercent, gradToRad, isAngle, isAngleUnit, isColorHint, isColorStop, isConfig, isGradient, isGradientColorSpace, isGradientHueInterpolation, isGradientPolarColorSpace, isPatternSyntaxValid, isPatternValid, isValid, matchExpression, normalizeAngleDeg, normalizeAngleRad, parse, parseStringToAbi, radToDeg, roundTo, splitTopLevelByWhitespace, toPercent, tokenizePattern, transformFrom, transformTo, truncTo, turnToRad, validate, validatePattern, validatePatternSemantic, validatePatternStructure, validatePatternSyntax };