@effing/canvas 0.23.1 → 0.24.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/README.md +46 -39
- package/dist/index.d.ts +29 -2
- package/dist/index.js +543 -437
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createCanvas as _createCanvas } from "@napi-rs/canvas";
|
|
|
3
3
|
import {
|
|
4
4
|
Canvas,
|
|
5
5
|
GlobalFonts as GlobalFonts2,
|
|
6
|
-
loadImage as
|
|
6
|
+
loadImage as loadImage3,
|
|
7
7
|
Image
|
|
8
8
|
} from "@napi-rs/canvas";
|
|
9
9
|
|
|
@@ -20,8 +20,17 @@ function renderLottieFrame(ctx, animation, frame) {
|
|
|
20
20
|
animation.render(ctx);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
// src/jsx/
|
|
24
|
-
import { loadImage
|
|
23
|
+
// src/jsx/image-cache.ts
|
|
24
|
+
import { loadImage } from "@napi-rs/canvas";
|
|
25
|
+
function cachedLoadImage(cache, src) {
|
|
26
|
+
if (Buffer.isBuffer(src)) return loadImage(src);
|
|
27
|
+
let entry = cache.get(src);
|
|
28
|
+
if (!entry) {
|
|
29
|
+
entry = loadImage(src);
|
|
30
|
+
cache.set(src, entry);
|
|
31
|
+
}
|
|
32
|
+
return entry;
|
|
33
|
+
}
|
|
25
34
|
|
|
26
35
|
// src/jsx/style/compute.ts
|
|
27
36
|
var ROOT_FONT_SIZE = 16;
|
|
@@ -627,6 +636,8 @@ function layoutText(text, style, maxWidth, ctx, emojiEnabled) {
|
|
|
627
636
|
const segments = [];
|
|
628
637
|
let totalHeight = 0;
|
|
629
638
|
let maxLineWidth = 0;
|
|
639
|
+
const isAutoLineHeight = style.lineHeight === void 0 || style.lineHeight === "normal";
|
|
640
|
+
let lastMetrics;
|
|
630
641
|
for (let i = 0; i < lines.length; i++) {
|
|
631
642
|
const line = lines[i];
|
|
632
643
|
const lineWidth = measure(line);
|
|
@@ -644,7 +655,16 @@ function layoutText(text, style, maxWidth, ctx, emojiEnabled) {
|
|
|
644
655
|
fontStyle,
|
|
645
656
|
ctx
|
|
646
657
|
);
|
|
647
|
-
|
|
658
|
+
lastMetrics = metrics;
|
|
659
|
+
const contentHeight = metrics.ascent + metrics.descent;
|
|
660
|
+
let effectiveAscent = metrics.ascent;
|
|
661
|
+
let effectiveDescent = metrics.descent;
|
|
662
|
+
if (isAutoLineHeight && contentHeight > lineHeightPx) {
|
|
663
|
+
const scale = lineHeightPx / contentHeight;
|
|
664
|
+
effectiveAscent = metrics.ascent * scale;
|
|
665
|
+
effectiveDescent = metrics.descent * scale;
|
|
666
|
+
}
|
|
667
|
+
const baselineY = totalHeight + (lineHeightPx + effectiveAscent - effectiveDescent) / 2;
|
|
648
668
|
segments.push({
|
|
649
669
|
text: line,
|
|
650
670
|
x,
|
|
@@ -666,6 +686,14 @@ function layoutText(text, style, maxWidth, ctx, emojiEnabled) {
|
|
|
666
686
|
totalHeight += lineHeightPx;
|
|
667
687
|
maxLineWidth = Math.max(maxLineWidth, lineWidth);
|
|
668
688
|
}
|
|
689
|
+
if (isAutoLineHeight && lastMetrics && segments.length > 0) {
|
|
690
|
+
const lastSeg = segments[segments.length - 1];
|
|
691
|
+
const descentOverflow = lastSeg.y + lastMetrics.descent - totalHeight;
|
|
692
|
+
if (descentOverflow > 0) {
|
|
693
|
+
totalHeight += descentOverflow;
|
|
694
|
+
}
|
|
695
|
+
totalHeight = Math.ceil(totalHeight);
|
|
696
|
+
}
|
|
669
697
|
const textBoxTrim = style.textBoxTrim;
|
|
670
698
|
if (textBoxTrim && textBoxTrim !== "none" && segments.length > 0) {
|
|
671
699
|
const textBoxEdge = style.textBoxEdge ?? "text";
|
|
@@ -997,9 +1025,6 @@ function splitGradientArgs(content) {
|
|
|
997
1025
|
return parts;
|
|
998
1026
|
}
|
|
999
1027
|
|
|
1000
|
-
// src/jsx/draw/image.ts
|
|
1001
|
-
import { loadImage } from "@napi-rs/canvas";
|
|
1002
|
-
|
|
1003
1028
|
// src/jsx/draw/object-fit.ts
|
|
1004
1029
|
function computeCover(imgW, imgH, boxX, boxY, boxW, boxH) {
|
|
1005
1030
|
const imgRatio = imgW / imgH;
|
|
@@ -1035,8 +1060,8 @@ function computeContain(imgW, imgH, boxX, boxY, boxW, boxH) {
|
|
|
1035
1060
|
}
|
|
1036
1061
|
|
|
1037
1062
|
// src/jsx/draw/image.ts
|
|
1038
|
-
async function drawImage(ctx, src, x, y, width, height,
|
|
1039
|
-
const image =
|
|
1063
|
+
async function drawImage(ctx, src, x, y, width, height, imageCache, style) {
|
|
1064
|
+
const image = await cachedLoadImage(imageCache, src);
|
|
1040
1065
|
const objectFit = style?.objectFit ?? "fill";
|
|
1041
1066
|
if (objectFit === "fill") {
|
|
1042
1067
|
ctx.drawImage(image, x, y, width, height);
|
|
@@ -1352,9 +1377,7 @@ function drawBoxShadow(ctx, x, y, width, height, boxShadow, borderRadius) {
|
|
|
1352
1377
|
ctx.restore();
|
|
1353
1378
|
}
|
|
1354
1379
|
|
|
1355
|
-
// src/jsx/draw/svg.ts
|
|
1356
|
-
import { Path2D } from "@napi-rs/canvas";
|
|
1357
|
-
import parseCssColor from "parse-css-color";
|
|
1380
|
+
// src/jsx/draw/svg/style.ts
|
|
1358
1381
|
function resolveCurrentColor(value, color) {
|
|
1359
1382
|
if (value?.toLowerCase() === "currentcolor") return color;
|
|
1360
1383
|
return value;
|
|
@@ -1364,6 +1387,37 @@ function mergeStyleIntoProps(props) {
|
|
|
1364
1387
|
if (!style) return props;
|
|
1365
1388
|
return { ...props, ...style };
|
|
1366
1389
|
}
|
|
1390
|
+
function svgLength(value, reference, fallback = 0) {
|
|
1391
|
+
if (value == null) return fallback;
|
|
1392
|
+
const s = String(value);
|
|
1393
|
+
if (s.endsWith("%")) return parseFloat(s) / 100 * reference;
|
|
1394
|
+
const n = Number(s);
|
|
1395
|
+
return isNaN(n) ? fallback : n;
|
|
1396
|
+
}
|
|
1397
|
+
function parseUrlRef(value) {
|
|
1398
|
+
if (typeof value !== "string") return void 0;
|
|
1399
|
+
const m = value.match(/^url\(#(.+)\)$/);
|
|
1400
|
+
return m?.[1];
|
|
1401
|
+
}
|
|
1402
|
+
function resolveInheritedStyle(merged, color, parent) {
|
|
1403
|
+
return {
|
|
1404
|
+
fill: resolveCurrentColor(merged.fill, color) ?? parent?.fill ?? "black",
|
|
1405
|
+
stroke: resolveCurrentColor(merged.stroke, color) ?? parent?.stroke,
|
|
1406
|
+
strokeWidth: merged.strokeWidth ?? merged["stroke-width"] ?? parent?.strokeWidth,
|
|
1407
|
+
strokeLinecap: merged.strokeLinecap ?? merged["stroke-linecap"] ?? parent?.strokeLinecap,
|
|
1408
|
+
strokeLinejoin: merged.strokeLinejoin ?? merged["stroke-linejoin"] ?? parent?.strokeLinejoin,
|
|
1409
|
+
strokeOpacity: merged.strokeOpacity ?? merged["stroke-opacity"] ?? parent?.strokeOpacity
|
|
1410
|
+
};
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
// src/jsx/draw/svg/tree.ts
|
|
1414
|
+
function normalizeChildren(node) {
|
|
1415
|
+
const raw = node.children ?? node.props.children;
|
|
1416
|
+
if (raw == null) return [];
|
|
1417
|
+
return Array.isArray(raw) ? raw : [raw];
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
// src/jsx/draw/svg/defs.ts
|
|
1367
1421
|
function collectDefs(children) {
|
|
1368
1422
|
const clips = /* @__PURE__ */ new Map();
|
|
1369
1423
|
const gradients = /* @__PURE__ */ new Map();
|
|
@@ -1387,122 +1441,85 @@ function collectDefs(children) {
|
|
|
1387
1441
|
}
|
|
1388
1442
|
return { clips, gradients, masks, filters };
|
|
1389
1443
|
}
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
if (
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
const n = Number(s);
|
|
1405
|
-
return isNaN(n) ? fallback : n;
|
|
1406
|
-
}
|
|
1407
|
-
function parseFrac(value, fallback) {
|
|
1408
|
-
if (value == null) return fallback;
|
|
1409
|
-
const s = String(value);
|
|
1410
|
-
if (s.endsWith("%")) return parseFloat(s) / 100;
|
|
1411
|
-
return Number(s);
|
|
1412
|
-
}
|
|
1413
|
-
function applyOpacity(color, opacity) {
|
|
1414
|
-
if (opacity >= 1) return color;
|
|
1415
|
-
const parsed = parseCssColor(color);
|
|
1416
|
-
if (!parsed) return color;
|
|
1417
|
-
const [r, g, b] = parsed.values;
|
|
1418
|
-
return `rgba(${r}, ${g}, ${b}, ${parsed.alpha * opacity})`;
|
|
1419
|
-
}
|
|
1420
|
-
function addGradientStops(gradient, def) {
|
|
1421
|
-
for (const stop of normalizeChildren(def)) {
|
|
1422
|
-
if (stop.type !== "stop") continue;
|
|
1423
|
-
const sp = stop.props;
|
|
1424
|
-
const offsetRaw = sp.offset ?? 0;
|
|
1425
|
-
const offset = typeof offsetRaw === "string" && offsetRaw.endsWith("%") ? parseFloat(offsetRaw) / 100 : Number(offsetRaw);
|
|
1426
|
-
const stopColor = sp.stopColor ?? sp["stop-color"] ?? "black";
|
|
1427
|
-
const stopOpacity = Number(sp.stopOpacity ?? sp["stop-opacity"] ?? 1);
|
|
1428
|
-
gradient.addColorStop(offset, applyOpacity(stopColor, stopOpacity));
|
|
1429
|
-
}
|
|
1430
|
-
}
|
|
1431
|
-
function fillWithSvgGradient(ctx, def, bbox, path, fillRule) {
|
|
1432
|
-
const props = def.props;
|
|
1433
|
-
if (def.type === "linearGradient") {
|
|
1434
|
-
const x1 = bbox.x + parseFrac(props.x1, 0) * bbox.width;
|
|
1435
|
-
const y1 = bbox.y + parseFrac(props.y1, 0) * bbox.height;
|
|
1436
|
-
const x2 = bbox.x + parseFrac(props.x2, 1) * bbox.width;
|
|
1437
|
-
const y2 = bbox.y + parseFrac(props.y2, 0) * bbox.height;
|
|
1438
|
-
const gradient = ctx.createLinearGradient(x1, y1, x2, y2);
|
|
1439
|
-
addGradientStops(gradient, def);
|
|
1440
|
-
ctx.fillStyle = gradient;
|
|
1441
|
-
ctx.fill(path, fillRule);
|
|
1442
|
-
return true;
|
|
1443
|
-
}
|
|
1444
|
-
if (def.type === "radialGradient") {
|
|
1445
|
-
const cxF = parseFrac(props.cx, 0.5);
|
|
1446
|
-
const cyF = parseFrac(props.cy, 0.5);
|
|
1447
|
-
const rF = parseFrac(props.r, 0.5);
|
|
1448
|
-
const fxF = parseFrac(props.fx, cxF);
|
|
1449
|
-
const fyF = parseFrac(props.fy, cyF);
|
|
1450
|
-
ctx.save();
|
|
1451
|
-
ctx.translate(bbox.x, bbox.y);
|
|
1452
|
-
ctx.scale(bbox.width, bbox.height);
|
|
1453
|
-
const gradient = ctx.createRadialGradient(fxF, fyF, 0, cxF, cyF, rF);
|
|
1454
|
-
addGradientStops(gradient, def);
|
|
1455
|
-
ctx.fillStyle = gradient;
|
|
1456
|
-
const unitPath = new Path2D();
|
|
1457
|
-
const invTransform = {
|
|
1458
|
-
a: 1 / bbox.width,
|
|
1459
|
-
b: 0,
|
|
1460
|
-
c: 0,
|
|
1461
|
-
d: 1 / bbox.height,
|
|
1462
|
-
e: -bbox.x / bbox.width,
|
|
1463
|
-
f: -bbox.y / bbox.height
|
|
1464
|
-
};
|
|
1465
|
-
unitPath.addPath(path, invTransform);
|
|
1466
|
-
ctx.fill(unitPath, fillRule);
|
|
1467
|
-
ctx.restore();
|
|
1468
|
-
return true;
|
|
1469
|
-
}
|
|
1470
|
-
return false;
|
|
1444
|
+
var EMPTY_DEFS = {
|
|
1445
|
+
clips: /* @__PURE__ */ new Map(),
|
|
1446
|
+
gradients: /* @__PURE__ */ new Map(),
|
|
1447
|
+
masks: /* @__PURE__ */ new Map(),
|
|
1448
|
+
filters: /* @__PURE__ */ new Map()
|
|
1449
|
+
};
|
|
1450
|
+
|
|
1451
|
+
// src/jsx/draw/svg/transform.ts
|
|
1452
|
+
function applyTransformAndOpacity(ctx, transform, opacity) {
|
|
1453
|
+
if (!transform && opacity >= 1) return false;
|
|
1454
|
+
ctx.save();
|
|
1455
|
+
if (transform) applySvgTransform(ctx, transform);
|
|
1456
|
+
if (opacity < 1) ctx.globalAlpha *= opacity;
|
|
1457
|
+
return true;
|
|
1471
1458
|
}
|
|
1472
|
-
function
|
|
1473
|
-
const
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
const
|
|
1477
|
-
const
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1459
|
+
function applySvgTransform(ctx, transform) {
|
|
1460
|
+
const re = /\b(translate|scale|rotate|skewX|skewY|matrix)\s*\(([^)]*)\)/g;
|
|
1461
|
+
let match;
|
|
1462
|
+
while ((match = re.exec(transform)) !== null) {
|
|
1463
|
+
const fn = match[1];
|
|
1464
|
+
const args = match[2].split(/[\s,]+/).filter(Boolean).map(Number);
|
|
1465
|
+
switch (fn) {
|
|
1466
|
+
case "translate":
|
|
1467
|
+
ctx.translate(args[0] ?? 0, args[1] ?? 0);
|
|
1468
|
+
break;
|
|
1469
|
+
case "scale": {
|
|
1470
|
+
const sx = args[0] ?? 1;
|
|
1471
|
+
ctx.scale(sx, args[1] ?? sx);
|
|
1472
|
+
break;
|
|
1473
|
+
}
|
|
1474
|
+
case "rotate": {
|
|
1475
|
+
const angle = (args[0] ?? 0) * Math.PI / 180;
|
|
1476
|
+
if (args.length >= 3) {
|
|
1477
|
+
const cx = args[1];
|
|
1478
|
+
const cy = args[2];
|
|
1479
|
+
ctx.translate(cx, cy);
|
|
1480
|
+
ctx.rotate(angle);
|
|
1481
|
+
ctx.translate(-cx, -cy);
|
|
1482
|
+
} else {
|
|
1483
|
+
ctx.rotate(angle);
|
|
1484
|
+
}
|
|
1485
|
+
break;
|
|
1486
|
+
}
|
|
1487
|
+
case "skewX":
|
|
1488
|
+
ctx.transform(
|
|
1489
|
+
1,
|
|
1490
|
+
0,
|
|
1491
|
+
Math.tan((args[0] ?? 0) * Math.PI / 180),
|
|
1492
|
+
1,
|
|
1493
|
+
0,
|
|
1494
|
+
0
|
|
1495
|
+
);
|
|
1496
|
+
break;
|
|
1497
|
+
case "skewY":
|
|
1498
|
+
ctx.transform(
|
|
1499
|
+
1,
|
|
1500
|
+
Math.tan((args[0] ?? 0) * Math.PI / 180),
|
|
1501
|
+
0,
|
|
1502
|
+
1,
|
|
1503
|
+
0,
|
|
1504
|
+
0
|
|
1505
|
+
);
|
|
1506
|
+
break;
|
|
1507
|
+
case "matrix":
|
|
1508
|
+
ctx.transform(
|
|
1509
|
+
args[0] ?? 1,
|
|
1510
|
+
args[1] ?? 0,
|
|
1511
|
+
args[2] ?? 0,
|
|
1512
|
+
args[3] ?? 1,
|
|
1513
|
+
args[4] ?? 0,
|
|
1514
|
+
args[5] ?? 0
|
|
1515
|
+
);
|
|
1516
|
+
break;
|
|
1517
|
+
}
|
|
1503
1518
|
}
|
|
1504
|
-
return false;
|
|
1505
1519
|
}
|
|
1520
|
+
|
|
1521
|
+
// src/jsx/draw/svg/path.ts
|
|
1522
|
+
import { Path2D } from "@napi-rs/canvas";
|
|
1506
1523
|
function pathBBox(d) {
|
|
1507
1524
|
let minX = Infinity;
|
|
1508
1525
|
let minY = Infinity;
|
|
@@ -1732,53 +1749,17 @@ function buildClipPath(shapes, vbW, vbH) {
|
|
|
1732
1749
|
}
|
|
1733
1750
|
return combined;
|
|
1734
1751
|
}
|
|
1735
|
-
function
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
const
|
|
1739
|
-
let
|
|
1740
|
-
|
|
1741
|
-
if (viewBox) {
|
|
1742
|
-
const parts = viewBox.split(/[\s,]+/).map(Number);
|
|
1743
|
-
if (parts.length === 4) {
|
|
1744
|
-
const [vbX, vbY, parsedW, parsedH] = parts;
|
|
1745
|
-
vbW = parsedW;
|
|
1746
|
-
vbH = parsedH;
|
|
1747
|
-
const scaleX = width / vbW;
|
|
1748
|
-
const scaleY = height / vbH;
|
|
1749
|
-
ctx.scale(scaleX, scaleY);
|
|
1750
|
-
ctx.translate(-vbX, -vbY);
|
|
1751
|
-
}
|
|
1752
|
-
}
|
|
1753
|
-
const color = node.style.color ?? "black";
|
|
1754
|
-
const merged = mergeStyleIntoProps(node.props);
|
|
1755
|
-
const inherited = {
|
|
1756
|
-
fill: resolveCurrentColor(merged.fill, color) ?? "black",
|
|
1757
|
-
stroke: resolveCurrentColor(merged.stroke, color),
|
|
1758
|
-
strokeWidth: merged.strokeWidth ?? merged["stroke-width"],
|
|
1759
|
-
strokeLinecap: merged.strokeLinecap ?? merged["stroke-linecap"],
|
|
1760
|
-
strokeLinejoin: merged.strokeLinejoin ?? merged["stroke-linejoin"],
|
|
1761
|
-
strokeOpacity: merged.strokeOpacity ?? merged["stroke-opacity"]
|
|
1762
|
-
};
|
|
1763
|
-
const children = node.props.children;
|
|
1764
|
-
if (children != null) {
|
|
1765
|
-
const childArray = Array.isArray(children) ? children : [children];
|
|
1766
|
-
const svgChildren = childArray.filter(
|
|
1767
|
-
(c) => c != null && typeof c === "object"
|
|
1768
|
-
);
|
|
1769
|
-
const defs = collectDefs(svgChildren);
|
|
1770
|
-
for (const child of svgChildren) {
|
|
1771
|
-
drawSvgChild(ctx, child, inherited, color, defs, vbW, vbH);
|
|
1772
|
-
}
|
|
1752
|
+
function parsePoints(value) {
|
|
1753
|
+
if (!value) return [];
|
|
1754
|
+
const nums = value.trim().split(/[\s,]+/).map(Number);
|
|
1755
|
+
const result = [];
|
|
1756
|
+
for (let i = 0; i + 1 < nums.length; i += 2) {
|
|
1757
|
+
result.push([nums[i], nums[i + 1]]);
|
|
1773
1758
|
}
|
|
1774
|
-
|
|
1759
|
+
return result;
|
|
1775
1760
|
}
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
gradients: /* @__PURE__ */ new Map(),
|
|
1779
|
-
masks: /* @__PURE__ */ new Map(),
|
|
1780
|
-
filters: /* @__PURE__ */ new Map()
|
|
1781
|
-
};
|
|
1761
|
+
|
|
1762
|
+
// src/jsx/draw/svg/filters.ts
|
|
1782
1763
|
function applyFeOffset(input, dx, dy, w, h) {
|
|
1783
1764
|
const [canvas, ctx] = acquireOffscreen(w, h);
|
|
1784
1765
|
ctx.drawImage(input, dx, dy);
|
|
@@ -1838,18 +1819,18 @@ function applyFeColorMatrix(input, type, values, w, h) {
|
|
|
1838
1819
|
}
|
|
1839
1820
|
return canvas;
|
|
1840
1821
|
}
|
|
1822
|
+
var BLEND_MODE_MAP = {
|
|
1823
|
+
normal: "source-over",
|
|
1824
|
+
multiply: "multiply",
|
|
1825
|
+
screen: "screen",
|
|
1826
|
+
darken: "darken",
|
|
1827
|
+
lighten: "lighten",
|
|
1828
|
+
overlay: "overlay"
|
|
1829
|
+
};
|
|
1841
1830
|
function applyFeBlend(in1, in2, mode, w, h) {
|
|
1842
|
-
const modeMap = {
|
|
1843
|
-
normal: "source-over",
|
|
1844
|
-
multiply: "multiply",
|
|
1845
|
-
screen: "screen",
|
|
1846
|
-
darken: "darken",
|
|
1847
|
-
lighten: "lighten",
|
|
1848
|
-
overlay: "overlay"
|
|
1849
|
-
};
|
|
1850
1831
|
const [canvas, ctx] = acquireOffscreen(w, h);
|
|
1851
1832
|
ctx.drawImage(in2, 0, 0);
|
|
1852
|
-
ctx.globalCompositeOperation =
|
|
1833
|
+
ctx.globalCompositeOperation = BLEND_MODE_MAP[mode] ?? "source-over";
|
|
1853
1834
|
ctx.drawImage(in1, 0, 0);
|
|
1854
1835
|
ctx.globalCompositeOperation = "source-over";
|
|
1855
1836
|
return canvas;
|
|
@@ -1925,103 +1906,181 @@ function applyFilter(filterDef, sourceGraphic, w, h) {
|
|
|
1925
1906
|
}
|
|
1926
1907
|
return lastResult;
|
|
1927
1908
|
}
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1909
|
+
|
|
1910
|
+
// src/jsx/draw/svg/shapes.ts
|
|
1911
|
+
import { Path2D as Path2D3 } from "@napi-rs/canvas";
|
|
1912
|
+
|
|
1913
|
+
// src/jsx/draw/svg/gradients.ts
|
|
1914
|
+
import { Path2D as Path2D2 } from "@napi-rs/canvas";
|
|
1915
|
+
import parseCssColor from "parse-css-color";
|
|
1916
|
+
function parseFrac(value, fallback) {
|
|
1917
|
+
if (value == null) return fallback;
|
|
1918
|
+
const s = String(value);
|
|
1919
|
+
if (s.endsWith("%")) return parseFloat(s) / 100;
|
|
1920
|
+
return Number(s);
|
|
1921
|
+
}
|
|
1922
|
+
function applyOpacity(color, opacity) {
|
|
1923
|
+
if (opacity >= 1) return color;
|
|
1924
|
+
const parsed = parseCssColor(color);
|
|
1925
|
+
if (!parsed) return color;
|
|
1926
|
+
const [r, g, b] = parsed.values;
|
|
1927
|
+
return `rgba(${r}, ${g}, ${b}, ${parsed.alpha * opacity})`;
|
|
1928
|
+
}
|
|
1929
|
+
function addGradientStops(gradient, def) {
|
|
1930
|
+
for (const stop of normalizeChildren(def)) {
|
|
1931
|
+
if (stop.type !== "stop") continue;
|
|
1932
|
+
const sp = stop.props;
|
|
1933
|
+
const offsetRaw = sp.offset ?? 0;
|
|
1934
|
+
const offset = typeof offsetRaw === "string" && offsetRaw.endsWith("%") ? parseFloat(offsetRaw) / 100 : Number(offsetRaw);
|
|
1935
|
+
const stopColor = sp.stopColor ?? sp["stop-color"] ?? "black";
|
|
1936
|
+
const stopOpacity = Number(sp.stopOpacity ?? sp["stop-opacity"] ?? 1);
|
|
1937
|
+
gradient.addColorStop(offset, applyOpacity(stopColor, stopOpacity));
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
function createLinearGradientFromBBox(ctx, props, bbox, def) {
|
|
1941
|
+
const x1 = bbox.x + parseFrac(props.x1, 0) * bbox.width;
|
|
1942
|
+
const y1 = bbox.y + parseFrac(props.y1, 0) * bbox.height;
|
|
1943
|
+
const x2 = bbox.x + parseFrac(props.x2, 1) * bbox.width;
|
|
1944
|
+
const y2 = bbox.y + parseFrac(props.y2, 0) * bbox.height;
|
|
1945
|
+
const gradient = ctx.createLinearGradient(x1, y1, x2, y2);
|
|
1946
|
+
addGradientStops(gradient, def);
|
|
1947
|
+
return gradient;
|
|
1948
|
+
}
|
|
1949
|
+
function fillWithSvgGradient(ctx, def, bbox, path, fillRule) {
|
|
1950
|
+
const props = def.props;
|
|
1951
|
+
if (def.type === "linearGradient") {
|
|
1952
|
+
ctx.fillStyle = createLinearGradientFromBBox(ctx, props, bbox, def);
|
|
1953
|
+
ctx.fill(path, fillRule);
|
|
1954
|
+
return true;
|
|
1955
|
+
}
|
|
1956
|
+
if (def.type === "radialGradient") {
|
|
1957
|
+
const cxF = parseFrac(props.cx, 0.5);
|
|
1958
|
+
const cyF = parseFrac(props.cy, 0.5);
|
|
1959
|
+
const rF = parseFrac(props.r, 0.5);
|
|
1960
|
+
const fxF = parseFrac(props.fx, cxF);
|
|
1961
|
+
const fyF = parseFrac(props.fy, cyF);
|
|
1936
1962
|
ctx.save();
|
|
1937
|
-
|
|
1938
|
-
|
|
1963
|
+
ctx.translate(bbox.x, bbox.y);
|
|
1964
|
+
ctx.scale(bbox.width, bbox.height);
|
|
1965
|
+
const gradient = ctx.createRadialGradient(fxF, fyF, 0, cxF, cyF, rF);
|
|
1966
|
+
addGradientStops(gradient, def);
|
|
1967
|
+
ctx.fillStyle = gradient;
|
|
1968
|
+
const unitPath = new Path2D2();
|
|
1969
|
+
const invTransform = {
|
|
1970
|
+
a: 1 / bbox.width,
|
|
1971
|
+
b: 0,
|
|
1972
|
+
c: 0,
|
|
1973
|
+
d: 1 / bbox.height,
|
|
1974
|
+
e: -bbox.x / bbox.width,
|
|
1975
|
+
f: -bbox.y / bbox.height
|
|
1976
|
+
};
|
|
1977
|
+
unitPath.addPath(path, invTransform);
|
|
1978
|
+
ctx.fill(unitPath, fillRule);
|
|
1979
|
+
ctx.restore();
|
|
1980
|
+
return true;
|
|
1939
1981
|
}
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
const
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
if (clipPath) ctx.clip(clipPath);
|
|
1949
|
-
const needsOffscreen = maskShapes || filterDef;
|
|
1950
|
-
let elemCanvas;
|
|
1951
|
-
const targetCtx = needsOffscreen ? (() => {
|
|
1952
|
-
const [c, cCtx] = acquireOffscreen(Math.ceil(vbW), Math.ceil(vbH));
|
|
1953
|
-
elemCanvas = c;
|
|
1954
|
-
return cCtx;
|
|
1955
|
-
})() : ctx;
|
|
1956
|
-
switch (type) {
|
|
1957
|
-
case "path":
|
|
1958
|
-
drawPath(targetCtx, props, inherited, color, defs);
|
|
1959
|
-
break;
|
|
1960
|
-
case "circle":
|
|
1961
|
-
drawCircle(targetCtx, props, inherited, color, defs, vbW, vbH);
|
|
1962
|
-
break;
|
|
1963
|
-
case "rect":
|
|
1964
|
-
drawSvgRect(targetCtx, props, inherited, color, defs, vbW, vbH);
|
|
1965
|
-
break;
|
|
1966
|
-
case "line":
|
|
1967
|
-
drawLine(targetCtx, props, inherited, color, defs, vbW, vbH);
|
|
1968
|
-
break;
|
|
1969
|
-
case "ellipse":
|
|
1970
|
-
drawEllipse(targetCtx, props, inherited, color, defs, vbW, vbH);
|
|
1971
|
-
break;
|
|
1972
|
-
case "polygon":
|
|
1973
|
-
drawPolygon(targetCtx, props, inherited, color, defs);
|
|
1974
|
-
break;
|
|
1975
|
-
case "polyline":
|
|
1976
|
-
drawPolyline(targetCtx, props, inherited, color, defs);
|
|
1977
|
-
break;
|
|
1978
|
-
case "g":
|
|
1979
|
-
drawGroup(targetCtx, child, inherited, color, defs, vbW, vbH);
|
|
1980
|
-
break;
|
|
1982
|
+
return false;
|
|
1983
|
+
}
|
|
1984
|
+
function strokeWithSvgGradient(ctx, def, bbox, path) {
|
|
1985
|
+
const props = def.props;
|
|
1986
|
+
if (def.type === "linearGradient") {
|
|
1987
|
+
ctx.strokeStyle = createLinearGradientFromBBox(ctx, props, bbox, def);
|
|
1988
|
+
ctx.stroke(path);
|
|
1989
|
+
return true;
|
|
1981
1990
|
}
|
|
1982
|
-
if (
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1991
|
+
if (def.type === "radialGradient") {
|
|
1992
|
+
const cxAbs = bbox.x + parseFrac(props.cx, 0.5) * bbox.width;
|
|
1993
|
+
const cyAbs = bbox.y + parseFrac(props.cy, 0.5) * bbox.height;
|
|
1994
|
+
const rAbs = parseFrac(props.r, 0.5) * Math.sqrt(bbox.width * bbox.height);
|
|
1995
|
+
const fxAbs = bbox.x + parseFrac(props.fx, 0.5) * bbox.width;
|
|
1996
|
+
const fyAbs = bbox.y + parseFrac(props.fy, 0.5) * bbox.height;
|
|
1997
|
+
const gradient = ctx.createRadialGradient(
|
|
1998
|
+
fxAbs,
|
|
1999
|
+
fyAbs,
|
|
2000
|
+
0,
|
|
2001
|
+
cxAbs,
|
|
2002
|
+
cyAbs,
|
|
2003
|
+
rAbs
|
|
2004
|
+
);
|
|
2005
|
+
addGradientStops(gradient, def);
|
|
2006
|
+
ctx.strokeStyle = gradient;
|
|
2007
|
+
ctx.stroke(path);
|
|
2008
|
+
return true;
|
|
2009
|
+
}
|
|
2010
|
+
return false;
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
// src/jsx/draw/svg/paint.ts
|
|
2014
|
+
function applyFillAndStroke(ctx, props, path, inherited, color, defs, bbox) {
|
|
2015
|
+
const fill = resolveCurrentColor(props.fill, color) ?? inherited.fill;
|
|
2016
|
+
const fillRule = props.fillRule ?? props["fill-rule"];
|
|
2017
|
+
const fillOpacity = Number(props.fillOpacity ?? props["fill-opacity"] ?? 1);
|
|
2018
|
+
const fillRef = parseUrlRef(fill);
|
|
2019
|
+
if (fillRef) {
|
|
2020
|
+
const gradientDef = defs.gradients.get(fillRef);
|
|
2021
|
+
if (gradientDef) {
|
|
2022
|
+
if (fillOpacity < 1) {
|
|
2023
|
+
ctx.save();
|
|
2024
|
+
ctx.globalAlpha *= fillOpacity;
|
|
2025
|
+
fillWithSvgGradient(
|
|
2026
|
+
ctx,
|
|
2027
|
+
gradientDef,
|
|
2028
|
+
bbox,
|
|
2029
|
+
path,
|
|
2030
|
+
fillRule ?? "nonzero"
|
|
2031
|
+
);
|
|
2032
|
+
ctx.restore();
|
|
2033
|
+
} else {
|
|
2034
|
+
fillWithSvgGradient(
|
|
2035
|
+
ctx,
|
|
2036
|
+
gradientDef,
|
|
2037
|
+
bbox,
|
|
2038
|
+
path,
|
|
2039
|
+
fillRule ?? "nonzero"
|
|
1997
2040
|
);
|
|
1998
2041
|
}
|
|
1999
|
-
targetCtx.globalCompositeOperation = "destination-in";
|
|
2000
|
-
targetCtx.drawImage(maskCanvas, 0, 0);
|
|
2001
|
-
targetCtx.globalCompositeOperation = "source-over";
|
|
2002
|
-
releaseOffscreen(maskCanvas);
|
|
2003
|
-
}
|
|
2004
|
-
if (filterDef) {
|
|
2005
|
-
const filteredCanvas = applyFilter(
|
|
2006
|
-
filterDef,
|
|
2007
|
-
elemCanvas,
|
|
2008
|
-
Math.ceil(vbW),
|
|
2009
|
-
Math.ceil(vbH)
|
|
2010
|
-
);
|
|
2011
|
-
ctx.drawImage(filteredCanvas, 0, 0);
|
|
2012
|
-
if (filteredCanvas !== elemCanvas) releaseOffscreen(filteredCanvas);
|
|
2013
|
-
} else {
|
|
2014
|
-
ctx.drawImage(elemCanvas, 0, 0);
|
|
2015
2042
|
}
|
|
2016
|
-
|
|
2043
|
+
} else if (fill !== "none") {
|
|
2044
|
+
ctx.fillStyle = applyOpacity(fill, fillOpacity);
|
|
2045
|
+
ctx.fill(path, fillRule ?? "nonzero");
|
|
2017
2046
|
}
|
|
2018
|
-
|
|
2019
|
-
|
|
2047
|
+
applyStroke(ctx, props, path, inherited, color, defs, bbox);
|
|
2048
|
+
}
|
|
2049
|
+
function applyStroke(ctx, props, path, inherited, color, defs, bbox) {
|
|
2050
|
+
const stroke = resolveCurrentColor(props.stroke, color) ?? inherited.stroke;
|
|
2051
|
+
if (!stroke || stroke === "none") return;
|
|
2052
|
+
const strokeOpacity = Number(
|
|
2053
|
+
props.strokeOpacity ?? props["stroke-opacity"] ?? inherited.strokeOpacity ?? 1
|
|
2054
|
+
);
|
|
2055
|
+
ctx.lineWidth = Number(
|
|
2056
|
+
props.strokeWidth ?? props["stroke-width"] ?? inherited.strokeWidth ?? 1
|
|
2057
|
+
);
|
|
2058
|
+
ctx.lineCap = props.strokeLinecap ?? props["stroke-linecap"] ?? inherited.strokeLinecap ?? "butt";
|
|
2059
|
+
ctx.lineJoin = props.strokeLinejoin ?? props["stroke-linejoin"] ?? inherited.strokeLinejoin ?? "miter";
|
|
2060
|
+
const strokeRef = parseUrlRef(stroke);
|
|
2061
|
+
if (strokeRef) {
|
|
2062
|
+
const gradientDef = defs.gradients.get(strokeRef);
|
|
2063
|
+
if (gradientDef) {
|
|
2064
|
+
if (strokeOpacity < 1) {
|
|
2065
|
+
ctx.save();
|
|
2066
|
+
ctx.globalAlpha *= strokeOpacity;
|
|
2067
|
+
strokeWithSvgGradient(ctx, gradientDef, bbox, path);
|
|
2068
|
+
ctx.restore();
|
|
2069
|
+
} else {
|
|
2070
|
+
strokeWithSvgGradient(ctx, gradientDef, bbox, path);
|
|
2071
|
+
}
|
|
2072
|
+
return;
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
ctx.strokeStyle = applyOpacity(stroke, strokeOpacity);
|
|
2076
|
+
ctx.stroke(path);
|
|
2020
2077
|
}
|
|
2078
|
+
|
|
2079
|
+
// src/jsx/draw/svg/shapes.ts
|
|
2021
2080
|
function drawPath(ctx, props, inherited, color, defs) {
|
|
2022
2081
|
const d = props.d;
|
|
2023
2082
|
if (!d) return;
|
|
2024
|
-
const path = new
|
|
2083
|
+
const path = new Path2D3(d);
|
|
2025
2084
|
const bbox = pathBBox(d);
|
|
2026
2085
|
applyFillAndStroke(ctx, props, path, inherited, color, defs, bbox);
|
|
2027
2086
|
}
|
|
@@ -2030,7 +2089,7 @@ function drawCircle(ctx, props, inherited, color, defs, vbW = 0, vbH = 0) {
|
|
|
2030
2089
|
const cy = svgLength(props.cy, vbH);
|
|
2031
2090
|
const r = svgLength(props.r, Math.min(vbW, vbH));
|
|
2032
2091
|
if (r <= 0) return;
|
|
2033
|
-
const path = new
|
|
2092
|
+
const path = new Path2D3();
|
|
2034
2093
|
path.arc(cx, cy, r, 0, Math.PI * 2);
|
|
2035
2094
|
const bbox = { x: cx - r, y: cy - r, width: 2 * r, height: 2 * r };
|
|
2036
2095
|
applyFillAndStroke(ctx, props, path, inherited, color, defs, bbox);
|
|
@@ -2047,7 +2106,7 @@ function drawSvgRect(ctx, props, inherited, color, defs, vbW = 0, vbH = 0) {
|
|
|
2047
2106
|
let ry = ryRaw >= 0 ? ryRaw : rx;
|
|
2048
2107
|
rx = Math.min(rx, w / 2);
|
|
2049
2108
|
ry = Math.min(ry, h / 2);
|
|
2050
|
-
const path = new
|
|
2109
|
+
const path = new Path2D3();
|
|
2051
2110
|
if (rx > 0 || ry > 0) {
|
|
2052
2111
|
path.roundRect(x, y, w, h, [rx]);
|
|
2053
2112
|
} else {
|
|
@@ -2061,7 +2120,7 @@ function drawLine(ctx, props, inherited, color, defs, vbW = 0, vbH = 0) {
|
|
|
2061
2120
|
const y1 = svgLength(props.y1, vbH);
|
|
2062
2121
|
const x2 = svgLength(props.x2, vbW);
|
|
2063
2122
|
const y2 = svgLength(props.y2, vbH);
|
|
2064
|
-
const path = new
|
|
2123
|
+
const path = new Path2D3();
|
|
2065
2124
|
path.moveTo(x1, y1);
|
|
2066
2125
|
path.lineTo(x2, y2);
|
|
2067
2126
|
const bbox = {
|
|
@@ -2078,7 +2137,7 @@ function drawEllipse(ctx, props, inherited, color, defs, vbW = 0, vbH = 0) {
|
|
|
2078
2137
|
const rx = svgLength(props.rx, vbW);
|
|
2079
2138
|
const ry = svgLength(props.ry, vbH);
|
|
2080
2139
|
if (rx <= 0 || ry <= 0) return;
|
|
2081
|
-
const path = new
|
|
2140
|
+
const path = new Path2D3();
|
|
2082
2141
|
path.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
|
|
2083
2142
|
const bbox = {
|
|
2084
2143
|
x: cx - rx,
|
|
@@ -2091,7 +2150,7 @@ function drawEllipse(ctx, props, inherited, color, defs, vbW = 0, vbH = 0) {
|
|
|
2091
2150
|
function drawPolygon(ctx, props, inherited, color, defs) {
|
|
2092
2151
|
const points = parsePoints(props.points);
|
|
2093
2152
|
if (points.length < 2) return;
|
|
2094
|
-
const path = new
|
|
2153
|
+
const path = new Path2D3();
|
|
2095
2154
|
path.moveTo(points[0][0], points[0][1]);
|
|
2096
2155
|
for (let i = 1; i < points.length; i++) {
|
|
2097
2156
|
path.lineTo(points[i][0], points[i][1]);
|
|
@@ -2103,7 +2162,7 @@ function drawPolygon(ctx, props, inherited, color, defs) {
|
|
|
2103
2162
|
function drawPolyline(ctx, props, inherited, color, defs) {
|
|
2104
2163
|
const points = parsePoints(props.points);
|
|
2105
2164
|
if (points.length < 2) return;
|
|
2106
|
-
const path = new
|
|
2165
|
+
const path = new Path2D3();
|
|
2107
2166
|
path.moveTo(points[0][0], points[0][1]);
|
|
2108
2167
|
for (let i = 1; i < points.length; i++) {
|
|
2109
2168
|
path.lineTo(points[i][0], points[i][1]);
|
|
@@ -2111,167 +2170,156 @@ function drawPolyline(ctx, props, inherited, color, defs) {
|
|
|
2111
2170
|
const bbox = pointsBBox(points);
|
|
2112
2171
|
applyFillAndStroke(ctx, props, path, inherited, color, defs, bbox);
|
|
2113
2172
|
}
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
);
|
|
2171
|
-
break;
|
|
2172
|
-
}
|
|
2173
|
+
|
|
2174
|
+
// src/jsx/draw/svg/traverse.ts
|
|
2175
|
+
function drawSvgChild(ctx, child, inherited, color, defs = EMPTY_DEFS, vbW = 0, vbH = 0) {
|
|
2176
|
+
const { type } = child;
|
|
2177
|
+
const props = mergeStyleIntoProps(child.props);
|
|
2178
|
+
if (type === "defs" || type === "clipPath" || type === "mask" || type === "filter")
|
|
2179
|
+
return;
|
|
2180
|
+
const transform = type !== "g" ? props.transform : void 0;
|
|
2181
|
+
const opacity = type !== "g" ? Number(props.opacity ?? 1) : 1;
|
|
2182
|
+
const savedTransform = applyTransformAndOpacity(ctx, transform, opacity);
|
|
2183
|
+
const clipRef = parseUrlRef(props.clipPath ?? props["clip-path"]);
|
|
2184
|
+
const clipShapes = clipRef ? defs.clips.get(clipRef) : void 0;
|
|
2185
|
+
const clipPath = clipShapes ? buildClipPath(clipShapes, vbW, vbH) : void 0;
|
|
2186
|
+
const maskRef = parseUrlRef(props.mask);
|
|
2187
|
+
const maskShapes = maskRef ? defs.masks.get(maskRef) : void 0;
|
|
2188
|
+
const filterRef = parseUrlRef(props.filter);
|
|
2189
|
+
const filterDef = filterRef ? defs.filters.get(filterRef) : void 0;
|
|
2190
|
+
if (clipPath) {
|
|
2191
|
+
ctx.save();
|
|
2192
|
+
ctx.clip(clipPath);
|
|
2193
|
+
}
|
|
2194
|
+
const needsOffscreen = maskShapes || filterDef;
|
|
2195
|
+
let elemCanvas;
|
|
2196
|
+
let targetCtx;
|
|
2197
|
+
if (needsOffscreen) {
|
|
2198
|
+
const [c, cCtx] = acquireOffscreen(Math.ceil(vbW), Math.ceil(vbH));
|
|
2199
|
+
elemCanvas = c;
|
|
2200
|
+
targetCtx = cCtx;
|
|
2201
|
+
} else {
|
|
2202
|
+
targetCtx = ctx;
|
|
2203
|
+
}
|
|
2204
|
+
switch (type) {
|
|
2205
|
+
case "path":
|
|
2206
|
+
drawPath(targetCtx, props, inherited, color, defs);
|
|
2207
|
+
break;
|
|
2208
|
+
case "circle":
|
|
2209
|
+
drawCircle(targetCtx, props, inherited, color, defs, vbW, vbH);
|
|
2210
|
+
break;
|
|
2211
|
+
case "rect":
|
|
2212
|
+
drawSvgRect(targetCtx, props, inherited, color, defs, vbW, vbH);
|
|
2213
|
+
break;
|
|
2214
|
+
case "line":
|
|
2215
|
+
drawLine(targetCtx, props, inherited, color, defs, vbW, vbH);
|
|
2216
|
+
break;
|
|
2217
|
+
case "ellipse":
|
|
2218
|
+
drawEllipse(targetCtx, props, inherited, color, defs, vbW, vbH);
|
|
2219
|
+
break;
|
|
2220
|
+
case "polygon":
|
|
2221
|
+
drawPolygon(targetCtx, props, inherited, color, defs);
|
|
2222
|
+
break;
|
|
2223
|
+
case "polyline":
|
|
2224
|
+
drawPolyline(targetCtx, props, inherited, color, defs);
|
|
2225
|
+
break;
|
|
2226
|
+
case "g":
|
|
2227
|
+
drawGroup(targetCtx, child, props, inherited, color, defs, vbW, vbH);
|
|
2228
|
+
break;
|
|
2173
2229
|
}
|
|
2230
|
+
if (needsOffscreen && elemCanvas) {
|
|
2231
|
+
applyOffscreenEffects(
|
|
2232
|
+
ctx,
|
|
2233
|
+
elemCanvas,
|
|
2234
|
+
targetCtx,
|
|
2235
|
+
maskShapes,
|
|
2236
|
+
filterDef,
|
|
2237
|
+
color,
|
|
2238
|
+
defs,
|
|
2239
|
+
vbW,
|
|
2240
|
+
vbH
|
|
2241
|
+
);
|
|
2242
|
+
}
|
|
2243
|
+
if (clipPath) ctx.restore();
|
|
2244
|
+
if (savedTransform) ctx.restore();
|
|
2174
2245
|
}
|
|
2175
|
-
function drawGroup(ctx, node, inherited, color, defs = EMPTY_DEFS, vbW = 0, vbH = 0) {
|
|
2246
|
+
function drawGroup(ctx, node, mergedProps, inherited, color, defs = EMPTY_DEFS, vbW = 0, vbH = 0) {
|
|
2176
2247
|
const children = normalizeChildren(node);
|
|
2177
2248
|
if (children.length === 0) return;
|
|
2178
|
-
const
|
|
2179
|
-
const
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
strokeWidth: merged.strokeWidth ?? merged["stroke-width"] ?? inherited.strokeWidth,
|
|
2183
|
-
strokeLinecap: merged.strokeLinecap ?? merged["stroke-linecap"] ?? inherited.strokeLinecap,
|
|
2184
|
-
strokeLinejoin: merged.strokeLinejoin ?? merged["stroke-linejoin"] ?? inherited.strokeLinejoin,
|
|
2185
|
-
strokeOpacity: merged.strokeOpacity ?? merged["stroke-opacity"] ?? inherited.strokeOpacity
|
|
2186
|
-
};
|
|
2187
|
-
const opacity = Number(merged.opacity ?? 1);
|
|
2188
|
-
const transform = merged.transform;
|
|
2189
|
-
if (transform || opacity < 1) {
|
|
2190
|
-
ctx.save();
|
|
2191
|
-
if (transform) applySvgTransform(ctx, transform);
|
|
2192
|
-
if (opacity < 1) ctx.globalAlpha *= opacity;
|
|
2193
|
-
}
|
|
2249
|
+
const groupInherited = resolveInheritedStyle(mergedProps, color, inherited);
|
|
2250
|
+
const opacity = Number(mergedProps.opacity ?? 1);
|
|
2251
|
+
const transform = mergedProps.transform;
|
|
2252
|
+
const saved = applyTransformAndOpacity(ctx, transform, opacity);
|
|
2194
2253
|
for (const child of children) {
|
|
2195
2254
|
if (child != null && typeof child === "object") {
|
|
2196
2255
|
drawSvgChild(ctx, child, groupInherited, color, defs, vbW, vbH);
|
|
2197
2256
|
}
|
|
2198
2257
|
}
|
|
2199
|
-
if (
|
|
2200
|
-
ctx.restore();
|
|
2201
|
-
}
|
|
2258
|
+
if (saved) ctx.restore();
|
|
2202
2259
|
}
|
|
2203
|
-
function
|
|
2204
|
-
if (
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2260
|
+
function applyOffscreenEffects(ctx, elemCanvas, targetCtx, maskShapes, filterDef, color, defs, vbW, vbH) {
|
|
2261
|
+
if (maskShapes) {
|
|
2262
|
+
const [maskCanvas, maskCtx] = acquireOffscreen(
|
|
2263
|
+
Math.ceil(vbW),
|
|
2264
|
+
Math.ceil(vbH)
|
|
2265
|
+
);
|
|
2266
|
+
for (const shape of maskShapes) {
|
|
2267
|
+
drawSvgChild(maskCtx, shape, { fill: "white" }, "white", defs, vbW, vbH);
|
|
2268
|
+
}
|
|
2269
|
+
targetCtx.globalCompositeOperation = "destination-in";
|
|
2270
|
+
targetCtx.drawImage(maskCanvas, 0, 0);
|
|
2271
|
+
targetCtx.globalCompositeOperation = "source-over";
|
|
2272
|
+
releaseOffscreen(maskCanvas);
|
|
2273
|
+
}
|
|
2274
|
+
if (filterDef) {
|
|
2275
|
+
const filteredCanvas = applyFilter(
|
|
2276
|
+
filterDef,
|
|
2277
|
+
elemCanvas,
|
|
2278
|
+
Math.ceil(vbW),
|
|
2279
|
+
Math.ceil(vbH)
|
|
2280
|
+
);
|
|
2281
|
+
ctx.drawImage(filteredCanvas, 0, 0);
|
|
2282
|
+
if (filteredCanvas !== elemCanvas) releaseOffscreen(filteredCanvas);
|
|
2283
|
+
} else {
|
|
2284
|
+
ctx.drawImage(elemCanvas, 0, 0);
|
|
2209
2285
|
}
|
|
2210
|
-
|
|
2286
|
+
releaseOffscreen(elemCanvas);
|
|
2211
2287
|
}
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
ctx.restore();
|
|
2231
|
-
} else {
|
|
2232
|
-
fillWithSvgGradient(
|
|
2233
|
-
ctx,
|
|
2234
|
-
gradientDef,
|
|
2235
|
-
bbox,
|
|
2236
|
-
path,
|
|
2237
|
-
fillRule ?? "nonzero"
|
|
2238
|
-
);
|
|
2239
|
-
}
|
|
2288
|
+
|
|
2289
|
+
// src/jsx/draw/svg/index.ts
|
|
2290
|
+
function drawSvgContainer(ctx, node, x, y, width, height) {
|
|
2291
|
+
ctx.save();
|
|
2292
|
+
ctx.translate(x, y);
|
|
2293
|
+
const viewBox = node.props.viewBox;
|
|
2294
|
+
let vbW = width;
|
|
2295
|
+
let vbH = height;
|
|
2296
|
+
if (viewBox) {
|
|
2297
|
+
const parts = viewBox.split(/[\s,]+/).map(Number);
|
|
2298
|
+
if (parts.length === 4) {
|
|
2299
|
+
const [vbX, vbY, parsedW, parsedH] = parts;
|
|
2300
|
+
vbW = parsedW;
|
|
2301
|
+
vbH = parsedH;
|
|
2302
|
+
const scaleX = width / vbW;
|
|
2303
|
+
const scaleY = height / vbH;
|
|
2304
|
+
ctx.scale(scaleX, scaleY);
|
|
2305
|
+
ctx.translate(-vbX, -vbY);
|
|
2240
2306
|
}
|
|
2241
|
-
} else if (fill !== "none") {
|
|
2242
|
-
ctx.fillStyle = applyOpacity(fill, fillOpacity);
|
|
2243
|
-
ctx.fill(path, fillRule ?? "nonzero");
|
|
2244
2307
|
}
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
const
|
|
2249
|
-
if (
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
ctx.lineJoin = props.strokeLinejoin ?? props["stroke-linejoin"] ?? inherited.strokeLinejoin ?? "miter";
|
|
2258
|
-
const strokeRef = parseUrlRef(stroke);
|
|
2259
|
-
if (strokeRef) {
|
|
2260
|
-
const gradientDef = defs.gradients.get(strokeRef);
|
|
2261
|
-
if (gradientDef) {
|
|
2262
|
-
if (strokeOpacity < 1) {
|
|
2263
|
-
ctx.save();
|
|
2264
|
-
ctx.globalAlpha *= strokeOpacity;
|
|
2265
|
-
strokeWithSvgGradient(ctx, gradientDef, bbox, path);
|
|
2266
|
-
ctx.restore();
|
|
2267
|
-
} else {
|
|
2268
|
-
strokeWithSvgGradient(ctx, gradientDef, bbox, path);
|
|
2269
|
-
}
|
|
2270
|
-
return;
|
|
2308
|
+
const color = node.style.color ?? "black";
|
|
2309
|
+
const merged = mergeStyleIntoProps(node.props);
|
|
2310
|
+
const inherited = resolveInheritedStyle(merged, color);
|
|
2311
|
+
const children = node.props.children;
|
|
2312
|
+
if (children != null) {
|
|
2313
|
+
const childArray = Array.isArray(children) ? children : [children];
|
|
2314
|
+
const svgChildren = childArray.filter(
|
|
2315
|
+
(c) => c != null && typeof c === "object"
|
|
2316
|
+
);
|
|
2317
|
+
const defs = collectDefs(svgChildren);
|
|
2318
|
+
for (const child of svgChildren) {
|
|
2319
|
+
drawSvgChild(ctx, child, inherited, color, defs, vbW, vbH);
|
|
2271
2320
|
}
|
|
2272
2321
|
}
|
|
2273
|
-
ctx.
|
|
2274
|
-
ctx.stroke(path);
|
|
2322
|
+
ctx.restore();
|
|
2275
2323
|
}
|
|
2276
2324
|
|
|
2277
2325
|
// src/jsx/draw/text.ts
|
|
@@ -2558,7 +2606,7 @@ function drawTextDecoration(ctx, seg, offsetX, offsetY) {
|
|
|
2558
2606
|
}
|
|
2559
2607
|
|
|
2560
2608
|
// src/jsx/draw/index.ts
|
|
2561
|
-
async function drawNode(ctx, node, parentX, parentY, debug, emojiStyle) {
|
|
2609
|
+
async function drawNode(ctx, node, parentX, parentY, debug, emojiStyle, imageCache) {
|
|
2562
2610
|
const x = parentX + node.x;
|
|
2563
2611
|
const y = parentY + node.y;
|
|
2564
2612
|
const { width, height, style } = node;
|
|
@@ -2578,6 +2626,7 @@ async function drawNode(ctx, node, parentX, parentY, debug, emojiStyle) {
|
|
|
2578
2626
|
const [offscreen, offCtx] = acquireOffscreen(bufW, bufH);
|
|
2579
2627
|
offCtx.save();
|
|
2580
2628
|
offCtx.scale(qx, qy);
|
|
2629
|
+
const cache2 = imageCache ?? /* @__PURE__ */ new Map();
|
|
2581
2630
|
await drawNodeCore(
|
|
2582
2631
|
offCtx,
|
|
2583
2632
|
node,
|
|
@@ -2587,6 +2636,7 @@ async function drawNode(ctx, node, parentX, parentY, debug, emojiStyle) {
|
|
|
2587
2636
|
1 - y,
|
|
2588
2637
|
debug,
|
|
2589
2638
|
emojiStyle,
|
|
2639
|
+
cache2,
|
|
2590
2640
|
transformWithoutScale
|
|
2591
2641
|
);
|
|
2592
2642
|
offCtx.restore();
|
|
@@ -2620,7 +2670,18 @@ async function drawNode(ctx, node, parentX, parentY, debug, emojiStyle) {
|
|
|
2620
2670
|
return;
|
|
2621
2671
|
}
|
|
2622
2672
|
}
|
|
2623
|
-
|
|
2673
|
+
const cache = imageCache ?? /* @__PURE__ */ new Map();
|
|
2674
|
+
await drawNodeCore(
|
|
2675
|
+
ctx,
|
|
2676
|
+
node,
|
|
2677
|
+
parentX,
|
|
2678
|
+
parentY,
|
|
2679
|
+
0,
|
|
2680
|
+
0,
|
|
2681
|
+
debug,
|
|
2682
|
+
emojiStyle,
|
|
2683
|
+
cache
|
|
2684
|
+
);
|
|
2624
2685
|
}
|
|
2625
2686
|
function extractScale(transform) {
|
|
2626
2687
|
const scaleMatch = transform.match(/\b(scale|scaleX|scaleY)\(([^)]+)\)/);
|
|
@@ -2632,7 +2693,7 @@ function extractScale(transform) {
|
|
|
2632
2693
|
const remaining = transform.replace(fullMatch, "").trim();
|
|
2633
2694
|
return { sx, sy, remaining };
|
|
2634
2695
|
}
|
|
2635
|
-
async function drawNodeCore(ctx, node, parentX, parentY, offsetX, offsetY, debug, emojiStyle, overrideTransform) {
|
|
2696
|
+
async function drawNodeCore(ctx, node, parentX, parentY, offsetX, offsetY, debug, emojiStyle, imageCache, overrideTransform) {
|
|
2636
2697
|
const x = parentX + node.x + offsetX;
|
|
2637
2698
|
const y = parentY + node.y + offsetY;
|
|
2638
2699
|
const { width, height, style } = node;
|
|
@@ -2698,7 +2759,7 @@ async function drawNodeCore(ctx, node, parentX, parentY, offsetX, offsetY, debug
|
|
|
2698
2759
|
if (hasRadius(borderRadius)) {
|
|
2699
2760
|
applyClip(ctx, x, y, width, height, borderRadius);
|
|
2700
2761
|
}
|
|
2701
|
-
const image = await
|
|
2762
|
+
const image = await cachedLoadImage(imageCache, urlMatch[1]);
|
|
2702
2763
|
const bgSize = style.backgroundSize;
|
|
2703
2764
|
if (bgSize === "cover") {
|
|
2704
2765
|
const r = computeCover(
|
|
@@ -2807,8 +2868,8 @@ async function drawNodeCore(ctx, node, parentX, parentY, offsetX, offsetY, debug
|
|
|
2807
2868
|
imgY,
|
|
2808
2869
|
imgW,
|
|
2809
2870
|
imgH,
|
|
2810
|
-
|
|
2811
|
-
|
|
2871
|
+
imageCache,
|
|
2872
|
+
style
|
|
2812
2873
|
);
|
|
2813
2874
|
}
|
|
2814
2875
|
if (node.type === "svg") {
|
|
@@ -2816,7 +2877,7 @@ async function drawNodeCore(ctx, node, parentX, parentY, offsetX, offsetY, debug
|
|
|
2816
2877
|
} else {
|
|
2817
2878
|
for (const child of node.children) {
|
|
2818
2879
|
if (offsetX === 0 && offsetY === 0) {
|
|
2819
|
-
await drawNode(ctx, child, x, y, debug, emojiStyle);
|
|
2880
|
+
await drawNode(ctx, child, x, y, debug, emojiStyle, imageCache);
|
|
2820
2881
|
} else {
|
|
2821
2882
|
await drawNodeCore(
|
|
2822
2883
|
ctx,
|
|
@@ -2827,7 +2888,7 @@ async function drawNodeCore(ctx, node, parentX, parentY, offsetX, offsetY, debug
|
|
|
2827
2888
|
0,
|
|
2828
2889
|
debug,
|
|
2829
2890
|
emojiStyle,
|
|
2830
|
-
|
|
2891
|
+
imageCache
|
|
2831
2892
|
);
|
|
2832
2893
|
}
|
|
2833
2894
|
}
|
|
@@ -2899,9 +2960,6 @@ function parseAngle(value) {
|
|
|
2899
2960
|
return parseFloat(value);
|
|
2900
2961
|
}
|
|
2901
2962
|
|
|
2902
|
-
// src/jsx/layout.ts
|
|
2903
|
-
import { loadImage as loadImage4 } from "@napi-rs/canvas";
|
|
2904
|
-
|
|
2905
2963
|
// src/jsx/style/expand.ts
|
|
2906
2964
|
var SIDES = ["Top", "Right", "Bottom", "Left"];
|
|
2907
2965
|
function parseSides(value) {
|
|
@@ -3288,6 +3346,7 @@ function applyEdgeValue(node, setter, edge, value) {
|
|
|
3288
3346
|
|
|
3289
3347
|
// src/jsx/layout.ts
|
|
3290
3348
|
async function buildLayoutTree(element, containerWidth, containerHeight, ctx, emojiEnabled, fontFamilies) {
|
|
3349
|
+
const imageCache = /* @__PURE__ */ new Map();
|
|
3291
3350
|
const elementYogaNode = createYogaNode();
|
|
3292
3351
|
const rootStyle = fontFamilies?.length ? { ...DEFAULT_STYLE, fontFamily: fontFamilies.join(", ") } : DEFAULT_STYLE;
|
|
3293
3352
|
const elementNode = await buildNode(
|
|
@@ -3298,7 +3357,8 @@ async function buildLayoutTree(element, containerWidth, containerHeight, ctx, em
|
|
|
3298
3357
|
containerHeight,
|
|
3299
3358
|
ctx,
|
|
3300
3359
|
emojiEnabled,
|
|
3301
|
-
fontFamilies
|
|
3360
|
+
fontFamilies,
|
|
3361
|
+
imageCache
|
|
3302
3362
|
);
|
|
3303
3363
|
const rootYogaNode = createYogaNode();
|
|
3304
3364
|
rootYogaNode.setWidth(containerWidth);
|
|
@@ -3308,7 +3368,7 @@ async function buildLayoutTree(element, containerWidth, containerHeight, ctx, em
|
|
|
3308
3368
|
rootYogaNode.calculateLayout(containerWidth, containerHeight);
|
|
3309
3369
|
const elementLayout = extractLayout(elementNode, elementYogaNode);
|
|
3310
3370
|
freeYogaNode(rootYogaNode);
|
|
3311
|
-
|
|
3371
|
+
const tree = {
|
|
3312
3372
|
type: "div",
|
|
3313
3373
|
style: rootStyle,
|
|
3314
3374
|
children: [elementLayout],
|
|
@@ -3318,8 +3378,9 @@ async function buildLayoutTree(element, containerWidth, containerHeight, ctx, em
|
|
|
3318
3378
|
width: containerWidth,
|
|
3319
3379
|
height: containerHeight
|
|
3320
3380
|
};
|
|
3381
|
+
return { tree, imageCache };
|
|
3321
3382
|
}
|
|
3322
|
-
async function buildNode(element, parentStyle, yogaNode, viewportWidth, viewportHeight, ctx, emojiEnabled, fontFamilies) {
|
|
3383
|
+
async function buildNode(element, parentStyle, yogaNode, viewportWidth, viewportHeight, ctx, emojiEnabled, fontFamilies, imageCache) {
|
|
3323
3384
|
if (element === null || element === void 0 || typeof element === "boolean") {
|
|
3324
3385
|
return {
|
|
3325
3386
|
type: "empty",
|
|
@@ -3357,7 +3418,8 @@ async function buildNode(element, parentStyle, yogaNode, viewportWidth, viewport
|
|
|
3357
3418
|
viewportHeight,
|
|
3358
3419
|
ctx,
|
|
3359
3420
|
emojiEnabled,
|
|
3360
|
-
fontFamilies
|
|
3421
|
+
fontFamilies,
|
|
3422
|
+
imageCache
|
|
3361
3423
|
);
|
|
3362
3424
|
}
|
|
3363
3425
|
const props = el.props ?? {};
|
|
@@ -3417,7 +3479,7 @@ async function buildNode(element, parentStyle, yogaNode, viewportWidth, viewport
|
|
|
3417
3479
|
const src = props.src;
|
|
3418
3480
|
if (src) {
|
|
3419
3481
|
try {
|
|
3420
|
-
const image = await
|
|
3482
|
+
const image = await cachedLoadImage(imageCache ?? /* @__PURE__ */ new Map(), src);
|
|
3421
3483
|
const naturalW = image.width;
|
|
3422
3484
|
const naturalH = image.height;
|
|
3423
3485
|
const wSet = style.width !== void 0;
|
|
@@ -3427,7 +3489,6 @@ async function buildNode(element, parentStyle, yogaNode, viewportWidth, viewport
|
|
|
3427
3489
|
} else if (hSet && !wSet && typeof style.height === "number" && naturalH > 0) {
|
|
3428
3490
|
style.width = style.height * (naturalW / naturalH);
|
|
3429
3491
|
}
|
|
3430
|
-
props.__loadedImage = image;
|
|
3431
3492
|
} catch {
|
|
3432
3493
|
}
|
|
3433
3494
|
}
|
|
@@ -3505,7 +3566,8 @@ async function buildNode(element, parentStyle, yogaNode, viewportWidth, viewport
|
|
|
3505
3566
|
viewportHeight,
|
|
3506
3567
|
ctx,
|
|
3507
3568
|
emojiEnabled,
|
|
3508
|
-
fontFamilies
|
|
3569
|
+
fontFamilies,
|
|
3570
|
+
imageCache
|
|
3509
3571
|
)
|
|
3510
3572
|
);
|
|
3511
3573
|
}
|
|
@@ -3579,7 +3641,7 @@ async function renderReactElement(ctx, element, options) {
|
|
|
3579
3641
|
const height = options.height ?? ctx.canvas.height;
|
|
3580
3642
|
const emojiStyle = options.emoji === "none" ? void 0 : options.emoji ?? "twemoji";
|
|
3581
3643
|
const fontFamilies = [...new Set(options.fonts.map((f) => f.name))];
|
|
3582
|
-
const layoutTree = await buildLayoutTree(
|
|
3644
|
+
const { tree: layoutTree, imageCache } = await buildLayoutTree(
|
|
3583
3645
|
element,
|
|
3584
3646
|
width,
|
|
3585
3647
|
height,
|
|
@@ -3587,7 +3649,50 @@ async function renderReactElement(ctx, element, options) {
|
|
|
3587
3649
|
!!emojiStyle,
|
|
3588
3650
|
fontFamilies
|
|
3589
3651
|
);
|
|
3590
|
-
await drawNode(
|
|
3652
|
+
await drawNode(
|
|
3653
|
+
ctx,
|
|
3654
|
+
layoutTree,
|
|
3655
|
+
0,
|
|
3656
|
+
0,
|
|
3657
|
+
options.debug ?? false,
|
|
3658
|
+
emojiStyle,
|
|
3659
|
+
imageCache
|
|
3660
|
+
);
|
|
3661
|
+
}
|
|
3662
|
+
|
|
3663
|
+
// src/fit-text.ts
|
|
3664
|
+
function findLargestUsableFontSize(options) {
|
|
3665
|
+
const {
|
|
3666
|
+
text,
|
|
3667
|
+
font,
|
|
3668
|
+
maxWidth,
|
|
3669
|
+
maxHeight,
|
|
3670
|
+
lineHeight = "normal",
|
|
3671
|
+
minFontSize = 1,
|
|
3672
|
+
maxFontSize = 1e3
|
|
3673
|
+
} = options;
|
|
3674
|
+
registerFont(font);
|
|
3675
|
+
let lo = minFontSize;
|
|
3676
|
+
let hi = maxFontSize;
|
|
3677
|
+
let best = minFontSize;
|
|
3678
|
+
while (lo <= hi) {
|
|
3679
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
3680
|
+
const style = {
|
|
3681
|
+
fontSize: mid,
|
|
3682
|
+
fontFamily: font.name,
|
|
3683
|
+
fontWeight: font.weight,
|
|
3684
|
+
fontStyle: font.style,
|
|
3685
|
+
lineHeight: lineHeight === "normal" ? void 0 : lineHeight
|
|
3686
|
+
};
|
|
3687
|
+
const result = layoutText(text, style, maxWidth);
|
|
3688
|
+
if (result.width <= maxWidth && result.height <= maxHeight) {
|
|
3689
|
+
best = mid;
|
|
3690
|
+
lo = mid + 1;
|
|
3691
|
+
} else {
|
|
3692
|
+
hi = mid - 1;
|
|
3693
|
+
}
|
|
3694
|
+
}
|
|
3695
|
+
return best;
|
|
3591
3696
|
}
|
|
3592
3697
|
|
|
3593
3698
|
// src/index.ts
|
|
@@ -3603,7 +3708,8 @@ export {
|
|
|
3603
3708
|
Image,
|
|
3604
3709
|
LottieAnimation,
|
|
3605
3710
|
createCanvas3 as createCanvas,
|
|
3606
|
-
|
|
3711
|
+
findLargestUsableFontSize,
|
|
3712
|
+
loadImage3 as loadImage,
|
|
3607
3713
|
loadLottie,
|
|
3608
3714
|
registerFont,
|
|
3609
3715
|
registerFontFromPath,
|