@effing/canvas 0.18.0 → 0.18.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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 loadImage4,
6
+ loadImage as loadImage5,
7
7
  Image
8
8
  } from "@napi-rs/canvas";
9
9
 
@@ -21,7 +21,7 @@ function renderLottieFrame(ctx, animation, frame) {
21
21
  }
22
22
 
23
23
  // src/jsx/draw/index.ts
24
- import { loadImage as loadImage3 } from "@napi-rs/canvas";
24
+ import { createCanvas as createCanvas2, loadImage as loadImage3 } from "@napi-rs/canvas";
25
25
 
26
26
  // src/jsx/language.ts
27
27
  function isEmoji(char) {
@@ -617,8 +617,8 @@ function computeContain(imgW, imgH, boxX, boxY, boxW, boxH) {
617
617
  }
618
618
 
619
619
  // src/jsx/draw/image.ts
620
- async function drawImage(ctx, src, x, y, width, height, style) {
621
- const image = await loadImage(src);
620
+ async function drawImage(ctx, src, x, y, width, height, style, preloadedImage) {
621
+ const image = preloadedImage ?? await loadImage(src);
622
622
  const objectFit = style?.objectFit ?? "fill";
623
623
  if (objectFit === "fill") {
624
624
  ctx.drawImage(image, x, y, width, height);
@@ -795,6 +795,11 @@ function toNumber(v) {
795
795
 
796
796
  // src/jsx/draw/svg.ts
797
797
  import { Path2D } from "@napi-rs/canvas";
798
+ function mergeStyleIntoProps(props) {
799
+ const style = props.style;
800
+ if (!style) return props;
801
+ return { ...props, ...style };
802
+ }
798
803
  function drawSvgContainer(ctx, node, x, y, width, height) {
799
804
  ctx.save();
800
805
  ctx.translate(x, y);
@@ -809,7 +814,8 @@ function drawSvgContainer(ctx, node, x, y, width, height) {
809
814
  ctx.translate(-vbX, -vbY);
810
815
  }
811
816
  }
812
- const inheritedFill = node.props.fill ?? "black";
817
+ const merged = mergeStyleIntoProps(node.props);
818
+ const inheritedFill = merged.fill ?? "black";
813
819
  const children = node.props.children;
814
820
  if (children != null) {
815
821
  const childArray = Array.isArray(children) ? children : [children];
@@ -822,7 +828,8 @@ function drawSvgContainer(ctx, node, x, y, width, height) {
822
828
  ctx.restore();
823
829
  }
824
830
  function drawSvgChild(ctx, child, inheritedFill) {
825
- const { type, props } = child;
831
+ const { type } = child;
832
+ const props = mergeStyleIntoProps(child.props);
826
833
  switch (type) {
827
834
  case "path":
828
835
  drawPath(ctx, props, inheritedFill);
@@ -919,7 +926,8 @@ function drawPolyline(ctx, props, inheritedFill) {
919
926
  function drawGroup(ctx, node, inheritedFill) {
920
927
  const children = node.children ?? node.props.children;
921
928
  if (children == null) return;
922
- const groupFill = node.props.fill ?? inheritedFill;
929
+ const merged = mergeStyleIntoProps(node.props);
930
+ const groupFill = merged.fill ?? inheritedFill;
923
931
  const childArray = Array.isArray(children) ? children : [children];
924
932
  for (const child of childArray) {
925
933
  if (child != null && typeof child === "object") {
@@ -948,9 +956,9 @@ function applyStroke(ctx, props, path) {
948
956
  const stroke = props.stroke;
949
957
  if (!stroke || stroke === "none") return;
950
958
  ctx.strokeStyle = stroke;
951
- ctx.lineWidth = Number(props.strokeWidth ?? 1);
952
- ctx.lineCap = props.strokeLinecap ?? "butt";
953
- ctx.lineJoin = props.strokeLinejoin ?? "miter";
959
+ ctx.lineWidth = Number(props.strokeWidth ?? props["stroke-width"] ?? 1);
960
+ ctx.lineCap = props.strokeLinecap ?? props["stroke-linecap"] ?? "butt";
961
+ ctx.lineJoin = props.strokeLinejoin ?? props["stroke-linejoin"] ?? "miter";
954
962
  ctx.stroke(path);
955
963
  }
956
964
 
@@ -1168,6 +1176,34 @@ function drawTextDecoration(ctx, seg, offsetX, offsetY) {
1168
1176
  }
1169
1177
 
1170
1178
  // src/jsx/draw/index.ts
1179
+ var canvasPool = /* @__PURE__ */ new Map();
1180
+ function acquireOffscreen(w, h) {
1181
+ const key = `${w}x${h}`;
1182
+ const stack = canvasPool.get(key);
1183
+ if (stack) {
1184
+ while (stack.length > 0) {
1185
+ const ref = stack.pop();
1186
+ const canvas2 = ref.deref();
1187
+ if (canvas2) {
1188
+ const ctx = canvas2.getContext("2d");
1189
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
1190
+ ctx.clearRect(0, 0, w, h);
1191
+ return [canvas2, ctx];
1192
+ }
1193
+ }
1194
+ }
1195
+ const canvas = createCanvas2(w, h);
1196
+ return [canvas, canvas.getContext("2d")];
1197
+ }
1198
+ function releaseOffscreen(canvas) {
1199
+ const key = `${canvas.width}x${canvas.height}`;
1200
+ let stack = canvasPool.get(key);
1201
+ if (!stack) {
1202
+ stack = [];
1203
+ canvasPool.set(key, stack);
1204
+ }
1205
+ stack.push(new WeakRef(canvas));
1206
+ }
1171
1207
  async function drawNode(ctx, node, parentX, parentY, debug, emojiStyle) {
1172
1208
  const x = parentX + node.x;
1173
1209
  const y = parentY + node.y;
@@ -1175,6 +1211,61 @@ async function drawNode(ctx, node, parentX, parentY, debug, emojiStyle) {
1175
1211
  if (style.display === "none") return;
1176
1212
  const opacity = style.opacity ?? 1;
1177
1213
  if (opacity <= 0) return;
1214
+ const scaleInfo = style.transform ? extractScale(style.transform) : null;
1215
+ if (scaleInfo && (scaleInfo.sx !== 1 || scaleInfo.sy !== 1)) {
1216
+ const sx = scaleInfo.sx;
1217
+ const sy = scaleInfo.sy;
1218
+ const transformWithoutScale = scaleInfo.remaining;
1219
+ const qx = Math.max(1, Math.ceil(Math.abs(sx)));
1220
+ const qy = Math.max(1, Math.ceil(Math.abs(sy)));
1221
+ const bufW = Math.ceil((width + 2) * qx);
1222
+ const bufH = Math.ceil((height + 2) * qy);
1223
+ if (bufW > 0 && bufH > 0) {
1224
+ const [offscreen, offCtx] = acquireOffscreen(bufW, bufH);
1225
+ offCtx.save();
1226
+ offCtx.scale(qx, qy);
1227
+ await drawNodeInner(
1228
+ offCtx,
1229
+ node,
1230
+ parentX,
1231
+ parentY,
1232
+ 1 - x,
1233
+ 1 - y,
1234
+ debug,
1235
+ emojiStyle,
1236
+ transformWithoutScale
1237
+ );
1238
+ offCtx.restore();
1239
+ ctx.save();
1240
+ if (opacity < 1) {
1241
+ ctx.globalAlpha *= opacity;
1242
+ }
1243
+ let ox = x + width / 2;
1244
+ let oy = y + height / 2;
1245
+ if (style.transformOrigin) {
1246
+ const parts = style.transformOrigin.split(/\s+/);
1247
+ ox = resolveOrigin(parts[0], x, width);
1248
+ oy = resolveOrigin(parts[1], y, height);
1249
+ }
1250
+ ctx.translate(ox, oy);
1251
+ ctx.scale(sx, sy);
1252
+ ctx.translate(-ox, -oy);
1253
+ ctx.drawImage(
1254
+ offscreen,
1255
+ 0,
1256
+ 0,
1257
+ bufW,
1258
+ bufH,
1259
+ x - 1,
1260
+ y - 1,
1261
+ width + 2,
1262
+ height + 2
1263
+ );
1264
+ releaseOffscreen(offscreen);
1265
+ ctx.restore();
1266
+ return;
1267
+ }
1268
+ }
1178
1269
  ctx.save();
1179
1270
  if (opacity < 1) {
1180
1271
  ctx.globalAlpha *= opacity;
@@ -1332,7 +1423,8 @@ async function drawNode(ctx, node, parentX, parentY, debug, emojiStyle) {
1332
1423
  imgY,
1333
1424
  imgW,
1334
1425
  imgH,
1335
- style
1426
+ style,
1427
+ node.props.__loadedImage
1336
1428
  );
1337
1429
  }
1338
1430
  if (node.type === "svg") {
@@ -1344,6 +1436,194 @@ async function drawNode(ctx, node, parentX, parentY, debug, emojiStyle) {
1344
1436
  }
1345
1437
  ctx.restore();
1346
1438
  }
1439
+ function extractScale(transform) {
1440
+ const scaleMatch = transform.match(/\b(scale|scaleX|scaleY)\(([^)]+)\)/);
1441
+ if (!scaleMatch) return null;
1442
+ const [fullMatch, name, args] = scaleMatch;
1443
+ const values = args.split(",").map((s) => s.trim());
1444
+ const sx = name === "scaleY" ? 1 : parseFloat(values[0]);
1445
+ const sy = name === "scaleX" ? 1 : parseFloat(values[name === "scale" ? 1 : 0] ?? String(sx));
1446
+ const remaining = transform.replace(fullMatch, "").trim();
1447
+ return { sx, sy, remaining };
1448
+ }
1449
+ async function drawNodeInner(ctx, node, parentX, parentY, offsetX, offsetY, debug, emojiStyle, overrideTransform) {
1450
+ const x = parentX + node.x + offsetX;
1451
+ const y = parentY + node.y + offsetY;
1452
+ const { width, height, style } = node;
1453
+ if (style.display === "none") return;
1454
+ const opacity = style.opacity ?? 1;
1455
+ if (opacity <= 0) return;
1456
+ ctx.save();
1457
+ if (opacity < 1) {
1458
+ ctx.globalAlpha *= opacity;
1459
+ }
1460
+ if (style.filter) {
1461
+ ctx.filter = style.filter;
1462
+ }
1463
+ const transformToApply = overrideTransform !== void 0 ? overrideTransform : style.transform;
1464
+ if (transformToApply) {
1465
+ applyTransform(
1466
+ ctx,
1467
+ transformToApply,
1468
+ x,
1469
+ y,
1470
+ width,
1471
+ height,
1472
+ style.transformOrigin
1473
+ );
1474
+ }
1475
+ const isClipped = style.overflow === "hidden" || style.overflowX === "hidden" || style.overflowY === "hidden";
1476
+ if (isClipped) {
1477
+ const borderRadius = getBorderRadiusFromStyle(style);
1478
+ applyClip(ctx, x, y, width, height, borderRadius);
1479
+ }
1480
+ if (style.backgroundColor || style.borderTopWidth || style.borderRightWidth || style.borderBottomWidth || style.borderLeftWidth || style.boxShadow) {
1481
+ drawRect(ctx, x, y, width, height, style);
1482
+ }
1483
+ if (style.backgroundImage) {
1484
+ const gradient = createGradientFromCSS(
1485
+ ctx,
1486
+ style.backgroundImage,
1487
+ x,
1488
+ y,
1489
+ width,
1490
+ height
1491
+ );
1492
+ if (gradient) {
1493
+ ctx.fillStyle = gradient;
1494
+ const borderRadius = getBorderRadiusFromStyle(style);
1495
+ if (borderRadius.topLeft > 0 || borderRadius.topRight > 0 || borderRadius.bottomRight > 0 || borderRadius.bottomLeft > 0) {
1496
+ ctx.beginPath();
1497
+ roundedRect(
1498
+ ctx,
1499
+ x,
1500
+ y,
1501
+ width,
1502
+ height,
1503
+ borderRadius.topLeft,
1504
+ borderRadius.topRight,
1505
+ borderRadius.bottomRight,
1506
+ borderRadius.bottomLeft
1507
+ );
1508
+ ctx.fill();
1509
+ } else {
1510
+ ctx.fillRect(x, y, width, height);
1511
+ }
1512
+ } else {
1513
+ const urlMatch = style.backgroundImage.match(/url\(["']?(.*?)["']?\)/);
1514
+ if (urlMatch) {
1515
+ const borderRadius = getBorderRadiusFromStyle(style);
1516
+ const hasRadius2 = borderRadius.topLeft > 0 || borderRadius.topRight > 0 || borderRadius.bottomRight > 0 || borderRadius.bottomLeft > 0;
1517
+ if (hasRadius2) {
1518
+ applyClip(ctx, x, y, width, height, borderRadius);
1519
+ }
1520
+ const image = await loadImage3(urlMatch[1]);
1521
+ const bgSize = style.backgroundSize;
1522
+ if (bgSize === "cover") {
1523
+ const r = computeCover(
1524
+ image.width,
1525
+ image.height,
1526
+ x,
1527
+ y,
1528
+ width,
1529
+ height
1530
+ );
1531
+ ctx.drawImage(image, r.sx, r.sy, r.sw, r.sh, r.dx, r.dy, r.dw, r.dh);
1532
+ } else {
1533
+ let tileW, tileH;
1534
+ if (bgSize === "contain") {
1535
+ const r = computeContain(
1536
+ image.width,
1537
+ image.height,
1538
+ 0,
1539
+ 0,
1540
+ width,
1541
+ height
1542
+ );
1543
+ tileW = r.dw;
1544
+ tileH = r.dh;
1545
+ } else if (bgSize === "100% 100%") {
1546
+ tileW = width;
1547
+ tileH = height;
1548
+ } else {
1549
+ tileW = image.width;
1550
+ tileH = image.height;
1551
+ }
1552
+ for (let ty = y; ty < y + height; ty += tileH) {
1553
+ for (let tx = x; tx < x + width; tx += tileW) {
1554
+ ctx.drawImage(image, tx, ty, tileW, tileH);
1555
+ }
1556
+ }
1557
+ }
1558
+ }
1559
+ }
1560
+ }
1561
+ if (debug) {
1562
+ ctx.strokeStyle = "rgba(255, 0, 0, 0.5)";
1563
+ ctx.lineWidth = 1;
1564
+ ctx.strokeRect(x, y, width, height);
1565
+ }
1566
+ if (node.textContent !== void 0 && node.textContent !== "") {
1567
+ const paddingTop = toNumber2(style.paddingTop);
1568
+ const paddingLeft = toNumber2(style.paddingLeft);
1569
+ const paddingRight = toNumber2(style.paddingRight);
1570
+ const borderTopW = toNumber2(style.borderTopWidth);
1571
+ const borderLeftW = toNumber2(style.borderLeftWidth);
1572
+ const borderRightW = toNumber2(style.borderRightWidth);
1573
+ const contentX = x + paddingLeft + borderLeftW;
1574
+ const contentY = y + paddingTop + borderTopW;
1575
+ const contentWidth = width - paddingLeft - paddingRight - borderLeftW - borderRightW;
1576
+ const textLayout = layoutText(
1577
+ node.textContent,
1578
+ style,
1579
+ contentWidth,
1580
+ ctx,
1581
+ !!emojiStyle
1582
+ );
1583
+ await drawText(
1584
+ ctx,
1585
+ textLayout.segments,
1586
+ contentX,
1587
+ contentY,
1588
+ style.textShadow,
1589
+ emojiStyle
1590
+ );
1591
+ }
1592
+ if (node.type === "img" && node.props.src) {
1593
+ const paddingTop = toNumber2(style.paddingTop);
1594
+ const paddingLeft = toNumber2(style.paddingLeft);
1595
+ const paddingRight = toNumber2(style.paddingRight);
1596
+ const paddingBottom = toNumber2(style.paddingBottom);
1597
+ const imgX = x + paddingLeft;
1598
+ const imgY = y + paddingTop;
1599
+ const imgW = width - paddingLeft - paddingRight;
1600
+ const imgH = height - paddingTop - paddingBottom;
1601
+ if (!isClipped) {
1602
+ const borderRadius = getBorderRadiusFromStyle(style);
1603
+ if (borderRadius.topLeft > 0 || borderRadius.topRight > 0 || borderRadius.bottomRight > 0 || borderRadius.bottomLeft > 0) {
1604
+ applyClip(ctx, imgX, imgY, imgW, imgH, borderRadius);
1605
+ }
1606
+ }
1607
+ await drawImage(
1608
+ ctx,
1609
+ node.props.src,
1610
+ imgX,
1611
+ imgY,
1612
+ imgW,
1613
+ imgH,
1614
+ style,
1615
+ node.props.__loadedImage
1616
+ );
1617
+ }
1618
+ if (node.type === "svg") {
1619
+ drawSvgContainer(ctx, node, x, y, width, height);
1620
+ } else {
1621
+ for (const child of node.children) {
1622
+ await drawNodeInner(ctx, child, x, y, 0, 0, debug, emojiStyle, void 0);
1623
+ }
1624
+ }
1625
+ ctx.restore();
1626
+ }
1347
1627
  function applyTransform(ctx, transform, x, y, width, height, transformOrigin) {
1348
1628
  let ox = x + width / 2;
1349
1629
  let oy = y + height / 2;
@@ -1435,6 +1715,9 @@ function ensureFontsRegistered(fonts) {
1435
1715
  }
1436
1716
  }
1437
1717
 
1718
+ // src/jsx/layout.ts
1719
+ import { loadImage as loadImage4 } from "@napi-rs/canvas";
1720
+
1438
1721
  // src/jsx/style/expand.ts
1439
1722
  var SIDES = ["Top", "Right", "Bottom", "Left"];
1440
1723
  function parseSides(value) {
@@ -1643,6 +1926,99 @@ function resolveStyle(rawStyle, parentStyle) {
1643
1926
  }
1644
1927
  return style;
1645
1928
  }
1929
+ var DIMENSION_PROPS = [
1930
+ "width",
1931
+ "height",
1932
+ "minWidth",
1933
+ "minHeight",
1934
+ "maxWidth",
1935
+ "maxHeight",
1936
+ "top",
1937
+ "right",
1938
+ "bottom",
1939
+ "left",
1940
+ "marginTop",
1941
+ "marginRight",
1942
+ "marginBottom",
1943
+ "marginLeft",
1944
+ "paddingTop",
1945
+ "paddingRight",
1946
+ "paddingBottom",
1947
+ "paddingLeft",
1948
+ "rowGap",
1949
+ "columnGap",
1950
+ "flexBasis"
1951
+ ];
1952
+ function resolveUnit(value, viewportWidth, viewportHeight, fontSize, rootFontSize) {
1953
+ if (value.endsWith("%") || value === "auto") return value;
1954
+ if (value.endsWith("vmin")) {
1955
+ const n = parseFloat(value);
1956
+ return isNaN(n) ? value : n / 100 * Math.min(viewportWidth, viewportHeight);
1957
+ }
1958
+ if (value.endsWith("vmax")) {
1959
+ const n = parseFloat(value);
1960
+ return isNaN(n) ? value : n / 100 * Math.max(viewportWidth, viewportHeight);
1961
+ }
1962
+ if (value.endsWith("vw")) {
1963
+ const n = parseFloat(value);
1964
+ return isNaN(n) ? value : n / 100 * viewportWidth;
1965
+ }
1966
+ if (value.endsWith("vh")) {
1967
+ const n = parseFloat(value);
1968
+ return isNaN(n) ? value : n / 100 * viewportHeight;
1969
+ }
1970
+ if (value.endsWith("rem")) {
1971
+ const n = parseFloat(value);
1972
+ return isNaN(n) ? value : n * rootFontSize;
1973
+ }
1974
+ if (value.endsWith("em")) {
1975
+ const n = parseFloat(value);
1976
+ return isNaN(n) ? value : n * fontSize;
1977
+ }
1978
+ if (value.endsWith("px")) {
1979
+ const n = parseFloat(value);
1980
+ return isNaN(n) ? value : n;
1981
+ }
1982
+ if (value.endsWith("pt")) {
1983
+ const n = parseFloat(value);
1984
+ return isNaN(n) ? value : n * (96 / 72);
1985
+ }
1986
+ if (value.endsWith("pc")) {
1987
+ const n = parseFloat(value);
1988
+ return isNaN(n) ? value : n * 16;
1989
+ }
1990
+ if (value.endsWith("in")) {
1991
+ const n = parseFloat(value);
1992
+ return isNaN(n) ? value : n * 96;
1993
+ }
1994
+ if (value.endsWith("cm")) {
1995
+ const n = parseFloat(value);
1996
+ return isNaN(n) ? value : n * (96 / 2.54);
1997
+ }
1998
+ if (value.endsWith("mm")) {
1999
+ const n = parseFloat(value);
2000
+ return isNaN(n) ? value : n * (96 / 25.4);
2001
+ }
2002
+ return value;
2003
+ }
2004
+ function resolveUnits(style, viewportWidth, viewportHeight, rootFontSize = DEFAULT_STYLE.fontSize) {
2005
+ const fontSize = typeof style.fontSize === "number" ? style.fontSize : rootFontSize;
2006
+ for (const prop of DIMENSION_PROPS) {
2007
+ const value = style[prop];
2008
+ if (typeof value !== "string") continue;
2009
+ const resolved = resolveUnit(
2010
+ value,
2011
+ viewportWidth,
2012
+ viewportHeight,
2013
+ fontSize,
2014
+ rootFontSize
2015
+ );
2016
+ if (resolved !== value) {
2017
+ style[prop] = resolved;
2018
+ }
2019
+ }
2020
+ return style;
2021
+ }
1646
2022
 
1647
2023
  // src/jsx/yoga.ts
1648
2024
  import Yoga, {
@@ -1831,12 +2207,14 @@ function applyEdgeValue(node, setter, edge, value) {
1831
2207
  }
1832
2208
 
1833
2209
  // src/jsx/layout.ts
1834
- function buildLayoutTree(element, containerWidth, containerHeight, ctx, emojiEnabled) {
2210
+ async function buildLayoutTree(element, containerWidth, containerHeight, ctx, emojiEnabled) {
1835
2211
  const rootYogaNode = createYogaNode();
1836
- const rootNode = buildNode(
2212
+ const rootNode = await buildNode(
1837
2213
  element,
1838
2214
  DEFAULT_STYLE,
1839
2215
  rootYogaNode,
2216
+ containerWidth,
2217
+ containerHeight,
1840
2218
  ctx,
1841
2219
  emojiEnabled
1842
2220
  );
@@ -1847,7 +2225,7 @@ function buildLayoutTree(element, containerWidth, containerHeight, ctx, emojiEna
1847
2225
  freeYogaNode(rootYogaNode);
1848
2226
  return layoutTree;
1849
2227
  }
1850
- function buildNode(element, parentStyle, yogaNode, ctx, emojiEnabled) {
2228
+ async function buildNode(element, parentStyle, yogaNode, viewportWidth, viewportHeight, ctx, emojiEnabled) {
1851
2229
  if (element === null || element === void 0 || typeof element === "boolean") {
1852
2230
  return {
1853
2231
  type: "empty",
@@ -1880,7 +2258,17 @@ function buildNode(element, parentStyle, yogaNode, ctx, emojiEnabled) {
1880
2258
  continue;
1881
2259
  const childYogaNode = createYogaNode();
1882
2260
  yogaNode.insertChild(childYogaNode, children2.length);
1883
- children2.push(buildNode(child, style2, childYogaNode, ctx, emojiEnabled));
2261
+ children2.push(
2262
+ await buildNode(
2263
+ child,
2264
+ style2,
2265
+ childYogaNode,
2266
+ viewportWidth,
2267
+ viewportHeight,
2268
+ ctx,
2269
+ emojiEnabled
2270
+ )
2271
+ );
1884
2272
  }
1885
2273
  return {
1886
2274
  type: "div",
@@ -1896,12 +2284,21 @@ function buildNode(element, parentStyle, yogaNode, ctx, emojiEnabled) {
1896
2284
  const rendered = type(
1897
2285
  el.props ?? {}
1898
2286
  );
1899
- return buildNode(rendered, parentStyle, yogaNode, ctx, emojiEnabled);
2287
+ return await buildNode(
2288
+ rendered,
2289
+ parentStyle,
2290
+ yogaNode,
2291
+ viewportWidth,
2292
+ viewportHeight,
2293
+ ctx,
2294
+ emojiEnabled
2295
+ );
1900
2296
  }
1901
2297
  const props = el.props ?? {};
1902
2298
  const rawStyle = props.style ?? {};
1903
2299
  const expanded = expandStyle(rawStyle);
1904
2300
  const style = resolveStyle(expanded, parentStyle);
2301
+ resolveUnits(style, viewportWidth, viewportHeight);
1905
2302
  const tagName = String(type);
1906
2303
  if (tagName === "svg") {
1907
2304
  if (props.width != null && style.width === void 0)
@@ -1928,6 +2325,32 @@ function buildNode(element, parentStyle, yogaNode, ctx, emojiEnabled) {
1928
2325
  }
1929
2326
  }
1930
2327
  }
2328
+ if (tagName === "img") {
2329
+ if (props.width != null && style.width === void 0)
2330
+ style.width = Number(props.width);
2331
+ if (props.height != null && style.height === void 0)
2332
+ style.height = Number(props.height);
2333
+ const src = props.src;
2334
+ if (src) {
2335
+ try {
2336
+ const image = await loadImage4(src);
2337
+ const naturalW = image.width;
2338
+ const naturalH = image.height;
2339
+ const w = typeof style.width === "number" ? style.width : void 0;
2340
+ const h = typeof style.height === "number" ? style.height : void 0;
2341
+ if (w !== void 0 && h === void 0 && naturalW > 0) {
2342
+ style.height = w * (naturalH / naturalW);
2343
+ } else if (h !== void 0 && w === void 0 && naturalH > 0) {
2344
+ style.width = h * (naturalW / naturalH);
2345
+ } else if (w === void 0 && h === void 0) {
2346
+ style.width = naturalW;
2347
+ style.height = naturalH;
2348
+ }
2349
+ props.__loadedImage = image;
2350
+ } catch {
2351
+ }
2352
+ }
2353
+ }
1931
2354
  applyStylesToYoga(yogaNode, style);
1932
2355
  if (tagName === "svg") {
1933
2356
  return {
@@ -1976,7 +2399,17 @@ function buildNode(element, parentStyle, yogaNode, ctx, emojiEnabled) {
1976
2399
  continue;
1977
2400
  const childYogaNode = createYogaNode();
1978
2401
  yogaNode.insertChild(childYogaNode, children.length);
1979
- children.push(buildNode(child, style, childYogaNode, ctx, emojiEnabled));
2402
+ children.push(
2403
+ await buildNode(
2404
+ child,
2405
+ style,
2406
+ childYogaNode,
2407
+ viewportWidth,
2408
+ viewportHeight,
2409
+ ctx,
2410
+ emojiEnabled
2411
+ )
2412
+ );
1980
2413
  }
1981
2414
  }
1982
2415
  return {
@@ -2042,12 +2475,18 @@ async function renderReactElement(ctx, element, options) {
2042
2475
  const width = ctx.canvas.width;
2043
2476
  const height = ctx.canvas.height;
2044
2477
  const emojiStyle = options.emoji === "none" ? void 0 : options.emoji ?? "twemoji";
2045
- const layoutTree = buildLayoutTree(element, width, height, ctx, !!emojiStyle);
2478
+ const layoutTree = await buildLayoutTree(
2479
+ element,
2480
+ width,
2481
+ height,
2482
+ ctx,
2483
+ !!emojiStyle
2484
+ );
2046
2485
  await drawNode(ctx, layoutTree, 0, 0, options.debug ?? false, emojiStyle);
2047
2486
  }
2048
2487
 
2049
2488
  // src/index.ts
2050
- function createCanvas2(width, height) {
2489
+ function createCanvas3(width, height) {
2051
2490
  const canvas = _createCanvas(width, height);
2052
2491
  const origEncode = canvas.encode.bind(canvas);
2053
2492
  canvas.encode = (async (...args) => Buffer.from(await origEncode(...args)));
@@ -2058,8 +2497,8 @@ export {
2058
2497
  GlobalFonts2 as GlobalFonts,
2059
2498
  Image,
2060
2499
  LottieAnimation,
2061
- createCanvas2 as createCanvas,
2062
- loadImage4 as loadImage,
2500
+ createCanvas3 as createCanvas,
2501
+ loadImage5 as loadImage,
2063
2502
  loadLottie,
2064
2503
  registerFont,
2065
2504
  registerFontFromPath,