@effing/canvas 0.22.0 → 0.22.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -93,7 +93,7 @@ type RenderReactElementOptions = {
93
93
  *
94
94
  * await renderReactElement(ctx, <MyComponent />, { fonts: [myFont] });
95
95
  *
96
- * const png = canvas.encodeSync("png");
96
+ * const png = await canvas.encode("png");
97
97
  * ```
98
98
  *
99
99
  * @example HiDPI rendering (2x)
package/dist/index.js CHANGED
@@ -764,8 +764,7 @@ function acquireOffscreen(w, h) {
764
764
  const canvas2 = ref.deref();
765
765
  if (canvas2) {
766
766
  const ctx = canvas2.getContext("2d");
767
- ctx.setTransform(1, 0, 0, 1, 0, 0);
768
- ctx.clearRect(0, 0, w, h);
767
+ ctx.reset();
769
768
  return [canvas2, ctx];
770
769
  }
771
770
  }
@@ -1041,6 +1040,7 @@ function drawBoxShadow(ctx, x, y, width, height, boxShadow, borderRadius) {
1041
1040
 
1042
1041
  // src/jsx/draw/svg.ts
1043
1042
  import { Path2D } from "@napi-rs/canvas";
1043
+ import parseCssColor from "parse-css-color";
1044
1044
  function resolveCurrentColor(value, color) {
1045
1045
  if (value?.toLowerCase() === "currentcolor") return color;
1046
1046
  return value;
@@ -1098,10 +1098,10 @@ function parseFrac(value, fallback) {
1098
1098
  }
1099
1099
  function applyOpacity(color, opacity) {
1100
1100
  if (opacity >= 1) return color;
1101
- const m = color.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i);
1102
- if (m)
1103
- return `rgba(${parseInt(m[1], 16)}, ${parseInt(m[2], 16)}, ${parseInt(m[3], 16)}, ${opacity})`;
1104
- return color;
1101
+ const parsed = parseCssColor(color);
1102
+ if (!parsed) return color;
1103
+ const [r, g, b] = parsed.values;
1104
+ return `rgba(${r}, ${g}, ${b}, ${parsed.alpha * opacity})`;
1105
1105
  }
1106
1106
  function addGradientStops(gradient, def) {
1107
1107
  for (const stop of normalizeChildren(def)) {
@@ -1438,7 +1438,14 @@ function drawSvgContainer(ctx, node, x, y, width, height) {
1438
1438
  }
1439
1439
  const color = node.style.color ?? "black";
1440
1440
  const merged = mergeStyleIntoProps(node.props);
1441
- const inheritedFill = resolveCurrentColor(merged.fill, color) ?? "black";
1441
+ const inherited = {
1442
+ fill: resolveCurrentColor(merged.fill, color) ?? "black",
1443
+ stroke: resolveCurrentColor(merged.stroke, color),
1444
+ strokeWidth: merged.strokeWidth ?? merged["stroke-width"],
1445
+ strokeLinecap: merged.strokeLinecap ?? merged["stroke-linecap"],
1446
+ strokeLinejoin: merged.strokeLinejoin ?? merged["stroke-linejoin"],
1447
+ strokeOpacity: merged.strokeOpacity ?? merged["stroke-opacity"]
1448
+ };
1442
1449
  const children = node.props.children;
1443
1450
  if (children != null) {
1444
1451
  const childArray = Array.isArray(children) ? children : [children];
@@ -1447,7 +1454,7 @@ function drawSvgContainer(ctx, node, x, y, width, height) {
1447
1454
  );
1448
1455
  const defs = collectDefs(svgChildren);
1449
1456
  for (const child of svgChildren) {
1450
- drawSvgChild(ctx, child, inheritedFill, color, defs, vbW, vbH);
1457
+ drawSvgChild(ctx, child, inherited, color, defs, vbW, vbH);
1451
1458
  }
1452
1459
  }
1453
1460
  ctx.restore();
@@ -1604,15 +1611,17 @@ function applyFilter(filterDef, sourceGraphic, w, h) {
1604
1611
  }
1605
1612
  return lastResult;
1606
1613
  }
1607
- function drawSvgChild(ctx, child, inheritedFill, color, defs = EMPTY_DEFS, vbW = 0, vbH = 0) {
1614
+ function drawSvgChild(ctx, child, inherited, color, defs = EMPTY_DEFS, vbW = 0, vbH = 0) {
1608
1615
  const { type } = child;
1609
1616
  const props = mergeStyleIntoProps(child.props);
1610
1617
  if (type === "defs" || type === "clipPath" || type === "mask" || type === "filter")
1611
1618
  return;
1612
1619
  const transform = type !== "g" ? props.transform : void 0;
1613
- if (transform) {
1620
+ const opacity = type !== "g" ? Number(props.opacity ?? 1) : 1;
1621
+ if (transform || opacity < 1) {
1614
1622
  ctx.save();
1615
- applySvgTransform(ctx, transform);
1623
+ if (transform) applySvgTransform(ctx, transform);
1624
+ if (opacity < 1) ctx.globalAlpha *= opacity;
1616
1625
  }
1617
1626
  const clipRef = parseUrlRef(props.clipPath ?? props["clip-path"]);
1618
1627
  const clipShapes = clipRef ? defs.clips.get(clipRef) : void 0;
@@ -1632,28 +1641,28 @@ function drawSvgChild(ctx, child, inheritedFill, color, defs = EMPTY_DEFS, vbW =
1632
1641
  })() : ctx;
1633
1642
  switch (type) {
1634
1643
  case "path":
1635
- drawPath(targetCtx, props, inheritedFill, color, defs);
1644
+ drawPath(targetCtx, props, inherited, color, defs);
1636
1645
  break;
1637
1646
  case "circle":
1638
- drawCircle(targetCtx, props, inheritedFill, color, defs, vbW, vbH);
1647
+ drawCircle(targetCtx, props, inherited, color, defs, vbW, vbH);
1639
1648
  break;
1640
1649
  case "rect":
1641
- drawSvgRect(targetCtx, props, inheritedFill, color, defs, vbW, vbH);
1650
+ drawSvgRect(targetCtx, props, inherited, color, defs, vbW, vbH);
1642
1651
  break;
1643
1652
  case "line":
1644
- drawLine(targetCtx, props, color, defs, vbW, vbH);
1653
+ drawLine(targetCtx, props, inherited, color, defs, vbW, vbH);
1645
1654
  break;
1646
1655
  case "ellipse":
1647
- drawEllipse(targetCtx, props, inheritedFill, color, defs, vbW, vbH);
1656
+ drawEllipse(targetCtx, props, inherited, color, defs, vbW, vbH);
1648
1657
  break;
1649
1658
  case "polygon":
1650
- drawPolygon(targetCtx, props, inheritedFill, color, defs);
1659
+ drawPolygon(targetCtx, props, inherited, color, defs);
1651
1660
  break;
1652
1661
  case "polyline":
1653
- drawPolyline(targetCtx, props, inheritedFill, color, defs);
1662
+ drawPolyline(targetCtx, props, inherited, color, defs);
1654
1663
  break;
1655
1664
  case "g":
1656
- drawGroup(targetCtx, child, inheritedFill, color, defs, vbW, vbH);
1665
+ drawGroup(targetCtx, child, inherited, color, defs, vbW, vbH);
1657
1666
  break;
1658
1667
  }
1659
1668
  if (needsOffscreen && elemCanvas) {
@@ -1663,7 +1672,15 @@ function drawSvgChild(ctx, child, inheritedFill, color, defs = EMPTY_DEFS, vbW =
1663
1672
  Math.ceil(vbH)
1664
1673
  );
1665
1674
  for (const shape of maskShapes) {
1666
- drawSvgChild(maskCtx, shape, "white", "white", defs, vbW, vbH);
1675
+ drawSvgChild(
1676
+ maskCtx,
1677
+ shape,
1678
+ { fill: "white" },
1679
+ "white",
1680
+ defs,
1681
+ vbW,
1682
+ vbH
1683
+ );
1667
1684
  }
1668
1685
  targetCtx.globalCompositeOperation = "destination-in";
1669
1686
  targetCtx.drawImage(maskCanvas, 0, 0);
@@ -1685,16 +1702,16 @@ function drawSvgChild(ctx, child, inheritedFill, color, defs = EMPTY_DEFS, vbW =
1685
1702
  releaseOffscreen(elemCanvas);
1686
1703
  }
1687
1704
  if (clipPath) ctx.restore();
1688
- if (transform) ctx.restore();
1705
+ if (transform || opacity < 1) ctx.restore();
1689
1706
  }
1690
- function drawPath(ctx, props, inheritedFill, color, defs) {
1707
+ function drawPath(ctx, props, inherited, color, defs) {
1691
1708
  const d = props.d;
1692
1709
  if (!d) return;
1693
1710
  const path = new Path2D(d);
1694
1711
  const bbox = pathBBox(d);
1695
- applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox);
1712
+ applyFillAndStroke(ctx, props, path, inherited, color, defs, bbox);
1696
1713
  }
1697
- function drawCircle(ctx, props, inheritedFill, color, defs, vbW = 0, vbH = 0) {
1714
+ function drawCircle(ctx, props, inherited, color, defs, vbW = 0, vbH = 0) {
1698
1715
  const cx = svgLength(props.cx, vbW);
1699
1716
  const cy = svgLength(props.cy, vbH);
1700
1717
  const r = svgLength(props.r, Math.min(vbW, vbH));
@@ -1702,9 +1719,9 @@ function drawCircle(ctx, props, inheritedFill, color, defs, vbW = 0, vbH = 0) {
1702
1719
  const path = new Path2D();
1703
1720
  path.arc(cx, cy, r, 0, Math.PI * 2);
1704
1721
  const bbox = { x: cx - r, y: cy - r, width: 2 * r, height: 2 * r };
1705
- applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox);
1722
+ applyFillAndStroke(ctx, props, path, inherited, color, defs, bbox);
1706
1723
  }
1707
- function drawSvgRect(ctx, props, inheritedFill, color, defs, vbW = 0, vbH = 0) {
1724
+ function drawSvgRect(ctx, props, inherited, color, defs, vbW = 0, vbH = 0) {
1708
1725
  const x = svgLength(props.x, vbW);
1709
1726
  const y = svgLength(props.y, vbH);
1710
1727
  const w = svgLength(props.width, vbW);
@@ -1723,9 +1740,9 @@ function drawSvgRect(ctx, props, inheritedFill, color, defs, vbW = 0, vbH = 0) {
1723
1740
  path.rect(x, y, w, h);
1724
1741
  }
1725
1742
  const bbox = { x, y, width: w, height: h };
1726
- applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox);
1743
+ applyFillAndStroke(ctx, props, path, inherited, color, defs, bbox);
1727
1744
  }
1728
- function drawLine(ctx, props, color, defs, vbW = 0, vbH = 0) {
1745
+ function drawLine(ctx, props, inherited, color, defs, vbW = 0, vbH = 0) {
1729
1746
  const x1 = svgLength(props.x1, vbW);
1730
1747
  const y1 = svgLength(props.y1, vbH);
1731
1748
  const x2 = svgLength(props.x2, vbW);
@@ -1739,9 +1756,9 @@ function drawLine(ctx, props, color, defs, vbW = 0, vbH = 0) {
1739
1756
  width: Math.abs(x2 - x1),
1740
1757
  height: Math.abs(y2 - y1)
1741
1758
  };
1742
- applyStroke(ctx, props, path, color, defs, bbox);
1759
+ applyStroke(ctx, props, path, inherited, color, defs, bbox);
1743
1760
  }
1744
- function drawEllipse(ctx, props, inheritedFill, color, defs, vbW = 0, vbH = 0) {
1761
+ function drawEllipse(ctx, props, inherited, color, defs, vbW = 0, vbH = 0) {
1745
1762
  const cx = svgLength(props.cx, vbW);
1746
1763
  const cy = svgLength(props.cy, vbH);
1747
1764
  const rx = svgLength(props.rx, vbW);
@@ -1755,9 +1772,9 @@ function drawEllipse(ctx, props, inheritedFill, color, defs, vbW = 0, vbH = 0) {
1755
1772
  width: 2 * rx,
1756
1773
  height: 2 * ry
1757
1774
  };
1758
- applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox);
1775
+ applyFillAndStroke(ctx, props, path, inherited, color, defs, bbox);
1759
1776
  }
1760
- function drawPolygon(ctx, props, inheritedFill, color, defs) {
1777
+ function drawPolygon(ctx, props, inherited, color, defs) {
1761
1778
  const points = parsePoints(props.points);
1762
1779
  if (points.length < 2) return;
1763
1780
  const path = new Path2D();
@@ -1767,9 +1784,9 @@ function drawPolygon(ctx, props, inheritedFill, color, defs) {
1767
1784
  }
1768
1785
  path.closePath();
1769
1786
  const bbox = pointsBBox(points);
1770
- applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox);
1787
+ applyFillAndStroke(ctx, props, path, inherited, color, defs, bbox);
1771
1788
  }
1772
- function drawPolyline(ctx, props, inheritedFill, color, defs) {
1789
+ function drawPolyline(ctx, props, inherited, color, defs) {
1773
1790
  const points = parsePoints(props.points);
1774
1791
  if (points.length < 2) return;
1775
1792
  const path = new Path2D();
@@ -1778,7 +1795,7 @@ function drawPolyline(ctx, props, inheritedFill, color, defs) {
1778
1795
  path.lineTo(points[i][0], points[i][1]);
1779
1796
  }
1780
1797
  const bbox = pointsBBox(points);
1781
- applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox);
1798
+ applyFillAndStroke(ctx, props, path, inherited, color, defs, bbox);
1782
1799
  }
1783
1800
  function applySvgTransform(ctx, transform) {
1784
1801
  const re = /\b(translate|scale|rotate|skewX|skewY|matrix)\s*\(([^)]*)\)/g;
@@ -1841,22 +1858,31 @@ function applySvgTransform(ctx, transform) {
1841
1858
  }
1842
1859
  }
1843
1860
  }
1844
- function drawGroup(ctx, node, inheritedFill, color, defs = EMPTY_DEFS, vbW = 0, vbH = 0) {
1861
+ function drawGroup(ctx, node, inherited, color, defs = EMPTY_DEFS, vbW = 0, vbH = 0) {
1845
1862
  const children = normalizeChildren(node);
1846
1863
  if (children.length === 0) return;
1847
1864
  const merged = mergeStyleIntoProps(node.props);
1848
- const groupFill = resolveCurrentColor(merged.fill, color) ?? inheritedFill;
1865
+ const groupInherited = {
1866
+ fill: resolveCurrentColor(merged.fill, color) ?? inherited.fill,
1867
+ stroke: resolveCurrentColor(merged.stroke, color) ?? inherited.stroke,
1868
+ strokeWidth: merged.strokeWidth ?? merged["stroke-width"] ?? inherited.strokeWidth,
1869
+ strokeLinecap: merged.strokeLinecap ?? merged["stroke-linecap"] ?? inherited.strokeLinecap,
1870
+ strokeLinejoin: merged.strokeLinejoin ?? merged["stroke-linejoin"] ?? inherited.strokeLinejoin,
1871
+ strokeOpacity: merged.strokeOpacity ?? merged["stroke-opacity"] ?? inherited.strokeOpacity
1872
+ };
1873
+ const opacity = Number(merged.opacity ?? 1);
1849
1874
  const transform = merged.transform;
1850
- if (transform) {
1875
+ if (transform || opacity < 1) {
1851
1876
  ctx.save();
1852
- applySvgTransform(ctx, transform);
1877
+ if (transform) applySvgTransform(ctx, transform);
1878
+ if (opacity < 1) ctx.globalAlpha *= opacity;
1853
1879
  }
1854
1880
  for (const child of children) {
1855
1881
  if (child != null && typeof child === "object") {
1856
- drawSvgChild(ctx, child, groupFill, color, defs, vbW, vbH);
1882
+ drawSvgChild(ctx, child, groupInherited, color, defs, vbW, vbH);
1857
1883
  }
1858
1884
  }
1859
- if (transform) {
1885
+ if (transform || opacity < 1) {
1860
1886
  ctx.restore();
1861
1887
  }
1862
1888
  }
@@ -1869,8 +1895,8 @@ function parsePoints(value) {
1869
1895
  }
1870
1896
  return result;
1871
1897
  }
1872
- function applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox) {
1873
- const fill = resolveCurrentColor(props.fill, color) ?? inheritedFill;
1898
+ function applyFillAndStroke(ctx, props, path, inherited, color, defs, bbox) {
1899
+ const fill = resolveCurrentColor(props.fill, color) ?? inherited.fill;
1874
1900
  const fillRule = props.fillRule ?? props["fill-rule"];
1875
1901
  const fillOpacity = Number(props.fillOpacity ?? props["fill-opacity"] ?? 1);
1876
1902
  const fillRef = parseUrlRef(fill);
@@ -1902,17 +1928,19 @@ function applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox)
1902
1928
  ctx.fillStyle = applyOpacity(fill, fillOpacity);
1903
1929
  ctx.fill(path, fillRule ?? "nonzero");
1904
1930
  }
1905
- applyStroke(ctx, props, path, color, defs, bbox);
1931
+ applyStroke(ctx, props, path, inherited, color, defs, bbox);
1906
1932
  }
1907
- function applyStroke(ctx, props, path, color, defs, bbox) {
1908
- const stroke = resolveCurrentColor(props.stroke, color);
1933
+ function applyStroke(ctx, props, path, inherited, color, defs, bbox) {
1934
+ const stroke = resolveCurrentColor(props.stroke, color) ?? inherited.stroke;
1909
1935
  if (!stroke || stroke === "none") return;
1910
1936
  const strokeOpacity = Number(
1911
- props.strokeOpacity ?? props["stroke-opacity"] ?? 1
1937
+ props.strokeOpacity ?? props["stroke-opacity"] ?? inherited.strokeOpacity ?? 1
1912
1938
  );
1913
- ctx.lineWidth = Number(props.strokeWidth ?? props["stroke-width"] ?? 1);
1914
- ctx.lineCap = props.strokeLinecap ?? props["stroke-linecap"] ?? "butt";
1915
- ctx.lineJoin = props.strokeLinejoin ?? props["stroke-linejoin"] ?? "miter";
1939
+ ctx.lineWidth = Number(
1940
+ props.strokeWidth ?? props["stroke-width"] ?? inherited.strokeWidth ?? 1
1941
+ );
1942
+ ctx.lineCap = props.strokeLinecap ?? props["stroke-linecap"] ?? inherited.strokeLinecap ?? "butt";
1943
+ ctx.lineJoin = props.strokeLinejoin ?? props["stroke-linejoin"] ?? inherited.strokeLinejoin ?? "miter";
1916
1944
  const strokeRef = parseUrlRef(stroke);
1917
1945
  if (strokeRef) {
1918
1946
  const gradientDef = defs.gradients.get(strokeRef);
@@ -3194,11 +3222,21 @@ async function buildNode(element, parentStyle, yogaNode, viewportWidth, viewport
3194
3222
  if (tagName === "svg") {
3195
3223
  if (props.width != null && style.width === void 0) {
3196
3224
  const v = props.width;
3197
- style.width = typeof v === "string" && v.endsWith("%") ? v : Number(v);
3225
+ if (typeof v === "string") {
3226
+ const resolved = resolveUnit(v, viewportWidth, viewportHeight, 16, 16);
3227
+ style.width = typeof resolved === "string" && resolved.endsWith("%") ? resolved : Number(resolved);
3228
+ } else {
3229
+ style.width = Number(v);
3230
+ }
3198
3231
  }
3199
3232
  if (props.height != null && style.height === void 0) {
3200
3233
  const v = props.height;
3201
- style.height = typeof v === "string" && v.endsWith("%") ? v : Number(v);
3234
+ if (typeof v === "string") {
3235
+ const resolved = resolveUnit(v, viewportWidth, viewportHeight, 16, 16);
3236
+ style.height = typeof resolved === "string" && resolved.endsWith("%") ? resolved : Number(resolved);
3237
+ } else {
3238
+ style.height = Number(v);
3239
+ }
3202
3240
  }
3203
3241
  const viewBox = props.viewBox;
3204
3242
  if (viewBox) {