@effing/canvas 0.20.0 → 0.20.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
@@ -21,7 +21,7 @@ function renderLottieFrame(ctx, animation, frame) {
21
21
  }
22
22
 
23
23
  // src/jsx/draw/index.ts
24
- import { createCanvas as createCanvas2, loadImage as loadImage3 } from "@napi-rs/canvas";
24
+ import { loadImage as loadImage3 } from "@napi-rs/canvas";
25
25
 
26
26
  // src/jsx/language.ts
27
27
  function isEmoji(char) {
@@ -752,6 +752,37 @@ async function drawImage(ctx, src, x, y, width, height, style, preloadedImage) {
752
752
  }
753
753
  }
754
754
 
755
+ // src/jsx/draw/offscreen.ts
756
+ import { createCanvas as createCanvas2 } from "@napi-rs/canvas";
757
+ var canvasPool = /* @__PURE__ */ new Map();
758
+ function acquireOffscreen(w, h) {
759
+ const key = `${w}x${h}`;
760
+ const stack = canvasPool.get(key);
761
+ if (stack) {
762
+ while (stack.length > 0) {
763
+ const ref = stack.pop();
764
+ const canvas2 = ref.deref();
765
+ if (canvas2) {
766
+ const ctx = canvas2.getContext("2d");
767
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
768
+ ctx.clearRect(0, 0, w, h);
769
+ return [canvas2, ctx];
770
+ }
771
+ }
772
+ }
773
+ const canvas = createCanvas2(w, h);
774
+ return [canvas, canvas.getContext("2d")];
775
+ }
776
+ function releaseOffscreen(canvas) {
777
+ const key = `${canvas.width}x${canvas.height}`;
778
+ let stack = canvasPool.get(key);
779
+ if (!stack) {
780
+ stack = [];
781
+ canvasPool.set(key, stack);
782
+ }
783
+ stack.push(new WeakRef(canvas));
784
+ }
785
+
755
786
  // src/jsx/draw/utils.ts
