@effing/canvas 0.21.1 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1054,18 +1054,24 @@ function collectDefs(children) {
1054
1054
  const clips = /* @__PURE__ */ new Map();
1055
1055
  const gradients = /* @__PURE__ */ new Map();
1056
1056
  const masks = /* @__PURE__ */ new Map();
1057
+ const filters = /* @__PURE__ */ new Map();
1058
+ function insertDef(def) {
1059
+ const id = def.props.id;
1060
+ if (!id) return;
1061
+ if (def.type === "clipPath") clips.set(id, normalizeChildren(def));
1062
+ else if (def.type === "mask") masks.set(id, normalizeChildren(def));
1063
+ else if (def.type === "filter") filters.set(id, def);
1064
+ else if (def.type === "radialGradient" || def.type === "linearGradient")
1065
+ gradients.set(id, def);
1066
+ }
1057
1067
  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);
1068
+ if (child.type === "defs") {
1069
+ for (const def of normalizeChildren(child)) insertDef(def);
1070
+ } else {
1071
+ insertDef(child);
1066
1072
  }
1067
1073
  }
1068
- return { clips, gradients, masks };
1074
+ return { clips, gradients, masks, filters };
1069
1075
  }
1070
1076
  function parseUrlRef(value) {
1071
1077
  if (typeof value !== "string") return void 0;
@@ -1449,21 +1455,177 @@ function drawSvgContainer(ctx, node, x, y, width, height) {
1449
1455
  var EMPTY_DEFS = {
1450
1456
  clips: /* @__PURE__ */ new Map(),
1451
1457
  gradients: /* @__PURE__ */ new Map(),
1452
- masks: /* @__PURE__ */ new Map()
1458
+ masks: /* @__PURE__ */ new Map(),
1459
+ filters: /* @__PURE__ */ new Map()
1453
1460
  };
1461
+ function applyFeOffset(input, dx, dy, w, h) {
1462
+ const [canvas, ctx] = acquireOffscreen(w, h);
1463
+ ctx.drawImage(input, dx, dy);
1464
+ return canvas;
1465
+ }
1466
+ function applyFeGaussianBlur(input, stdDeviation, w, h) {
1467
+ const [canvas, ctx] = acquireOffscreen(w, h);
1468
+ ctx.filter = `blur(${stdDeviation}px)`;
1469
+ ctx.drawImage(input, 0, 0);
1470
+ ctx.filter = "none";
1471
+ return canvas;
1472
+ }
1473
+ function applyFeColorMatrix(input, type, values, w, h) {
1474
+ const [canvas, ctx] = acquireOffscreen(w, h);
1475
+ ctx.drawImage(input, 0, 0);
1476
+ if (type === "matrix" && values) {
1477
+ const m = values.trim().split(/[\s,]+/).map(Number);
1478
+ if (m.length >= 20) {
1479
+ const imageData = ctx.getImageData(0, 0, w, h);
1480
+ const d = imageData.data;
1481
+ for (let i = 0; i < d.length; i += 4) {
1482
+ const r = d[i];
1483
+ const g = d[i + 1];
1484
+ const b = d[i + 2];
1485
+ const a = d[i + 3];
1486
+ d[i] = Math.min(
1487
+ 255,
1488
+ Math.max(
1489
+ 0,
1490
+ m[0] * r + m[1] * g + m[2] * b + m[3] * a + m[4] * 255
1491
+ )
1492
+ );
1493
+ d[i + 1] = Math.min(
1494
+ 255,
1495
+ Math.max(
1496
+ 0,
1497
+ m[5] * r + m[6] * g + m[7] * b + m[8] * a + m[9] * 255
1498
+ )
1499
+ );
1500
+ d[i + 2] = Math.min(
1501
+ 255,
1502
+ Math.max(
1503
+ 0,
1504
+ m[10] * r + m[11] * g + m[12] * b + m[13] * a + m[14] * 255
1505
+ )
1506
+ );
1507
+ d[i + 3] = Math.min(
1508
+ 255,
1509
+ Math.max(
1510
+ 0,
1511
+ m[15] * r + m[16] * g + m[17] * b + m[18] * a + m[19] * 255
1512
+ )
1513
+ );
1514
+ }
1515
+ ctx.putImageData(imageData, 0, 0);
1516
+ }
1517
+ }
1518
+ return canvas;
1519
+ }
1520
+ function applyFeBlend(in1, in2, mode, w, h) {
1521
+ const modeMap = {
1522
+ normal: "source-over",
1523
+ multiply: "multiply",
1524
+ screen: "screen",
1525
+ darken: "darken",
1526
+ lighten: "lighten",
1527
+ overlay: "overlay"
1528
+ };
1529
+ const [canvas, ctx] = acquireOffscreen(w, h);
1530
+ ctx.drawImage(in2, 0, 0);
1531
+ ctx.globalCompositeOperation = modeMap[mode] ?? "source-over";
1532
+ ctx.drawImage(in1, 0, 0);
1533
+ ctx.globalCompositeOperation = "source-over";
1534
+ return canvas;
1535
+ }
1536
+ function extractSourceAlpha(input, w, h) {
1537
+ const [canvas, ctx] = acquireOffscreen(w, h);
1538
+ ctx.fillStyle = "black";
1539
+ ctx.fillRect(0, 0, w, h);
1540
+ ctx.globalCompositeOperation = "destination-in";
1541
+ ctx.drawImage(input, 0, 0);
1542
+ ctx.globalCompositeOperation = "source-over";
1543
+ return canvas;
1544
+ }
1545
+ function applyFilter(filterDef, sourceGraphic, w, h) {
1546
+ const buffers = /* @__PURE__ */ new Map();
1547
+ buffers.set("SourceGraphic", sourceGraphic);
1548
+ let sourceAlpha;
1549
+ const getSourceAlpha = () => {
1550
+ if (!sourceAlpha) {
1551
+ sourceAlpha = extractSourceAlpha(sourceGraphic, w, h);
1552
+ buffers.set("SourceAlpha", sourceAlpha);
1553
+ }
1554
+ return sourceAlpha;
1555
+ };
1556
+ const children = normalizeChildren(filterDef);
1557
+ let lastResult = sourceGraphic;
1558
+ for (const prim of children) {
1559
+ const p = prim.props;
1560
+ const inName = p.in ?? "";
1561
+ const resolve = (name) => {
1562
+ if (name === "SourceAlpha") return getSourceAlpha();
1563
+ return buffers.get(name) ?? lastResult;
1564
+ };
1565
+ const input = inName ? resolve(inName) : lastResult;
1566
+ let output;
1567
+ switch (prim.type) {
1568
+ case "feOffset":
1569
+ output = applyFeOffset(
1570
+ input,
1571
+ Number(p.dx ?? 0),
1572
+ Number(p.dy ?? 0),
1573
+ w,
1574
+ h
1575
+ );
1576
+ break;
1577
+ case "feGaussianBlur": {
1578
+ const std = Number(p.stdDeviation ?? 0);
1579
+ output = std > 0 ? applyFeGaussianBlur(input, std, w, h) : input;
1580
+ break;
1581
+ }
1582
+ case "feColorMatrix":
1583
+ output = applyFeColorMatrix(
1584
+ input,
1585
+ p.type ?? "matrix",
1586
+ p.values,
1587
+ w,
1588
+ h
1589
+ );
1590
+ break;
1591
+ case "feBlend": {
1592
+ const in2Name = p.in2 ?? "";
1593
+ const in2 = in2Name ? resolve(in2Name) : lastResult;
1594
+ output = applyFeBlend(input, in2, p.mode ?? "normal", w, h);
1595
+ break;
1596
+ }
1597
+ default:
1598
+ output = input;
1599
+ break;
1600
+ }
1601
+ const resultName = p.result;
1602
+ if (resultName) buffers.set(resultName, output);
1603
+ lastResult = output;
1604
+ }
1605
+ return lastResult;
1606
+ }
1454
1607
  function drawSvgChild(ctx, child, inheritedFill, color, defs = EMPTY_DEFS, vbW = 0, vbH = 0) {
1455
1608
  const { type } = child;
1456
1609
  const props = mergeStyleIntoProps(child.props);
1457
- if (type === "defs" || type === "clipPath" || type === "mask") return;
1610
+ if (type === "defs" || type === "clipPath" || type === "mask" || type === "filter")
1611
+ return;
1612
+ const transform = type !== "g" ? props.transform : void 0;
1613
+ if (transform) {
1614
+ ctx.save();
1615
+ applySvgTransform(ctx, transform);
1616
+ }
1458
1617
  const clipRef = parseUrlRef(props.clipPath ?? props["clip-path"]);
1459
1618
  const clipShapes = clipRef ? defs.clips.get(clipRef) : void 0;
1460
1619
  const clipPath = clipShapes ? buildClipPath(clipShapes, vbW, vbH) : void 0;
1461
1620
  const maskRef = parseUrlRef(props.mask);
1462
1621
  const maskShapes = maskRef ? defs.masks.get(maskRef) : void 0;
1622
+ const filterRef = parseUrlRef(props.filter);
1623
+ const filterDef = filterRef ? defs.filters.get(filterRef) : void 0;
1463
1624
  if (clipPath) ctx.save();
1464
1625
  if (clipPath) ctx.clip(clipPath);
1626
+ const needsOffscreen = maskShapes || filterDef;
1465
1627
  let elemCanvas;
1466
- const targetCtx = maskShapes ? (() => {
1628
+ const targetCtx = needsOffscreen ? (() => {
1467
1629
  const [c, cCtx] = acquireOffscreen(Math.ceil(vbW), Math.ceil(vbH));
1468
1630
  elemCanvas = c;
1469
1631
  return cCtx;
@@ -1494,22 +1656,36 @@ function drawSvgChild(ctx, child, inheritedFill, color, defs = EMPTY_DEFS, vbW =
1494
1656
  drawGroup(targetCtx, child, inheritedFill, color, defs, vbW, vbH);
1495
1657
  break;
1496
1658
  }
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);
1659
+ if (needsOffscreen && elemCanvas) {
1660
+ if (maskShapes) {
1661
+ const [maskCanvas, maskCtx] = acquireOffscreen(
1662
+ Math.ceil(vbW),
1663
+ Math.ceil(vbH)
1664
+ );
1665
+ for (const shape of maskShapes) {
1666
+ drawSvgChild(maskCtx, shape, "white", "white", defs, vbW, vbH);
1667
+ }
1668
+ targetCtx.globalCompositeOperation = "destination-in";
1669
+ targetCtx.drawImage(maskCanvas, 0, 0);
1670
+ targetCtx.globalCompositeOperation = "source-over";
1671
+ releaseOffscreen(maskCanvas);
1672
+ }
1673
+ if (filterDef) {
1674
+ const filteredCanvas = applyFilter(
1675
+ filterDef,
1676
+ elemCanvas,
1677
+ Math.ceil(vbW),
1678
+ Math.ceil(vbH)
1679
+ );
1680
+ ctx.drawImage(filteredCanvas, 0, 0);
1681
+ if (filteredCanvas !== elemCanvas) releaseOffscreen(filteredCanvas);
1682
+ } else {
1683
+ ctx.drawImage(elemCanvas, 0, 0);
1684
+ }
1510
1685
  releaseOffscreen(elemCanvas);
1511
1686
  }
1512
1687
  if (clipPath) ctx.restore();
1688
+ if (transform) ctx.restore();
1513
1689
  }
1514
1690
  function drawPath(ctx, props, inheritedFill, color, defs) {
1515
1691
  const d = props.d;
@@ -1696,14 +1872,34 @@ function parsePoints(value) {
1696
1872
  function applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox) {
1697
1873
  const fill = resolveCurrentColor(props.fill, color) ?? inheritedFill;
1698
1874
  const fillRule = props.fillRule ?? props["fill-rule"];
1875
+ const fillOpacity = Number(props.fillOpacity ?? props["fill-opacity"] ?? 1);
1699
1876
  const fillRef = parseUrlRef(fill);
1700
1877
  if (fillRef) {
1701
1878
  const gradientDef = defs.gradients.get(fillRef);
1702
1879
  if (gradientDef) {
1703
- fillWithSvgGradient(ctx, gradientDef, bbox, path, fillRule ?? "nonzero");
1880
+ if (fillOpacity < 1) {
1881
+ ctx.save();
1882
+ ctx.globalAlpha *= fillOpacity;
1883
+ fillWithSvgGradient(
1884
+ ctx,
1885
+ gradientDef,
1886
+ bbox,
1887
+ path,
1888
+ fillRule ?? "nonzero"
1889
+ );
1890
+ ctx.restore();
1891
+ } else {
1892
+ fillWithSvgGradient(
1893
+ ctx,
1894
+ gradientDef,
1895
+ bbox,
1896
+ path,
1897
+ fillRule ?? "nonzero"
1898
+ );
1899
+ }
1704
1900
  }
1705
1901
  } else if (fill !== "none") {
1706
- ctx.fillStyle = fill;
1902
+ ctx.fillStyle = applyOpacity(fill, fillOpacity);
1707
1903
  ctx.fill(path, fillRule ?? "nonzero");
1708
1904
  }
1709
1905
  applyStroke(ctx, props, path, color, defs, bbox);
@@ -1711,6 +1907,9 @@ function applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox)
1711
1907
  function applyStroke(ctx, props, path, color, defs, bbox) {
1712
1908
  const stroke = resolveCurrentColor(props.stroke, color);
1713
1909
  if (!stroke || stroke === "none") return;
1910
+ const strokeOpacity = Number(
1911
+ props.strokeOpacity ?? props["stroke-opacity"] ?? 1
1912
+ );
1714
1913
  ctx.lineWidth = Number(props.strokeWidth ?? props["stroke-width"] ?? 1);
1715
1914
  ctx.lineCap = props.strokeLinecap ?? props["stroke-linecap"] ?? "butt";
1716
1915
  ctx.lineJoin = props.strokeLinejoin ?? props["stroke-linejoin"] ?? "miter";
@@ -1718,11 +1917,18 @@ function applyStroke(ctx, props, path, color, defs, bbox) {
1718
1917
  if (strokeRef) {
1719
1918
  const gradientDef = defs.gradients.get(strokeRef);
1720
1919
  if (gradientDef) {
1721
- strokeWithSvgGradient(ctx, gradientDef, bbox, path);
1920
+ if (strokeOpacity < 1) {
1921
+ ctx.save();
1922
+ ctx.globalAlpha *= strokeOpacity;
1923
+ strokeWithSvgGradient(ctx, gradientDef, bbox, path);
1924
+ ctx.restore();
1925
+ } else {
1926
+ strokeWithSvgGradient(ctx, gradientDef, bbox, path);
1927
+ }
1722
1928
  return;
1723
1929
  }
1724
1930
  }
1725
- ctx.strokeStyle = stroke;
1931
+ ctx.strokeStyle = applyOpacity(stroke, strokeOpacity);
1726
1932
  ctx.stroke(path);
1727
1933
  }
1728
1934
 
@@ -2177,10 +2383,14 @@ async function drawNodeCore(ctx, node, parentX, parentY, offsetX, offsetY, debug
2177
2383
  const paddingLeft = resolveBoxValue(style.paddingLeft, width);
2178
2384
  const paddingRight = resolveBoxValue(style.paddingRight, width);
2179
2385
  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;
2386
+ const borderTopW = resolveBoxValue(style.borderTopWidth, width);
2387
+ const borderLeftW = resolveBoxValue(style.borderLeftWidth, width);
2388
+ const borderRightW = resolveBoxValue(style.borderRightWidth, width);
2389
+ const borderBottomW = resolveBoxValue(style.borderBottomWidth, width);
2390
+ const imgX = x + paddingLeft + borderLeftW;
2391
+ const imgY = y + paddingTop + borderTopW;
2392
+ const imgW = width - paddingLeft - paddingRight - borderLeftW - borderRightW;
2393
+ const imgH = height - paddingTop - paddingBottom - borderTopW - borderBottomW;
2184
2394
  if (!isClipped) {
2185
2395
  const borderRadius = getBorderRadiusFromStyle(style, width, height);
2186
2396
  if (hasRadius(borderRadius)) {
@@ -2421,23 +2631,32 @@ function expandStyle(raw, fontFamilies) {
2421
2631
  }
2422
2632
  if (style.flex !== void 0) {
2423
2633
  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;
2634
+ if (val === "none") {
2635
+ if (style.flexGrow === void 0) style.flexGrow = 0;
2636
+ if (style.flexShrink === void 0) style.flexShrink = 0;
2637
+ if (style.flexBasis === void 0) style.flexBasis = "auto";
2638
+ } else {
2639
+ const parts = val.split(/\s+/);
2640
+ if (parts.length === 1) {
2641
+ const n = parseFloat(parts[0]);
2642
+ if (!isNaN(n)) {
2643
+ if (style.flexGrow === void 0) style.flexGrow = n;
2644
+ if (style.flexShrink === void 0) style.flexShrink = 1;
2645
+ if (style.flexBasis === void 0) style.flexBasis = 0;
2646
+ }
2647
+ } else if (parts.length === 2) {
2648
+ if (style.flexGrow === void 0)
2649
+ style.flexGrow = parseFloat(parts[0]);
2650
+ if (style.flexShrink === void 0)
2651
+ style.flexShrink = parseFloat(parts[1]);
2652
+ } else if (parts.length >= 3) {
2653
+ if (style.flexGrow === void 0)
2654
+ style.flexGrow = parseFloat(parts[0]);
2655
+ if (style.flexShrink === void 0)
2656
+ style.flexShrink = parseFloat(parts[1]);
2657
+ if (style.flexBasis === void 0)
2658
+ style.flexBasis = parseValue(parts[2]);
2431
2659
  }
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
2660
  }
2442
2661
  delete style.flex;
2443
2662
  }
@@ -3049,9 +3268,15 @@ async function buildNode(element, parentStyle, yogaNode, viewportWidth, viewport
3049
3268
  emojiEnabled
3050
3269
  );
3051
3270
  childYogaNode.setMeasureFunc(measureFunc);
3052
- childYogaNode.setFlexGrow(1);
3271
+ const jc = style.justifyContent;
3272
+ if (!jc || jc === "flex-start") {
3273
+ childYogaNode.setFlexGrow(1);
3274
+ }
3053
3275
  childYogaNode.setFlexShrink(1);
3054
3276
  yogaNode.insertChild(childYogaNode, 0);
3277
+ if (style.flexShrink === void 0) {
3278
+ yogaNode.setFlexShrink(1);
3279
+ }
3055
3280
  return {
3056
3281
  type: tagName,
3057
3282
  style,