gradiente 2.2.0 → 2.4.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/gradiente.global.iife.js +136 -6
- package/dist/index.d.ts +182 -17
- package/dist/index.js +2345 -255
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1430,6 +1430,31 @@ var RadialGradient = class RadialGradient extends GradientBase {
|
|
|
1430
1430
|
}
|
|
1431
1431
|
};
|
|
1432
1432
|
//#endregion
|
|
1433
|
+
//#region src/gradients/DiamondGradient.ts
|
|
1434
|
+
var DiamondGradient = class DiamondGradient extends RadialGradient {
|
|
1435
|
+
type = "diamond-gradient";
|
|
1436
|
+
constructor(input) {
|
|
1437
|
+
super(input);
|
|
1438
|
+
}
|
|
1439
|
+
static fromString(input) {
|
|
1440
|
+
return DiamondGradient.fromAbi(parseStringToAbi(input));
|
|
1441
|
+
}
|
|
1442
|
+
static fromAbi(abi) {
|
|
1443
|
+
if (abi.functionName !== "diamond-gradient") throw new Error("Invalid function name for DiamondGradient");
|
|
1444
|
+
const config = this._parseConfig(abi.inputs);
|
|
1445
|
+
const inputsWithoutConfig = abi.inputs[0]?.type === "config" ? abi.inputs.slice(1) : abi.inputs;
|
|
1446
|
+
const stops = this._normalizeAbiInputsToStops(inputsWithoutConfig);
|
|
1447
|
+
return new DiamondGradient({
|
|
1448
|
+
isRepeating: abi.isRepeating,
|
|
1449
|
+
config,
|
|
1450
|
+
stops
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
clone() {
|
|
1454
|
+
return new DiamondGradient(this.toJSON());
|
|
1455
|
+
}
|
|
1456
|
+
};
|
|
1457
|
+
//#endregion
|
|
1433
1458
|
//#region src/gradients/ConicGradient.ts
|
|
1434
1459
|
var ConicGradient = class ConicGradient extends GradientBase {
|
|
1435
1460
|
type = "conic-gradient";
|
|
@@ -1460,18 +1485,12 @@ var ConicGradient = class ConicGradient extends GradientBase {
|
|
|
1460
1485
|
_validateConfig(config) {}
|
|
1461
1486
|
_serializeConfig() {
|
|
1462
1487
|
const parts = [];
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
if (this.config.interpolation) {
|
|
1467
|
-
const i = this.config.interpolation;
|
|
1468
|
-
if (i.kind === "rectangular") parts.push(`in ${i.space}`);
|
|
1469
|
-
else {
|
|
1470
|
-
let str = `in ${i.space}`;
|
|
1471
|
-
if (i.hueMethod) str += ` ${i.hueMethod} hue`;
|
|
1472
|
-
parts.push(str);
|
|
1473
|
-
}
|
|
1488
|
+
if (!this._isDefaultFrom(this.config.from)) {
|
|
1489
|
+
const angle = this.config.from;
|
|
1490
|
+
parts.push(`from ${angle.value}${angle.unit}`);
|
|
1474
1491
|
}
|
|
1492
|
+
if (!this._isDefaultPosition(this.config.position)) parts.push(`at ${this._serializePosition(this.config.position)}`);
|
|
1493
|
+
if (this.config.interpolation !== void 0) parts.push(this._serializeInterpolation(this.config.interpolation));
|
|
1475
1494
|
return parts.join(" ");
|
|
1476
1495
|
}
|
|
1477
1496
|
_serializePosition(position) {
|
|
@@ -1484,6 +1503,17 @@ var ConicGradient = class ConicGradient extends GradientBase {
|
|
|
1484
1503
|
if (value.kind === "percent") return `${value.value}%`;
|
|
1485
1504
|
return `${value.value}${value.unit}`;
|
|
1486
1505
|
}
|
|
1506
|
+
_serializeInterpolation(interpolation) {
|
|
1507
|
+
const { colorSpace, hue } = interpolation;
|
|
1508
|
+
if (hue === void 0) return `in ${colorSpace}`;
|
|
1509
|
+
return `in ${colorSpace} ${hue} hue`;
|
|
1510
|
+
}
|
|
1511
|
+
_isDefaultFrom(from) {
|
|
1512
|
+
return from.value === 0 && from.unit === "deg";
|
|
1513
|
+
}
|
|
1514
|
+
_isDefaultPosition(position) {
|
|
1515
|
+
return position.kind === "keywords" && position.x === "center" && position.y === "center";
|
|
1516
|
+
}
|
|
1487
1517
|
static _parseConfig(input) {
|
|
1488
1518
|
const config = {
|
|
1489
1519
|
from: {
|
|
@@ -1499,46 +1529,86 @@ var ConicGradient = class ConicGradient extends GradientBase {
|
|
|
1499
1529
|
};
|
|
1500
1530
|
if (!input) return config;
|
|
1501
1531
|
const tokens = splitTopLevelByWhitespace(input);
|
|
1532
|
+
const isLengthPercentage = (value) => {
|
|
1533
|
+
if (value === void 0) return false;
|
|
1534
|
+
return value.endsWith("%") || /^-?\d*\.?\d+[a-zA-Z]+$/.test(value);
|
|
1535
|
+
};
|
|
1502
1536
|
for (let i = 0; i < tokens.length; i++) {
|
|
1503
1537
|
const token = tokens[i];
|
|
1504
1538
|
if (token === "from") {
|
|
1505
|
-
|
|
1539
|
+
const angleToken = tokens[i + 1];
|
|
1540
|
+
if (angleToken === void 0) throw new Error("Invalid conic-gradient config: missing angle after from");
|
|
1541
|
+
config.from = this._parseAngle(angleToken);
|
|
1506
1542
|
i += 1;
|
|
1507
1543
|
continue;
|
|
1508
1544
|
}
|
|
1509
1545
|
if (token === "at") {
|
|
1510
1546
|
const xToken = tokens[i + 1];
|
|
1511
1547
|
const yToken = tokens[i + 2];
|
|
1512
|
-
if (
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
}
|
|
1522
|
-
|
|
1523
|
-
|
|
1548
|
+
if (xToken === void 0) throw new Error("Invalid conic-gradient config: missing position after at");
|
|
1549
|
+
if (xToken === "center" && (yToken === void 0 || yToken === "in")) {
|
|
1550
|
+
config.position = {
|
|
1551
|
+
kind: "keywords",
|
|
1552
|
+
x: "center",
|
|
1553
|
+
y: "center"
|
|
1554
|
+
};
|
|
1555
|
+
i += 1;
|
|
1556
|
+
continue;
|
|
1557
|
+
}
|
|
1558
|
+
if ((xToken === "left" || xToken === "right") && (yToken === void 0 || yToken === "in")) {
|
|
1559
|
+
config.position = {
|
|
1560
|
+
kind: "keywords",
|
|
1561
|
+
x: xToken,
|
|
1562
|
+
y: "center"
|
|
1563
|
+
};
|
|
1564
|
+
i += 1;
|
|
1565
|
+
continue;
|
|
1566
|
+
}
|
|
1567
|
+
if ((xToken === "top" || xToken === "bottom") && (yToken === void 0 || yToken === "in")) {
|
|
1568
|
+
config.position = {
|
|
1569
|
+
kind: "keywords",
|
|
1570
|
+
x: "center",
|
|
1571
|
+
y: xToken
|
|
1572
|
+
};
|
|
1573
|
+
i += 1;
|
|
1574
|
+
continue;
|
|
1575
|
+
}
|
|
1576
|
+
if ((xToken === "left" || xToken === "center" || xToken === "right") && (yToken === "top" || yToken === "center" || yToken === "bottom")) {
|
|
1577
|
+
config.position = {
|
|
1578
|
+
kind: "keywords",
|
|
1579
|
+
x: xToken,
|
|
1580
|
+
y: yToken
|
|
1581
|
+
};
|
|
1582
|
+
i += 2;
|
|
1583
|
+
continue;
|
|
1584
|
+
}
|
|
1585
|
+
if (isLengthPercentage(xToken) && isLengthPercentage(yToken)) {
|
|
1586
|
+
config.position = {
|
|
1587
|
+
kind: "values",
|
|
1588
|
+
x: this._parseLengthPercentage(xToken),
|
|
1589
|
+
y: this._parseLengthPercentage(yToken)
|
|
1590
|
+
};
|
|
1591
|
+
i += 2;
|
|
1592
|
+
continue;
|
|
1593
|
+
}
|
|
1594
|
+
throw new Error(`Invalid conic-gradient position: ${xToken} ${yToken ?? ""}`);
|
|
1524
1595
|
}
|
|
1525
1596
|
if (token === "in") {
|
|
1526
|
-
const
|
|
1527
|
-
const
|
|
1528
|
-
|
|
1597
|
+
const colorSpace = tokens[i + 1];
|
|
1598
|
+
const maybeHue = tokens[i + 2];
|
|
1599
|
+
const maybeHueKeyword = tokens[i + 3];
|
|
1600
|
+
if (colorSpace === void 0) throw new Error("Invalid conic-gradient interpolation: missing color space");
|
|
1601
|
+
if (maybeHueKeyword === "hue" && (maybeHue === "shorter" || maybeHue === "longer" || maybeHue === "increasing" || maybeHue === "decreasing")) {
|
|
1529
1602
|
config.interpolation = {
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
hueMethod
|
|
1603
|
+
colorSpace,
|
|
1604
|
+
hue: maybeHue
|
|
1533
1605
|
};
|
|
1534
1606
|
i += 3;
|
|
1535
1607
|
continue;
|
|
1536
1608
|
}
|
|
1537
|
-
config.interpolation = {
|
|
1538
|
-
kind: "rectangular",
|
|
1539
|
-
space
|
|
1540
|
-
};
|
|
1609
|
+
config.interpolation = { colorSpace };
|
|
1541
1610
|
i += 1;
|
|
1611
|
+
continue;
|
|
1542
1612
|
}
|
|
1543
1613
|
}
|
|
1544
1614
|
return config;
|
|
@@ -1567,6 +1637,421 @@ var ConicGradient = class ConicGradient extends GradientBase {
|
|
|
1567
1637
|
}
|
|
1568
1638
|
};
|
|
1569
1639
|
//#endregion
|
|
1640
|
+
//#region src/gradients/MeshGradient.ts
|
|
1641
|
+
const MESH_ID_PATTERN = /^[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
1642
|
+
const MESH_LENGTH_UNITS = [
|
|
1643
|
+
"px",
|
|
1644
|
+
"em",
|
|
1645
|
+
"rem",
|
|
1646
|
+
"vw",
|
|
1647
|
+
"vh",
|
|
1648
|
+
"vmin",
|
|
1649
|
+
"vmax",
|
|
1650
|
+
"cm",
|
|
1651
|
+
"mm",
|
|
1652
|
+
"in",
|
|
1653
|
+
"pt",
|
|
1654
|
+
"pc"
|
|
1655
|
+
];
|
|
1656
|
+
var MeshGradient = class MeshGradient {
|
|
1657
|
+
type = "mesh-gradient";
|
|
1658
|
+
_config;
|
|
1659
|
+
_vertices;
|
|
1660
|
+
_patches;
|
|
1661
|
+
constructor(input) {
|
|
1662
|
+
const config = structuredClone(input.config);
|
|
1663
|
+
if (config.interpolation) config.interpolation = MeshGradient._normalizeInterpolation(config.interpolation);
|
|
1664
|
+
this._config = config;
|
|
1665
|
+
this._vertices = structuredClone(input.vertices);
|
|
1666
|
+
this._patches = structuredClone(input.patches);
|
|
1667
|
+
this._validate();
|
|
1668
|
+
}
|
|
1669
|
+
get config() {
|
|
1670
|
+
return structuredClone(this._config);
|
|
1671
|
+
}
|
|
1672
|
+
get vertices() {
|
|
1673
|
+
return structuredClone(this._vertices);
|
|
1674
|
+
}
|
|
1675
|
+
get patches() {
|
|
1676
|
+
return structuredClone(this._patches);
|
|
1677
|
+
}
|
|
1678
|
+
static fromString(input) {
|
|
1679
|
+
const { functionName, isRepeating, inputs } = MeshGradient._parseFunction(input);
|
|
1680
|
+
if (functionName !== "mesh-gradient") throw new Error("Invalid function name for MeshGradient");
|
|
1681
|
+
if (isRepeating) throw new Error("MeshGradient does not support repeating gradients");
|
|
1682
|
+
let config = null;
|
|
1683
|
+
const vertices = [];
|
|
1684
|
+
const patches = [];
|
|
1685
|
+
const handles = [];
|
|
1686
|
+
for (const rawInput of inputs) {
|
|
1687
|
+
const tokens = splitTopLevelByWhitespace(rawInput);
|
|
1688
|
+
const kind = tokens[0];
|
|
1689
|
+
if (kind === "grid") {
|
|
1690
|
+
if (config !== null) throw new Error("mesh-gradient can only contain one grid config");
|
|
1691
|
+
config = this._parseConfig(tokens);
|
|
1692
|
+
continue;
|
|
1693
|
+
}
|
|
1694
|
+
if (kind === "vertex") {
|
|
1695
|
+
vertices.push(this._parseVertex(tokens));
|
|
1696
|
+
continue;
|
|
1697
|
+
}
|
|
1698
|
+
if (kind === "patch") {
|
|
1699
|
+
patches.push(this._parsePatch(tokens));
|
|
1700
|
+
continue;
|
|
1701
|
+
}
|
|
1702
|
+
if (kind === "handle") {
|
|
1703
|
+
handles.push(this._parseHandle(tokens));
|
|
1704
|
+
continue;
|
|
1705
|
+
}
|
|
1706
|
+
throw new Error(`Unsupported mesh-gradient input: ${rawInput}`);
|
|
1707
|
+
}
|
|
1708
|
+
if (!config) throw new Error("mesh-gradient requires a grid config");
|
|
1709
|
+
const patchesWithHandles = this._attachHandles(patches, handles);
|
|
1710
|
+
return new MeshGradient({
|
|
1711
|
+
config,
|
|
1712
|
+
vertices,
|
|
1713
|
+
patches: patchesWithHandles
|
|
1714
|
+
});
|
|
1715
|
+
}
|
|
1716
|
+
static fromAbi(abi) {
|
|
1717
|
+
if (abi.functionName !== "mesh-gradient") throw new Error("Invalid function name for MeshGradient");
|
|
1718
|
+
if (abi.isRepeating) throw new Error("MeshGradient does not support repeating gradients");
|
|
1719
|
+
return MeshGradient.fromString(`${abi.functionName}(${abi.inputs.map((input) => input.value).join(", ")})`);
|
|
1720
|
+
}
|
|
1721
|
+
clone() {
|
|
1722
|
+
return new MeshGradient(this.toJSON());
|
|
1723
|
+
}
|
|
1724
|
+
toJSON() {
|
|
1725
|
+
return {
|
|
1726
|
+
type: this.type,
|
|
1727
|
+
config: this.config,
|
|
1728
|
+
vertices: this.vertices,
|
|
1729
|
+
patches: this.patches
|
|
1730
|
+
};
|
|
1731
|
+
}
|
|
1732
|
+
toString() {
|
|
1733
|
+
const parts = [
|
|
1734
|
+
this._serializeConfig(),
|
|
1735
|
+
...this._vertices.map((vertex) => this._serializeVertex(vertex)),
|
|
1736
|
+
...this._patches.map((patch) => this._serializePatch(patch)),
|
|
1737
|
+
...this._patches.flatMap((patch) => this._serializeHandles(patch))
|
|
1738
|
+
];
|
|
1739
|
+
return `${this.type}(${parts.join(", ")})`;
|
|
1740
|
+
}
|
|
1741
|
+
getVertex(id) {
|
|
1742
|
+
const vertex = this._vertices.find((item) => item.id === id);
|
|
1743
|
+
return vertex ? structuredClone(vertex) : null;
|
|
1744
|
+
}
|
|
1745
|
+
_serializeConfig() {
|
|
1746
|
+
const parts = [
|
|
1747
|
+
"grid",
|
|
1748
|
+
String(this._config.rows),
|
|
1749
|
+
String(this._config.columns),
|
|
1750
|
+
"method",
|
|
1751
|
+
this._config.method
|
|
1752
|
+
];
|
|
1753
|
+
if (this._config.interpolation) {
|
|
1754
|
+
parts.push("in", this._config.interpolation.colorSpace);
|
|
1755
|
+
if (this._config.interpolation.hue) parts.push(this._config.interpolation.hue, "hue");
|
|
1756
|
+
}
|
|
1757
|
+
return parts.join(" ");
|
|
1758
|
+
}
|
|
1759
|
+
_serializeVertex(vertex) {
|
|
1760
|
+
return [
|
|
1761
|
+
"vertex",
|
|
1762
|
+
vertex.id,
|
|
1763
|
+
this._formatLengthPercentage(vertex.x),
|
|
1764
|
+
this._formatLengthPercentage(vertex.y),
|
|
1765
|
+
vertex.color
|
|
1766
|
+
].join(" ");
|
|
1767
|
+
}
|
|
1768
|
+
_serializePatch(patch) {
|
|
1769
|
+
return [
|
|
1770
|
+
"patch",
|
|
1771
|
+
patch.id,
|
|
1772
|
+
patch.topLeft,
|
|
1773
|
+
patch.topRight,
|
|
1774
|
+
patch.bottomRight,
|
|
1775
|
+
patch.bottomLeft
|
|
1776
|
+
].join(" ");
|
|
1777
|
+
}
|
|
1778
|
+
_serializeHandles(patch) {
|
|
1779
|
+
if (!patch.handles) return [];
|
|
1780
|
+
const result = [];
|
|
1781
|
+
for (const side of [
|
|
1782
|
+
"top",
|
|
1783
|
+
"right",
|
|
1784
|
+
"bottom",
|
|
1785
|
+
"left"
|
|
1786
|
+
]) {
|
|
1787
|
+
const handle = patch.handles[side];
|
|
1788
|
+
if (!handle) continue;
|
|
1789
|
+
result.push([
|
|
1790
|
+
"handle",
|
|
1791
|
+
patch.id,
|
|
1792
|
+
side,
|
|
1793
|
+
this._formatLengthPercentage(handle.from.x),
|
|
1794
|
+
this._formatLengthPercentage(handle.from.y),
|
|
1795
|
+
this._formatLengthPercentage(handle.to.x),
|
|
1796
|
+
this._formatLengthPercentage(handle.to.y)
|
|
1797
|
+
].join(" "));
|
|
1798
|
+
}
|
|
1799
|
+
return result;
|
|
1800
|
+
}
|
|
1801
|
+
_formatLengthPercentage(value) {
|
|
1802
|
+
if (value.kind === "percent") return `${value.value}%`;
|
|
1803
|
+
return `${value.value}${value.unit}`;
|
|
1804
|
+
}
|
|
1805
|
+
_validate() {
|
|
1806
|
+
if (!Number.isInteger(this._config.rows) || this._config.rows < 2) throw new Error("Mesh gradient rows must be an integer >= 2");
|
|
1807
|
+
if (!Number.isInteger(this._config.columns) || this._config.columns < 2) throw new Error("Mesh gradient columns must be an integer >= 2");
|
|
1808
|
+
if (this._config.method !== "bilinear" && this._config.method !== "bicubic") throw new Error("Invalid mesh gradient interpolation method");
|
|
1809
|
+
if (this._config.interpolation && !isGradientColorSpace(this._config.interpolation.colorSpace)) throw new Error(`Invalid mesh gradient color space: ${this._config.interpolation.colorSpace}`);
|
|
1810
|
+
const expectedVertexCount = this._config.rows * this._config.columns;
|
|
1811
|
+
const expectedPatchCount = (this._config.rows - 1) * (this._config.columns - 1);
|
|
1812
|
+
if (this._vertices.length !== expectedVertexCount) throw new Error(`Mesh gradient expected ${expectedVertexCount} vertices for ${this._config.rows}x${this._config.columns} grid, received ${this._vertices.length}`);
|
|
1813
|
+
if (this._patches.length !== expectedPatchCount) throw new Error(`Mesh gradient expected ${expectedPatchCount} patches for ${this._config.rows}x${this._config.columns} grid, received ${this._patches.length}`);
|
|
1814
|
+
const vertexIds = /* @__PURE__ */ new Set();
|
|
1815
|
+
for (const vertex of this._vertices) {
|
|
1816
|
+
this._validateId(vertex.id, "vertex");
|
|
1817
|
+
if (vertexIds.has(vertex.id)) throw new Error(`Duplicate mesh vertex id: ${vertex.id}`);
|
|
1818
|
+
vertexIds.add(vertex.id);
|
|
1819
|
+
this._validateLengthPercentage(vertex.x, `vertex ${vertex.id} x`);
|
|
1820
|
+
this._validateLengthPercentage(vertex.y, `vertex ${vertex.id} y`);
|
|
1821
|
+
if (!parse$1(vertex.color)) throw new Error(`Invalid mesh vertex color: ${vertex.color}`);
|
|
1822
|
+
}
|
|
1823
|
+
const patchIds = /* @__PURE__ */ new Set();
|
|
1824
|
+
for (const patch of this._patches) {
|
|
1825
|
+
this._validateId(patch.id, "patch");
|
|
1826
|
+
if (patchIds.has(patch.id)) throw new Error(`Duplicate mesh patch id: ${patch.id}`);
|
|
1827
|
+
patchIds.add(patch.id);
|
|
1828
|
+
const ids = [
|
|
1829
|
+
patch.topLeft,
|
|
1830
|
+
patch.topRight,
|
|
1831
|
+
patch.bottomRight,
|
|
1832
|
+
patch.bottomLeft
|
|
1833
|
+
];
|
|
1834
|
+
if (new Set(ids).size !== 4) throw new Error(`Mesh patch must use 4 unique vertices: ${patch.id}`);
|
|
1835
|
+
for (const id of ids) if (!vertexIds.has(id)) throw new Error(`Mesh patch references missing vertex: ${id}`);
|
|
1836
|
+
if (patch.handles) this._validateHandles(patch);
|
|
1837
|
+
}
|
|
1838
|
+
if (this._patches.length === 0) throw new Error("Mesh gradient requires at least one patch");
|
|
1839
|
+
}
|
|
1840
|
+
_validateId(id, label) {
|
|
1841
|
+
if (!MESH_ID_PATTERN.test(id)) throw new Error(`Invalid mesh ${label} id: ${id}`);
|
|
1842
|
+
}
|
|
1843
|
+
_validateHandles(patch) {
|
|
1844
|
+
if (!patch.handles) return;
|
|
1845
|
+
for (const side of [
|
|
1846
|
+
"top",
|
|
1847
|
+
"right",
|
|
1848
|
+
"bottom",
|
|
1849
|
+
"left"
|
|
1850
|
+
]) {
|
|
1851
|
+
const handle = patch.handles[side];
|
|
1852
|
+
if (!handle) continue;
|
|
1853
|
+
this._validateLengthPercentage(handle.from.x, `patch ${patch.id} ${side} handle from x`);
|
|
1854
|
+
this._validateLengthPercentage(handle.from.y, `patch ${patch.id} ${side} handle from y`);
|
|
1855
|
+
this._validateLengthPercentage(handle.to.x, `patch ${patch.id} ${side} handle to x`);
|
|
1856
|
+
this._validateLengthPercentage(handle.to.y, `patch ${patch.id} ${side} handle to y`);
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
_validateLengthPercentage(value, label) {
|
|
1860
|
+
if (value.kind === "percent") {
|
|
1861
|
+
if (!Number.isFinite(value.value)) throw new Error(`Invalid mesh ${label}`);
|
|
1862
|
+
return;
|
|
1863
|
+
}
|
|
1864
|
+
if (!Number.isFinite(value.value)) throw new Error(`Invalid mesh ${label}`);
|
|
1865
|
+
}
|
|
1866
|
+
static _parseConfig(tokens) {
|
|
1867
|
+
if (tokens.length < 3) throw new Error("Invalid mesh grid config");
|
|
1868
|
+
const rows = Number(tokens[1]);
|
|
1869
|
+
const columns = Number(tokens[2]);
|
|
1870
|
+
let method = "bilinear";
|
|
1871
|
+
let interpolation;
|
|
1872
|
+
for (let index = 3; index < tokens.length; index += 1) {
|
|
1873
|
+
const token = tokens[index];
|
|
1874
|
+
if (token === "method") {
|
|
1875
|
+
const value = tokens[index + 1];
|
|
1876
|
+
if (value !== "bilinear" && value !== "bicubic") throw new Error(`Invalid mesh gradient method: ${value}`);
|
|
1877
|
+
method = value;
|
|
1878
|
+
index += 1;
|
|
1879
|
+
continue;
|
|
1880
|
+
}
|
|
1881
|
+
if (token === "in") {
|
|
1882
|
+
const colorSpace = tokens[index + 1];
|
|
1883
|
+
const maybeHue = tokens[index + 2];
|
|
1884
|
+
const maybeHueKeyword = tokens[index + 3];
|
|
1885
|
+
if (!isGradientColorSpace(colorSpace)) throw new Error(`Invalid mesh gradient color space: ${colorSpace}`);
|
|
1886
|
+
if (maybeHue !== void 0 && maybeHueKeyword === "hue" && isGradientHueInterpolation(maybeHue)) {
|
|
1887
|
+
interpolation = this._normalizeInterpolation({
|
|
1888
|
+
colorSpace,
|
|
1889
|
+
hue: maybeHue
|
|
1890
|
+
});
|
|
1891
|
+
index += 3;
|
|
1892
|
+
continue;
|
|
1893
|
+
}
|
|
1894
|
+
interpolation = { colorSpace };
|
|
1895
|
+
index += 1;
|
|
1896
|
+
continue;
|
|
1897
|
+
}
|
|
1898
|
+
throw new Error(`Unsupported mesh grid config token: ${token}`);
|
|
1899
|
+
}
|
|
1900
|
+
return {
|
|
1901
|
+
rows,
|
|
1902
|
+
columns,
|
|
1903
|
+
method,
|
|
1904
|
+
interpolation
|
|
1905
|
+
};
|
|
1906
|
+
}
|
|
1907
|
+
static _parseVertex(tokens) {
|
|
1908
|
+
if (tokens.length < 5) throw new Error("Invalid mesh vertex input");
|
|
1909
|
+
return {
|
|
1910
|
+
id: tokens[1],
|
|
1911
|
+
x: this._parseLengthPercentage(tokens[2]),
|
|
1912
|
+
y: this._parseLengthPercentage(tokens[3]),
|
|
1913
|
+
color: tokens.slice(4).join(" ")
|
|
1914
|
+
};
|
|
1915
|
+
}
|
|
1916
|
+
static _parsePatch(tokens) {
|
|
1917
|
+
if (tokens.length !== 6) throw new Error("Invalid mesh patch input");
|
|
1918
|
+
return {
|
|
1919
|
+
id: tokens[1],
|
|
1920
|
+
topLeft: tokens[2],
|
|
1921
|
+
topRight: tokens[3],
|
|
1922
|
+
bottomRight: tokens[4],
|
|
1923
|
+
bottomLeft: tokens[5]
|
|
1924
|
+
};
|
|
1925
|
+
}
|
|
1926
|
+
static _parseHandle(tokens) {
|
|
1927
|
+
if (tokens.length !== 7) throw new Error("Invalid mesh handle input");
|
|
1928
|
+
const side = tokens[2];
|
|
1929
|
+
if (side !== "top" && side !== "right" && side !== "bottom" && side !== "left") throw new Error(`Invalid mesh handle side: ${side}`);
|
|
1930
|
+
return {
|
|
1931
|
+
patchId: tokens[1],
|
|
1932
|
+
side,
|
|
1933
|
+
from: {
|
|
1934
|
+
x: this._parseLengthPercentage(tokens[3]),
|
|
1935
|
+
y: this._parseLengthPercentage(tokens[4])
|
|
1936
|
+
},
|
|
1937
|
+
to: {
|
|
1938
|
+
x: this._parseLengthPercentage(tokens[5]),
|
|
1939
|
+
y: this._parseLengthPercentage(tokens[6])
|
|
1940
|
+
}
|
|
1941
|
+
};
|
|
1942
|
+
}
|
|
1943
|
+
static _attachHandles(patches, handles) {
|
|
1944
|
+
if (handles.length === 0) return patches;
|
|
1945
|
+
const patchMap = new Map(patches.map((patch) => [patch.id, patch]));
|
|
1946
|
+
const nextPatches = patches.map((patch) => structuredClone(patch));
|
|
1947
|
+
const nextPatchMap = new Map(nextPatches.map((patch) => [patch.id, patch]));
|
|
1948
|
+
for (const handle of handles) {
|
|
1949
|
+
if (!patchMap.has(handle.patchId)) throw new Error(`Mesh handle references missing patch: ${handle.patchId}`);
|
|
1950
|
+
const patch = nextPatchMap.get(handle.patchId);
|
|
1951
|
+
patch.handles ??= {};
|
|
1952
|
+
if (patch.handles[handle.side]) throw new Error(`Duplicate mesh handle for patch ${handle.patchId} side ${handle.side}`);
|
|
1953
|
+
patch.handles[handle.side] = {
|
|
1954
|
+
from: handle.from,
|
|
1955
|
+
to: handle.to
|
|
1956
|
+
};
|
|
1957
|
+
}
|
|
1958
|
+
return nextPatches;
|
|
1959
|
+
}
|
|
1960
|
+
static _parseLengthPercentage(input) {
|
|
1961
|
+
if (input.endsWith("%")) {
|
|
1962
|
+
const value = parseFloat(input);
|
|
1963
|
+
if (!Number.isFinite(value)) throw new Error(`Invalid mesh length-percentage: ${input}`);
|
|
1964
|
+
return {
|
|
1965
|
+
kind: "percent",
|
|
1966
|
+
value
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1969
|
+
const match = input.match(/^(-?\d*\.?\d+)([a-zA-Z]+)$/);
|
|
1970
|
+
if (!match) throw new Error(`Invalid mesh length-percentage: ${input}`);
|
|
1971
|
+
const unit = match[2];
|
|
1972
|
+
if (!MESH_LENGTH_UNITS.includes(unit)) throw new Error(`Unsupported mesh length unit: ${match[2]}`);
|
|
1973
|
+
return {
|
|
1974
|
+
kind: "length",
|
|
1975
|
+
value: parseFloat(match[1]),
|
|
1976
|
+
unit
|
|
1977
|
+
};
|
|
1978
|
+
}
|
|
1979
|
+
static _normalizeInterpolation(value) {
|
|
1980
|
+
const { colorSpace, hue } = value;
|
|
1981
|
+
if (hue === void 0 || !isGradientPolarColorSpace(colorSpace)) return { colorSpace };
|
|
1982
|
+
return {
|
|
1983
|
+
colorSpace,
|
|
1984
|
+
hue
|
|
1985
|
+
};
|
|
1986
|
+
}
|
|
1987
|
+
static _parseFunction(input) {
|
|
1988
|
+
const source = input.trim();
|
|
1989
|
+
const openIndex = source.indexOf("(");
|
|
1990
|
+
if (openIndex <= 0) throw new Error("Expected mesh-gradient function call");
|
|
1991
|
+
let functionName = source.slice(0, openIndex).trim();
|
|
1992
|
+
const isRepeating = functionName.startsWith("repeating-");
|
|
1993
|
+
if (isRepeating) functionName = functionName.slice(10);
|
|
1994
|
+
const closeIndex = this._findOuterClosingParenIndex(source, openIndex);
|
|
1995
|
+
if (closeIndex === -1) throw new Error("Unclosed mesh-gradient function parenthesis");
|
|
1996
|
+
const trailing = source.slice(closeIndex + 1).trim();
|
|
1997
|
+
if (trailing.length > 0 && !trailing.startsWith(`${functionName}(`) && !trailing.startsWith(`repeating-${functionName}(`)) throw new Error(`Unexpected mesh-gradient trailing input: ${trailing}`);
|
|
1998
|
+
const body = source.slice(openIndex + 1, closeIndex);
|
|
1999
|
+
return {
|
|
2000
|
+
functionName,
|
|
2001
|
+
isRepeating,
|
|
2002
|
+
inputs: this._splitTopLevelInputs(body)
|
|
2003
|
+
};
|
|
2004
|
+
}
|
|
2005
|
+
static _findOuterClosingParenIndex(value, openIndex) {
|
|
2006
|
+
let depth = 0;
|
|
2007
|
+
for (let index = openIndex; index < value.length; index += 1) {
|
|
2008
|
+
const char = value[index];
|
|
2009
|
+
if (char === "(") {
|
|
2010
|
+
depth += 1;
|
|
2011
|
+
continue;
|
|
2012
|
+
}
|
|
2013
|
+
if (char === ")") {
|
|
2014
|
+
depth -= 1;
|
|
2015
|
+
if (depth === 0) return index;
|
|
2016
|
+
if (depth < 0) return -1;
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
return -1;
|
|
2020
|
+
}
|
|
2021
|
+
static _splitTopLevelInputs(value) {
|
|
2022
|
+
const result = [];
|
|
2023
|
+
let current = "";
|
|
2024
|
+
let parenDepth = 0;
|
|
2025
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
2026
|
+
const char = value[index];
|
|
2027
|
+
if (char === "(") {
|
|
2028
|
+
parenDepth += 1;
|
|
2029
|
+
current += char;
|
|
2030
|
+
continue;
|
|
2031
|
+
}
|
|
2032
|
+
if (char === ")") {
|
|
2033
|
+
parenDepth -= 1;
|
|
2034
|
+
if (parenDepth < 0) throw new Error("Unbalanced mesh-gradient input parentheses");
|
|
2035
|
+
current += char;
|
|
2036
|
+
continue;
|
|
2037
|
+
}
|
|
2038
|
+
if (char === "," && parenDepth === 0) {
|
|
2039
|
+
this._pushTrimmed(result, current);
|
|
2040
|
+
current = "";
|
|
2041
|
+
continue;
|
|
2042
|
+
}
|
|
2043
|
+
current += char;
|
|
2044
|
+
}
|
|
2045
|
+
this._pushTrimmed(result, current);
|
|
2046
|
+
if (parenDepth !== 0) throw new Error("Unbalanced mesh-gradient input parentheses");
|
|
2047
|
+
return result;
|
|
2048
|
+
}
|
|
2049
|
+
static _pushTrimmed(target, value) {
|
|
2050
|
+
const trimmed = value.trim();
|
|
2051
|
+
if (trimmed.length > 0) target.push(trimmed);
|
|
2052
|
+
}
|
|
2053
|
+
};
|
|
2054
|
+
//#endregion
|
|
1570
2055
|
//#region src/gradient-transformer/modules/css/ModuleTransformerLinearGradientToCss.ts
|
|
1571
2056
|
var ModuleTransformerLinearGradientToCss = class {
|
|
1572
2057
|
target = "css";
|
|
@@ -1587,21 +2072,11 @@ var ModuleTransformerRadialGradientToCss = class {
|
|
|
1587
2072
|
}
|
|
1588
2073
|
};
|
|
1589
2074
|
//#endregion
|
|
1590
|
-
//#region src/gradient-transformer/modules/css/ModuleTransformerConicGradientToCss.ts
|
|
1591
|
-
var ModuleTransformerConicGradientToCss = class {
|
|
1592
|
-
target = "css";
|
|
1593
|
-
gradientType = "conic-gradient";
|
|
1594
|
-
to(input) {
|
|
1595
|
-
if (!(input instanceof ConicGradient)) throw new Error("Expected ConicGradient");
|
|
1596
|
-
return input.toString();
|
|
1597
|
-
}
|
|
1598
|
-
};
|
|
1599
|
-
//#endregion
|
|
1600
2075
|
//#region src/gradient-transformer/modules/helpers/expand-repeating-stops.ts
|
|
1601
2076
|
function positiveModulo(value, modulo) {
|
|
1602
2077
|
return (value % modulo + modulo) % modulo;
|
|
1603
2078
|
}
|
|
1604
|
-
function sampleColorAtPosition(stops, position) {
|
|
2079
|
+
function sampleColorAtPosition$2(stops, position) {
|
|
1605
2080
|
const colorStops = stops.filter((stop) => stop.type === "color-stop" && stop.position != null).sort((a, b) => a.position - b.position);
|
|
1606
2081
|
if (colorStops.length === 0) throw new Error("Cannot sample color from empty stops.");
|
|
1607
2082
|
if (position <= colorStops[0].position) return colorStops[0].value;
|
|
@@ -1619,7 +2094,7 @@ function sampleColorAtPosition(stops, position) {
|
|
|
1619
2094
|
return lastStop.value;
|
|
1620
2095
|
}
|
|
1621
2096
|
function sampleRepeatingColorAtPosition(stops, position, firstPosition, period) {
|
|
1622
|
-
return sampleColorAtPosition(stops, firstPosition + positiveModulo(position - firstPosition, period));
|
|
2097
|
+
return sampleColorAtPosition$2(stops, firstPosition + positiveModulo(position - firstPosition, period));
|
|
1623
2098
|
}
|
|
1624
2099
|
function expandRepeatingStopsTo(stops, from, to) {
|
|
1625
2100
|
const colorStops = stops.filter((stop) => stop.type === "color-stop" && stop.position != null).sort((a, b) => a.position - b.position);
|
|
@@ -1738,12 +2213,12 @@ function getColorStopsWithPositions(stops) {
|
|
|
1738
2213
|
});
|
|
1739
2214
|
}
|
|
1740
2215
|
function formatColorForCanvas(input) {
|
|
1741
|
-
const color = toRgb$
|
|
2216
|
+
const color = toRgb$9(input);
|
|
1742
2217
|
if (!color) throw new Error("Failed to convert interpolated color to rgb.");
|
|
1743
2218
|
return formatRgb(color);
|
|
1744
2219
|
}
|
|
1745
2220
|
const DEFAULT_SAMPLE_COUNT = 64;
|
|
1746
|
-
const toRgb$
|
|
2221
|
+
const toRgb$9 = converter("rgb");
|
|
1747
2222
|
function resolveRenderableGradientStops(gradient, sampleCount = DEFAULT_SAMPLE_COUNT) {
|
|
1748
2223
|
const colorStops = getColorStopsWithPositions(gradient.stops);
|
|
1749
2224
|
const interpolation = gradient.config.interpolation;
|
|
@@ -1773,77 +2248,677 @@ function resolveRenderableGradientStops(gradient, sampleCount = DEFAULT_SAMPLE_C
|
|
|
1773
2248
|
return gradient.isRepeating ? expandRepeatingStops(sampledStops) : sampledStops;
|
|
1774
2249
|
}
|
|
1775
2250
|
//#endregion
|
|
1776
|
-
//#region src/gradient-transformer/modules/
|
|
1777
|
-
const toRgb$
|
|
1778
|
-
function
|
|
1779
|
-
const color = toRgb$
|
|
2251
|
+
//#region src/gradient-transformer/modules/helpers/mesh-rendering.ts
|
|
2252
|
+
const toRgb$8 = converter("rgb");
|
|
2253
|
+
function toColor(input) {
|
|
2254
|
+
const color = toRgb$8(input);
|
|
1780
2255
|
if (!color) throw new Error(`Failed to convert color: ${input}`);
|
|
1781
|
-
return
|
|
2256
|
+
return [
|
|
2257
|
+
color.r ?? 0,
|
|
2258
|
+
color.g ?? 0,
|
|
2259
|
+
color.b ?? 0,
|
|
2260
|
+
color.alpha ?? 1
|
|
2261
|
+
];
|
|
1782
2262
|
}
|
|
1783
|
-
function
|
|
1784
|
-
|
|
1785
|
-
if (
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
};
|
|
2263
|
+
function resolveLengthPercentage$2(value, reference) {
|
|
2264
|
+
if (value.kind === "percent") return value.value / 100 * reference;
|
|
2265
|
+
if (value.unit === "px") return value.value;
|
|
2266
|
+
throw new Error(`Unsupported mesh-gradient length unit: ${value.unit}`);
|
|
2267
|
+
}
|
|
2268
|
+
function resolveMeshVertex(vertex, width, height) {
|
|
1790
2269
|
return {
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
2270
|
+
id: vertex.id,
|
|
2271
|
+
x: resolveLengthPercentage$2(vertex.x, width),
|
|
2272
|
+
y: resolveLengthPercentage$2(vertex.y, height),
|
|
2273
|
+
color: toColor(vertex.color)
|
|
1794
2274
|
};
|
|
1795
2275
|
}
|
|
1796
|
-
function
|
|
1797
|
-
|
|
1798
|
-
return stops.filter((stop) => stop.type === "color-stop" && stop.position != null).map((stop) => ({
|
|
1799
|
-
...stop,
|
|
1800
|
-
position: (stop.position - min) / range
|
|
1801
|
-
}));
|
|
2276
|
+
function buildMeshVertexMap(gradient, width, height) {
|
|
2277
|
+
return new Map(gradient.vertices.map((vertex) => [vertex.id, resolveMeshVertex(vertex, width, height)]));
|
|
1802
2278
|
}
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
const
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
const { min, max, stops } = getStopRange$2(resolveRenderableGradientStops(gradient));
|
|
1820
|
-
let normalizedStops = stops;
|
|
1821
|
-
if (min < 0 || max > 1) {
|
|
1822
|
-
const vx = endX - startX;
|
|
1823
|
-
const vy = endY - startY;
|
|
1824
|
-
const baseStartX = startX;
|
|
1825
|
-
const baseStartY = startY;
|
|
1826
|
-
startX = baseStartX + vx * min;
|
|
1827
|
-
startY = baseStartY + vy * min;
|
|
1828
|
-
endX = baseStartX + vx * max;
|
|
1829
|
-
endY = baseStartY + vy * max;
|
|
1830
|
-
normalizedStops = normalizeStops$3(stops, min, max);
|
|
1831
|
-
}
|
|
1832
|
-
const canvasGradient = ctx.createLinearGradient(startX, startY, endX, endY);
|
|
1833
|
-
for (const stop of normalizedStops) canvasGradient.addColorStop(stop.position, toCanvasColor$1(stop.value));
|
|
1834
|
-
ctx.clearRect(0, 0, width, height);
|
|
1835
|
-
ctx.fillStyle = canvasGradient;
|
|
1836
|
-
ctx.fillRect(0, 0, width, height);
|
|
1837
|
-
} };
|
|
2279
|
+
function buildRegularMeshGrid(gradient, vertexMap) {
|
|
2280
|
+
const idGrid = buildRegularMeshGridFromVertexIds(gradient, vertexMap);
|
|
2281
|
+
if (idGrid) return idGrid;
|
|
2282
|
+
const vertices = gradient.vertices.map((vertex) => {
|
|
2283
|
+
const resolved = vertexMap.get(vertex.id);
|
|
2284
|
+
if (!resolved) throw new Error(`Missing mesh vertex: ${vertex.id}`);
|
|
2285
|
+
return resolved;
|
|
2286
|
+
}).sort((a, b) => {
|
|
2287
|
+
if (Math.abs(a.y - b.y) > 1e-4) return a.y - b.y;
|
|
2288
|
+
return a.x - b.x;
|
|
2289
|
+
});
|
|
2290
|
+
const result = [];
|
|
2291
|
+
for (let row = 0; row < gradient.config.rows; row += 1) {
|
|
2292
|
+
const start = row * gradient.config.columns;
|
|
2293
|
+
const end = start + gradient.config.columns;
|
|
2294
|
+
result.push(vertices.slice(start, end));
|
|
1838
2295
|
}
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
const
|
|
1843
|
-
const
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
2296
|
+
return result;
|
|
2297
|
+
}
|
|
2298
|
+
function buildRegularMeshGridFromVertexIds(gradient, vertexMap) {
|
|
2299
|
+
const result = Array.from({ length: gradient.config.rows }, () => []);
|
|
2300
|
+
for (const vertex of gradient.vertices) {
|
|
2301
|
+
const match = vertex.id.match(/^v(\d+)(\d+)$/);
|
|
2302
|
+
if (!match) return null;
|
|
2303
|
+
const column = Number(match[1]);
|
|
2304
|
+
const row = Number(match[2]);
|
|
2305
|
+
if (!Number.isInteger(row) || !Number.isInteger(column) || row < 0 || column < 0 || row >= gradient.config.rows || column >= gradient.config.columns) return null;
|
|
2306
|
+
const resolved = vertexMap.get(vertex.id);
|
|
2307
|
+
if (!resolved) throw new Error(`Missing mesh vertex: ${vertex.id}`);
|
|
2308
|
+
if (result[row][column]) return null;
|
|
2309
|
+
result[row][column] = resolved;
|
|
2310
|
+
}
|
|
2311
|
+
if (result.some((row) => row.length !== gradient.config.columns || row.some((vertex) => vertex === void 0))) return null;
|
|
2312
|
+
return result;
|
|
2313
|
+
}
|
|
2314
|
+
function findPatchCell(grid, patch) {
|
|
2315
|
+
for (let row = 0; row < grid.length - 1; row += 1) for (let column = 0; column < grid[row].length - 1; column += 1) if (grid[row][column].id === patch.topLeft && grid[row][column + 1].id === patch.topRight && grid[row + 1][column + 1].id === patch.bottomRight && grid[row + 1][column].id === patch.bottomLeft) return {
|
|
2316
|
+
row,
|
|
2317
|
+
column
|
|
2318
|
+
};
|
|
2319
|
+
throw new Error(`Mesh patch does not match adjacent regular grid vertices: ${patch.id}`);
|
|
2320
|
+
}
|
|
2321
|
+
function clampIndex(index, length) {
|
|
2322
|
+
return Math.min(length - 1, Math.max(0, index));
|
|
2323
|
+
}
|
|
2324
|
+
function catmullRom(p0, p1, p2, p3, t) {
|
|
2325
|
+
const t2 = t * t;
|
|
2326
|
+
const t3 = t2 * t;
|
|
2327
|
+
return .5 * (2 * p1 + (-p0 + p2) * t + (2 * p0 - 5 * p1 + 4 * p2 - p3) * t2 + (-p0 + 3 * p1 - 3 * p2 + p3) * t3);
|
|
2328
|
+
}
|
|
2329
|
+
function clampColor(value) {
|
|
2330
|
+
return Math.min(1, Math.max(0, value));
|
|
2331
|
+
}
|
|
2332
|
+
function mix(a, b, t) {
|
|
2333
|
+
return a + (b - a) * t;
|
|
2334
|
+
}
|
|
2335
|
+
function mixColor(a, b, t) {
|
|
2336
|
+
return [
|
|
2337
|
+
mix(a[0], b[0], t),
|
|
2338
|
+
mix(a[1], b[1], t),
|
|
2339
|
+
mix(a[2], b[2], t),
|
|
2340
|
+
mix(a[3], b[3], t)
|
|
2341
|
+
];
|
|
2342
|
+
}
|
|
2343
|
+
function sampleBilinearColor(topLeft, topRight, bottomRight, bottomLeft, u, v) {
|
|
2344
|
+
return mixColor(mixColor(topLeft.color, topRight.color, u), mixColor(bottomLeft.color, bottomRight.color, u), v);
|
|
2345
|
+
}
|
|
2346
|
+
function sampleBicubicColor(grid, row, column, u, v) {
|
|
2347
|
+
const rows = grid.length;
|
|
2348
|
+
const columns = grid[0]?.length ?? 0;
|
|
2349
|
+
const color = [
|
|
2350
|
+
0,
|
|
2351
|
+
0,
|
|
2352
|
+
0,
|
|
2353
|
+
0
|
|
2354
|
+
];
|
|
2355
|
+
for (let channel = 0; channel < 4; channel += 1) {
|
|
2356
|
+
const values = [];
|
|
2357
|
+
for (let y = -1; y <= 2; y += 1) {
|
|
2358
|
+
const sampleRow = grid[clampIndex(row + y, rows)];
|
|
2359
|
+
const p0 = sampleRow[clampIndex(column - 1, columns)].color[channel];
|
|
2360
|
+
const p1 = sampleRow[clampIndex(column, columns)].color[channel];
|
|
2361
|
+
const p2 = sampleRow[clampIndex(column + 1, columns)].color[channel];
|
|
2362
|
+
const p3 = sampleRow[clampIndex(column + 2, columns)].color[channel];
|
|
2363
|
+
values.push(catmullRom(p0, p1, p2, p3, u));
|
|
2364
|
+
}
|
|
2365
|
+
color[channel] = clampColor(catmullRom(values[0], values[1], values[2], values[3], v));
|
|
2366
|
+
}
|
|
2367
|
+
return color;
|
|
2368
|
+
}
|
|
2369
|
+
function samplePosition(topLeft, topRight, bottomRight, bottomLeft, u, v) {
|
|
2370
|
+
const topX = mix(topLeft.x, topRight.x, u);
|
|
2371
|
+
const topY = mix(topLeft.y, topRight.y, u);
|
|
2372
|
+
const bottomX = mix(bottomLeft.x, bottomRight.x, u);
|
|
2373
|
+
const bottomY = mix(bottomLeft.y, bottomRight.y, u);
|
|
2374
|
+
return {
|
|
2375
|
+
x: mix(topX, bottomX, v),
|
|
2376
|
+
y: mix(topY, bottomY, v)
|
|
2377
|
+
};
|
|
2378
|
+
}
|
|
2379
|
+
function samplePatchColor(gradient, grid, row, column, topLeft, topRight, bottomRight, bottomLeft, u, v) {
|
|
2380
|
+
if (gradient.config.method === "bicubic") return sampleBicubicColor(grid, row, column, u, v);
|
|
2381
|
+
return sampleBilinearColor(topLeft, topRight, bottomRight, bottomLeft, u, v).map(clampColor);
|
|
2382
|
+
}
|
|
2383
|
+
function buildPatchTriangles(gradient, grid, patch, vertexMap, subdivisions) {
|
|
2384
|
+
const topLeft = vertexMap.get(patch.topLeft);
|
|
2385
|
+
const topRight = vertexMap.get(patch.topRight);
|
|
2386
|
+
const bottomRight = vertexMap.get(patch.bottomRight);
|
|
2387
|
+
const bottomLeft = vertexMap.get(patch.bottomLeft);
|
|
2388
|
+
if (!topLeft || !topRight || !bottomRight || !bottomLeft) throw new Error(`Mesh patch references missing vertex: ${patch.id}`);
|
|
2389
|
+
const cell = findPatchCell(grid, patch);
|
|
2390
|
+
const samples = [];
|
|
2391
|
+
for (let y = 0; y <= subdivisions; y += 1) {
|
|
2392
|
+
const row = [];
|
|
2393
|
+
const v = y / subdivisions;
|
|
2394
|
+
for (let x = 0; x <= subdivisions; x += 1) {
|
|
2395
|
+
const u = x / subdivisions;
|
|
2396
|
+
const position = samplePosition(topLeft, topRight, bottomRight, bottomLeft, u, v);
|
|
2397
|
+
const color = samplePatchColor(gradient, grid, cell.row, cell.column, topLeft, topRight, bottomRight, bottomLeft, u, v);
|
|
2398
|
+
row.push({
|
|
2399
|
+
id: `${patch.id}:${x}:${y}`,
|
|
2400
|
+
x: position.x,
|
|
2401
|
+
y: position.y,
|
|
2402
|
+
color
|
|
2403
|
+
});
|
|
2404
|
+
}
|
|
2405
|
+
samples.push(row);
|
|
2406
|
+
}
|
|
2407
|
+
const triangles = [];
|
|
2408
|
+
for (let y = 0; y < subdivisions; y += 1) for (let x = 0; x < subdivisions; x += 1) {
|
|
2409
|
+
const topLeftSample = samples[y][x];
|
|
2410
|
+
const topRightSample = samples[y][x + 1];
|
|
2411
|
+
const bottomRightSample = samples[y + 1][x + 1];
|
|
2412
|
+
const bottomLeftSample = samples[y + 1][x];
|
|
2413
|
+
triangles.push([
|
|
2414
|
+
topLeftSample,
|
|
2415
|
+
topRightSample,
|
|
2416
|
+
bottomRightSample
|
|
2417
|
+
], [
|
|
2418
|
+
topLeftSample,
|
|
2419
|
+
bottomRightSample,
|
|
2420
|
+
bottomLeftSample
|
|
2421
|
+
]);
|
|
2422
|
+
}
|
|
2423
|
+
return triangles;
|
|
2424
|
+
}
|
|
2425
|
+
function samplePatchVertexAt(gradient, grid, row, column, topLeft, topRight, bottomRight, bottomLeft, u, v, x, y, id) {
|
|
2426
|
+
return {
|
|
2427
|
+
id,
|
|
2428
|
+
x,
|
|
2429
|
+
y,
|
|
2430
|
+
color: samplePatchColor(gradient, grid, row, column, topLeft, topRight, bottomRight, bottomLeft, u, v)
|
|
2431
|
+
};
|
|
2432
|
+
}
|
|
2433
|
+
function buildMeshEdgeSkirtTriangles(gradient, grid, width, height, subdivisions) {
|
|
2434
|
+
const triangles = [];
|
|
2435
|
+
const rows = grid.length;
|
|
2436
|
+
const columns = grid[0]?.length ?? 0;
|
|
2437
|
+
if (rows < 2 || columns < 2) return triangles;
|
|
2438
|
+
for (let column = 0; column < columns - 1; column += 1) {
|
|
2439
|
+
const topLeft = grid[0][column];
|
|
2440
|
+
const topRight = grid[0][column + 1];
|
|
2441
|
+
const bottomRight = grid[1][column + 1];
|
|
2442
|
+
const bottomLeft = grid[1][column];
|
|
2443
|
+
for (let index = 0; index < subdivisions; index += 1) {
|
|
2444
|
+
const u0 = index / subdivisions;
|
|
2445
|
+
const u1 = (index + 1) / subdivisions;
|
|
2446
|
+
const top0 = samplePosition(topLeft, topRight, bottomRight, bottomLeft, u0, 0);
|
|
2447
|
+
const top1 = samplePosition(topLeft, topRight, bottomRight, bottomLeft, u1, 0);
|
|
2448
|
+
const bottom0 = samplePosition(topLeft, topRight, bottomRight, bottomLeft, u0, 1);
|
|
2449
|
+
const bottom1 = samplePosition(topLeft, topRight, bottomRight, bottomLeft, u1, 1);
|
|
2450
|
+
const v0 = Math.min(0, (0 - top0.y) / Math.max(bottom0.y - top0.y, 1e-4));
|
|
2451
|
+
const v1 = Math.min(0, (0 - top1.y) / Math.max(bottom1.y - top1.y, 1e-4));
|
|
2452
|
+
const boundary0 = samplePatchVertexAt(gradient, grid, 0, column, topLeft, topRight, bottomRight, bottomLeft, u0, 0, top0.x, top0.y, `top:${column}:${index}:b0`);
|
|
2453
|
+
const boundary1 = samplePatchVertexAt(gradient, grid, 0, column, topLeft, topRight, bottomRight, bottomLeft, u1, 0, top1.x, top1.y, `top:${column}:${index}:b1`);
|
|
2454
|
+
const projected0 = samplePatchVertexAt(gradient, grid, 0, column, topLeft, topRight, bottomRight, bottomLeft, u0, v0, top0.x, 0, `top:${column}:${index}:p0`);
|
|
2455
|
+
const projected1 = samplePatchVertexAt(gradient, grid, 0, column, topLeft, topRight, bottomRight, bottomLeft, u1, v1, top1.x, 0, `top:${column}:${index}:p1`);
|
|
2456
|
+
triangles.push([
|
|
2457
|
+
projected0,
|
|
2458
|
+
projected1,
|
|
2459
|
+
boundary1
|
|
2460
|
+
], [
|
|
2461
|
+
projected0,
|
|
2462
|
+
boundary1,
|
|
2463
|
+
boundary0
|
|
2464
|
+
]);
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
for (let column = 0; column < columns - 1; column += 1) {
|
|
2468
|
+
const row = rows - 2;
|
|
2469
|
+
const topLeft = grid[row][column];
|
|
2470
|
+
const topRight = grid[row][column + 1];
|
|
2471
|
+
const bottomRight = grid[row + 1][column + 1];
|
|
2472
|
+
const bottomLeft = grid[row + 1][column];
|
|
2473
|
+
for (let index = 0; index < subdivisions; index += 1) {
|
|
2474
|
+
const u0 = index / subdivisions;
|
|
2475
|
+
const u1 = (index + 1) / subdivisions;
|
|
2476
|
+
const top0 = samplePosition(topLeft, topRight, bottomRight, bottomLeft, u0, 0);
|
|
2477
|
+
const top1 = samplePosition(topLeft, topRight, bottomRight, bottomLeft, u1, 0);
|
|
2478
|
+
const bottom0 = samplePosition(topLeft, topRight, bottomRight, bottomLeft, u0, 1);
|
|
2479
|
+
const bottom1 = samplePosition(topLeft, topRight, bottomRight, bottomLeft, u1, 1);
|
|
2480
|
+
const v0 = Math.max(1, (height - top0.y) / Math.max(bottom0.y - top0.y, 1e-4));
|
|
2481
|
+
const v1 = Math.max(1, (height - top1.y) / Math.max(bottom1.y - top1.y, 1e-4));
|
|
2482
|
+
const boundary0 = samplePatchVertexAt(gradient, grid, row, column, topLeft, topRight, bottomRight, bottomLeft, u0, 1, bottom0.x, bottom0.y, `bottom:${column}:${index}:b0`);
|
|
2483
|
+
const boundary1 = samplePatchVertexAt(gradient, grid, row, column, topLeft, topRight, bottomRight, bottomLeft, u1, 1, bottom1.x, bottom1.y, `bottom:${column}:${index}:b1`);
|
|
2484
|
+
const projected0 = samplePatchVertexAt(gradient, grid, row, column, topLeft, topRight, bottomRight, bottomLeft, u0, v0, bottom0.x, height, `bottom:${column}:${index}:p0`);
|
|
2485
|
+
const projected1 = samplePatchVertexAt(gradient, grid, row, column, topLeft, topRight, bottomRight, bottomLeft, u1, v1, bottom1.x, height, `bottom:${column}:${index}:p1`);
|
|
2486
|
+
triangles.push([
|
|
2487
|
+
boundary0,
|
|
2488
|
+
boundary1,
|
|
2489
|
+
projected1
|
|
2490
|
+
], [
|
|
2491
|
+
boundary0,
|
|
2492
|
+
projected1,
|
|
2493
|
+
projected0
|
|
2494
|
+
]);
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
for (let row = 0; row < rows - 1; row += 1) {
|
|
2498
|
+
const topLeft = grid[row][0];
|
|
2499
|
+
const topRight = grid[row][1];
|
|
2500
|
+
const bottomRight = grid[row + 1][1];
|
|
2501
|
+
const bottomLeft = grid[row + 1][0];
|
|
2502
|
+
for (let index = 0; index < subdivisions; index += 1) {
|
|
2503
|
+
const v0 = index / subdivisions;
|
|
2504
|
+
const v1 = (index + 1) / subdivisions;
|
|
2505
|
+
const left0 = samplePosition(topLeft, topRight, bottomRight, bottomLeft, 0, v0);
|
|
2506
|
+
const left1 = samplePosition(topLeft, topRight, bottomRight, bottomLeft, 0, v1);
|
|
2507
|
+
const right0 = samplePosition(topLeft, topRight, bottomRight, bottomLeft, 1, v0);
|
|
2508
|
+
const right1 = samplePosition(topLeft, topRight, bottomRight, bottomLeft, 1, v1);
|
|
2509
|
+
const u0 = Math.min(0, (0 - left0.x) / Math.max(right0.x - left0.x, 1e-4));
|
|
2510
|
+
const u1 = Math.min(0, (0 - left1.x) / Math.max(right1.x - left1.x, 1e-4));
|
|
2511
|
+
const boundary0 = samplePatchVertexAt(gradient, grid, row, 0, topLeft, topRight, bottomRight, bottomLeft, 0, v0, left0.x, left0.y, `left:${row}:${index}:b0`);
|
|
2512
|
+
const boundary1 = samplePatchVertexAt(gradient, grid, row, 0, topLeft, topRight, bottomRight, bottomLeft, 0, v1, left1.x, left1.y, `left:${row}:${index}:b1`);
|
|
2513
|
+
const projected0 = samplePatchVertexAt(gradient, grid, row, 0, topLeft, topRight, bottomRight, bottomLeft, u0, v0, 0, left0.y, `left:${row}:${index}:p0`);
|
|
2514
|
+
const projected1 = samplePatchVertexAt(gradient, grid, row, 0, topLeft, topRight, bottomRight, bottomLeft, u1, v1, 0, left1.y, `left:${row}:${index}:p1`);
|
|
2515
|
+
triangles.push([
|
|
2516
|
+
projected0,
|
|
2517
|
+
boundary0,
|
|
2518
|
+
boundary1
|
|
2519
|
+
], [
|
|
2520
|
+
projected0,
|
|
2521
|
+
boundary1,
|
|
2522
|
+
projected1
|
|
2523
|
+
]);
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
for (let row = 0; row < rows - 1; row += 1) {
|
|
2527
|
+
const column = columns - 2;
|
|
2528
|
+
const topLeft = grid[row][column];
|
|
2529
|
+
const topRight = grid[row][column + 1];
|
|
2530
|
+
const bottomRight = grid[row + 1][column + 1];
|
|
2531
|
+
const bottomLeft = grid[row + 1][column];
|
|
2532
|
+
for (let index = 0; index < subdivisions; index += 1) {
|
|
2533
|
+
const v0 = index / subdivisions;
|
|
2534
|
+
const v1 = (index + 1) / subdivisions;
|
|
2535
|
+
const left0 = samplePosition(topLeft, topRight, bottomRight, bottomLeft, 0, v0);
|
|
2536
|
+
const left1 = samplePosition(topLeft, topRight, bottomRight, bottomLeft, 0, v1);
|
|
2537
|
+
const right0 = samplePosition(topLeft, topRight, bottomRight, bottomLeft, 1, v0);
|
|
2538
|
+
const right1 = samplePosition(topLeft, topRight, bottomRight, bottomLeft, 1, v1);
|
|
2539
|
+
const u0 = Math.max(1, (width - left0.x) / Math.max(right0.x - left0.x, 1e-4));
|
|
2540
|
+
const u1 = Math.max(1, (width - left1.x) / Math.max(right1.x - left1.x, 1e-4));
|
|
2541
|
+
const boundary0 = samplePatchVertexAt(gradient, grid, row, column, topLeft, topRight, bottomRight, bottomLeft, 1, v0, right0.x, right0.y, `right:${row}:${index}:b0`);
|
|
2542
|
+
const boundary1 = samplePatchVertexAt(gradient, grid, row, column, topLeft, topRight, bottomRight, bottomLeft, 1, v1, right1.x, right1.y, `right:${row}:${index}:b1`);
|
|
2543
|
+
const projected0 = samplePatchVertexAt(gradient, grid, row, column, topLeft, topRight, bottomRight, bottomLeft, u0, v0, width, right0.y, `right:${row}:${index}:p0`);
|
|
2544
|
+
const projected1 = samplePatchVertexAt(gradient, grid, row, column, topLeft, topRight, bottomRight, bottomLeft, u1, v1, width, right1.y, `right:${row}:${index}:p1`);
|
|
2545
|
+
triangles.push([
|
|
2546
|
+
boundary0,
|
|
2547
|
+
projected0,
|
|
2548
|
+
projected1
|
|
2549
|
+
], [
|
|
2550
|
+
boundary0,
|
|
2551
|
+
projected1,
|
|
2552
|
+
boundary1
|
|
2553
|
+
]);
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
return triangles;
|
|
2557
|
+
}
|
|
2558
|
+
//#endregion
|
|
2559
|
+
//#region src/gradient-transformer/modules/css/ModuleTransformerDiamondGradientToCss.ts
|
|
2560
|
+
const DIAMOND_SAMPLE_COUNT = 96;
|
|
2561
|
+
function getColorStops$1(stops) {
|
|
2562
|
+
return stops.filter((stop) => stop.type === "color-stop" && stop.position != null).sort((a, b) => a.position - b.position);
|
|
2563
|
+
}
|
|
2564
|
+
function sampleColorAtPosition$1(stops, position) {
|
|
2565
|
+
if (stops.length === 0) throw new Error("Cannot sample color from empty diamond gradient stops.");
|
|
2566
|
+
if (stops.length === 1 || position <= stops[0].position) return stops[0].value;
|
|
2567
|
+
const lastStop = stops[stops.length - 1];
|
|
2568
|
+
if (position >= lastStop.position) return lastStop.value;
|
|
2569
|
+
for (let index = 0; index < stops.length - 1; index += 1) {
|
|
2570
|
+
const current = stops[index];
|
|
2571
|
+
const next = stops[index + 1];
|
|
2572
|
+
if (position >= current.position && position <= next.position) {
|
|
2573
|
+
const range = next.position - current.position || 1;
|
|
2574
|
+
const localT = (position - current.position) / range;
|
|
2575
|
+
return formatRgb(interpolate([current.value, next.value], "rgb")(localT));
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
return lastStop.value;
|
|
2579
|
+
}
|
|
2580
|
+
function resolvePosition(position) {
|
|
2581
|
+
if (position.kind === "keywords") return {
|
|
2582
|
+
x: resolveKeywordX(position.x),
|
|
2583
|
+
y: resolveKeywordY(position.y)
|
|
2584
|
+
};
|
|
2585
|
+
return {
|
|
2586
|
+
x: resolveLengthPercentage$1(position.x, 100),
|
|
2587
|
+
y: resolveLengthPercentage$1(position.y, 100)
|
|
2588
|
+
};
|
|
2589
|
+
}
|
|
2590
|
+
function resolveKeywordX(value) {
|
|
2591
|
+
if (value === "left") return 0;
|
|
2592
|
+
if (value === "right") return 100;
|
|
2593
|
+
return 50;
|
|
2594
|
+
}
|
|
2595
|
+
function resolveKeywordY(value) {
|
|
2596
|
+
if (value === "top") return 0;
|
|
2597
|
+
if (value === "bottom") return 100;
|
|
2598
|
+
return 50;
|
|
2599
|
+
}
|
|
2600
|
+
function resolveLengthPercentage$1(value, reference) {
|
|
2601
|
+
if (value.kind === "percent") return value.value / 100 * reference;
|
|
2602
|
+
if (value.unit === "px") return value.value;
|
|
2603
|
+
throw new Error(`Unsupported diamond-gradient length unit for CSS transformer: ${value.unit}`);
|
|
2604
|
+
}
|
|
2605
|
+
function getDistanceToCorner$4(center, corner) {
|
|
2606
|
+
return Math.abs(corner.x - center.x) + Math.abs(corner.y - center.y);
|
|
2607
|
+
}
|
|
2608
|
+
function getCornerDeltas$4(center) {
|
|
2609
|
+
return [
|
|
2610
|
+
{
|
|
2611
|
+
dx: -center.x,
|
|
2612
|
+
dy: -center.y
|
|
2613
|
+
},
|
|
2614
|
+
{
|
|
2615
|
+
dx: 100 - center.x,
|
|
2616
|
+
dy: -center.y
|
|
2617
|
+
},
|
|
2618
|
+
{
|
|
2619
|
+
dx: -center.x,
|
|
2620
|
+
dy: 100 - center.y
|
|
2621
|
+
},
|
|
2622
|
+
{
|
|
2623
|
+
dx: 100 - center.x,
|
|
2624
|
+
dy: 100 - center.y
|
|
2625
|
+
}
|
|
2626
|
+
];
|
|
2627
|
+
}
|
|
2628
|
+
function scaleDiamondRadiiToCorner$2(radiusX, radiusY, dx, dy) {
|
|
2629
|
+
const safeRadiusX = Math.max(radiusX, 1e-4);
|
|
2630
|
+
const safeRadiusY = Math.max(radiusY, 1e-4);
|
|
2631
|
+
const scale = Math.abs(dx) / safeRadiusX + Math.abs(dy) / safeRadiusY;
|
|
2632
|
+
return {
|
|
2633
|
+
x: safeRadiusX * scale,
|
|
2634
|
+
y: safeRadiusY * scale
|
|
2635
|
+
};
|
|
2636
|
+
}
|
|
2637
|
+
function getMaxVisibleDiamondT$2(center, radii) {
|
|
2638
|
+
return Math.max(...[
|
|
2639
|
+
{
|
|
2640
|
+
x: 0,
|
|
2641
|
+
y: 0
|
|
2642
|
+
},
|
|
2643
|
+
{
|
|
2644
|
+
x: 100,
|
|
2645
|
+
y: 0
|
|
2646
|
+
},
|
|
2647
|
+
{
|
|
2648
|
+
x: 0,
|
|
2649
|
+
y: 100
|
|
2650
|
+
},
|
|
2651
|
+
{
|
|
2652
|
+
x: 100,
|
|
2653
|
+
y: 100
|
|
2654
|
+
}
|
|
2655
|
+
].map((corner) => Math.abs(corner.x - center.x) / Math.max(radii.x, 1e-4) + Math.abs(corner.y - center.y) / Math.max(radii.y, 1e-4)));
|
|
2656
|
+
}
|
|
2657
|
+
function resolveDiamondRadii$1(size, shape, center) {
|
|
2658
|
+
if (size.kind === "explicit") {
|
|
2659
|
+
const radiusX = resolveLengthPercentage$1(size.x, 100);
|
|
2660
|
+
const radiusY = size.y ? resolveLengthPercentage$1(size.y, 100) : radiusX;
|
|
2661
|
+
return {
|
|
2662
|
+
x: Math.max(radiusX, 1e-4),
|
|
2663
|
+
y: Math.max(shape === "circle" ? radiusX : radiusY, 1e-4)
|
|
2664
|
+
};
|
|
2665
|
+
}
|
|
2666
|
+
const left = center.x;
|
|
2667
|
+
const right = 100 - center.x;
|
|
2668
|
+
const top = center.y;
|
|
2669
|
+
const bottom = 100 - center.y;
|
|
2670
|
+
if (shape === "circle") {
|
|
2671
|
+
const cornerDistances = [
|
|
2672
|
+
{
|
|
2673
|
+
x: 0,
|
|
2674
|
+
y: 0
|
|
2675
|
+
},
|
|
2676
|
+
{
|
|
2677
|
+
x: 100,
|
|
2678
|
+
y: 0
|
|
2679
|
+
},
|
|
2680
|
+
{
|
|
2681
|
+
x: 0,
|
|
2682
|
+
y: 100
|
|
2683
|
+
},
|
|
2684
|
+
{
|
|
2685
|
+
x: 100,
|
|
2686
|
+
y: 100
|
|
2687
|
+
}
|
|
2688
|
+
].map((corner) => getDistanceToCorner$4(center, corner));
|
|
2689
|
+
if (size.value === "closest-side") {
|
|
2690
|
+
const radius = Math.max(Math.min(left, right, top, bottom), 1e-4);
|
|
2691
|
+
return {
|
|
2692
|
+
x: radius,
|
|
2693
|
+
y: radius
|
|
2694
|
+
};
|
|
2695
|
+
}
|
|
2696
|
+
if (size.value === "farthest-side") {
|
|
2697
|
+
const radius = Math.max(Math.max(left, right, top, bottom), 1e-4);
|
|
2698
|
+
return {
|
|
2699
|
+
x: radius,
|
|
2700
|
+
y: radius
|
|
2701
|
+
};
|
|
2702
|
+
}
|
|
2703
|
+
if (size.value === "closest-corner") {
|
|
2704
|
+
const radius = Math.max(Math.min(...cornerDistances), 1e-4);
|
|
2705
|
+
return {
|
|
2706
|
+
x: radius,
|
|
2707
|
+
y: radius
|
|
2708
|
+
};
|
|
2709
|
+
}
|
|
2710
|
+
const radius = Math.max(Math.max(...cornerDistances), 1e-4);
|
|
2711
|
+
return {
|
|
2712
|
+
x: radius,
|
|
2713
|
+
y: radius
|
|
2714
|
+
};
|
|
2715
|
+
}
|
|
2716
|
+
const closestSideRadiusX = Math.min(left, right);
|
|
2717
|
+
const closestSideRadiusY = Math.min(top, bottom);
|
|
2718
|
+
const farthestSideRadiusX = Math.max(left, right);
|
|
2719
|
+
const farthestSideRadiusY = Math.max(top, bottom);
|
|
2720
|
+
if (size.value === "closest-side") return {
|
|
2721
|
+
x: Math.max(closestSideRadiusX, 1e-4),
|
|
2722
|
+
y: Math.max(closestSideRadiusY, 1e-4)
|
|
2723
|
+
};
|
|
2724
|
+
if (size.value === "farthest-side") return {
|
|
2725
|
+
x: Math.max(farthestSideRadiusX, 1e-4),
|
|
2726
|
+
y: Math.max(farthestSideRadiusY, 1e-4)
|
|
2727
|
+
};
|
|
2728
|
+
const corners = getCornerDeltas$4(center);
|
|
2729
|
+
if (size.value === "closest-corner") return corners.map((corner) => scaleDiamondRadiiToCorner$2(closestSideRadiusX, closestSideRadiusY, corner.dx, corner.dy)).reduce((closest, current) => current.x * current.y < closest.x * closest.y ? current : closest);
|
|
2730
|
+
return corners.map((corner) => scaleDiamondRadiiToCorner$2(farthestSideRadiusX, farthestSideRadiusY, corner.dx, corner.dy)).reduce((farthest, current) => current.x * current.y > farthest.x * farthest.y ? current : farthest);
|
|
2731
|
+
}
|
|
2732
|
+
function formatPoint(value) {
|
|
2733
|
+
return Number(value.toFixed(3)).toString();
|
|
2734
|
+
}
|
|
2735
|
+
function buildDiamondPolygon(center, radii, position) {
|
|
2736
|
+
const x = radii.x * position;
|
|
2737
|
+
const y = radii.y * position;
|
|
2738
|
+
return [
|
|
2739
|
+
`${formatPoint(center.x)} ${formatPoint(center.y - y)}`,
|
|
2740
|
+
`${formatPoint(center.x + x)} ${formatPoint(center.y)}`,
|
|
2741
|
+
`${formatPoint(center.x)} ${formatPoint(center.y + y)}`,
|
|
2742
|
+
`${formatPoint(center.x - x)} ${formatPoint(center.y)}`
|
|
2743
|
+
].join(" ");
|
|
2744
|
+
}
|
|
2745
|
+
function encodeSvgDataUrl$1(svg) {
|
|
2746
|
+
return `url("data:image/svg+xml,${encodeURIComponent(svg)}")`;
|
|
2747
|
+
}
|
|
2748
|
+
var ModuleTransformerDiamondGradientToCss = class {
|
|
2749
|
+
target = "css";
|
|
2750
|
+
gradientType = "diamond-gradient";
|
|
2751
|
+
to(input) {
|
|
2752
|
+
if (!(input instanceof DiamondGradient)) throw new Error("Expected DiamondGradient");
|
|
2753
|
+
const center = resolvePosition(input.config.position);
|
|
2754
|
+
const radii = resolveDiamondRadii$1(input.config.size, input.config.shape, center);
|
|
2755
|
+
const maxVisibleT = getMaxVisibleDiamondT$2(center, radii);
|
|
2756
|
+
const maxT = input.isRepeating ? maxVisibleT : 1;
|
|
2757
|
+
const baseStops = resolveRenderableGradientStops(input, DIAMOND_SAMPLE_COUNT);
|
|
2758
|
+
const stops = getColorStops$1(input.isRepeating ? expandRepeatingStopsTo(baseStops, 0, maxVisibleT) : baseStops);
|
|
2759
|
+
const outerColor = sampleColorAtPosition$1(stops, maxT);
|
|
2760
|
+
const polygons = [];
|
|
2761
|
+
const sampleCount = Math.max(DIAMOND_SAMPLE_COUNT, Math.ceil(DIAMOND_SAMPLE_COUNT * maxT));
|
|
2762
|
+
for (let index = sampleCount; index >= 0; index -= 1) {
|
|
2763
|
+
const position = index / sampleCount * maxT;
|
|
2764
|
+
const color = sampleColorAtPosition$1(stops, position);
|
|
2765
|
+
const points = buildDiamondPolygon(center, radii, position);
|
|
2766
|
+
polygons.push(`<polygon points="${points}" fill="${color}"/>`);
|
|
2767
|
+
}
|
|
2768
|
+
return encodeSvgDataUrl$1([
|
|
2769
|
+
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\" preserveAspectRatio=\"none\">",
|
|
2770
|
+
`<rect width="100" height="100" fill="${outerColor}"/>`,
|
|
2771
|
+
...polygons,
|
|
2772
|
+
"</svg>"
|
|
2773
|
+
].join(""));
|
|
2774
|
+
}
|
|
2775
|
+
};
|
|
2776
|
+
//#endregion
|
|
2777
|
+
//#region src/gradient-transformer/modules/css/ModuleTransformerConicGradientToCss.ts
|
|
2778
|
+
var ModuleTransformerConicGradientToCss = class {
|
|
2779
|
+
target = "css";
|
|
2780
|
+
gradientType = "conic-gradient";
|
|
2781
|
+
to(input) {
|
|
2782
|
+
if (!(input instanceof ConicGradient)) throw new Error("Expected ConicGradient");
|
|
2783
|
+
return input.toString();
|
|
2784
|
+
}
|
|
2785
|
+
};
|
|
2786
|
+
//#endregion
|
|
2787
|
+
//#region src/gradient-transformer/modules/css/ModuleTransformerMeshGradientToCss.ts
|
|
2788
|
+
const CSS_SAMPLE_SIZE = 96;
|
|
2789
|
+
const BICUBIC_SUBDIVISIONS$2 = 24;
|
|
2790
|
+
function formatRgba(color) {
|
|
2791
|
+
return `rgba(${color[0]},${color[1]},${color[2]},${Number(color[3].toFixed(3))})`;
|
|
2792
|
+
}
|
|
2793
|
+
function getBarycentric$1(x, y, a, b, c) {
|
|
2794
|
+
const denominator = (b.y - c.y) * (a.x - c.x) + (c.x - b.x) * (a.y - c.y);
|
|
2795
|
+
if (Math.abs(denominator) < 1e-6) return null;
|
|
2796
|
+
const wA = ((b.y - c.y) * (x - c.x) + (c.x - b.x) * (y - c.y)) / denominator;
|
|
2797
|
+
const wB = ((c.y - a.y) * (x - c.x) + (a.x - c.x) * (y - c.y)) / denominator;
|
|
2798
|
+
const wC = 1 - wA - wB;
|
|
2799
|
+
if (wA < -1e-4 || wB < -1e-4 || wC < -1e-4) return null;
|
|
2800
|
+
return [
|
|
2801
|
+
wA,
|
|
2802
|
+
wB,
|
|
2803
|
+
wC
|
|
2804
|
+
];
|
|
2805
|
+
}
|
|
2806
|
+
function mixTriangleColor$1(weights, a, b, c) {
|
|
2807
|
+
const [wA, wB, wC] = weights;
|
|
2808
|
+
return [
|
|
2809
|
+
Math.round((a.color[0] * wA + b.color[0] * wB + c.color[0] * wC) * 255),
|
|
2810
|
+
Math.round((a.color[1] * wA + b.color[1] * wB + c.color[1] * wC) * 255),
|
|
2811
|
+
Math.round((a.color[2] * wA + b.color[2] * wB + c.color[2] * wC) * 255),
|
|
2812
|
+
a.color[3] * wA + b.color[3] * wB + c.color[3] * wC
|
|
2813
|
+
];
|
|
2814
|
+
}
|
|
2815
|
+
function encodeSvgDataUrl(svg) {
|
|
2816
|
+
return `url("data:image/svg+xml,${encodeURIComponent(svg)}")`;
|
|
2817
|
+
}
|
|
2818
|
+
var ModuleTransformerMeshGradientToCss = class {
|
|
2819
|
+
target = "css";
|
|
2820
|
+
gradientType = "mesh-gradient";
|
|
2821
|
+
to(input) {
|
|
2822
|
+
if (!(input instanceof MeshGradient)) throw new Error("Expected MeshGradient");
|
|
2823
|
+
const vertexMap = buildMeshVertexMap(input, CSS_SAMPLE_SIZE, CSS_SAMPLE_SIZE);
|
|
2824
|
+
const grid = buildRegularMeshGrid(input, vertexMap);
|
|
2825
|
+
const subdivisions = input.config.method === "bicubic" ? BICUBIC_SUBDIVISIONS$2 : 1;
|
|
2826
|
+
const triangles = input.patches.flatMap((patch) => buildPatchTriangles(input, grid, patch, vertexMap, subdivisions));
|
|
2827
|
+
const edgeTriangles = buildMeshEdgeSkirtTriangles(input, grid, CSS_SAMPLE_SIZE, CSS_SAMPLE_SIZE, subdivisions);
|
|
2828
|
+
const renderTriangles = [...triangles, ...edgeTriangles];
|
|
2829
|
+
const rects = [];
|
|
2830
|
+
for (let y = 0; y < CSS_SAMPLE_SIZE; y += 1) for (let x = 0; x < CSS_SAMPLE_SIZE; x += 1) {
|
|
2831
|
+
const sampleX = x + .5;
|
|
2832
|
+
const sampleY = y + .5;
|
|
2833
|
+
let color = null;
|
|
2834
|
+
for (const [a, b, c] of renderTriangles) {
|
|
2835
|
+
const weights = getBarycentric$1(sampleX, sampleY, a, b, c);
|
|
2836
|
+
if (!weights) continue;
|
|
2837
|
+
color = mixTriangleColor$1(weights, a, b, c);
|
|
2838
|
+
break;
|
|
2839
|
+
}
|
|
2840
|
+
if (!color) continue;
|
|
2841
|
+
rects.push(`<rect x="${x}" y="${y}" width="1" height="1" fill="${formatRgba(color)}"/>`);
|
|
2842
|
+
}
|
|
2843
|
+
return encodeSvgDataUrl([
|
|
2844
|
+
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${CSS_SAMPLE_SIZE} ${CSS_SAMPLE_SIZE}" preserveAspectRatio="none" shape-rendering="crispEdges">`,
|
|
2845
|
+
...rects,
|
|
2846
|
+
"</svg>"
|
|
2847
|
+
].join(""));
|
|
2848
|
+
}
|
|
2849
|
+
};
|
|
2850
|
+
//#endregion
|
|
2851
|
+
//#region src/gradient-transformer/modules/canvas/ModuleTransformerLinearGradientToCanvas.ts
|
|
2852
|
+
const toRgb$7 = converter("rgb");
|
|
2853
|
+
function toCanvasColor$2(input) {
|
|
2854
|
+
const color = toRgb$7(input);
|
|
2855
|
+
if (!color) throw new Error(`Failed to convert color: ${input}`);
|
|
2856
|
+
return formatRgb(color);
|
|
2857
|
+
}
|
|
2858
|
+
function getStopRange$2(stops) {
|
|
2859
|
+
const colorStops = stops.filter((stop) => stop.type === "color-stop" && stop.position != null);
|
|
2860
|
+
if (!colorStops.length) return {
|
|
2861
|
+
min: 0,
|
|
2862
|
+
max: 1,
|
|
2863
|
+
stops: []
|
|
2864
|
+
};
|
|
2865
|
+
return {
|
|
2866
|
+
min: Math.min(...colorStops.map((stop) => stop.position)),
|
|
2867
|
+
max: Math.max(...colorStops.map((stop) => stop.position)),
|
|
2868
|
+
stops: colorStops
|
|
2869
|
+
};
|
|
2870
|
+
}
|
|
2871
|
+
function normalizeStops$4(stops, min, max) {
|
|
2872
|
+
const range = max - min || 1;
|
|
2873
|
+
return stops.filter((stop) => stop.type === "color-stop" && stop.position != null).map((stop) => ({
|
|
2874
|
+
...stop,
|
|
2875
|
+
position: (stop.position - min) / range
|
|
2876
|
+
}));
|
|
2877
|
+
}
|
|
2878
|
+
var ModuleTransformerLinearGradientToCanvas = class {
|
|
2879
|
+
target = "canvas-2d";
|
|
2880
|
+
gradientType = "linear-gradient";
|
|
2881
|
+
to(input) {
|
|
2882
|
+
const gradient = input;
|
|
2883
|
+
return { draw: (ctx, width, height) => {
|
|
2884
|
+
const angle = gradient.config.angle;
|
|
2885
|
+
const dirX = Math.sin(angle);
|
|
2886
|
+
const dirY = -Math.cos(angle);
|
|
2887
|
+
const centerX = width / 2;
|
|
2888
|
+
const centerY = height / 2;
|
|
2889
|
+
const lineLength = Math.abs(width * dirX) + Math.abs(height * dirY);
|
|
2890
|
+
let startX = centerX - dirX * lineLength / 2;
|
|
2891
|
+
let startY = centerY - dirY * lineLength / 2;
|
|
2892
|
+
let endX = centerX + dirX * lineLength / 2;
|
|
2893
|
+
let endY = centerY + dirY * lineLength / 2;
|
|
2894
|
+
const { min, max, stops } = getStopRange$2(resolveRenderableGradientStops(gradient));
|
|
2895
|
+
let normalizedStops = stops;
|
|
2896
|
+
if (min < 0 || max > 1) {
|
|
2897
|
+
const vx = endX - startX;
|
|
2898
|
+
const vy = endY - startY;
|
|
2899
|
+
const baseStartX = startX;
|
|
2900
|
+
const baseStartY = startY;
|
|
2901
|
+
startX = baseStartX + vx * min;
|
|
2902
|
+
startY = baseStartY + vy * min;
|
|
2903
|
+
endX = baseStartX + vx * max;
|
|
2904
|
+
endY = baseStartY + vy * max;
|
|
2905
|
+
normalizedStops = normalizeStops$4(stops, min, max);
|
|
2906
|
+
}
|
|
2907
|
+
const canvasGradient = ctx.createLinearGradient(startX, startY, endX, endY);
|
|
2908
|
+
for (const stop of normalizedStops) canvasGradient.addColorStop(stop.position, toCanvasColor$2(stop.value));
|
|
2909
|
+
ctx.clearRect(0, 0, width, height);
|
|
2910
|
+
ctx.fillStyle = canvasGradient;
|
|
2911
|
+
ctx.fillRect(0, 0, width, height);
|
|
2912
|
+
} };
|
|
2913
|
+
}
|
|
2914
|
+
};
|
|
2915
|
+
//#endregion
|
|
2916
|
+
//#region src/gradient-transformer/modules/canvas/ModuleTransformerRadialGradientToCanvas.ts
|
|
2917
|
+
const toRgb$6 = converter("rgb");
|
|
2918
|
+
const RADIAL_GRADIENT_SAMPLE_COUNT = 128;
|
|
2919
|
+
function toCanvasColor$1(input) {
|
|
2920
|
+
const color = toRgb$6(input);
|
|
2921
|
+
if (!color) throw new Error(`Failed to convert color: ${input}`);
|
|
1847
2922
|
return formatRgb(color);
|
|
1848
2923
|
}
|
|
1849
2924
|
function getStopRange$1(stops) {
|
|
@@ -1859,7 +2934,7 @@ function getStopRange$1(stops) {
|
|
|
1859
2934
|
stops: colorStops
|
|
1860
2935
|
};
|
|
1861
2936
|
}
|
|
1862
|
-
function normalizeStops$
|
|
2937
|
+
function normalizeStops$3(stops, min, max) {
|
|
1863
2938
|
const range = max - min || 1;
|
|
1864
2939
|
return stops.map((stop) => ({
|
|
1865
2940
|
...stop,
|
|
@@ -1872,12 +2947,12 @@ function getDistanceToSide$1(center, width, height, side) {
|
|
|
1872
2947
|
if (side === "top") return center.y;
|
|
1873
2948
|
return height - center.y;
|
|
1874
2949
|
}
|
|
1875
|
-
function getDistanceToCorner$
|
|
2950
|
+
function getDistanceToCorner$3(center, corner) {
|
|
1876
2951
|
const dx = corner.x - center.x;
|
|
1877
2952
|
const dy = corner.y - center.y;
|
|
1878
2953
|
return Math.sqrt(dx * dx + dy * dy);
|
|
1879
2954
|
}
|
|
1880
|
-
function getCornerDeltas$
|
|
2955
|
+
function getCornerDeltas$3(center, width, height) {
|
|
1881
2956
|
return [
|
|
1882
2957
|
{
|
|
1883
2958
|
dx: -center.x,
|
|
@@ -1920,9 +2995,9 @@ var ModuleTransformerRadialGradientToCanvas = class {
|
|
|
1920
2995
|
let normalizedStops = stops;
|
|
1921
2996
|
let innerFactor = 0;
|
|
1922
2997
|
let outerFactor = gradient.isRepeating ? maxVisibleT : 1;
|
|
1923
|
-
if (gradient.isRepeating) normalizedStops = normalizeStops$
|
|
2998
|
+
if (gradient.isRepeating) normalizedStops = normalizeStops$3(stops, 0, maxVisibleT);
|
|
1924
2999
|
else if (min < 0 || max > 1) {
|
|
1925
|
-
normalizedStops = normalizeStops$
|
|
3000
|
+
normalizedStops = normalizeStops$3(stops, min, max);
|
|
1926
3001
|
innerFactor = min;
|
|
1927
3002
|
outerFactor = max;
|
|
1928
3003
|
}
|
|
@@ -1931,7 +3006,7 @@ var ModuleTransformerRadialGradientToCanvas = class {
|
|
|
1931
3006
|
const innerRadius = Math.max(0, baseRadius * innerFactor);
|
|
1932
3007
|
const outerRadius = Math.max(innerRadius + 1e-4, baseRadius * outerFactor);
|
|
1933
3008
|
const g = ctx.createRadialGradient(center.x, center.y, innerRadius, center.x, center.y, outerRadius);
|
|
1934
|
-
for (const stop of normalizedStops) g.addColorStop(stop.position, toCanvasColor(stop.value));
|
|
3009
|
+
for (const stop of normalizedStops) g.addColorStop(stop.position, toCanvasColor$1(stop.value));
|
|
1935
3010
|
ctx.fillStyle = g;
|
|
1936
3011
|
ctx.fillRect(0, 0, width, height);
|
|
1937
3012
|
return;
|
|
@@ -1945,7 +3020,7 @@ var ModuleTransformerRadialGradientToCanvas = class {
|
|
|
1945
3020
|
ctx.translate(center.x, center.y);
|
|
1946
3021
|
ctx.scale(scaleX, scaleY);
|
|
1947
3022
|
const g = ctx.createRadialGradient(0, 0, innerRadius, 0, 0, scaledOuterRadius);
|
|
1948
|
-
for (const stop of normalizedStops) g.addColorStop(stop.position, toCanvasColor(stop.value));
|
|
3023
|
+
for (const stop of normalizedStops) g.addColorStop(stop.position, toCanvasColor$1(stop.value));
|
|
1949
3024
|
ctx.fillStyle = g;
|
|
1950
3025
|
const drawRadius = scaledOuterRadius + 2;
|
|
1951
3026
|
ctx.fillRect(-drawRadius / scaleX * 2, -drawRadius / scaleY * 2, drawRadius / scaleX * 4, drawRadius / scaleY * 4);
|
|
@@ -2003,7 +3078,7 @@ var ModuleTransformerRadialGradientToCanvas = class {
|
|
|
2003
3078
|
x: width,
|
|
2004
3079
|
y: height
|
|
2005
3080
|
}
|
|
2006
|
-
].map((corner) => getDistanceToCorner$
|
|
3081
|
+
].map((corner) => getDistanceToCorner$3(center, corner));
|
|
2007
3082
|
if (size.value === "closest-side") {
|
|
2008
3083
|
const radius = Math.min(left, right, top, bottom);
|
|
2009
3084
|
return {
|
|
@@ -2043,7 +3118,7 @@ var ModuleTransformerRadialGradientToCanvas = class {
|
|
|
2043
3118
|
x: Math.max(farthestSideRadiusX, 1e-4),
|
|
2044
3119
|
y: Math.max(farthestSideRadiusY, 1e-4)
|
|
2045
3120
|
};
|
|
2046
|
-
const corners = getCornerDeltas$
|
|
3121
|
+
const corners = getCornerDeltas$3(center, width, height);
|
|
2047
3122
|
if (size.value === "closest-corner") return corners.map((corner) => scaleEllipseRadiiToCorner$1(closestSideRadiusX, closestSideRadiusY, corner.dx, corner.dy)).reduce((closest, current) => {
|
|
2048
3123
|
const closestArea = closest.x * closest.y;
|
|
2049
3124
|
return current.x * current.y < closestArea ? current : closest;
|
|
@@ -2060,8 +3135,241 @@ var ModuleTransformerRadialGradientToCanvas = class {
|
|
|
2060
3135
|
}
|
|
2061
3136
|
};
|
|
2062
3137
|
//#endregion
|
|
3138
|
+
//#region src/gradient-transformer/modules/canvas/ModuleTransformerDiamondGradientToCanvas.ts
|
|
3139
|
+
const toRgb$5 = converter("rgb");
|
|
3140
|
+
const DIAMOND_GRADIENT_SAMPLE_COUNT = 128;
|
|
3141
|
+
const DIAMOND_COLOR_LOOKUP_SIZE = 1024;
|
|
3142
|
+
function toCanvasColor(input) {
|
|
3143
|
+
const color = toRgb$5(input);
|
|
3144
|
+
if (!color) throw new Error(`Failed to convert color: ${input}`);
|
|
3145
|
+
return [
|
|
3146
|
+
Math.round((color.r ?? 0) * 255),
|
|
3147
|
+
Math.round((color.g ?? 0) * 255),
|
|
3148
|
+
Math.round((color.b ?? 0) * 255),
|
|
3149
|
+
Math.round((color.alpha ?? 1) * 255)
|
|
3150
|
+
];
|
|
3151
|
+
}
|
|
3152
|
+
function getColorStops(stops) {
|
|
3153
|
+
return stops.filter((stop) => stop.type === "color-stop" && stop.position != null).sort((a, b) => a.position - b.position);
|
|
3154
|
+
}
|
|
3155
|
+
function sampleColorAtPosition(stops, position) {
|
|
3156
|
+
if (stops.length === 0) throw new Error("Cannot sample color from empty diamond gradient stops.");
|
|
3157
|
+
if (stops.length === 1 || position <= stops[0].position) return toCanvasColor(stops[0].value);
|
|
3158
|
+
const lastStop = stops[stops.length - 1];
|
|
3159
|
+
if (position >= lastStop.position) return toCanvasColor(lastStop.value);
|
|
3160
|
+
for (let index = 0; index < stops.length - 1; index += 1) {
|
|
3161
|
+
const current = stops[index];
|
|
3162
|
+
const next = stops[index + 1];
|
|
3163
|
+
if (position >= current.position && position <= next.position) {
|
|
3164
|
+
const range = next.position - current.position || 1;
|
|
3165
|
+
const localT = (position - current.position) / range;
|
|
3166
|
+
const currentColor = toCanvasColor(current.value);
|
|
3167
|
+
const nextColor = toCanvasColor(next.value);
|
|
3168
|
+
return [
|
|
3169
|
+
Math.round(currentColor[0] + (nextColor[0] - currentColor[0]) * localT),
|
|
3170
|
+
Math.round(currentColor[1] + (nextColor[1] - currentColor[1]) * localT),
|
|
3171
|
+
Math.round(currentColor[2] + (nextColor[2] - currentColor[2]) * localT),
|
|
3172
|
+
Math.round(currentColor[3] + (nextColor[3] - currentColor[3]) * localT)
|
|
3173
|
+
];
|
|
3174
|
+
}
|
|
3175
|
+
}
|
|
3176
|
+
return toCanvasColor(lastStop.value);
|
|
3177
|
+
}
|
|
3178
|
+
function buildColorLookup(stops, maxT) {
|
|
3179
|
+
const lookup = new Uint8ClampedArray(DIAMOND_COLOR_LOOKUP_SIZE * 4);
|
|
3180
|
+
for (let index = 0; index < DIAMOND_COLOR_LOOKUP_SIZE; index += 1) {
|
|
3181
|
+
const color = sampleColorAtPosition(stops, index / (DIAMOND_COLOR_LOOKUP_SIZE - 1) * maxT);
|
|
3182
|
+
const offset = index * 4;
|
|
3183
|
+
lookup[offset] = color[0];
|
|
3184
|
+
lookup[offset + 1] = color[1];
|
|
3185
|
+
lookup[offset + 2] = color[2];
|
|
3186
|
+
lookup[offset + 3] = color[3];
|
|
3187
|
+
}
|
|
3188
|
+
return lookup;
|
|
3189
|
+
}
|
|
3190
|
+
function getMaxVisibleDiamondT$1(center, radii, width, height) {
|
|
3191
|
+
return Math.max(...[
|
|
3192
|
+
{
|
|
3193
|
+
x: 0,
|
|
3194
|
+
y: 0
|
|
3195
|
+
},
|
|
3196
|
+
{
|
|
3197
|
+
x: width,
|
|
3198
|
+
y: 0
|
|
3199
|
+
},
|
|
3200
|
+
{
|
|
3201
|
+
x: 0,
|
|
3202
|
+
y: height
|
|
3203
|
+
},
|
|
3204
|
+
{
|
|
3205
|
+
x: width,
|
|
3206
|
+
y: height
|
|
3207
|
+
}
|
|
3208
|
+
].map((corner) => Math.abs(corner.x - center.x) / Math.max(radii.x, 1e-4) + Math.abs(corner.y - center.y) / Math.max(radii.y, 1e-4)));
|
|
3209
|
+
}
|
|
3210
|
+
function getDistanceToCorner$2(center, corner) {
|
|
3211
|
+
return Math.abs(corner.x - center.x) + Math.abs(corner.y - center.y);
|
|
3212
|
+
}
|
|
3213
|
+
function getCornerDeltas$2(center, width, height) {
|
|
3214
|
+
return [
|
|
3215
|
+
{
|
|
3216
|
+
dx: -center.x,
|
|
3217
|
+
dy: -center.y
|
|
3218
|
+
},
|
|
3219
|
+
{
|
|
3220
|
+
dx: width - center.x,
|
|
3221
|
+
dy: -center.y
|
|
3222
|
+
},
|
|
3223
|
+
{
|
|
3224
|
+
dx: -center.x,
|
|
3225
|
+
dy: height - center.y
|
|
3226
|
+
},
|
|
3227
|
+
{
|
|
3228
|
+
dx: width - center.x,
|
|
3229
|
+
dy: height - center.y
|
|
3230
|
+
}
|
|
3231
|
+
];
|
|
3232
|
+
}
|
|
3233
|
+
function scaleDiamondRadiiToCorner$1(radiusX, radiusY, dx, dy) {
|
|
3234
|
+
const safeRadiusX = Math.max(radiusX, 1e-4);
|
|
3235
|
+
const safeRadiusY = Math.max(radiusY, 1e-4);
|
|
3236
|
+
const scale = Math.abs(dx) / safeRadiusX + Math.abs(dy) / safeRadiusY;
|
|
3237
|
+
return {
|
|
3238
|
+
x: safeRadiusX * scale,
|
|
3239
|
+
y: safeRadiusY * scale
|
|
3240
|
+
};
|
|
3241
|
+
}
|
|
3242
|
+
var ModuleTransformerDiamondGradientToCanvas = class {
|
|
3243
|
+
target = "canvas-2d";
|
|
3244
|
+
gradientType = "diamond-gradient";
|
|
3245
|
+
to(input) {
|
|
3246
|
+
if (!(input instanceof DiamondGradient)) throw new Error("Expected DiamondGradient");
|
|
3247
|
+
const gradient = input;
|
|
3248
|
+
return { draw: (ctx, width, height) => {
|
|
3249
|
+
const center = this._resolveCenter(gradient.config.position, width, height);
|
|
3250
|
+
const radii = this._resolveDiamondRadii(gradient.config.size, gradient.config.shape, center, width, height);
|
|
3251
|
+
const maxVisibleT = getMaxVisibleDiamondT$1(center, radii, width, height);
|
|
3252
|
+
const baseStops = resolveRenderableGradientStops(gradient, DIAMOND_GRADIENT_SAMPLE_COUNT);
|
|
3253
|
+
const colorLookup = buildColorLookup(getColorStops(gradient.isRepeating ? expandRepeatingStopsTo(baseStops, 0, maxVisibleT) : baseStops), maxVisibleT);
|
|
3254
|
+
const imageData = ctx.createImageData(width, height);
|
|
3255
|
+
const data = imageData.data;
|
|
3256
|
+
for (let y = 0; y < height; y += 1) for (let x = 0; x < width; x += 1) {
|
|
3257
|
+
const t = Math.abs(x - center.x) / Math.max(radii.x, 1e-4) + Math.abs(y - center.y) / Math.max(radii.y, 1e-4);
|
|
3258
|
+
const lookupOffset = Math.min(DIAMOND_COLOR_LOOKUP_SIZE - 1, Math.max(0, Math.round(t / Math.max(maxVisibleT, 1e-4) * (DIAMOND_COLOR_LOOKUP_SIZE - 1)))) * 4;
|
|
3259
|
+
const offset = (y * width + x) * 4;
|
|
3260
|
+
data[offset] = colorLookup[lookupOffset];
|
|
3261
|
+
data[offset + 1] = colorLookup[lookupOffset + 1];
|
|
3262
|
+
data[offset + 2] = colorLookup[lookupOffset + 2];
|
|
3263
|
+
data[offset + 3] = colorLookup[lookupOffset + 3];
|
|
3264
|
+
}
|
|
3265
|
+
ctx.putImageData(imageData, 0, 0);
|
|
3266
|
+
} };
|
|
3267
|
+
}
|
|
3268
|
+
_resolveCenter(position, width, height) {
|
|
3269
|
+
if (position.kind === "keywords") return {
|
|
3270
|
+
x: this._resolveKeywordX(position.x, width),
|
|
3271
|
+
y: this._resolveKeywordY(position.y, height)
|
|
3272
|
+
};
|
|
3273
|
+
return {
|
|
3274
|
+
x: this._resolve(position.x, width),
|
|
3275
|
+
y: this._resolve(position.y, height)
|
|
3276
|
+
};
|
|
3277
|
+
}
|
|
3278
|
+
_resolveKeywordX(value, width) {
|
|
3279
|
+
if (value === "left") return 0;
|
|
3280
|
+
if (value === "center") return width / 2;
|
|
3281
|
+
return width;
|
|
3282
|
+
}
|
|
3283
|
+
_resolveKeywordY(value, height) {
|
|
3284
|
+
if (value === "top") return 0;
|
|
3285
|
+
if (value === "center") return height / 2;
|
|
3286
|
+
return height;
|
|
3287
|
+
}
|
|
3288
|
+
_resolveDiamondRadii(size, shape, center, width, height) {
|
|
3289
|
+
if (size.kind === "explicit") {
|
|
3290
|
+
const radiusX = this._resolve(size.x, width);
|
|
3291
|
+
const radiusY = size.y ? this._resolve(size.y, height) : radiusX;
|
|
3292
|
+
return {
|
|
3293
|
+
x: Math.max(radiusX, 1e-4),
|
|
3294
|
+
y: Math.max(shape === "circle" ? radiusX : radiusY, 1e-4)
|
|
3295
|
+
};
|
|
3296
|
+
}
|
|
3297
|
+
const left = center.x;
|
|
3298
|
+
const right = width - center.x;
|
|
3299
|
+
const top = center.y;
|
|
3300
|
+
const bottom = height - center.y;
|
|
3301
|
+
if (shape === "circle") {
|
|
3302
|
+
const cornerDistances = [
|
|
3303
|
+
{
|
|
3304
|
+
x: 0,
|
|
3305
|
+
y: 0
|
|
3306
|
+
},
|
|
3307
|
+
{
|
|
3308
|
+
x: width,
|
|
3309
|
+
y: 0
|
|
3310
|
+
},
|
|
3311
|
+
{
|
|
3312
|
+
x: 0,
|
|
3313
|
+
y: height
|
|
3314
|
+
},
|
|
3315
|
+
{
|
|
3316
|
+
x: width,
|
|
3317
|
+
y: height
|
|
3318
|
+
}
|
|
3319
|
+
].map((corner) => getDistanceToCorner$2(center, corner));
|
|
3320
|
+
if (size.value === "closest-side") {
|
|
3321
|
+
const radius = Math.max(Math.min(left, right, top, bottom), 1e-4);
|
|
3322
|
+
return {
|
|
3323
|
+
x: radius,
|
|
3324
|
+
y: radius
|
|
3325
|
+
};
|
|
3326
|
+
}
|
|
3327
|
+
if (size.value === "farthest-side") {
|
|
3328
|
+
const radius = Math.max(Math.max(left, right, top, bottom), 1e-4);
|
|
3329
|
+
return {
|
|
3330
|
+
x: radius,
|
|
3331
|
+
y: radius
|
|
3332
|
+
};
|
|
3333
|
+
}
|
|
3334
|
+
if (size.value === "closest-corner") {
|
|
3335
|
+
const radius = Math.max(Math.min(...cornerDistances), 1e-4);
|
|
3336
|
+
return {
|
|
3337
|
+
x: radius,
|
|
3338
|
+
y: radius
|
|
3339
|
+
};
|
|
3340
|
+
}
|
|
3341
|
+
const radius = Math.max(Math.max(...cornerDistances), 1e-4);
|
|
3342
|
+
return {
|
|
3343
|
+
x: radius,
|
|
3344
|
+
y: radius
|
|
3345
|
+
};
|
|
3346
|
+
}
|
|
3347
|
+
const closestSideRadiusX = Math.min(left, right);
|
|
3348
|
+
const closestSideRadiusY = Math.min(top, bottom);
|
|
3349
|
+
const farthestSideRadiusX = Math.max(left, right);
|
|
3350
|
+
const farthestSideRadiusY = Math.max(top, bottom);
|
|
3351
|
+
if (size.value === "closest-side") return {
|
|
3352
|
+
x: Math.max(closestSideRadiusX, 1e-4),
|
|
3353
|
+
y: Math.max(closestSideRadiusY, 1e-4)
|
|
3354
|
+
};
|
|
3355
|
+
if (size.value === "farthest-side") return {
|
|
3356
|
+
x: Math.max(farthestSideRadiusX, 1e-4),
|
|
3357
|
+
y: Math.max(farthestSideRadiusY, 1e-4)
|
|
3358
|
+
};
|
|
3359
|
+
const corners = getCornerDeltas$2(center, width, height);
|
|
3360
|
+
if (size.value === "closest-corner") return corners.map((corner) => scaleDiamondRadiiToCorner$1(closestSideRadiusX, closestSideRadiusY, corner.dx, corner.dy)).reduce((closest, current) => current.x * current.y < closest.x * closest.y ? current : closest);
|
|
3361
|
+
return corners.map((corner) => scaleDiamondRadiiToCorner$1(farthestSideRadiusX, farthestSideRadiusY, corner.dx, corner.dy)).reduce((farthest, current) => current.x * current.y > farthest.x * farthest.y ? current : farthest);
|
|
3362
|
+
}
|
|
3363
|
+
_resolve(value, size) {
|
|
3364
|
+
if (value.kind === "percent") return value.value / 100 * size;
|
|
3365
|
+
if (value.unit === "px") return value.value;
|
|
3366
|
+
throw new Error(`Unsupported diamond-gradient length unit for Canvas transformer: ${value.unit}`);
|
|
3367
|
+
}
|
|
3368
|
+
};
|
|
3369
|
+
//#endregion
|
|
2063
3370
|
//#region src/gradient-transformer/modules/canvas/ModuleTransformerConicGradientToCanvas.ts
|
|
2064
|
-
const
|
|
3371
|
+
const CONIC_GRADIENT_SAMPLE_COUNT = 128;
|
|
3372
|
+
const toRgb$4 = converter("rgb");
|
|
2065
3373
|
var ModuleTransformerConicGradientToCanvas = class {
|
|
2066
3374
|
target = "canvas-2d";
|
|
2067
3375
|
gradientType = "conic-gradient";
|
|
@@ -2072,7 +3380,8 @@ var ModuleTransformerConicGradientToCanvas = class {
|
|
|
2072
3380
|
const data = imageData.data;
|
|
2073
3381
|
const { x: cx, y: cy } = this._resolvePosition(gradient.config.position, width, height);
|
|
2074
3382
|
const from = this._toRad(gradient.config.from);
|
|
2075
|
-
const
|
|
3383
|
+
const renderStops = resolveRenderableGradientStops(gradient, CONIC_GRADIENT_SAMPLE_COUNT);
|
|
3384
|
+
const stops = this._normalizeStops(renderStops);
|
|
2076
3385
|
if (stops.length === 0) {
|
|
2077
3386
|
ctx.putImageData(imageData, 0, 0);
|
|
2078
3387
|
return;
|
|
@@ -2160,7 +3469,7 @@ var ModuleTransformerConicGradientToCanvas = class {
|
|
|
2160
3469
|
};
|
|
2161
3470
|
}
|
|
2162
3471
|
_parseColor(input) {
|
|
2163
|
-
const color = toRgb$
|
|
3472
|
+
const color = toRgb$4(input);
|
|
2164
3473
|
if (!color) throw new Error(`Failed to convert color: ${input}`);
|
|
2165
3474
|
return {
|
|
2166
3475
|
r: Math.round((color.r ?? 0) * 255),
|
|
@@ -2174,11 +3483,74 @@ var ModuleTransformerConicGradientToCanvas = class {
|
|
|
2174
3483
|
}
|
|
2175
3484
|
};
|
|
2176
3485
|
//#endregion
|
|
3486
|
+
//#region src/gradient-transformer/modules/canvas/ModuleTransformerMeshGradientToCanvas.ts
|
|
3487
|
+
const BICUBIC_SUBDIVISIONS$1 = 24;
|
|
3488
|
+
function getBarycentric(x, y, a, b, c) {
|
|
3489
|
+
const denominator = (b.y - c.y) * (a.x - c.x) + (c.x - b.x) * (a.y - c.y);
|
|
3490
|
+
if (Math.abs(denominator) < 1e-6) return null;
|
|
3491
|
+
const wA = ((b.y - c.y) * (x - c.x) + (c.x - b.x) * (y - c.y)) / denominator;
|
|
3492
|
+
const wB = ((c.y - a.y) * (x - c.x) + (a.x - c.x) * (y - c.y)) / denominator;
|
|
3493
|
+
const wC = 1 - wA - wB;
|
|
3494
|
+
const epsilon = -1e-4;
|
|
3495
|
+
if (wA < epsilon || wB < epsilon || wC < epsilon) return null;
|
|
3496
|
+
return [
|
|
3497
|
+
wA,
|
|
3498
|
+
wB,
|
|
3499
|
+
wC
|
|
3500
|
+
];
|
|
3501
|
+
}
|
|
3502
|
+
function mixTriangleColor(weights, a, b, c) {
|
|
3503
|
+
const [wA, wB, wC] = weights;
|
|
3504
|
+
return [
|
|
3505
|
+
Math.round((a.color[0] * wA + b.color[0] * wB + c.color[0] * wC) * 255),
|
|
3506
|
+
Math.round((a.color[1] * wA + b.color[1] * wB + c.color[1] * wC) * 255),
|
|
3507
|
+
Math.round((a.color[2] * wA + b.color[2] * wB + c.color[2] * wC) * 255),
|
|
3508
|
+
Math.round((a.color[3] * wA + b.color[3] * wB + c.color[3] * wC) * 255)
|
|
3509
|
+
];
|
|
3510
|
+
}
|
|
3511
|
+
function fillTriangle(data, width, height, a, b, c) {
|
|
3512
|
+
const minX = Math.max(0, Math.floor(Math.min(a.x, b.x, c.x)));
|
|
3513
|
+
const maxX = Math.min(width - 1, Math.ceil(Math.max(a.x, b.x, c.x)));
|
|
3514
|
+
const minY = Math.max(0, Math.floor(Math.min(a.y, b.y, c.y)));
|
|
3515
|
+
const maxY = Math.min(height - 1, Math.ceil(Math.max(a.y, b.y, c.y)));
|
|
3516
|
+
for (let y = minY; y <= maxY; y += 1) for (let x = minX; x <= maxX; x += 1) {
|
|
3517
|
+
const weights = getBarycentric(x + .5, y + .5, a, b, c);
|
|
3518
|
+
if (!weights) continue;
|
|
3519
|
+
const color = mixTriangleColor(weights, a, b, c);
|
|
3520
|
+
const offset = (y * width + x) * 4;
|
|
3521
|
+
data[offset] = color[0];
|
|
3522
|
+
data[offset + 1] = color[1];
|
|
3523
|
+
data[offset + 2] = color[2];
|
|
3524
|
+
data[offset + 3] = color[3];
|
|
3525
|
+
}
|
|
3526
|
+
}
|
|
3527
|
+
var ModuleTransformerMeshGradientToCanvas = class {
|
|
3528
|
+
target = "canvas-2d";
|
|
3529
|
+
gradientType = "mesh-gradient";
|
|
3530
|
+
to(input) {
|
|
3531
|
+
if (!(input instanceof MeshGradient)) throw new Error("Expected MeshGradient");
|
|
3532
|
+
const gradient = input;
|
|
3533
|
+
return { draw: (ctx, width, height) => {
|
|
3534
|
+
const imageData = ctx.createImageData(width, height);
|
|
3535
|
+
const vertexMap = buildMeshVertexMap(gradient, width, height);
|
|
3536
|
+
const grid = buildRegularMeshGrid(gradient, vertexMap);
|
|
3537
|
+
const subdivisions = gradient.config.method === "bicubic" ? BICUBIC_SUBDIVISIONS$1 : 1;
|
|
3538
|
+
const edgeTriangles = buildMeshEdgeSkirtTriangles(gradient, grid, width, height, subdivisions);
|
|
3539
|
+
for (const patch of gradient.patches) {
|
|
3540
|
+
const triangles = buildPatchTriangles(gradient, grid, patch, vertexMap, subdivisions);
|
|
3541
|
+
for (const [a, b, c] of triangles) fillTriangle(imageData.data, width, height, a, b, c);
|
|
3542
|
+
}
|
|
3543
|
+
for (const [a, b, c] of edgeTriangles) fillTriangle(imageData.data, width, height, a, b, c);
|
|
3544
|
+
ctx.putImageData(imageData, 0, 0);
|
|
3545
|
+
} };
|
|
3546
|
+
}
|
|
3547
|
+
};
|
|
3548
|
+
//#endregion
|
|
2177
3549
|
//#region src/gradient-transformer/modules/webgl/ModuleTransformerLinearGradientToWebgl.ts
|
|
2178
|
-
const toRgb$
|
|
2179
|
-
const MAX_STOPS$
|
|
2180
|
-
function toWebGLColor$
|
|
2181
|
-
const color = toRgb$
|
|
3550
|
+
const toRgb$3 = converter("rgb");
|
|
3551
|
+
const MAX_STOPS$3 = 128;
|
|
3552
|
+
function toWebGLColor$3(input) {
|
|
3553
|
+
const color = toRgb$3(input);
|
|
2182
3554
|
if (!color) throw new Error(`Failed to convert color: ${input}`);
|
|
2183
3555
|
return [
|
|
2184
3556
|
color.r ?? 0,
|
|
@@ -2200,14 +3572,14 @@ function getStopRange(stops) {
|
|
|
2200
3572
|
stops: colorStops
|
|
2201
3573
|
};
|
|
2202
3574
|
}
|
|
2203
|
-
function normalizeStops$
|
|
3575
|
+
function normalizeStops$2(stops, min, max) {
|
|
2204
3576
|
const range = max - min || 1;
|
|
2205
3577
|
return stops.filter((stop) => stop.type === "color-stop" && stop.position != null).map((stop) => ({
|
|
2206
3578
|
...stop,
|
|
2207
3579
|
position: (stop.position - min) / range
|
|
2208
3580
|
}));
|
|
2209
3581
|
}
|
|
2210
|
-
function createShader$
|
|
3582
|
+
function createShader$4(gl, type, source) {
|
|
2211
3583
|
const shader = gl.createShader(type);
|
|
2212
3584
|
if (!shader) throw new Error("Failed to create WebGL shader.");
|
|
2213
3585
|
gl.shaderSource(shader, source);
|
|
@@ -2219,9 +3591,9 @@ function createShader$1(gl, type, source) {
|
|
|
2219
3591
|
}
|
|
2220
3592
|
return shader;
|
|
2221
3593
|
}
|
|
2222
|
-
function createProgram$
|
|
2223
|
-
const vertexShader = createShader$
|
|
2224
|
-
const fragmentShader = createShader$
|
|
3594
|
+
function createProgram$4(gl, vertexSource, fragmentSource) {
|
|
3595
|
+
const vertexShader = createShader$4(gl, gl.VERTEX_SHADER, vertexSource);
|
|
3596
|
+
const fragmentShader = createShader$4(gl, gl.FRAGMENT_SHADER, fragmentSource);
|
|
2225
3597
|
const program = gl.createProgram();
|
|
2226
3598
|
if (!program) throw new Error("Failed to create WebGL program.");
|
|
2227
3599
|
gl.attachShader(program, vertexShader);
|
|
@@ -2234,15 +3606,15 @@ function createProgram$1(gl, vertexSource, fragmentSource) {
|
|
|
2234
3606
|
}
|
|
2235
3607
|
return program;
|
|
2236
3608
|
}
|
|
2237
|
-
function getColorStopCount$
|
|
3609
|
+
function getColorStopCount$3(stops) {
|
|
2238
3610
|
return stops.filter((stop) => stop.type === "color-stop").length;
|
|
2239
3611
|
}
|
|
2240
|
-
function getWebGLSampleCount$
|
|
2241
|
-
const colorStopCount = getColorStopCount$
|
|
3612
|
+
function getWebGLSampleCount$3(gradient, maxStops) {
|
|
3613
|
+
const colorStopCount = getColorStopCount$3(gradient.stops);
|
|
2242
3614
|
const segmentCount = Math.max(1, colorStopCount - 1);
|
|
2243
3615
|
return Math.max(2, Math.floor((maxStops - 1) / segmentCount));
|
|
2244
3616
|
}
|
|
2245
|
-
function getColorAtPosition$
|
|
3617
|
+
function getColorAtPosition$3(stops, position) {
|
|
2246
3618
|
const colorStops = stops.filter((stop) => stop.type === "color-stop" && stop.position != null).sort((a, b) => a.position - b.position);
|
|
2247
3619
|
if (colorStops.length === 0) throw new Error("Cannot sample color from empty gradient stops.");
|
|
2248
3620
|
if (position <= colorStops[0].position) return colorStops[0].value;
|
|
@@ -2257,25 +3629,640 @@ function getColorAtPosition$1(stops, position) {
|
|
|
2257
3629
|
return formatRgb(interpolate([current.value, next.value], "rgb")(localT));
|
|
2258
3630
|
}
|
|
2259
3631
|
}
|
|
2260
|
-
return lastStop.value;
|
|
2261
|
-
}
|
|
2262
|
-
function fitStopsToWebGLLimit$
|
|
2263
|
-
const colorStops = stops.filter((stop) => stop.type === "color-stop" && stop.position != null).sort((a, b) => a.position - b.position);
|
|
2264
|
-
if (colorStops.length <= maxStops) return colorStops;
|
|
2265
|
-
const sampledStops = [];
|
|
2266
|
-
for (let index = 0; index < maxStops; index += 1) {
|
|
2267
|
-
const position = index / (maxStops - 1);
|
|
2268
|
-
sampledStops.push({
|
|
2269
|
-
type: "color-stop",
|
|
2270
|
-
value: getColorAtPosition$
|
|
2271
|
-
position
|
|
2272
|
-
});
|
|
2273
|
-
}
|
|
2274
|
-
return sampledStops;
|
|
3632
|
+
return lastStop.value;
|
|
3633
|
+
}
|
|
3634
|
+
function fitStopsToWebGLLimit$3(stops, maxStops) {
|
|
3635
|
+
const colorStops = stops.filter((stop) => stop.type === "color-stop" && stop.position != null).sort((a, b) => a.position - b.position);
|
|
3636
|
+
if (colorStops.length <= maxStops) return colorStops;
|
|
3637
|
+
const sampledStops = [];
|
|
3638
|
+
for (let index = 0; index < maxStops; index += 1) {
|
|
3639
|
+
const position = index / (maxStops - 1);
|
|
3640
|
+
sampledStops.push({
|
|
3641
|
+
type: "color-stop",
|
|
3642
|
+
value: getColorAtPosition$3(colorStops, position),
|
|
3643
|
+
position
|
|
3644
|
+
});
|
|
3645
|
+
}
|
|
3646
|
+
return sampledStops;
|
|
3647
|
+
}
|
|
3648
|
+
var ModuleTransformerLinearGradientToCanvasWebGL = class {
|
|
3649
|
+
target = "canvas-webgl";
|
|
3650
|
+
gradientType = "linear-gradient";
|
|
3651
|
+
to(input) {
|
|
3652
|
+
const gradient = input;
|
|
3653
|
+
return { draw: (canvas, width, height) => {
|
|
3654
|
+
const gl = canvas.getContext("webgl");
|
|
3655
|
+
if (!gl) throw new Error("WebGL is not supported.");
|
|
3656
|
+
canvas.width = width;
|
|
3657
|
+
canvas.height = height;
|
|
3658
|
+
gl.viewport(0, 0, width, height);
|
|
3659
|
+
const program = createProgram$4(gl, `
|
|
3660
|
+
attribute vec2 a_position;
|
|
3661
|
+
varying vec2 v_uv;
|
|
3662
|
+
|
|
3663
|
+
void main() {
|
|
3664
|
+
v_uv = a_position * 0.5 + 0.5;
|
|
3665
|
+
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
3666
|
+
}
|
|
3667
|
+
`, `
|
|
3668
|
+
precision mediump float;
|
|
3669
|
+
|
|
3670
|
+
varying vec2 v_uv;
|
|
3671
|
+
|
|
3672
|
+
uniform vec2 u_start;
|
|
3673
|
+
uniform vec2 u_end;
|
|
3674
|
+
uniform int u_stopCount;
|
|
3675
|
+
uniform float u_positions[${MAX_STOPS$3}];
|
|
3676
|
+
uniform vec4 u_colors[${MAX_STOPS$3}];
|
|
3677
|
+
|
|
3678
|
+
vec4 getGradientColor(float t) {
|
|
3679
|
+
vec4 result = u_colors[0];
|
|
3680
|
+
|
|
3681
|
+
for (int i = 0; i < ${MAX_STOPS$3 - 1}; i++) {
|
|
3682
|
+
if (i >= u_stopCount - 1) {
|
|
3683
|
+
break;
|
|
3684
|
+
}
|
|
3685
|
+
|
|
3686
|
+
float currentPosition = u_positions[i];
|
|
3687
|
+
float nextPosition = u_positions[i + 1];
|
|
3688
|
+
|
|
3689
|
+
if (t <= currentPosition) {
|
|
3690
|
+
return u_colors[i];
|
|
3691
|
+
}
|
|
3692
|
+
|
|
3693
|
+
if (t >= currentPosition && t <= nextPosition) {
|
|
3694
|
+
float localT = (t - currentPosition) / max(nextPosition - currentPosition, 0.00001);
|
|
3695
|
+
return mix(u_colors[i], u_colors[i + 1], localT);
|
|
3696
|
+
}
|
|
3697
|
+
|
|
3698
|
+
result = u_colors[i + 1];
|
|
3699
|
+
}
|
|
3700
|
+
|
|
3701
|
+
return result;
|
|
3702
|
+
}
|
|
3703
|
+
|
|
3704
|
+
void main() {
|
|
3705
|
+
vec2 axis = u_end - u_start;
|
|
3706
|
+
vec2 point = v_uv;
|
|
3707
|
+
|
|
3708
|
+
float axisLengthSquared = dot(axis, axis);
|
|
3709
|
+
float t = dot(point - u_start, axis) / axisLengthSquared;
|
|
3710
|
+
|
|
3711
|
+
t = clamp(t, 0.0, 1.0);
|
|
3712
|
+
|
|
3713
|
+
gl_FragColor = getGradientColor(t);
|
|
3714
|
+
}
|
|
3715
|
+
`);
|
|
3716
|
+
gl.useProgram(program);
|
|
3717
|
+
const positionBuffer = gl.createBuffer();
|
|
3718
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
|
3719
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
|
|
3720
|
+
-1,
|
|
3721
|
+
-1,
|
|
3722
|
+
1,
|
|
3723
|
+
-1,
|
|
3724
|
+
-1,
|
|
3725
|
+
1,
|
|
3726
|
+
-1,
|
|
3727
|
+
1,
|
|
3728
|
+
1,
|
|
3729
|
+
-1,
|
|
3730
|
+
1,
|
|
3731
|
+
1
|
|
3732
|
+
]), gl.STATIC_DRAW);
|
|
3733
|
+
const positionLocation = gl.getAttribLocation(program, "a_position");
|
|
3734
|
+
gl.enableVertexAttribArray(positionLocation);
|
|
3735
|
+
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
|
|
3736
|
+
const angle = gradient.config.angle;
|
|
3737
|
+
const dirX = Math.sin(angle);
|
|
3738
|
+
const dirY = -Math.cos(angle);
|
|
3739
|
+
const centerX = width / 2;
|
|
3740
|
+
const centerY = height / 2;
|
|
3741
|
+
const lineLength = Math.abs(width * dirX) + Math.abs(height * dirY);
|
|
3742
|
+
let startX = centerX - dirX * lineLength / 2;
|
|
3743
|
+
let startY = centerY - dirY * lineLength / 2;
|
|
3744
|
+
let endX = centerX + dirX * lineLength / 2;
|
|
3745
|
+
let endY = centerY + dirY * lineLength / 2;
|
|
3746
|
+
const { min, max, stops } = getStopRange(resolveRenderableGradientStops(gradient, getWebGLSampleCount$3(gradient, MAX_STOPS$3)));
|
|
3747
|
+
let normalizedStops = stops;
|
|
3748
|
+
if (min < 0 || max > 1) {
|
|
3749
|
+
const vx = endX - startX;
|
|
3750
|
+
const vy = endY - startY;
|
|
3751
|
+
const baseStartX = startX;
|
|
3752
|
+
const baseStartY = startY;
|
|
3753
|
+
startX = baseStartX + vx * min;
|
|
3754
|
+
startY = baseStartY + vy * min;
|
|
3755
|
+
endX = baseStartX + vx * max;
|
|
3756
|
+
endY = baseStartY + vy * max;
|
|
3757
|
+
normalizedStops = normalizeStops$2(stops, min, max);
|
|
3758
|
+
}
|
|
3759
|
+
const startU = startX / width;
|
|
3760
|
+
const startV = 1 - startY / height;
|
|
3761
|
+
const endU = endX / width;
|
|
3762
|
+
const endV = 1 - endY / height;
|
|
3763
|
+
const limitedStops = fitStopsToWebGLLimit$3(normalizedStops, MAX_STOPS$3);
|
|
3764
|
+
const positions = new Float32Array(MAX_STOPS$3);
|
|
3765
|
+
const colors = new Float32Array(MAX_STOPS$3 * 4);
|
|
3766
|
+
limitedStops.forEach((stop, index) => {
|
|
3767
|
+
const color = toWebGLColor$3(stop.value);
|
|
3768
|
+
positions[index] = stop.position;
|
|
3769
|
+
colors[index * 4 + 0] = color[0];
|
|
3770
|
+
colors[index * 4 + 1] = color[1];
|
|
3771
|
+
colors[index * 4 + 2] = color[2];
|
|
3772
|
+
colors[index * 4 + 3] = color[3];
|
|
3773
|
+
});
|
|
3774
|
+
gl.uniform2f(gl.getUniformLocation(program, "u_start"), startU, startV);
|
|
3775
|
+
gl.uniform2f(gl.getUniformLocation(program, "u_end"), endU, endV);
|
|
3776
|
+
gl.uniform1i(gl.getUniformLocation(program, "u_stopCount"), limitedStops.length);
|
|
3777
|
+
gl.uniform1fv(gl.getUniformLocation(program, "u_positions"), positions);
|
|
3778
|
+
gl.uniform4fv(gl.getUniformLocation(program, "u_colors"), colors);
|
|
3779
|
+
gl.clearColor(0, 0, 0, 0);
|
|
3780
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
3781
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
3782
|
+
} };
|
|
3783
|
+
}
|
|
3784
|
+
};
|
|
3785
|
+
//#endregion
|
|
3786
|
+
//#region src/gradient-transformer/modules/webgl/ModuleTransformerConicGradientToWebgl.ts
|
|
3787
|
+
const toRgb$2 = converter("rgb");
|
|
3788
|
+
const MAX_STOPS$2 = 128;
|
|
3789
|
+
const TWO_PI = Math.PI * 2;
|
|
3790
|
+
function toWebGLColor$2(input) {
|
|
3791
|
+
const color = toRgb$2(input);
|
|
3792
|
+
if (!color) throw new Error(`Failed to convert color: ${input}`);
|
|
3793
|
+
return [
|
|
3794
|
+
color.r ?? 0,
|
|
3795
|
+
color.g ?? 0,
|
|
3796
|
+
color.b ?? 0,
|
|
3797
|
+
color.alpha ?? 1
|
|
3798
|
+
];
|
|
3799
|
+
}
|
|
3800
|
+
function createShader$3(gl, type, source) {
|
|
3801
|
+
const shader = gl.createShader(type);
|
|
3802
|
+
if (!shader) throw new Error("Failed to create WebGL shader.");
|
|
3803
|
+
gl.shaderSource(shader, source);
|
|
3804
|
+
gl.compileShader(shader);
|
|
3805
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
3806
|
+
const error = gl.getShaderInfoLog(shader);
|
|
3807
|
+
gl.deleteShader(shader);
|
|
3808
|
+
throw new Error(`WebGL shader compile error: ${error}`);
|
|
3809
|
+
}
|
|
3810
|
+
return shader;
|
|
3811
|
+
}
|
|
3812
|
+
function createProgram$3(gl, vertexSource, fragmentSource) {
|
|
3813
|
+
const vertexShader = createShader$3(gl, gl.VERTEX_SHADER, vertexSource);
|
|
3814
|
+
const fragmentShader = createShader$3(gl, gl.FRAGMENT_SHADER, fragmentSource);
|
|
3815
|
+
const program = gl.createProgram();
|
|
3816
|
+
if (!program) throw new Error("Failed to create WebGL program.");
|
|
3817
|
+
gl.attachShader(program, vertexShader);
|
|
3818
|
+
gl.attachShader(program, fragmentShader);
|
|
3819
|
+
gl.linkProgram(program);
|
|
3820
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
3821
|
+
const error = gl.getProgramInfoLog(program);
|
|
3822
|
+
gl.deleteProgram(program);
|
|
3823
|
+
throw new Error(`WebGL program link error: ${error}`);
|
|
3824
|
+
}
|
|
3825
|
+
return program;
|
|
3826
|
+
}
|
|
3827
|
+
function getColorStopCount$2(stops) {
|
|
3828
|
+
return stops.filter((stop) => stop.type === "color-stop").length;
|
|
3829
|
+
}
|
|
3830
|
+
function getWebGLSampleCount$2(gradient, maxStops) {
|
|
3831
|
+
const colorStopCount = getColorStopCount$2(gradient.stops);
|
|
3832
|
+
const segmentCount = Math.max(1, colorStopCount - 1);
|
|
3833
|
+
return Math.max(2, Math.floor((maxStops - 1) / segmentCount));
|
|
3834
|
+
}
|
|
3835
|
+
function getColorAtPosition$2(stops, position) {
|
|
3836
|
+
const colorStops = stops.filter((stop) => stop.type === "color-stop" && stop.position != null).sort((a, b) => a.position - b.position);
|
|
3837
|
+
if (colorStops.length === 0) throw new Error("Cannot sample color from empty gradient stops.");
|
|
3838
|
+
if (position <= colorStops[0].position) return colorStops[0].value;
|
|
3839
|
+
const lastStop = colorStops[colorStops.length - 1];
|
|
3840
|
+
if (position >= lastStop.position) return lastStop.value;
|
|
3841
|
+
for (let index = 0; index < colorStops.length - 1; index += 1) {
|
|
3842
|
+
const current = colorStops[index];
|
|
3843
|
+
const next = colorStops[index + 1];
|
|
3844
|
+
if (position >= current.position && position <= next.position) {
|
|
3845
|
+
const range = next.position - current.position || 1;
|
|
3846
|
+
const localT = (position - current.position) / range;
|
|
3847
|
+
return formatRgb(interpolate([current.value, next.value], "rgb")(localT));
|
|
3848
|
+
}
|
|
3849
|
+
}
|
|
3850
|
+
return lastStop.value;
|
|
3851
|
+
}
|
|
3852
|
+
function fitStopsToWebGLLimit$2(stops, maxStops) {
|
|
3853
|
+
const colorStops = stops.filter((stop) => stop.type === "color-stop" && stop.position != null).sort((a, b) => a.position - b.position);
|
|
3854
|
+
if (colorStops.length <= maxStops) return colorStops;
|
|
3855
|
+
const sampledStops = [];
|
|
3856
|
+
for (let index = 0; index < maxStops; index += 1) {
|
|
3857
|
+
const position = index / (maxStops - 1);
|
|
3858
|
+
sampledStops.push({
|
|
3859
|
+
type: "color-stop",
|
|
3860
|
+
value: getColorAtPosition$2(colorStops, position),
|
|
3861
|
+
position
|
|
3862
|
+
});
|
|
3863
|
+
}
|
|
3864
|
+
return sampledStops;
|
|
3865
|
+
}
|
|
3866
|
+
function resolveLengthPercentage(value, reference) {
|
|
3867
|
+
if (value.kind === "percent") return value.value / 100 * reference;
|
|
3868
|
+
if (value.kind === "length") {
|
|
3869
|
+
if (value.unit === "px") return value.value;
|
|
3870
|
+
throw new Error(`Unsupported gradient length unit for WebGL conic gradient: ${value.unit}`);
|
|
3871
|
+
}
|
|
3872
|
+
return value;
|
|
3873
|
+
}
|
|
3874
|
+
function resolveAngleToRadians(angle) {
|
|
3875
|
+
if (angle.unit === "rad") return angle.value;
|
|
3876
|
+
if (angle.unit === "deg") return angle.value / 360 * TWO_PI;
|
|
3877
|
+
if (angle.unit === "turn") return angle.value * TWO_PI;
|
|
3878
|
+
if (angle.unit === "grad") return angle.value / 400 * TWO_PI;
|
|
3879
|
+
return angle.unit;
|
|
3880
|
+
}
|
|
3881
|
+
function resolveKeywordPositionX$2(x, width) {
|
|
3882
|
+
if (x === "left") return 0;
|
|
3883
|
+
if (x === "center") return width / 2;
|
|
3884
|
+
if (x === "right") return width;
|
|
3885
|
+
return width / 2;
|
|
3886
|
+
}
|
|
3887
|
+
function resolveKeywordPositionY$2(y, height) {
|
|
3888
|
+
if (y === "top") return 0;
|
|
3889
|
+
if (y === "center") return height / 2;
|
|
3890
|
+
if (y === "bottom") return height;
|
|
3891
|
+
return height / 2;
|
|
3892
|
+
}
|
|
3893
|
+
function resolveConicCenter(position, width, height) {
|
|
3894
|
+
if (position.kind === "keywords") return {
|
|
3895
|
+
x: resolveKeywordPositionX$2(position.x, width),
|
|
3896
|
+
y: resolveKeywordPositionY$2(position.y, height)
|
|
3897
|
+
};
|
|
3898
|
+
if (position.kind === "values") return {
|
|
3899
|
+
x: resolveLengthPercentage(position.x, width),
|
|
3900
|
+
y: resolveLengthPercentage(position.y, height)
|
|
3901
|
+
};
|
|
3902
|
+
return {
|
|
3903
|
+
x: width / 2,
|
|
3904
|
+
y: height / 2
|
|
3905
|
+
};
|
|
3906
|
+
}
|
|
3907
|
+
var ModuleTransformerConicGradientToCanvasWebGL = class {
|
|
3908
|
+
target = "canvas-webgl";
|
|
3909
|
+
gradientType = "conic-gradient";
|
|
3910
|
+
to(input) {
|
|
3911
|
+
const gradient = input;
|
|
3912
|
+
return { draw: (canvas, width, height) => {
|
|
3913
|
+
const gl = canvas.getContext("webgl");
|
|
3914
|
+
if (!gl) throw new Error("WebGL is not supported.");
|
|
3915
|
+
canvas.width = width;
|
|
3916
|
+
canvas.height = height;
|
|
3917
|
+
gl.viewport(0, 0, width, height);
|
|
3918
|
+
const program = createProgram$3(gl, `
|
|
3919
|
+
attribute vec2 a_position;
|
|
3920
|
+
varying vec2 v_uv;
|
|
3921
|
+
|
|
3922
|
+
void main() {
|
|
3923
|
+
v_uv = a_position * 0.5 + 0.5;
|
|
3924
|
+
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
3925
|
+
}
|
|
3926
|
+
`, `
|
|
3927
|
+
precision mediump float;
|
|
3928
|
+
|
|
3929
|
+
const float PI = 3.141592653589793;
|
|
3930
|
+
const float TWO_PI = 6.283185307179586;
|
|
3931
|
+
|
|
3932
|
+
varying vec2 v_uv;
|
|
3933
|
+
|
|
3934
|
+
uniform vec2 u_center;
|
|
3935
|
+
uniform float u_startAngle;
|
|
3936
|
+
uniform int u_stopCount;
|
|
3937
|
+
uniform float u_positions[${MAX_STOPS$2}];
|
|
3938
|
+
uniform vec4 u_colors[${MAX_STOPS$2}];
|
|
3939
|
+
|
|
3940
|
+
vec4 getGradientColor(float t) {
|
|
3941
|
+
vec4 result = u_colors[0];
|
|
3942
|
+
|
|
3943
|
+
for (int i = 0; i < ${MAX_STOPS$2 - 1}; i++) {
|
|
3944
|
+
if (i >= u_stopCount - 1) {
|
|
3945
|
+
break;
|
|
3946
|
+
}
|
|
3947
|
+
|
|
3948
|
+
float currentPosition = u_positions[i];
|
|
3949
|
+
float nextPosition = u_positions[i + 1];
|
|
3950
|
+
|
|
3951
|
+
if (t <= currentPosition) {
|
|
3952
|
+
return u_colors[i];
|
|
3953
|
+
}
|
|
3954
|
+
|
|
3955
|
+
if (t >= currentPosition && t <= nextPosition) {
|
|
3956
|
+
float localT = (t - currentPosition) / max(nextPosition - currentPosition, 0.00001);
|
|
3957
|
+
return mix(u_colors[i], u_colors[i + 1], localT);
|
|
3958
|
+
}
|
|
3959
|
+
|
|
3960
|
+
result = u_colors[i + 1];
|
|
3961
|
+
}
|
|
3962
|
+
|
|
3963
|
+
return result;
|
|
3964
|
+
}
|
|
3965
|
+
|
|
3966
|
+
void main() {
|
|
3967
|
+
vec2 delta = v_uv - u_center;
|
|
3968
|
+
|
|
3969
|
+
float angle = atan(delta.y, delta.x);
|
|
3970
|
+
float cssAngle = mod((PI * 0.5) - angle + TWO_PI, TWO_PI);
|
|
3971
|
+
|
|
3972
|
+
float t = mod(cssAngle - u_startAngle + TWO_PI, TWO_PI) / TWO_PI;
|
|
3973
|
+
|
|
3974
|
+
gl_FragColor = getGradientColor(t);
|
|
3975
|
+
}
|
|
3976
|
+
`);
|
|
3977
|
+
gl.useProgram(program);
|
|
3978
|
+
const positionBuffer = gl.createBuffer();
|
|
3979
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
|
3980
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
|
|
3981
|
+
-1,
|
|
3982
|
+
-1,
|
|
3983
|
+
1,
|
|
3984
|
+
-1,
|
|
3985
|
+
-1,
|
|
3986
|
+
1,
|
|
3987
|
+
-1,
|
|
3988
|
+
1,
|
|
3989
|
+
1,
|
|
3990
|
+
-1,
|
|
3991
|
+
1,
|
|
3992
|
+
1
|
|
3993
|
+
]), gl.STATIC_DRAW);
|
|
3994
|
+
const positionLocation = gl.getAttribLocation(program, "a_position");
|
|
3995
|
+
gl.enableVertexAttribArray(positionLocation);
|
|
3996
|
+
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
|
|
3997
|
+
const center = resolveConicCenter(gradient.config.position, width, height);
|
|
3998
|
+
const limitedStops = fitStopsToWebGLLimit$2(resolveRenderableGradientStops(gradient, getWebGLSampleCount$2(gradient, MAX_STOPS$2)), MAX_STOPS$2);
|
|
3999
|
+
const positions = new Float32Array(MAX_STOPS$2);
|
|
4000
|
+
const colors = new Float32Array(MAX_STOPS$2 * 4);
|
|
4001
|
+
limitedStops.forEach((stop, index) => {
|
|
4002
|
+
const color = toWebGLColor$2(stop.value);
|
|
4003
|
+
positions[index] = stop.position;
|
|
4004
|
+
colors[index * 4 + 0] = color[0];
|
|
4005
|
+
colors[index * 4 + 1] = color[1];
|
|
4006
|
+
colors[index * 4 + 2] = color[2];
|
|
4007
|
+
colors[index * 4 + 3] = color[3];
|
|
4008
|
+
});
|
|
4009
|
+
gl.uniform2f(gl.getUniformLocation(program, "u_center"), center.x / width, 1 - center.y / height);
|
|
4010
|
+
gl.uniform1f(gl.getUniformLocation(program, "u_startAngle"), resolveAngleToRadians(gradient.config.from));
|
|
4011
|
+
gl.uniform1i(gl.getUniformLocation(program, "u_stopCount"), limitedStops.length);
|
|
4012
|
+
gl.uniform1fv(gl.getUniformLocation(program, "u_positions"), positions);
|
|
4013
|
+
gl.uniform4fv(gl.getUniformLocation(program, "u_colors"), colors);
|
|
4014
|
+
gl.clearColor(0, 0, 0, 0);
|
|
4015
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
4016
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
4017
|
+
} };
|
|
4018
|
+
}
|
|
4019
|
+
};
|
|
4020
|
+
//#endregion
|
|
4021
|
+
//#region src/gradient-transformer/modules/webgl/ModuleTransformerRadialGradientToWebgl.ts
|
|
4022
|
+
const toRgb$1 = converter("rgb");
|
|
4023
|
+
const MAX_STOPS$1 = 128;
|
|
4024
|
+
const MAX_REPEATING_RADIAL_T = 16;
|
|
4025
|
+
function toWebGLColor$1(input) {
|
|
4026
|
+
const color = toRgb$1(input);
|
|
4027
|
+
if (!color) throw new Error(`Failed to convert color: ${input}`);
|
|
4028
|
+
return [
|
|
4029
|
+
color.r ?? 0,
|
|
4030
|
+
color.g ?? 0,
|
|
4031
|
+
color.b ?? 0,
|
|
4032
|
+
color.alpha ?? 1
|
|
4033
|
+
];
|
|
4034
|
+
}
|
|
4035
|
+
function createShader$2(gl, type, source) {
|
|
4036
|
+
const shader = gl.createShader(type);
|
|
4037
|
+
if (!shader) throw new Error("Failed to create WebGL shader.");
|
|
4038
|
+
gl.shaderSource(shader, source);
|
|
4039
|
+
gl.compileShader(shader);
|
|
4040
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
4041
|
+
const error = gl.getShaderInfoLog(shader);
|
|
4042
|
+
gl.deleteShader(shader);
|
|
4043
|
+
throw new Error(`WebGL shader compile error: ${error}`);
|
|
4044
|
+
}
|
|
4045
|
+
return shader;
|
|
4046
|
+
}
|
|
4047
|
+
function createProgram$2(gl, vertexSource, fragmentSource) {
|
|
4048
|
+
const vertexShader = createShader$2(gl, gl.VERTEX_SHADER, vertexSource);
|
|
4049
|
+
const fragmentShader = createShader$2(gl, gl.FRAGMENT_SHADER, fragmentSource);
|
|
4050
|
+
const program = gl.createProgram();
|
|
4051
|
+
if (!program) throw new Error("Failed to create WebGL program.");
|
|
4052
|
+
gl.attachShader(program, vertexShader);
|
|
4053
|
+
gl.attachShader(program, fragmentShader);
|
|
4054
|
+
gl.linkProgram(program);
|
|
4055
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
4056
|
+
const error = gl.getProgramInfoLog(program);
|
|
4057
|
+
gl.deleteProgram(program);
|
|
4058
|
+
throw new Error(`WebGL program link error: ${error}`);
|
|
4059
|
+
}
|
|
4060
|
+
return program;
|
|
4061
|
+
}
|
|
4062
|
+
function getColorStopCount$1(stops) {
|
|
4063
|
+
return stops.filter((stop) => stop.type === "color-stop").length;
|
|
4064
|
+
}
|
|
4065
|
+
function getWebGLSampleCount$1(gradient, maxStops) {
|
|
4066
|
+
const colorStopCount = getColorStopCount$1(gradient.stops);
|
|
4067
|
+
const segmentCount = Math.max(1, colorStopCount - 1);
|
|
4068
|
+
return Math.max(2, Math.floor((maxStops - 1) / segmentCount));
|
|
4069
|
+
}
|
|
4070
|
+
function getColorAtPosition$1(stops, position) {
|
|
4071
|
+
const colorStops = stops.filter((stop) => stop.type === "color-stop" && stop.position != null).sort((a, b) => a.position - b.position);
|
|
4072
|
+
if (colorStops.length === 0) throw new Error("Cannot sample color from empty gradient stops.");
|
|
4073
|
+
if (position <= colorStops[0].position) return colorStops[0].value;
|
|
4074
|
+
const lastStop = colorStops[colorStops.length - 1];
|
|
4075
|
+
if (position >= lastStop.position) return lastStop.value;
|
|
4076
|
+
for (let index = 0; index < colorStops.length - 1; index += 1) {
|
|
4077
|
+
const current = colorStops[index];
|
|
4078
|
+
const next = colorStops[index + 1];
|
|
4079
|
+
if (position >= current.position && position <= next.position) {
|
|
4080
|
+
const range = next.position - current.position || 1;
|
|
4081
|
+
const localT = (position - current.position) / range;
|
|
4082
|
+
return formatRgb(interpolate([current.value, next.value], "rgb")(localT));
|
|
4083
|
+
}
|
|
4084
|
+
}
|
|
4085
|
+
return lastStop.value;
|
|
4086
|
+
}
|
|
4087
|
+
function fitStopsToWebGLLimit$1(stops, maxStops) {
|
|
4088
|
+
const colorStops = stops.filter((stop) => stop.type === "color-stop" && stop.position != null).sort((a, b) => a.position - b.position);
|
|
4089
|
+
if (colorStops.length <= maxStops) return colorStops;
|
|
4090
|
+
const sampledStops = [];
|
|
4091
|
+
for (let index = 0; index < maxStops; index += 1) {
|
|
4092
|
+
const position = index / (maxStops - 1);
|
|
4093
|
+
sampledStops.push({
|
|
4094
|
+
type: "color-stop",
|
|
4095
|
+
value: getColorAtPosition$1(colorStops, position),
|
|
4096
|
+
position
|
|
4097
|
+
});
|
|
4098
|
+
}
|
|
4099
|
+
return sampledStops;
|
|
4100
|
+
}
|
|
4101
|
+
function parseLengthPercentage$1(value, reference) {
|
|
4102
|
+
if (value.kind === "percent") return value.value / 100 * reference;
|
|
4103
|
+
if (value.kind === "length") {
|
|
4104
|
+
if (value.unit === "px") return value.value;
|
|
4105
|
+
throw new Error(`Unsupported gradient length unit for WebGL radial gradient: ${value.unit}`);
|
|
4106
|
+
}
|
|
4107
|
+
return value;
|
|
4108
|
+
}
|
|
4109
|
+
function resolveKeywordPositionX$1(x, width) {
|
|
4110
|
+
if (x === "left") return 0;
|
|
4111
|
+
if (x === "center") return width / 2;
|
|
4112
|
+
if (x === "right") return width;
|
|
4113
|
+
return width / 2;
|
|
4114
|
+
}
|
|
4115
|
+
function resolveKeywordPositionY$1(y, height) {
|
|
4116
|
+
if (y === "top") return 0;
|
|
4117
|
+
if (y === "center") return height / 2;
|
|
4118
|
+
if (y === "bottom") return height;
|
|
4119
|
+
return height / 2;
|
|
4120
|
+
}
|
|
4121
|
+
function resolveRadialCenter(position, width, height) {
|
|
4122
|
+
if (position.kind === "keywords") return {
|
|
4123
|
+
x: resolveKeywordPositionX$1(position.x, width),
|
|
4124
|
+
y: resolveKeywordPositionY$1(position.y, height)
|
|
4125
|
+
};
|
|
4126
|
+
if (position.kind === "values") return {
|
|
4127
|
+
x: parseLengthPercentage$1(position.x, width),
|
|
4128
|
+
y: parseLengthPercentage$1(position.y, height)
|
|
4129
|
+
};
|
|
4130
|
+
return {
|
|
4131
|
+
x: width / 2,
|
|
4132
|
+
y: height / 2
|
|
4133
|
+
};
|
|
4134
|
+
}
|
|
4135
|
+
function getDistanceToSide(center, width, height, side) {
|
|
4136
|
+
if (side === "left") return center.x;
|
|
4137
|
+
if (side === "right") return width - center.x;
|
|
4138
|
+
if (side === "top") return center.y;
|
|
4139
|
+
return height - center.y;
|
|
4140
|
+
}
|
|
4141
|
+
function getDistanceToCorner$1(center, corner) {
|
|
4142
|
+
const dx = corner.x - center.x;
|
|
4143
|
+
const dy = corner.y - center.y;
|
|
4144
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
4145
|
+
}
|
|
4146
|
+
function normalizeStops$1(stops, min, max) {
|
|
4147
|
+
const range = max - min || 1;
|
|
4148
|
+
return stops.filter((stop) => stop.type === "color-stop" && stop.position != null).map((stop) => ({
|
|
4149
|
+
...stop,
|
|
4150
|
+
position: (stop.position - min) / range
|
|
4151
|
+
}));
|
|
4152
|
+
}
|
|
4153
|
+
function getCornerDeltas$1(center, width, height) {
|
|
4154
|
+
return [
|
|
4155
|
+
{
|
|
4156
|
+
dx: -center.x,
|
|
4157
|
+
dy: -center.y
|
|
4158
|
+
},
|
|
4159
|
+
{
|
|
4160
|
+
dx: width - center.x,
|
|
4161
|
+
dy: -center.y
|
|
4162
|
+
},
|
|
4163
|
+
{
|
|
4164
|
+
dx: -center.x,
|
|
4165
|
+
dy: height - center.y
|
|
4166
|
+
},
|
|
4167
|
+
{
|
|
4168
|
+
dx: width - center.x,
|
|
4169
|
+
dy: height - center.y
|
|
4170
|
+
}
|
|
4171
|
+
];
|
|
4172
|
+
}
|
|
4173
|
+
function scaleEllipseRadiiToCorner(radiusX, radiusY, dx, dy) {
|
|
4174
|
+
const safeRadiusX = Math.max(radiusX, 1e-4);
|
|
4175
|
+
const safeRadiusY = Math.max(radiusY, 1e-4);
|
|
4176
|
+
const scale = Math.sqrt(dx * dx / (safeRadiusX * safeRadiusX) + dy * dy / (safeRadiusY * safeRadiusY));
|
|
4177
|
+
return {
|
|
4178
|
+
x: safeRadiusX * scale,
|
|
4179
|
+
y: safeRadiusY * scale
|
|
4180
|
+
};
|
|
4181
|
+
}
|
|
4182
|
+
function resolveRadialRadii(size, shape, center, width, height) {
|
|
4183
|
+
if (size.kind === "explicit") {
|
|
4184
|
+
const radiusX = parseLengthPercentage$1(size.x, width);
|
|
4185
|
+
const radiusY = size.y ? parseLengthPercentage$1(size.y, height) : radiusX;
|
|
4186
|
+
return {
|
|
4187
|
+
x: Math.max(radiusX, 1e-4),
|
|
4188
|
+
y: Math.max(shape === "circle" ? radiusX : radiusY, 1e-4)
|
|
4189
|
+
};
|
|
4190
|
+
}
|
|
4191
|
+
const left = getDistanceToSide(center, width, height, "left");
|
|
4192
|
+
const right = getDistanceToSide(center, width, height, "right");
|
|
4193
|
+
const top = getDistanceToSide(center, width, height, "top");
|
|
4194
|
+
const bottom = getDistanceToSide(center, width, height, "bottom");
|
|
4195
|
+
if (shape === "circle") {
|
|
4196
|
+
const cornerDistances = [
|
|
4197
|
+
{
|
|
4198
|
+
x: 0,
|
|
4199
|
+
y: 0
|
|
4200
|
+
},
|
|
4201
|
+
{
|
|
4202
|
+
x: width,
|
|
4203
|
+
y: 0
|
|
4204
|
+
},
|
|
4205
|
+
{
|
|
4206
|
+
x: 0,
|
|
4207
|
+
y: height
|
|
4208
|
+
},
|
|
4209
|
+
{
|
|
4210
|
+
x: width,
|
|
4211
|
+
y: height
|
|
4212
|
+
}
|
|
4213
|
+
].map((corner) => getDistanceToCorner$1(center, corner));
|
|
4214
|
+
if (size.value === "closest-side") {
|
|
4215
|
+
const radius = Math.max(Math.min(left, right, top, bottom), 1e-4);
|
|
4216
|
+
return {
|
|
4217
|
+
x: radius,
|
|
4218
|
+
y: radius
|
|
4219
|
+
};
|
|
4220
|
+
}
|
|
4221
|
+
if (size.value === "farthest-side") {
|
|
4222
|
+
const radius = Math.max(Math.max(left, right, top, bottom), 1e-4);
|
|
4223
|
+
return {
|
|
4224
|
+
x: radius,
|
|
4225
|
+
y: radius
|
|
4226
|
+
};
|
|
4227
|
+
}
|
|
4228
|
+
if (size.value === "closest-corner") {
|
|
4229
|
+
const radius = Math.max(Math.min(...cornerDistances), 1e-4);
|
|
4230
|
+
return {
|
|
4231
|
+
x: radius,
|
|
4232
|
+
y: radius
|
|
4233
|
+
};
|
|
4234
|
+
}
|
|
4235
|
+
const radius = Math.max(Math.max(...cornerDistances), 1e-4);
|
|
4236
|
+
return {
|
|
4237
|
+
x: radius,
|
|
4238
|
+
y: radius
|
|
4239
|
+
};
|
|
4240
|
+
}
|
|
4241
|
+
const closestSideRadiusX = Math.min(left, right);
|
|
4242
|
+
const closestSideRadiusY = Math.min(top, bottom);
|
|
4243
|
+
const farthestSideRadiusX = Math.max(left, right);
|
|
4244
|
+
const farthestSideRadiusY = Math.max(top, bottom);
|
|
4245
|
+
if (size.value === "closest-side") return {
|
|
4246
|
+
x: Math.max(closestSideRadiusX, 1e-4),
|
|
4247
|
+
y: Math.max(closestSideRadiusY, 1e-4)
|
|
4248
|
+
};
|
|
4249
|
+
if (size.value === "farthest-side") return {
|
|
4250
|
+
x: Math.max(farthestSideRadiusX, 1e-4),
|
|
4251
|
+
y: Math.max(farthestSideRadiusY, 1e-4)
|
|
4252
|
+
};
|
|
4253
|
+
const corners = getCornerDeltas$1(center, width, height);
|
|
4254
|
+
if (size.value === "closest-corner") return corners.map((corner) => scaleEllipseRadiiToCorner(closestSideRadiusX, closestSideRadiusY, corner.dx, corner.dy)).reduce((closest, current) => {
|
|
4255
|
+
const closestArea = closest.x * closest.y;
|
|
4256
|
+
return current.x * current.y < closestArea ? current : closest;
|
|
4257
|
+
});
|
|
4258
|
+
return corners.map((corner) => scaleEllipseRadiiToCorner(farthestSideRadiusX, farthestSideRadiusY, corner.dx, corner.dy)).reduce((farthest, current) => {
|
|
4259
|
+
const farthestArea = farthest.x * farthest.y;
|
|
4260
|
+
return current.x * current.y > farthestArea ? current : farthest;
|
|
4261
|
+
});
|
|
2275
4262
|
}
|
|
2276
|
-
var
|
|
4263
|
+
var ModuleTransformerRadialGradientToCanvasWebGL = class {
|
|
2277
4264
|
target = "canvas-webgl";
|
|
2278
|
-
gradientType = "
|
|
4265
|
+
gradientType = "radial-gradient";
|
|
2279
4266
|
to(input) {
|
|
2280
4267
|
const gradient = input;
|
|
2281
4268
|
return { draw: (canvas, width, height) => {
|
|
@@ -2284,7 +4271,7 @@ var ModuleTransformerLinearGradientToCanvasWebGL = class {
|
|
|
2284
4271
|
canvas.width = width;
|
|
2285
4272
|
canvas.height = height;
|
|
2286
4273
|
gl.viewport(0, 0, width, height);
|
|
2287
|
-
const program = createProgram$
|
|
4274
|
+
const program = createProgram$2(gl, `
|
|
2288
4275
|
attribute vec2 a_position;
|
|
2289
4276
|
varying vec2 v_uv;
|
|
2290
4277
|
|
|
@@ -2297,11 +4284,12 @@ var ModuleTransformerLinearGradientToCanvasWebGL = class {
|
|
|
2297
4284
|
|
|
2298
4285
|
varying vec2 v_uv;
|
|
2299
4286
|
|
|
2300
|
-
uniform vec2
|
|
2301
|
-
uniform vec2
|
|
4287
|
+
uniform vec2 u_center;
|
|
4288
|
+
uniform vec2 u_radius;
|
|
2302
4289
|
uniform int u_stopCount;
|
|
2303
4290
|
uniform float u_positions[${MAX_STOPS$1}];
|
|
2304
4291
|
uniform vec4 u_colors[${MAX_STOPS$1}];
|
|
4292
|
+
uniform float u_tMax;
|
|
2305
4293
|
|
|
2306
4294
|
vec4 getGradientColor(float t) {
|
|
2307
4295
|
vec4 result = u_colors[0];
|
|
@@ -2330,13 +4318,12 @@ var ModuleTransformerLinearGradientToCanvasWebGL = class {
|
|
|
2330
4318
|
}
|
|
2331
4319
|
|
|
2332
4320
|
void main() {
|
|
2333
|
-
vec2
|
|
2334
|
-
vec2
|
|
2335
|
-
|
|
2336
|
-
float axisLengthSquared = dot(axis, axis);
|
|
2337
|
-
float t = dot(point - u_start, axis) / axisLengthSquared;
|
|
4321
|
+
vec2 delta = v_uv - u_center;
|
|
4322
|
+
vec2 normalized = delta / max(u_radius, vec2(0.00001));
|
|
4323
|
+
float t = length(normalized);
|
|
2338
4324
|
|
|
2339
|
-
t = clamp(t, 0.0,
|
|
4325
|
+
t = clamp(t, 0.0, u_tMax);
|
|
4326
|
+
t = t / max(u_tMax, 0.00001);
|
|
2340
4327
|
|
|
2341
4328
|
gl_FragColor = getGradientColor(t);
|
|
2342
4329
|
}
|
|
@@ -2361,34 +4348,14 @@ var ModuleTransformerLinearGradientToCanvasWebGL = class {
|
|
|
2361
4348
|
const positionLocation = gl.getAttribLocation(program, "a_position");
|
|
2362
4349
|
gl.enableVertexAttribArray(positionLocation);
|
|
2363
4350
|
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
|
|
2364
|
-
const
|
|
2365
|
-
const
|
|
2366
|
-
const
|
|
2367
|
-
const
|
|
2368
|
-
const
|
|
2369
|
-
const
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
let endX = centerX + dirX * lineLength / 2;
|
|
2373
|
-
let endY = centerY + dirY * lineLength / 2;
|
|
2374
|
-
const { min, max, stops } = getStopRange(resolveRenderableGradientStops(gradient, getWebGLSampleCount$1(gradient, MAX_STOPS$1)));
|
|
2375
|
-
let normalizedStops = stops;
|
|
2376
|
-
if (min < 0 || max > 1) {
|
|
2377
|
-
const vx = endX - startX;
|
|
2378
|
-
const vy = endY - startY;
|
|
2379
|
-
const baseStartX = startX;
|
|
2380
|
-
const baseStartY = startY;
|
|
2381
|
-
startX = baseStartX + vx * min;
|
|
2382
|
-
startY = baseStartY + vy * min;
|
|
2383
|
-
endX = baseStartX + vx * max;
|
|
2384
|
-
endY = baseStartY + vy * max;
|
|
2385
|
-
normalizedStops = normalizeStops$1(stops, min, max);
|
|
2386
|
-
}
|
|
2387
|
-
const startU = startX / width;
|
|
2388
|
-
const startV = 1 - startY / height;
|
|
2389
|
-
const endU = endX / width;
|
|
2390
|
-
const endV = 1 - endY / height;
|
|
2391
|
-
const limitedStops = fitStopsToWebGLLimit$1(normalizedStops, MAX_STOPS$1);
|
|
4351
|
+
const center = resolveRadialCenter(gradient.config.position, width, height);
|
|
4352
|
+
const radius = resolveRadialRadii(gradient.config.size, gradient.config.shape, center, width, height);
|
|
4353
|
+
const maxVisibleT = getMaxVisibleRadialT(center, radius, width, height);
|
|
4354
|
+
const baseStops = resolveRenderableGradientStops(gradient, getWebGLSampleCount$1(gradient, MAX_STOPS$1));
|
|
4355
|
+
const repeatMaxT = Math.min(maxVisibleT, MAX_REPEATING_RADIAL_T);
|
|
4356
|
+
const maxT = gradient.isRepeating ? repeatMaxT : 1;
|
|
4357
|
+
const renderStops = gradient.isRepeating ? expandRepeatingStopsTo(baseStops, 0, repeatMaxT) : baseStops;
|
|
4358
|
+
const limitedStops = fitStopsToWebGLLimit$1(gradient.isRepeating ? normalizeStops$1(renderStops, 0, repeatMaxT) : renderStops, MAX_STOPS$1);
|
|
2392
4359
|
const positions = new Float32Array(MAX_STOPS$1);
|
|
2393
4360
|
const colors = new Float32Array(MAX_STOPS$1 * 4);
|
|
2394
4361
|
limitedStops.forEach((stop, index) => {
|
|
@@ -2399,8 +4366,9 @@ var ModuleTransformerLinearGradientToCanvasWebGL = class {
|
|
|
2399
4366
|
colors[index * 4 + 2] = color[2];
|
|
2400
4367
|
colors[index * 4 + 3] = color[3];
|
|
2401
4368
|
});
|
|
2402
|
-
gl.uniform2f(gl.getUniformLocation(program, "
|
|
2403
|
-
gl.uniform2f(gl.getUniformLocation(program, "
|
|
4369
|
+
gl.uniform2f(gl.getUniformLocation(program, "u_center"), center.x / width, 1 - center.y / height);
|
|
4370
|
+
gl.uniform2f(gl.getUniformLocation(program, "u_radius"), radius.x / width, radius.y / height);
|
|
4371
|
+
gl.uniform1f(gl.getUniformLocation(program, "u_tMax"), maxT);
|
|
2404
4372
|
gl.uniform1i(gl.getUniformLocation(program, "u_stopCount"), limitedStops.length);
|
|
2405
4373
|
gl.uniform1fv(gl.getUniformLocation(program, "u_positions"), positions);
|
|
2406
4374
|
gl.uniform4fv(gl.getUniformLocation(program, "u_colors"), colors);
|
|
@@ -2411,10 +4379,10 @@ var ModuleTransformerLinearGradientToCanvasWebGL = class {
|
|
|
2411
4379
|
}
|
|
2412
4380
|
};
|
|
2413
4381
|
//#endregion
|
|
2414
|
-
//#region src/gradient-transformer/modules/webgl/
|
|
4382
|
+
//#region src/gradient-transformer/modules/webgl/ModuleTransformerDiamondGradientToWebgl.ts
|
|
2415
4383
|
const toRgb = converter("rgb");
|
|
2416
4384
|
const MAX_STOPS = 128;
|
|
2417
|
-
const
|
|
4385
|
+
const MAX_REPEATING_DIAMOND_T = 16;
|
|
2418
4386
|
function toWebGLColor(input) {
|
|
2419
4387
|
const color = toRgb(input);
|
|
2420
4388
|
if (!color) throw new Error(`Failed to convert color: ${input}`);
|
|
@@ -2425,7 +4393,7 @@ function toWebGLColor(input) {
|
|
|
2425
4393
|
color.alpha ?? 1
|
|
2426
4394
|
];
|
|
2427
4395
|
}
|
|
2428
|
-
function createShader(gl, type, source) {
|
|
4396
|
+
function createShader$1(gl, type, source) {
|
|
2429
4397
|
const shader = gl.createShader(type);
|
|
2430
4398
|
if (!shader) throw new Error("Failed to create WebGL shader.");
|
|
2431
4399
|
gl.shaderSource(shader, source);
|
|
@@ -2437,9 +4405,9 @@ function createShader(gl, type, source) {
|
|
|
2437
4405
|
}
|
|
2438
4406
|
return shader;
|
|
2439
4407
|
}
|
|
2440
|
-
function createProgram(gl, vertexSource, fragmentSource) {
|
|
2441
|
-
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexSource);
|
|
2442
|
-
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource);
|
|
4408
|
+
function createProgram$1(gl, vertexSource, fragmentSource) {
|
|
4409
|
+
const vertexShader = createShader$1(gl, gl.VERTEX_SHADER, vertexSource);
|
|
4410
|
+
const fragmentShader = createShader$1(gl, gl.FRAGMENT_SHADER, fragmentSource);
|
|
2443
4411
|
const program = gl.createProgram();
|
|
2444
4412
|
if (!program) throw new Error("Failed to create WebGL program.");
|
|
2445
4413
|
gl.attachShader(program, vertexShader);
|
|
@@ -2462,7 +4430,7 @@ function getWebGLSampleCount(gradient, maxStops) {
|
|
|
2462
4430
|
}
|
|
2463
4431
|
function getColorAtPosition(stops, position) {
|
|
2464
4432
|
const colorStops = stops.filter((stop) => stop.type === "color-stop" && stop.position != null).sort((a, b) => a.position - b.position);
|
|
2465
|
-
if (colorStops.length === 0) throw new Error("Cannot sample color from empty gradient stops.");
|
|
4433
|
+
if (colorStops.length === 0) throw new Error("Cannot sample color from empty diamond gradient stops.");
|
|
2466
4434
|
if (position <= colorStops[0].position) return colorStops[0].value;
|
|
2467
4435
|
const lastStop = colorStops[colorStops.length - 1];
|
|
2468
4436
|
if (position >= lastStop.position) return lastStop.value;
|
|
@@ -2491,57 +4459,40 @@ function fitStopsToWebGLLimit(stops, maxStops) {
|
|
|
2491
4459
|
}
|
|
2492
4460
|
return sampledStops;
|
|
2493
4461
|
}
|
|
4462
|
+
function normalizeStops(stops, min, max) {
|
|
4463
|
+
const range = max - min || 1;
|
|
4464
|
+
return stops.filter((stop) => stop.type === "color-stop" && stop.position != null).map((stop) => ({
|
|
4465
|
+
...stop,
|
|
4466
|
+
position: (stop.position - min) / range
|
|
4467
|
+
}));
|
|
4468
|
+
}
|
|
2494
4469
|
function parseLengthPercentage(value, reference) {
|
|
2495
4470
|
if (value.kind === "percent") return value.value / 100 * reference;
|
|
2496
|
-
if (value.
|
|
2497
|
-
|
|
2498
|
-
throw new Error(`Unsupported gradient length unit for WebGL radial gradient: ${value.unit}`);
|
|
2499
|
-
}
|
|
2500
|
-
return value;
|
|
4471
|
+
if (value.unit === "px") return value.value;
|
|
4472
|
+
throw new Error(`Unsupported diamond-gradient length unit for WebGL transformer: ${value.unit}`);
|
|
2501
4473
|
}
|
|
2502
4474
|
function resolveKeywordPositionX(x, width) {
|
|
2503
4475
|
if (x === "left") return 0;
|
|
2504
|
-
if (x === "center") return width / 2;
|
|
2505
4476
|
if (x === "right") return width;
|
|
2506
4477
|
return width / 2;
|
|
2507
4478
|
}
|
|
2508
4479
|
function resolveKeywordPositionY(y, height) {
|
|
2509
4480
|
if (y === "top") return 0;
|
|
2510
|
-
if (y === "center") return height / 2;
|
|
2511
4481
|
if (y === "bottom") return height;
|
|
2512
4482
|
return height / 2;
|
|
2513
4483
|
}
|
|
2514
|
-
function
|
|
4484
|
+
function resolveDiamondCenter(position, width, height) {
|
|
2515
4485
|
if (position.kind === "keywords") return {
|
|
2516
4486
|
x: resolveKeywordPositionX(position.x, width),
|
|
2517
4487
|
y: resolveKeywordPositionY(position.y, height)
|
|
2518
4488
|
};
|
|
2519
|
-
|
|
4489
|
+
return {
|
|
2520
4490
|
x: parseLengthPercentage(position.x, width),
|
|
2521
4491
|
y: parseLengthPercentage(position.y, height)
|
|
2522
4492
|
};
|
|
2523
|
-
return {
|
|
2524
|
-
x: width / 2,
|
|
2525
|
-
y: height / 2
|
|
2526
|
-
};
|
|
2527
|
-
}
|
|
2528
|
-
function getDistanceToSide(center, width, height, side) {
|
|
2529
|
-
if (side === "left") return center.x;
|
|
2530
|
-
if (side === "right") return width - center.x;
|
|
2531
|
-
if (side === "top") return center.y;
|
|
2532
|
-
return height - center.y;
|
|
2533
4493
|
}
|
|
2534
4494
|
function getDistanceToCorner(center, corner) {
|
|
2535
|
-
|
|
2536
|
-
const dy = corner.y - center.y;
|
|
2537
|
-
return Math.sqrt(dx * dx + dy * dy);
|
|
2538
|
-
}
|
|
2539
|
-
function normalizeStops(stops, min, max) {
|
|
2540
|
-
const range = max - min || 1;
|
|
2541
|
-
return stops.filter((stop) => stop.type === "color-stop" && stop.position != null).map((stop) => ({
|
|
2542
|
-
...stop,
|
|
2543
|
-
position: (stop.position - min) / range
|
|
2544
|
-
}));
|
|
4495
|
+
return Math.abs(corner.x - center.x) + Math.abs(corner.y - center.y);
|
|
2545
4496
|
}
|
|
2546
4497
|
function getCornerDeltas(center, width, height) {
|
|
2547
4498
|
return [
|
|
@@ -2563,16 +4514,16 @@ function getCornerDeltas(center, width, height) {
|
|
|
2563
4514
|
}
|
|
2564
4515
|
];
|
|
2565
4516
|
}
|
|
2566
|
-
function
|
|
4517
|
+
function scaleDiamondRadiiToCorner(radiusX, radiusY, dx, dy) {
|
|
2567
4518
|
const safeRadiusX = Math.max(radiusX, 1e-4);
|
|
2568
4519
|
const safeRadiusY = Math.max(radiusY, 1e-4);
|
|
2569
|
-
const scale = Math.
|
|
4520
|
+
const scale = Math.abs(dx) / safeRadiusX + Math.abs(dy) / safeRadiusY;
|
|
2570
4521
|
return {
|
|
2571
4522
|
x: safeRadiusX * scale,
|
|
2572
4523
|
y: safeRadiusY * scale
|
|
2573
4524
|
};
|
|
2574
4525
|
}
|
|
2575
|
-
function
|
|
4526
|
+
function resolveDiamondRadii(size, shape, center, width, height) {
|
|
2576
4527
|
if (size.kind === "explicit") {
|
|
2577
4528
|
const radiusX = parseLengthPercentage(size.x, width);
|
|
2578
4529
|
const radiusY = size.y ? parseLengthPercentage(size.y, height) : radiusX;
|
|
@@ -2581,10 +4532,10 @@ function resolveRadialRadii(size, shape, center, width, height) {
|
|
|
2581
4532
|
y: Math.max(shape === "circle" ? radiusX : radiusY, 1e-4)
|
|
2582
4533
|
};
|
|
2583
4534
|
}
|
|
2584
|
-
const left =
|
|
2585
|
-
const right =
|
|
2586
|
-
const top =
|
|
2587
|
-
const bottom =
|
|
4535
|
+
const left = center.x;
|
|
4536
|
+
const right = width - center.x;
|
|
4537
|
+
const top = center.y;
|
|
4538
|
+
const bottom = height - center.y;
|
|
2588
4539
|
if (shape === "circle") {
|
|
2589
4540
|
const cornerDistances = [
|
|
2590
4541
|
{
|
|
@@ -2644,19 +4595,34 @@ function resolveRadialRadii(size, shape, center, width, height) {
|
|
|
2644
4595
|
y: Math.max(farthestSideRadiusY, 1e-4)
|
|
2645
4596
|
};
|
|
2646
4597
|
const corners = getCornerDeltas(center, width, height);
|
|
2647
|
-
if (size.value === "closest-corner") return corners.map((corner) =>
|
|
2648
|
-
|
|
2649
|
-
return current.x * current.y < closestArea ? current : closest;
|
|
2650
|
-
});
|
|
2651
|
-
return corners.map((corner) => scaleEllipseRadiiToCorner(farthestSideRadiusX, farthestSideRadiusY, corner.dx, corner.dy)).reduce((farthest, current) => {
|
|
2652
|
-
const farthestArea = farthest.x * farthest.y;
|
|
2653
|
-
return current.x * current.y > farthestArea ? current : farthest;
|
|
2654
|
-
});
|
|
4598
|
+
if (size.value === "closest-corner") return corners.map((corner) => scaleDiamondRadiiToCorner(closestSideRadiusX, closestSideRadiusY, corner.dx, corner.dy)).reduce((closest, current) => current.x * current.y < closest.x * closest.y ? current : closest);
|
|
4599
|
+
return corners.map((corner) => scaleDiamondRadiiToCorner(farthestSideRadiusX, farthestSideRadiusY, corner.dx, corner.dy)).reduce((farthest, current) => current.x * current.y > farthest.x * farthest.y ? current : farthest);
|
|
2655
4600
|
}
|
|
2656
|
-
|
|
4601
|
+
function getMaxVisibleDiamondT(center, radii, width, height) {
|
|
4602
|
+
return Math.max(...[
|
|
4603
|
+
{
|
|
4604
|
+
x: 0,
|
|
4605
|
+
y: 0
|
|
4606
|
+
},
|
|
4607
|
+
{
|
|
4608
|
+
x: width,
|
|
4609
|
+
y: 0
|
|
4610
|
+
},
|
|
4611
|
+
{
|
|
4612
|
+
x: 0,
|
|
4613
|
+
y: height
|
|
4614
|
+
},
|
|
4615
|
+
{
|
|
4616
|
+
x: width,
|
|
4617
|
+
y: height
|
|
4618
|
+
}
|
|
4619
|
+
].map((corner) => Math.abs(corner.x - center.x) / Math.max(radii.x, 1e-4) + Math.abs(corner.y - center.y) / Math.max(radii.y, 1e-4)));
|
|
4620
|
+
}
|
|
4621
|
+
var ModuleTransformerDiamondGradientToCanvasWebGL = class {
|
|
2657
4622
|
target = "canvas-webgl";
|
|
2658
|
-
gradientType = "
|
|
4623
|
+
gradientType = "diamond-gradient";
|
|
2659
4624
|
to(input) {
|
|
4625
|
+
if (!(input instanceof DiamondGradient)) throw new Error("Expected DiamondGradient");
|
|
2660
4626
|
const gradient = input;
|
|
2661
4627
|
return { draw: (canvas, width, height) => {
|
|
2662
4628
|
const gl = canvas.getContext("webgl");
|
|
@@ -2664,7 +4630,7 @@ var ModuleTransformerRadialGradientToCanvasWebGL = class {
|
|
|
2664
4630
|
canvas.width = width;
|
|
2665
4631
|
canvas.height = height;
|
|
2666
4632
|
gl.viewport(0, 0, width, height);
|
|
2667
|
-
const program = createProgram(gl, `
|
|
4633
|
+
const program = createProgram$1(gl, `
|
|
2668
4634
|
attribute vec2 a_position;
|
|
2669
4635
|
varying vec2 v_uv;
|
|
2670
4636
|
|
|
@@ -2712,8 +4678,8 @@ var ModuleTransformerRadialGradientToCanvasWebGL = class {
|
|
|
2712
4678
|
|
|
2713
4679
|
void main() {
|
|
2714
4680
|
vec2 delta = v_uv - u_center;
|
|
2715
|
-
vec2 normalized = delta / max(u_radius, vec2(0.00001));
|
|
2716
|
-
float t =
|
|
4681
|
+
vec2 normalized = abs(delta) / max(u_radius, vec2(0.00001));
|
|
4682
|
+
float t = normalized.x + normalized.y;
|
|
2717
4683
|
|
|
2718
4684
|
t = clamp(t, 0.0, u_tMax);
|
|
2719
4685
|
t = t / max(u_tMax, 0.00001);
|
|
@@ -2741,11 +4707,11 @@ var ModuleTransformerRadialGradientToCanvasWebGL = class {
|
|
|
2741
4707
|
const positionLocation = gl.getAttribLocation(program, "a_position");
|
|
2742
4708
|
gl.enableVertexAttribArray(positionLocation);
|
|
2743
4709
|
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
|
|
2744
|
-
const center =
|
|
2745
|
-
const radius =
|
|
2746
|
-
const maxVisibleT =
|
|
4710
|
+
const center = resolveDiamondCenter(gradient.config.position, width, height);
|
|
4711
|
+
const radius = resolveDiamondRadii(gradient.config.size, gradient.config.shape, center, width, height);
|
|
4712
|
+
const maxVisibleT = getMaxVisibleDiamondT(center, radius, width, height);
|
|
2747
4713
|
const baseStops = resolveRenderableGradientStops(gradient, getWebGLSampleCount(gradient, MAX_STOPS));
|
|
2748
|
-
const repeatMaxT = Math.min(maxVisibleT,
|
|
4714
|
+
const repeatMaxT = Math.min(maxVisibleT, MAX_REPEATING_DIAMOND_T);
|
|
2749
4715
|
const maxT = gradient.isRepeating ? repeatMaxT : 1;
|
|
2750
4716
|
const renderStops = gradient.isRepeating ? expandRepeatingStopsTo(baseStops, 0, repeatMaxT) : baseStops;
|
|
2751
4717
|
const limitedStops = fitStopsToWebGLLimit(gradient.isRepeating ? normalizeStops(renderStops, 0, repeatMaxT) : renderStops, MAX_STOPS);
|
|
@@ -2772,6 +4738,107 @@ var ModuleTransformerRadialGradientToCanvasWebGL = class {
|
|
|
2772
4738
|
}
|
|
2773
4739
|
};
|
|
2774
4740
|
//#endregion
|
|
4741
|
+
//#region src/gradient-transformer/modules/webgl/ModuleTransformerMeshGradientToWebgl.ts
|
|
4742
|
+
const BICUBIC_SUBDIVISIONS = 24;
|
|
4743
|
+
function createShader(gl, type, source) {
|
|
4744
|
+
const shader = gl.createShader(type);
|
|
4745
|
+
if (!shader) throw new Error("Failed to create WebGL shader.");
|
|
4746
|
+
gl.shaderSource(shader, source);
|
|
4747
|
+
gl.compileShader(shader);
|
|
4748
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
4749
|
+
const error = gl.getShaderInfoLog(shader);
|
|
4750
|
+
gl.deleteShader(shader);
|
|
4751
|
+
throw new Error(`WebGL shader compile error: ${error}`);
|
|
4752
|
+
}
|
|
4753
|
+
return shader;
|
|
4754
|
+
}
|
|
4755
|
+
function createProgram(gl, vertexSource, fragmentSource) {
|
|
4756
|
+
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexSource);
|
|
4757
|
+
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource);
|
|
4758
|
+
const program = gl.createProgram();
|
|
4759
|
+
if (!program) throw new Error("Failed to create WebGL program.");
|
|
4760
|
+
gl.attachShader(program, vertexShader);
|
|
4761
|
+
gl.attachShader(program, fragmentShader);
|
|
4762
|
+
gl.linkProgram(program);
|
|
4763
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
4764
|
+
const error = gl.getProgramInfoLog(program);
|
|
4765
|
+
gl.deleteProgram(program);
|
|
4766
|
+
throw new Error(`WebGL program link error: ${error}`);
|
|
4767
|
+
}
|
|
4768
|
+
return program;
|
|
4769
|
+
}
|
|
4770
|
+
function toClipX(value, width) {
|
|
4771
|
+
return value / width * 2 - 1;
|
|
4772
|
+
}
|
|
4773
|
+
function toClipY(value, height) {
|
|
4774
|
+
return 1 - value / height * 2;
|
|
4775
|
+
}
|
|
4776
|
+
var ModuleTransformerMeshGradientToCanvasWebGL = class {
|
|
4777
|
+
target = "canvas-webgl";
|
|
4778
|
+
gradientType = "mesh-gradient";
|
|
4779
|
+
to(input) {
|
|
4780
|
+
if (!(input instanceof MeshGradient)) throw new Error("Expected MeshGradient");
|
|
4781
|
+
const gradient = input;
|
|
4782
|
+
return { draw: (canvas, width, height) => {
|
|
4783
|
+
const gl = canvas.getContext("webgl");
|
|
4784
|
+
if (!gl) throw new Error("WebGL is not supported.");
|
|
4785
|
+
canvas.width = width;
|
|
4786
|
+
canvas.height = height;
|
|
4787
|
+
gl.viewport(0, 0, width, height);
|
|
4788
|
+
const program = createProgram(gl, `
|
|
4789
|
+
attribute vec2 a_position;
|
|
4790
|
+
attribute vec4 a_color;
|
|
4791
|
+
varying vec4 v_color;
|
|
4792
|
+
|
|
4793
|
+
void main() {
|
|
4794
|
+
v_color = a_color;
|
|
4795
|
+
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
4796
|
+
}
|
|
4797
|
+
`, `
|
|
4798
|
+
precision mediump float;
|
|
4799
|
+
varying vec4 v_color;
|
|
4800
|
+
|
|
4801
|
+
void main() {
|
|
4802
|
+
gl_FragColor = v_color;
|
|
4803
|
+
}
|
|
4804
|
+
`);
|
|
4805
|
+
const vertexMap = buildMeshVertexMap(gradient, width, height);
|
|
4806
|
+
const grid = buildRegularMeshGrid(gradient, vertexMap);
|
|
4807
|
+
const subdivisions = gradient.config.method === "bicubic" ? BICUBIC_SUBDIVISIONS : 1;
|
|
4808
|
+
const edgeTriangles = buildMeshEdgeSkirtTriangles(gradient, grid, width, height, subdivisions);
|
|
4809
|
+
const positions = [];
|
|
4810
|
+
const colors = [];
|
|
4811
|
+
for (const patch of gradient.patches) {
|
|
4812
|
+
const triangles = buildPatchTriangles(gradient, grid, patch, vertexMap, subdivisions);
|
|
4813
|
+
for (const triangle of triangles) for (const vertex of triangle) {
|
|
4814
|
+
positions.push(toClipX(vertex.x, width), toClipY(vertex.y, height));
|
|
4815
|
+
colors.push(...vertex.color);
|
|
4816
|
+
}
|
|
4817
|
+
}
|
|
4818
|
+
for (const triangle of edgeTriangles) for (const vertex of triangle) {
|
|
4819
|
+
positions.push(toClipX(vertex.x, width), toClipY(vertex.y, height));
|
|
4820
|
+
colors.push(...vertex.color);
|
|
4821
|
+
}
|
|
4822
|
+
const positionBuffer = gl.createBuffer();
|
|
4823
|
+
const colorBuffer = gl.createBuffer();
|
|
4824
|
+
gl.useProgram(program);
|
|
4825
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
|
4826
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
|
|
4827
|
+
const positionLocation = gl.getAttribLocation(program, "a_position");
|
|
4828
|
+
gl.enableVertexAttribArray(positionLocation);
|
|
4829
|
+
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
|
|
4830
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
|
|
4831
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
|
|
4832
|
+
const colorLocation = gl.getAttribLocation(program, "a_color");
|
|
4833
|
+
gl.enableVertexAttribArray(colorLocation);
|
|
4834
|
+
gl.vertexAttribPointer(colorLocation, 4, gl.FLOAT, false, 0, 0);
|
|
4835
|
+
gl.clearColor(0, 0, 0, 0);
|
|
4836
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
4837
|
+
gl.drawArrays(gl.TRIANGLES, 0, positions.length / 2);
|
|
4838
|
+
} };
|
|
4839
|
+
}
|
|
4840
|
+
};
|
|
4841
|
+
//#endregion
|
|
2775
4842
|
//#region src/gradient-transformer/GradientTransformer.ts
|
|
2776
4843
|
var GradientTransformer = class {
|
|
2777
4844
|
static _modules = /* @__PURE__ */ new Map();
|
|
@@ -2804,12 +4871,19 @@ var GradientTransformer = class {
|
|
|
2804
4871
|
this._initialized = true;
|
|
2805
4872
|
this.add(new ModuleTransformerLinearGradientToCss());
|
|
2806
4873
|
this.add(new ModuleTransformerRadialGradientToCss());
|
|
4874
|
+
this.add(new ModuleTransformerDiamondGradientToCss());
|
|
2807
4875
|
this.add(new ModuleTransformerConicGradientToCss());
|
|
4876
|
+
this.add(new ModuleTransformerMeshGradientToCss());
|
|
2808
4877
|
this.add(new ModuleTransformerLinearGradientToCanvas());
|
|
2809
4878
|
this.add(new ModuleTransformerRadialGradientToCanvas());
|
|
4879
|
+
this.add(new ModuleTransformerDiamondGradientToCanvas());
|
|
2810
4880
|
this.add(new ModuleTransformerConicGradientToCanvas());
|
|
4881
|
+
this.add(new ModuleTransformerMeshGradientToCanvas());
|
|
2811
4882
|
this.add(new ModuleTransformerLinearGradientToCanvasWebGL());
|
|
2812
4883
|
this.add(new ModuleTransformerRadialGradientToCanvasWebGL());
|
|
4884
|
+
this.add(new ModuleTransformerDiamondGradientToCanvasWebGL());
|
|
4885
|
+
this.add(new ModuleTransformerConicGradientToCanvasWebGL());
|
|
4886
|
+
this.add(new ModuleTransformerMeshGradientToCanvasWebGL());
|
|
2813
4887
|
}
|
|
2814
4888
|
static _getKey(target, gradientType) {
|
|
2815
4889
|
return `${target}:${gradientType}`;
|
|
@@ -2832,7 +4906,13 @@ var GradientFactory = class {
|
|
|
2832
4906
|
return this._registry.delete(functionName);
|
|
2833
4907
|
}
|
|
2834
4908
|
static create(input) {
|
|
2835
|
-
|
|
4909
|
+
if (typeof input === "string") {
|
|
4910
|
+
const functionName = this._getFunctionName(input);
|
|
4911
|
+
const adapter = this.get(functionName);
|
|
4912
|
+
if (!adapter) throw new Error(`No gradient registered for: ${functionName}`);
|
|
4913
|
+
return adapter.fromString(input);
|
|
4914
|
+
}
|
|
4915
|
+
const abi = input;
|
|
2836
4916
|
const adapter = this.get(abi.functionName);
|
|
2837
4917
|
if (!adapter) throw new Error(`No gradient registered for: ${abi.functionName}`);
|
|
2838
4918
|
return adapter.fromAbi(abi);
|
|
@@ -2850,7 +4930,17 @@ var GradientFactory = class {
|
|
|
2850
4930
|
this._initialized = true;
|
|
2851
4931
|
this.add("linear-gradient", LinearGradient);
|
|
2852
4932
|
this.add("radial-gradient", RadialGradient);
|
|
4933
|
+
this.add("diamond-gradient", DiamondGradient);
|
|
2853
4934
|
this.add("conic-gradient", ConicGradient);
|
|
4935
|
+
this.add("mesh-gradient", MeshGradient);
|
|
4936
|
+
}
|
|
4937
|
+
static _getFunctionName(input) {
|
|
4938
|
+
const source = input.trim();
|
|
4939
|
+
const openIndex = source.indexOf("(");
|
|
4940
|
+
if (openIndex <= 0) throw new Error("Expected function opening parenthesis");
|
|
4941
|
+
let functionName = source.slice(0, openIndex).trim();
|
|
4942
|
+
if (functionName.startsWith("repeating-")) functionName = functionName.slice(10);
|
|
4943
|
+
return functionName;
|
|
2854
4944
|
}
|
|
2855
4945
|
};
|
|
2856
4946
|
function parse(input) {
|
|
@@ -2871,4 +4961,4 @@ function transformFrom(target, gradientType, input) {
|
|
|
2871
4961
|
return GradientTransformer.from(target, gradientType, input);
|
|
2872
4962
|
}
|
|
2873
4963
|
//#endregion
|
|
2874
|
-
export { ConicGradient, GRADIENT_COLOR_SPACE, GRADIENT_HUE_INTERPOLATIONS, GRADIENT_POLAR_COLOR_SPACES, GradientBase, GradientFactory, GradientTransformer, LinearGradient, ModuleTransformerConicGradientToCanvas, ModuleTransformerConicGradientToCss, ModuleTransformerLinearGradientToCanvas, ModuleTransformerLinearGradientToCanvasWebGL, ModuleTransformerLinearGradientToCss, ModuleTransformerRadialGradientToCanvas, ModuleTransformerRadialGradientToCanvasWebGL, 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 };
|
|
4964
|
+
export { ConicGradient, DiamondGradient, GRADIENT_COLOR_SPACE, GRADIENT_HUE_INTERPOLATIONS, GRADIENT_POLAR_COLOR_SPACES, GradientBase, GradientFactory, GradientTransformer, LinearGradient, MeshGradient, ModuleTransformerConicGradientToCanvas, ModuleTransformerConicGradientToCanvasWebGL, ModuleTransformerConicGradientToCss, ModuleTransformerDiamondGradientToCanvas, ModuleTransformerDiamondGradientToCanvasWebGL, ModuleTransformerDiamondGradientToCss, ModuleTransformerLinearGradientToCanvas, ModuleTransformerLinearGradientToCanvasWebGL, ModuleTransformerLinearGradientToCss, ModuleTransformerMeshGradientToCanvas, ModuleTransformerMeshGradientToCanvasWebGL, ModuleTransformerMeshGradientToCss, ModuleTransformerRadialGradientToCanvas, ModuleTransformerRadialGradientToCanvasWebGL, 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 };
|