756
787
  function parseCSSLength(value, referenceSize) {
757
788
  if (value.endsWith("%")) return parseFloat(value) / 100 * referenceSize;
@@ -926,17 +957,19 @@ function mergeStyleIntoProps(props) {
926
957
  function collectDefs(children) {
927
958
  const clips = /* @__PURE__ */ new Map();
928
959
  const gradients = /* @__PURE__ */ new Map();
960
+ const masks = /* @__PURE__ */ new Map();
929
961
  for (const child of children) {
930
962
  if (child.type !== "defs") continue;
931
963
  for (const def of normalizeChildren(child)) {
932
964
  const id = def.props.id;
933
965
  if (!id) continue;
934
966
  if (def.type === "clipPath") clips.set(id, normalizeChildren(def));
967
+ else if (def.type === "mask") masks.set(id, normalizeChildren(def));
935
968
  else if (def.type === "radialGradient" || def.type === "linearGradient")
936
969
  gradients.set(id, def);
937
970
  }
938
971
  }
939
- return { clips, gradients };
972
+ return { clips, gradients, masks };
940
973
  }
941
974
  function parseUrlRef(value) {
942
975
  if (typeof value !== "string") return void 0;
@@ -1226,13 +1259,23 @@ function buildPath(child, vbW, vbH) {
1226
1259
  return p;
1227
1260
  }
1228
1261
  case "rect": {
1229
- const rx = svgLength(props.x, vbW);
1230
- const ry = svgLength(props.y, vbH);
1262
+ const x = svgLength(props.x, vbW);
1263
+ const y = svgLength(props.y, vbH);
1231
1264
  const w = svgLength(props.width, vbW);
1232
1265
  const h = svgLength(props.height, vbH);
1233
1266
  if (w <= 0 || h <= 0) return void 0;
1267
+ const rxRaw = svgLength(props.rx, vbW, -1);
1268
+ const ryRaw = svgLength(props.ry, vbH, -1);
1269
+ let rx = rxRaw >= 0 ? rxRaw : ryRaw >= 0 ? ryRaw : 0;
1270
+ let ry = ryRaw >= 0 ? ryRaw : rx;
1271
+ rx = Math.min(rx, w / 2);
1272
+ ry = Math.min(ry, h / 2);
1234
1273
  const p = new Path2D();
1235
- p.rect(rx, ry, w, h);
1274
+ if (rx > 0 || ry > 0) {
1275
+ p.roundRect(x, y, w, h, [rx]);
1276
+ } else {
1277
+ p.rect(x, y, w, h);
1278
+ }
1236
1279
  return p;
1237
1280
  }
1238
1281
  case "ellipse": {
@@ -1309,43 +1352,67 @@ function drawSvgContainer(ctx, node, x, y, width, height) {
1309
1352
  }
1310
1353
  var EMPTY_DEFS = {
1311
1354
  clips: /* @__PURE__ */ new Map(),
1312
- gradients: /* @__PURE__ */ new Map()
1355
+ gradients: /* @__PURE__ */ new Map(),
1356
+ masks: /* @__PURE__ */ new Map()
1313
1357
  };
1314
1358
  function drawSvgChild(ctx, child, inheritedFill, color, defs = EMPTY_DEFS, vbW = 0, vbH = 0) {
1315
1359
  const { type } = child;
1316
1360
  const props = mergeStyleIntoProps(child.props);
1317
- if (type === "defs" || type === "clipPath") return;
1361
+ if (type === "defs" || type === "clipPath" || type === "mask") return;
1318
1362
  const clipRef = parseUrlRef(props.clipPath ?? props["clip-path"]);
1319
1363
  const clipShapes = clipRef ? defs.clips.get(clipRef) : void 0;
1320
1364
  const clipPath = clipShapes ? buildClipPath(clipShapes, vbW, vbH) : void 0;
1365
+ const maskRef = parseUrlRef(props.mask);
1366
+ const maskShapes = maskRef ? defs.masks.get(maskRef) : void 0;
1321
1367
  if (clipPath) ctx.save();
1322
1368
  if (clipPath) ctx.clip(clipPath);
1369
+ let elemCanvas;
1370
+ const targetCtx = maskShapes ? (() => {
1371
+ const [c, cCtx] = acquireOffscreen(Math.ceil(vbW), Math.ceil(vbH));
1372
+ elemCanvas = c;
1373
+ return cCtx;
1374
+ })() : ctx;
1323
1375
  switch (type) {
1324
1376
  case "path":
1325
- drawPath(ctx, props, inheritedFill, color, defs);
1377
+ drawPath(targetCtx, props, inheritedFill, color, defs);
1326
1378
  break;
1327
1379
  case "circle":
1328
- drawCircle(ctx, props, inheritedFill, color, defs, vbW, vbH);
1380
+ drawCircle(targetCtx, props, inheritedFill, color, defs, vbW, vbH);
1329
1381
  break;
1330
1382
  case "rect":
1331
- drawSvgRect(ctx, props, inheritedFill, color, defs, vbW, vbH);
1383
+ drawSvgRect(targetCtx, props, inheritedFill, color, defs, vbW, vbH);
1332
1384
  break;
1333
1385
  case "line":
1334
- drawLine(ctx, props, color, defs, vbW, vbH);
1386
+ drawLine(targetCtx, props, color, defs, vbW, vbH);
1335
1387
  break;
1336
1388
  case "ellipse":
1337
- drawEllipse(ctx, props, inheritedFill, color, defs, vbW, vbH);
1389
+ drawEllipse(targetCtx, props, inheritedFill, color, defs, vbW, vbH);
1338
1390
  break;
1339
1391
  case "polygon":
1340
- drawPolygon(ctx, props, inheritedFill, color, defs);
1392
+ drawPolygon(targetCtx, props, inheritedFill, color, defs);
1341
1393
  break;
1342
1394
  case "polyline":
1343
- drawPolyline(ctx, props, inheritedFill, color, defs);
1395
+ drawPolyline(targetCtx, props, inheritedFill, color, defs);
1344
1396
  break;
1345
1397
  case "g":
1346
- drawGroup(ctx, child, inheritedFill, color, defs, vbW, vbH);
1398
+ drawGroup(targetCtx, child, inheritedFill, color, defs, vbW, vbH);
1347
1399
  break;
1348
1400
  }
1401
+ if (maskShapes && elemCanvas) {
1402
+ const [maskCanvas, maskCtx] = acquireOffscreen(
1403
+ Math.ceil(vbW),
1404
+ Math.ceil(vbH)
1405
+ );
1406
+ for (const shape of maskShapes) {
1407
+ drawSvgChild(maskCtx, shape, "white", "white", defs, vbW, vbH);
1408
+ }
1409
+ targetCtx.globalCompositeOperation = "destination-in";
1410
+ targetCtx.drawImage(maskCanvas, 0, 0);
1411
+ targetCtx.globalCompositeOperation = "source-over";
1412
+ ctx.drawImage(elemCanvas, 0, 0);
1413
+ releaseOffscreen(maskCanvas);
1414
+ releaseOffscreen(elemCanvas);
1415
+ }
1349
1416
  if (clipPath) ctx.restore();
1350
1417
  }
1351
1418
  function drawPath(ctx, props, inheritedFill, color, defs) {
@@ -1366,14 +1433,24 @@ function drawCircle(ctx, props, inheritedFill, color, defs, vbW = 0, vbH = 0) {
1366
1433
  applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox);
1367
1434
  }
1368
1435
  function drawSvgRect(ctx, props, inheritedFill, color, defs, vbW = 0, vbH = 0) {
1369
- const rx = svgLength(props.x, vbW);
1370
- const ry = svgLength(props.y, vbH);
1436
+ const x = svgLength(props.x, vbW);
1437
+ const y = svgLength(props.y, vbH);
1371
1438
  const w = svgLength(props.width, vbW);
1372
1439
  const h = svgLength(props.height, vbH);
1373
1440
  if (w <= 0 || h <= 0) return;
1441
+ const rxRaw = svgLength(props.rx, vbW, -1);
1442
+ const ryRaw = svgLength(props.ry, vbH, -1);
1443
+ let rx = rxRaw >= 0 ? rxRaw : ryRaw >= 0 ? ryRaw : 0;
1444
+ let ry = ryRaw >= 0 ? ryRaw : rx;
1445
+ rx = Math.min(rx, w / 2);
1446
+ ry = Math.min(ry, h / 2);
1374
1447
  const path = new Path2D();
1375
- path.rect(rx, ry, w, h);
1376
- const bbox = { x: rx, y: ry, width: w, height: h };
1448
+ if (rx > 0 || ry > 0) {
1449
+ path.roundRect(x, y, w, h, [rx]);
1450
+ } else {
1451
+ path.rect(x, y, w, h);
1452
+ }
1453
+ const bbox = { x, y, width: w, height: h };
1377
1454
  applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox);
1378
1455
  }
1379
1456
  function drawLine(ctx, props, color, defs, vbW = 0, vbH = 0) {
@@ -1776,34 +1853,6 @@ function drawTextDecoration(ctx, seg, offsetX, offsetY) {
1776
1853
  }
1777
1854
 
1778
1855
  // src/jsx/draw/index.ts
1779
- var canvasPool = /* @__PURE__ */ new Map();
1780
- function acquireOffscreen(w, h) {
1781
- const key = `${w}x${h}`;
1782
- const stack = canvasPool.get(key);
1783
- if (stack) {
1784
- while (stack.length > 0) {
1785
- const ref = stack.pop();
1786
- const canvas2 = ref.deref();
1787
- if (canvas2) {
1788
- const ctx = canvas2.getContext("2d");
1789
- ctx.setTransform(1, 0, 0, 1, 0, 0);
1790
- ctx.clearRect(0, 0, w, h);
1791
- return [canvas2, ctx];
1792
- }
1793
- }
1794
- }
1795
- const canvas = createCanvas2(w, h);
1796
- return [canvas, canvas.getContext("2d")];
1797
- }
1798
- function releaseOffscreen(canvas) {
1799
- const key = `${canvas.width}x${canvas.height}`;
1800
- let stack = canvasPool.get(key);
1801
- if (!stack) {
1802
- stack = [];
1803
- canvasPool.set(key, stack);
1804
- }
1805
- stack.push(new WeakRef(canvas));
1806
- }
1807
1856
  async function drawNode(ctx, node, parentX, parentY, debug, emojiStyle) {
1808
1857
  const x = parentX + node.x;
1809
1858
  const y = parentY + node.y;