@effing/canvas 0.21.1 → 0.22.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1041,6 +1041,7 @@ function drawBoxShadow(ctx, x, y, width, height, boxShadow, borderRadius) {
1041
1041
 
1042
1042
  // src/jsx/draw/svg.ts
1043
1043
  import { Path2D } from "@napi-rs/canvas";
1044
+ import parseCssColor from "parse-css-color";
1044
1045
  function resolveCurrentColor(value, color) {
1045
1046
  if (value?.toLowerCase() === "currentcolor") return color;
1046
1047
  return value;
@@ -1054,18 +1055,24 @@ function collectDefs(children) {
1054
1055
  const clips = /* @__PURE__ */ new Map();
1055
1056
  const gradients = /* @__PURE__ */ new Map();
1056
1057
  const masks = /* @__PURE__ */ new Map();
1058
+ const filters = /* @__PURE__ */ new Map();
1059
+ function insertDef(def) {
1060
+ const id = def.props.id;
1061
+ if (!id) return;
1062
+ if (def.type === "clipPath") clips.set(id, normalizeChildren(def));
1063
+ else if (def.type === "mask") masks.set(id, normalizeChildren(def));
1064
+ else if (def.type === "filter") filters.set(id, def);
1065
+ else if (def.type === "radialGradient" || def.type === "linearGradient")
1066
+ gradients.set(id, def);
1067
+ }
1057
1068
  for (const child of children) {
1058
- if (child.type !== "defs") continue;
1059
- for (const def of normalizeChildren(child)) {
1060
- const id = def.props.id;
1061
- if (!id) continue;
1062
- if (def.type === "clipPath") clips.set(id, normalizeChildren(def));
1063
- else if (def.type === "mask") masks.set(id, normalizeChildren(def));
1064
- else if (def.type === "radialGradient" || def.type === "linearGradient")
1065
- gradients.set(id, def);
1069
+ if (child.type === "defs") {
1070
+ for (const def of normalizeChildren(child)) insertDef(def);
1071
+ } else {
1072
+ insertDef(child);
1066
1073
  }
1067
1074
  }
1068
- return { clips, gradients, masks };
1075
+ return { clips, gradients, masks, filters };
1069
1076
  }
1070
1077
  function parseUrlRef(value) {
1071
1078
  if (typeof value !== "string") return void 0;
@@ -1092,10 +1099,10 @@ function parseFrac(value, fallback) {
1092
1099
  }
1093
1100
  function applyOpacity(color, opacity) {
1094
1101
  if (opacity >= 1) return color;
1095
- const m = color.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i);
1096
- if (m)
1097
- return `rgba(${parseInt(m[1], 16)}, ${parseInt(m[2], 16)}, ${parseInt(m[3], 16)}, ${opacity})`;
1098
- return color;
1102
+ const parsed = parseCssColor(color);
1103
+ if (!parsed) return color;
1104
+ const [r, g, b] = parsed.values;
1105
+ return `rgba(${r}, ${g}, ${b}, ${parsed.alpha * opacity})`;
1099
1106
  }
1100
1107
  function addGradientStops(gradient, def) {
1101
1108
  for (const stop of normalizeChildren(def)) {
@@ -1432,7 +1439,14 @@ function drawSvgContainer(ctx, node, x, y, width, height) {
1432
1439
  }
1433
1440
  const color = node.style.color ?? "black";
1434
1441
  const merged = mergeStyleIntoProps(node.props);
1435
- const inheritedFill = resolveCurrentColor(merged.fill, color) ?? "black";
1442
+ const inherited = {
1443
+ fill: resolveCurrentColor(merged.fill, color) ?? "black",
1444
+ stroke: resolveCurrentColor(merged.stroke, color),
1445
+ strokeWidth: merged.strokeWidth ?? merged["stroke-width"],
1446
+ strokeLinecap: merged.strokeLinecap ?? merged["stroke-linecap"],
1447
+ strokeLinejoin: merged.strokeLinejoin ?? merged["stroke-linejoin"],
1448
+ strokeOpacity: merged.strokeOpacity ?? merged["stroke-opacity"]
1449
+ };
1436
1450
  const children = node.props.children;
1437
1451
  if (children != null) {
1438
1452
  const childArray = Array.isArray(children) ? children : [children];
@@ -1441,7 +1455,7 @@ function drawSvgContainer(ctx, node, x, y, width, height) {
1441
1455
  );
1442
1456
  const defs = collectDefs(svgChildren);
1443
1457
  for (const child of svgChildren) {
1444
- drawSvgChild(ctx, child, inheritedFill, color, defs, vbW, vbH);
1458
+ drawSvgChild(ctx, child, inherited, color, defs, vbW, vbH);
1445
1459
  }
1446
1460
  }
1447
1461
  ctx.restore();
@@ -1449,76 +1463,254 @@ function drawSvgContainer(ctx, node, x, y, width, height) {
1449
1463
  var EMPTY_DEFS = {
1450
1464
  clips: /* @__PURE__ */ new Map(),
1451
1465
  gradients: /* @__PURE__ */ new Map(),
1452
- masks: /* @__PURE__ */ new Map()
1466
+ masks: /* @__PURE__ */ new Map(),
1467
+ filters: /* @__PURE__ */ new Map()
1453
1468
  };
1454
- function drawSvgChild(ctx, child, inheritedFill, color, defs = EMPTY_DEFS, vbW = 0, vbH = 0) {
1469
+ function applyFeOffset(input, dx, dy, w, h) {
1470
+ const [canvas, ctx] = acquireOffscreen(w, h);
1471
+ ctx.drawImage(input, dx, dy);
1472
+ return canvas;
1473
+ }
1474
+ function applyFeGaussianBlur(input, stdDeviation, w, h) {
1475
+ const [canvas, ctx] = acquireOffscreen(w, h);
1476
+ ctx.filter = `blur(${stdDeviation}px)`;
1477
+ ctx.drawImage(input, 0, 0);
1478
+ ctx.filter = "none";
1479
+ return canvas;
1480
+ }
1481
+ function applyFeColorMatrix(input, type, values, w, h) {
1482
+ const [canvas, ctx] = acquireOffscreen(w, h);
1483
+ ctx.drawImage(input, 0, 0);
1484
+ if (type === "matrix" && values) {
1485
+ const m = values.trim().split(/[\s,]+/).map(Number);
1486
+ if (m.length >= 20) {
1487
+ const imageData = ctx.getImageData(0, 0, w, h);
1488
+ const d = imageData.data;
1489
+ for (let i = 0; i < d.length; i += 4) {
1490
+ const r = d[i];
1491
+ const g = d[i + 1];
1492
+ const b = d[i + 2];
1493
+ const a = d[i + 3];
1494
+ d[i] = Math.min(
1495
+ 255,
1496
+ Math.max(
1497
+ 0,
1498
+ m[0] * r + m[1] * g + m[2] * b + m[3] * a + m[4] * 255
1499
+ )
1500
+ );
1501
+ d[i + 1] = Math.min(
1502
+ 255,
1503
+ Math.max(
1504
+ 0,
1505
+ m[5] * r + m[6] * g + m[7] * b + m[8] * a + m[9] * 255
1506
+ )
1507
+ );
1508
+ d[i + 2] = Math.min(
1509
+ 255,
1510
+ Math.max(
1511
+ 0,
1512
+ m[10] * r + m[11] * g + m[12] * b + m[13] * a + m[14] * 255
1513
+ )
1514
+ );
1515
+ d[i + 3] = Math.min(
1516
+ 255,
1517
+ Math.max(
1518
+ 0,
1519
+ m[15] * r + m[16] * g + m[17] * b + m[18] * a + m[19] * 255
1520
+ )
1521
+ );
1522
+ }
1523
+ ctx.putImageData(imageData, 0, 0);
1524
+ }
1525
+ }
1526
+ return canvas;
1527
+ }
1528
+ function applyFeBlend(in1, in2, mode, w, h) {
1529
+ const modeMap = {
1530
+ normal: "source-over",
1531
+ multiply: "multiply",
1532
+ screen: "screen",
1533
+ darken: "darken",
1534
+ lighten: "lighten",
1535
+ overlay: "overlay"
1536
+ };
1537
+ const [canvas, ctx] = acquireOffscreen(w, h);
1538
+ ctx.drawImage(in2, 0, 0);
1539
+ ctx.globalCompositeOperation = modeMap[mode] ?? "source-over";
1540
+ ctx.drawImage(in1, 0, 0);
1541
+ ctx.globalCompositeOperation = "source-over";
1542
+ return canvas;
1543
+ }
1544
+ function extractSourceAlpha(input, w, h) {
1545
+ const [canvas, ctx] = acquireOffscreen(w, h);
1546
+ ctx.fillStyle = "black";
1547
+ ctx.fillRect(0, 0, w, h);
1548
+ ctx.globalCompositeOperation = "destination-in";
1549
+ ctx.drawImage(input, 0, 0);
1550
+ ctx.globalCompositeOperation = "source-over";
1551
+ return canvas;
1552
+ }
1553
+ function applyFilter(filterDef, sourceGraphic, w, h) {
1554
+ const buffers = /* @__PURE__ */ new Map();
1555
+ buffers.set("SourceGraphic", sourceGraphic);
1556
+ let sourceAlpha;
1557
+ const getSourceAlpha = () => {
1558
+ if (!sourceAlpha) {
1559
+ sourceAlpha = extractSourceAlpha(sourceGraphic, w, h);
1560
+ buffers.set("SourceAlpha", sourceAlpha);
1561
+ }
1562
+ return sourceAlpha;
1563
+ };
1564
+ const children = normalizeChildren(filterDef);
1565
+ let lastResult = sourceGraphic;
1566
+ for (const prim of children) {
1567
+ const p = prim.props;
1568
+ const inName = p.in ?? "";
1569
+ const resolve = (name) => {
1570
+ if (name === "SourceAlpha") return getSourceAlpha();
1571
+ return buffers.get(name) ?? lastResult;
1572
+ };
1573
+ const input = inName ? resolve(inName) : lastResult;
1574
+ let output;
1575
+ switch (prim.type) {
1576
+ case "feOffset":
1577
+ output = applyFeOffset(
1578
+ input,
1579
+ Number(p.dx ?? 0),
1580
+ Number(p.dy ?? 0),
1581
+ w,
1582
+ h
1583
+ );
1584
+ break;
1585
+ case "feGaussianBlur": {
1586
+ const std = Number(p.stdDeviation ?? 0);
1587
+ output = std > 0 ? applyFeGaussianBlur(input, std, w, h) : input;
1588
+ break;
1589
+ }
1590
+ case "feColorMatrix":
1591
+ output = applyFeColorMatrix(
1592
+ input,
1593
+ p.type ?? "matrix",
1594
+ p.values,
1595
+ w,
1596
+ h
1597
+ );
1598
+ break;
1599
+ case "feBlend": {
1600
+ const in2Name = p.in2 ?? "";
1601
+ const in2 = in2Name ? resolve(in2Name) : lastResult;
1602
+ output = applyFeBlend(input, in2, p.mode ?? "normal", w, h);
1603
+ break;
1604
+ }
1605
+ default:
1606
+ output = input;
1607
+ break;
1608
+ }
1609
+ const resultName = p.result;
1610
+ if (resultName) buffers.set(resultName, output);
1611
+ lastResult = output;
1612
+ }
1613
+ return lastResult;
1614
+ }
1615
+ function drawSvgChild(ctx, child, inherited, color, defs = EMPTY_DEFS, vbW = 0, vbH = 0) {
1455
1616
  const { type } = child;
1456
1617
  const props = mergeStyleIntoProps(child.props);
1457
- if (type === "defs" || type === "clipPath" || type === "mask") return;
1618
+ if (type === "defs" || type === "clipPath" || type === "mask" || type === "filter")
1619
+ return;
1620
+ const transform = type !== "g" ? props.transform : void 0;
1621
+ if (transform) {
1622
+ ctx.save();
1623
+ applySvgTransform(ctx, transform);
1624
+ }
1458
1625
  const clipRef = parseUrlRef(props.clipPath ?? props["clip-path"]);
1459
1626
  const clipShapes = clipRef ? defs.clips.get(clipRef) : void 0;
1460
1627
  const clipPath = clipShapes ? buildClipPath(clipShapes, vbW, vbH) : void 0;
1461
1628
  const maskRef = parseUrlRef(props.mask);
1462
1629
  const maskShapes = maskRef ? defs.masks.get(maskRef) : void 0;
1630
+ const filterRef = parseUrlRef(props.filter);
1631
+ const filterDef = filterRef ? defs.filters.get(filterRef) : void 0;
1463
1632
  if (clipPath) ctx.save();
1464
1633
  if (clipPath) ctx.clip(clipPath);
1634
+ const needsOffscreen = maskShapes || filterDef;
1465
1635
  let elemCanvas;
1466
- const targetCtx = maskShapes ? (() => {
1636
+ const targetCtx = needsOffscreen ? (() => {
1467
1637
  const [c, cCtx] = acquireOffscreen(Math.ceil(vbW), Math.ceil(vbH));
1468
1638
  elemCanvas = c;
1469
1639
  return cCtx;
1470
1640
  })() : ctx;
1471
1641
  switch (type) {
1472
1642
  case "path":
1473
- drawPath(targetCtx, props, inheritedFill, color, defs);
1643
+ drawPath(targetCtx, props, inherited, color, defs);
1474
1644
  break;
1475
1645
  case "circle":
1476
- drawCircle(targetCtx, props, inheritedFill, color, defs, vbW, vbH);
1646
+ drawCircle(targetCtx, props, inherited, color, defs, vbW, vbH);
1477
1647
  break;
1478
1648
  case "rect":
1479
- drawSvgRect(targetCtx, props, inheritedFill, color, defs, vbW, vbH);
1649
+ drawSvgRect(targetCtx, props, inherited, color, defs, vbW, vbH);
1480
1650
  break;
1481
1651
  case "line":
1482
- drawLine(targetCtx, props, color, defs, vbW, vbH);
1652
+ drawLine(targetCtx, props, inherited, color, defs, vbW, vbH);
1483
1653
  break;
1484
1654
  case "ellipse":
1485
- drawEllipse(targetCtx, props, inheritedFill, color, defs, vbW, vbH);
1655
+ drawEllipse(targetCtx, props, inherited, color, defs, vbW, vbH);
1486
1656
  break;
1487
1657
  case "polygon":
1488
- drawPolygon(targetCtx, props, inheritedFill, color, defs);
1658
+ drawPolygon(targetCtx, props, inherited, color, defs);
1489
1659
  break;
1490
1660
  case "polyline":
1491
- drawPolyline(targetCtx, props, inheritedFill, color, defs);
1661
+ drawPolyline(targetCtx, props, inherited, color, defs);
1492
1662
  break;
1493
1663
  case "g":
1494
- drawGroup(targetCtx, child, inheritedFill, color, defs, vbW, vbH);
1664
+ drawGroup(targetCtx, child, inherited, color, defs, vbW, vbH);
1495
1665
  break;
1496
1666
  }
1497
- if (maskShapes && elemCanvas) {
1498
- const [maskCanvas, maskCtx] = acquireOffscreen(
1499
- Math.ceil(vbW),
1500
- Math.ceil(vbH)
1501
- );
1502
- for (const shape of maskShapes) {
1503
- drawSvgChild(maskCtx, shape, "white", "white", defs, vbW, vbH);
1504
- }
1505
- targetCtx.globalCompositeOperation = "destination-in";
1506
- targetCtx.drawImage(maskCanvas, 0, 0);
1507
- targetCtx.globalCompositeOperation = "source-over";
1508
- ctx.drawImage(elemCanvas, 0, 0);
1509
- releaseOffscreen(maskCanvas);
1667
+ if (needsOffscreen && elemCanvas) {
1668
+ if (maskShapes) {
1669
+ const [maskCanvas, maskCtx] = acquireOffscreen(
1670
+ Math.ceil(vbW),
1671
+ Math.ceil(vbH)
1672
+ );
1673
+ for (const shape of maskShapes) {
1674
+ drawSvgChild(
1675
+ maskCtx,
1676
+ shape,
1677
+ { fill: "white" },
1678
+ "white",
1679
+ defs,
1680
+ vbW,
1681
+ vbH
1682
+ );
1683
+ }
1684
+ targetCtx.globalCompositeOperation = "destination-in";
1685
+ targetCtx.drawImage(maskCanvas, 0, 0);
1686
+ targetCtx.globalCompositeOperation = "source-over";
1687
+ releaseOffscreen(maskCanvas);
1688
+ }
1689
+ if (filterDef) {
1690
+ const filteredCanvas = applyFilter(
1691
+ filterDef,
1692
+ elemCanvas,
1693
+ Math.ceil(vbW),
1694
+ Math.ceil(vbH)
1695
+ );
1696
+ ctx.drawImage(filteredCanvas, 0, 0);
1697
+ if (filteredCanvas !== elemCanvas) releaseOffscreen(filteredCanvas);
1698
+ } else {
1699
+ ctx.drawImage(elemCanvas, 0, 0);
1700
+ }
1510
1701
  releaseOffscreen(elemCanvas);
1511
1702
  }
1512
1703
  if (clipPath) ctx.restore();
1704
+ if (transform) ctx.restore();
1513
1705
  }
1514
- function drawPath(ctx, props, inheritedFill, color, defs) {
1706
+ function drawPath(ctx, props, inherited, color, defs) {
1515
1707
  const d = props.d;
1516
1708
  if (!d) return;
1517
1709
  const path = new Path2D(d);
1518
1710
  const bbox = pathBBox(d);
1519
- applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox);
1711
+ applyFillAndStroke(ctx, props, path, inherited, color, defs, bbox);
1520
1712
  }
1521
- function drawCircle(ctx, props, inheritedFill, color, defs, vbW = 0, vbH = 0) {
1713
+ function drawCircle(ctx, props, inherited, color, defs, vbW = 0, vbH = 0) {
1522
1714
  const cx = svgLength(props.cx, vbW);
1523
1715
  const cy = svgLength(props.cy, vbH);
1524
1716
  const r = svgLength(props.r, Math.min(vbW, vbH));
@@ -1526,9 +1718,9 @@ function drawCircle(ctx, props, inheritedFill, color, defs, vbW = 0, vbH = 0) {
1526
1718
  const path = new Path2D();
1527
1719
  path.arc(cx, cy, r, 0, Math.PI * 2);
1528
1720
  const bbox = { x: cx - r, y: cy - r, width: 2 * r, height: 2 * r };
1529
- applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox);
1721
+ applyFillAndStroke(ctx, props, path, inherited, color, defs, bbox);
1530
1722
  }
1531
- function drawSvgRect(ctx, props, inheritedFill, color, defs, vbW = 0, vbH = 0) {
1723
+ function drawSvgRect(ctx, props, inherited, color, defs, vbW = 0, vbH = 0) {
1532
1724
  const x = svgLength(props.x, vbW);
1533
1725
  const y = svgLength(props.y, vbH);
1534
1726
  const w = svgLength(props.width, vbW);
@@ -1547,9 +1739,9 @@ function drawSvgRect(ctx, props, inheritedFill, color, defs, vbW = 0, vbH = 0) {
1547
1739
  path.rect(x, y, w, h);
1548
1740
  }
1549
1741
  const bbox = { x, y, width: w, height: h };
1550
- applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox);
1742
+ applyFillAndStroke(ctx, props, path, inherited, color, defs, bbox);
1551
1743
  }
1552
- function drawLine(ctx, props, color, defs, vbW = 0, vbH = 0) {
1744
+ function drawLine(ctx, props, inherited, color, defs, vbW = 0, vbH = 0) {
1553
1745
  const x1 = svgLength(props.x1, vbW);
1554
1746
  const y1 = svgLength(props.y1, vbH);
1555
1747
  const x2 = svgLength(props.x2, vbW);
@@ -1563,9 +1755,9 @@ function drawLine(ctx, props, color, defs, vbW = 0, vbH = 0) {
1563
1755
  width: Math.abs(x2 - x1),
1564
1756
  height: Math.abs(y2 - y1)
1565
1757
  };
1566
- applyStroke(ctx, props, path, color, defs, bbox);
1758
+ applyStroke(ctx, props, path, inherited, color, defs, bbox);
1567
1759
  }
1568
- function drawEllipse(ctx, props, inheritedFill, color, defs, vbW = 0, vbH = 0) {
1760
+ function drawEllipse(ctx, props, inherited, color, defs, vbW = 0, vbH = 0) {
1569
1761
  const cx = svgLength(props.cx, vbW);
1570
1762
  const cy = svgLength(props.cy, vbH);
1571
1763
  const rx = svgLength(props.rx, vbW);
@@ -1579,9 +1771,9 @@ function drawEllipse(ctx, props, inheritedFill, color, defs, vbW = 0, vbH = 0) {
1579
1771
  width: 2 * rx,
1580
1772
  height: 2 * ry
1581
1773
  };
1582
- applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox);
1774
+ applyFillAndStroke(ctx, props, path, inherited, color, defs, bbox);
1583
1775
  }
1584
- function drawPolygon(ctx, props, inheritedFill, color, defs) {
1776
+ function drawPolygon(ctx, props, inherited, color, defs) {
1585
1777
  const points = parsePoints(props.points);
1586
1778
  if (points.length < 2) return;
1587
1779
  const path = new Path2D();
@@ -1591,9 +1783,9 @@ function drawPolygon(ctx, props, inheritedFill, color, defs) {
1591
1783
  }
1592
1784
  path.closePath();
1593
1785
  const bbox = pointsBBox(points);
1594
- applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox);
1786
+ applyFillAndStroke(ctx, props, path, inherited, color, defs, bbox);
1595
1787
  }
1596
- function drawPolyline(ctx, props, inheritedFill, color, defs) {
1788
+ function drawPolyline(ctx, props, inherited, color, defs) {
1597
1789
  const points = parsePoints(props.points);
1598
1790
  if (points.length < 2) return;
1599
1791
  const path = new Path2D();
@@ -1602,7 +1794,7 @@ function drawPolyline(ctx, props, inheritedFill, color, defs) {
1602
1794
  path.lineTo(points[i][0], points[i][1]);
1603
1795
  }
1604
1796
  const bbox = pointsBBox(points);
1605
- applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox);
1797
+ applyFillAndStroke(ctx, props, path, inherited, color, defs, bbox);
1606
1798
  }
1607
1799
  function applySvgTransform(ctx, transform) {
1608
1800
  const re = /\b(translate|scale|rotate|skewX|skewY|matrix)\s*\(([^)]*)\)/g;
@@ -1665,11 +1857,18 @@ function applySvgTransform(ctx, transform) {
1665
1857
  }
1666
1858
  }
1667
1859
  }
1668
- function drawGroup(ctx, node, inheritedFill, color, defs = EMPTY_DEFS, vbW = 0, vbH = 0) {
1860
+ function drawGroup(ctx, node, inherited, color, defs = EMPTY_DEFS, vbW = 0, vbH = 0) {
1669
1861
  const children = normalizeChildren(node);
1670
1862
  if (children.length === 0) return;
1671
1863
  const merged = mergeStyleIntoProps(node.props);
1672
- const groupFill = resolveCurrentColor(merged.fill, color) ?? inheritedFill;
1864
+ const groupInherited = {
1865
+ fill: resolveCurrentColor(merged.fill, color) ?? inherited.fill,
1866
+ stroke: resolveCurrentColor(merged.stroke, color) ?? inherited.stroke,
1867
+ strokeWidth: merged.strokeWidth ?? merged["stroke-width"] ?? inherited.strokeWidth,
1868
+ strokeLinecap: merged.strokeLinecap ?? merged["stroke-linecap"] ?? inherited.strokeLinecap,
1869
+ strokeLinejoin: merged.strokeLinejoin ?? merged["stroke-linejoin"] ?? inherited.strokeLinejoin,
1870
+ strokeOpacity: merged.strokeOpacity ?? merged["stroke-opacity"] ?? inherited.strokeOpacity
1871
+ };
1673
1872
  const transform = merged.transform;
1674
1873
  if (transform) {
1675
1874
  ctx.save();
@@ -1677,7 +1876,7 @@ function drawGroup(ctx, node, inheritedFill, color, defs = EMPTY_DEFS, vbW = 0,
1677
1876
  }
1678
1877
  for (const child of children) {
1679
1878
  if (child != null && typeof child === "object") {
1680
- drawSvgChild(ctx, child, groupFill, color, defs, vbW, vbH);
1879
+ drawSvgChild(ctx, child, groupInherited, color, defs, vbW, vbH);
1681
1880
  }
1682
1881
  }
1683
1882
  if (transform) {
@@ -1693,36 +1892,68 @@ function parsePoints(value) {
1693
1892
  }
1694
1893
  return result;
1695
1894
  }
1696
- function applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox) {
1697
- const fill = resolveCurrentColor(props.fill, color) ?? inheritedFill;
1895
+ function applyFillAndStroke(ctx, props, path, inherited, color, defs, bbox) {
1896
+ const fill = resolveCurrentColor(props.fill, color) ?? inherited.fill;
1698
1897
  const fillRule = props.fillRule ?? props["fill-rule"];
1898
+ const fillOpacity = Number(props.fillOpacity ?? props["fill-opacity"] ?? 1);
1699
1899
  const fillRef = parseUrlRef(fill);
1700
1900
  if (fillRef) {
1701
1901
  const gradientDef = defs.gradients.get(fillRef);
1702
1902
  if (gradientDef) {
1703
- fillWithSvgGradient(ctx, gradientDef, bbox, path, fillRule ?? "nonzero");
1903
+ if (fillOpacity < 1) {
1904
+ ctx.save();
1905
+ ctx.globalAlpha *= fillOpacity;
1906
+ fillWithSvgGradient(
1907
+ ctx,
1908
+ gradientDef,
1909
+ bbox,
1910
+ path,
1911
+ fillRule ?? "nonzero"
1912
+ );
1913
+ ctx.restore();
1914
+ } else {
1915
+ fillWithSvgGradient(
1916
+ ctx,
1917
+ gradientDef,
1918
+ bbox,
1919
+ path,
1920
+ fillRule ?? "nonzero"
1921
+ );
1922
+ }
1704
1923
  }
1705
1924
  } else if (fill !== "none") {
1706
- ctx.fillStyle = fill;
1925
+ ctx.fillStyle = applyOpacity(fill, fillOpacity);
1707
1926
  ctx.fill(path, fillRule ?? "nonzero");
1708
1927
  }
1709
- applyStroke(ctx, props, path, color, defs, bbox);
1928
+ applyStroke(ctx, props, path, inherited, color, defs, bbox);
1710
1929
  }
1711
- function applyStroke(ctx, props, path, color, defs, bbox) {
1712
- const stroke = resolveCurrentColor(props.stroke, color);
1930
+ function applyStroke(ctx, props, path, inherited, color, defs, bbox) {
1931
+ const stroke = resolveCurrentColor(props.stroke, color) ?? inherited.stroke;
1713
1932
  if (!stroke || stroke === "none") return;
1714
- ctx.lineWidth = Number(props.strokeWidth ?? props["stroke-width"] ?? 1);
1715
- ctx.lineCap = props.strokeLinecap ?? props["stroke-linecap"] ?? "butt";
1716
- ctx.lineJoin = props.strokeLinejoin ?? props["stroke-linejoin"] ?? "miter";
1933
+ const strokeOpacity = Number(
1934
+ props.strokeOpacity ?? props["stroke-opacity"] ?? inherited.strokeOpacity ?? 1
1935
+ );
1936
+ ctx.lineWidth = Number(
1937
+ props.strokeWidth ?? props["stroke-width"] ?? inherited.strokeWidth ?? 1
1938
+ );
1939
+ ctx.lineCap = props.strokeLinecap ?? props["stroke-linecap"] ?? inherited.strokeLinecap ?? "butt";
1940
+ ctx.lineJoin = props.strokeLinejoin ?? props["stroke-linejoin"] ?? inherited.strokeLinejoin ?? "miter";
1717
1941
  const strokeRef = parseUrlRef(stroke);
1718
1942
  if (strokeRef) {
1719
1943
  const gradientDef = defs.gradients.get(strokeRef);
1720
1944
  if (gradientDef) {
1721
- strokeWithSvgGradient(ctx, gradientDef, bbox, path);
1945
+ if (strokeOpacity < 1) {
1946
+ ctx.save();
1947
+ ctx.globalAlpha *= strokeOpacity;
1948
+ strokeWithSvgGradient(ctx, gradientDef, bbox, path);
1949
+ ctx.restore();
1950
+ } else {
1951
+ strokeWithSvgGradient(ctx, gradientDef, bbox, path);
1952
+ }
1722
1953
  return;
1723
1954
  }
1724
1955
  }
1725
- ctx.strokeStyle = stroke;
1956
+ ctx.strokeStyle = applyOpacity(stroke, strokeOpacity);
1726
1957
  ctx.stroke(path);
1727
1958
  }
1728
1959
 
@@ -2177,10 +2408,14 @@ async function drawNodeCore(ctx, node, parentX, parentY, offsetX, offsetY, debug
2177
2408
  const paddingLeft = resolveBoxValue(style.paddingLeft, width);
2178
2409
  const paddingRight = resolveBoxValue(style.paddingRight, width);
2179
2410
  const paddingBottom = resolveBoxValue(style.paddingBottom, width);
2180
- const imgX = x + paddingLeft;
2181
- const imgY = y + paddingTop;
2182
- const imgW = width - paddingLeft - paddingRight;
2183
- const imgH = height - paddingTop - paddingBottom;
2411
+ const borderTopW = resolveBoxValue(style.borderTopWidth, width);
2412
+ const borderLeftW = resolveBoxValue(style.borderLeftWidth, width);
2413
+ const borderRightW = resolveBoxValue(style.borderRightWidth, width);
2414
+ const borderBottomW = resolveBoxValue(style.borderBottomWidth, width);
2415
+ const imgX = x + paddingLeft + borderLeftW;
2416
+ const imgY = y + paddingTop + borderTopW;
2417
+ const imgW = width - paddingLeft - paddingRight - borderLeftW - borderRightW;
2418
+ const imgH = height - paddingTop - paddingBottom - borderTopW - borderBottomW;
2184
2419
  if (!isClipped) {
2185
2420
  const borderRadius = getBorderRadiusFromStyle(style, width, height);
2186
2421
  if (hasRadius(borderRadius)) {
@@ -2421,23 +2656,32 @@ function expandStyle(raw, fontFamilies) {
2421
2656
  }
2422
2657
  if (style.flex !== void 0) {
2423
2658
  const val = String(style.flex);
2424
- const parts = val.split(/\s+/);
2425
- if (parts.length === 1) {
2426
- const n = parseFloat(parts[0]);
2427
- if (!isNaN(n)) {
2428
- if (style.flexGrow === void 0) style.flexGrow = n;
2429
- if (style.flexShrink === void 0) style.flexShrink = 1;
2430
- if (style.flexBasis === void 0) style.flexBasis = 0;
2659
+ if (val === "none") {
2660
+ if (style.flexGrow === void 0) style.flexGrow = 0;
2661
+ if (style.flexShrink === void 0) style.flexShrink = 0;
2662
+ if (style.flexBasis === void 0) style.flexBasis = "auto";
2663
+ } else {
2664
+ const parts = val.split(/\s+/);
2665
+ if (parts.length === 1) {
2666
+ const n = parseFloat(parts[0]);
2667
+ if (!isNaN(n)) {
2668
+ if (style.flexGrow === void 0) style.flexGrow = n;
2669
+ if (style.flexShrink === void 0) style.flexShrink = 1;
2670
+ if (style.flexBasis === void 0) style.flexBasis = 0;
2671
+ }
2672
+ } else if (parts.length === 2) {
2673
+ if (style.flexGrow === void 0)
2674
+ style.flexGrow = parseFloat(parts[0]);
2675
+ if (style.flexShrink === void 0)
2676
+ style.flexShrink = parseFloat(parts[1]);
2677
+ } else if (parts.length >= 3) {
2678
+ if (style.flexGrow === void 0)
2679
+ style.flexGrow = parseFloat(parts[0]);
2680
+ if (style.flexShrink === void 0)
2681
+ style.flexShrink = parseFloat(parts[1]);
2682
+ if (style.flexBasis === void 0)
2683
+ style.flexBasis = parseValue(parts[2]);
2431
2684
  }
2432
- } else if (parts.length === 2) {
2433
- if (style.flexGrow === void 0) style.flexGrow = parseFloat(parts[0]);
2434
- if (style.flexShrink === void 0)
2435
- style.flexShrink = parseFloat(parts[1]);
2436
- } else if (parts.length >= 3) {
2437
- if (style.flexGrow === void 0) style.flexGrow = parseFloat(parts[0]);
2438
- if (style.flexShrink === void 0)
2439
- style.flexShrink = parseFloat(parts[1]);
2440
- if (style.flexBasis === void 0) style.flexBasis = parseValue(parts[2]);
2441
2685
  }
2442
2686
  delete style.flex;
2443
2687
  }
@@ -3049,9 +3293,15 @@ async function buildNode(element, parentStyle, yogaNode, viewportWidth, viewport
3049
3293
  emojiEnabled
3050
3294
  );
3051
3295
  childYogaNode.setMeasureFunc(measureFunc);
3052
- childYogaNode.setFlexGrow(1);
3296
+ const jc = style.justifyContent;
3297
+ if (!jc || jc === "flex-start") {
3298
+ childYogaNode.setFlexGrow(1);
3299
+ }
3053
3300
  childYogaNode.setFlexShrink(1);
3054
3301
  yogaNode.insertChild(childYogaNode, 0);
3302
+ if (style.flexShrink === void 0) {
3303
+ yogaNode.setFlexShrink(1);
3304
+ }
3055
3305
  return {
3056
3306
  type: tagName,
3057
3307
  style,