@effing/canvas 0.19.0 → 0.19.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
@@ -784,10 +784,10 @@ function getBorderRadiusFromStyle(style, width, height) {
784
784
  }
785
785
  function drawBorders(ctx, x, y, width, height, style, borderRadius) {
786
786
  const hasRoundedCorners = borderRadius.topLeft > 0 || borderRadius.topRight > 0 || borderRadius.bottomRight > 0 || borderRadius.bottomLeft > 0;
787
- const tw = toNumber(style.borderTopWidth);
788
- const rw = toNumber(style.borderRightWidth);
789
- const bw = toNumber(style.borderBottomWidth);
790
- const lw = toNumber(style.borderLeftWidth);
787
+ const tw = resolveBoxValue(style.borderTopWidth, width);
788
+ const rw = resolveBoxValue(style.borderRightWidth, width);
789
+ const bw = resolveBoxValue(style.borderBottomWidth, width);
790
+ const lw = resolveBoxValue(style.borderLeftWidth, width);
791
791
  if (tw === 0 && rw === 0 && bw === 0 && lw === 0) return;
792
792
  const allSameWidth = tw === rw && rw === bw && bw === lw && tw > 0;
793
793
  const tc = style.borderTopColor ?? "black";
@@ -917,6 +917,13 @@ function normalizeChildren(node) {
917
917
  if (raw == null) return [];
918
918
  return Array.isArray(raw) ? raw : [raw];
919
919
  }
920
+ function svgLength(value, reference, fallback = 0) {
921
+ if (value == null) return fallback;
922
+ const s = String(value);
923
+ if (s.endsWith("%")) return parseFloat(s) / 100 * reference;
924
+ const n = Number(s);
925
+ return isNaN(n) ? fallback : n;
926
+ }
920
927
  function parseFrac(value, fallback) {
921
928
  if (value == null) return fallback;
922
929
  const s = String(value);
@@ -1170,7 +1177,7 @@ function pointsBBox(points) {
1170
1177
  if (!isFinite(minX)) return { x: 0, y: 0, width: 0, height: 0 };
1171
1178
  return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
1172
1179
  }
1173
- function buildPath(child) {
1180
+ function buildPath(child, vbW, vbH) {
1174
1181
  const props = mergeStyleIntoProps(child.props);
1175
1182
  switch (child.type) {
1176
1183
  case "path": {
@@ -1179,29 +1186,29 @@ function buildPath(child) {
1179
1186
  return new Path2D(d);
1180
1187
  }
1181
1188
  case "circle": {
1182
- const cx = Number(props.cx ?? 0);
1183
- const cy = Number(props.cy ?? 0);
1184
- const r = Number(props.r ?? 0);
1189
+ const cx = svgLength(props.cx, vbW);
1190
+ const cy = svgLength(props.cy, vbH);
1191
+ const r = svgLength(props.r, Math.min(vbW, vbH));
1185
1192
  if (r <= 0) return void 0;
1186
1193
  const p = new Path2D();
1187
1194
  p.arc(cx, cy, r, 0, Math.PI * 2);
1188
1195
  return p;
1189
1196
  }
1190
1197
  case "rect": {
1191
- const rx = Number(props.x ?? 0);
1192
- const ry = Number(props.y ?? 0);
1193
- const w = Number(props.width ?? 0);
1194
- const h = Number(props.height ?? 0);
1198
+ const rx = svgLength(props.x, vbW);
1199
+ const ry = svgLength(props.y, vbH);
1200
+ const w = svgLength(props.width, vbW);
1201
+ const h = svgLength(props.height, vbH);
1195
1202
  if (w <= 0 || h <= 0) return void 0;
1196
1203
  const p = new Path2D();
1197
1204
  p.rect(rx, ry, w, h);
1198
1205
  return p;
1199
1206
  }
1200
1207
  case "ellipse": {
1201
- const cx = Number(props.cx ?? 0);
1202
- const cy = Number(props.cy ?? 0);
1203
- const rx = Number(props.rx ?? 0);
1204
- const ry = Number(props.ry ?? 0);
1208
+ const cx = svgLength(props.cx, vbW);
1209
+ const cy = svgLength(props.cy, vbH);
1210
+ const rx = svgLength(props.rx, vbW);
1211
+ const ry = svgLength(props.ry, vbH);
1205
1212
  if (rx <= 0 || ry <= 0) return void 0;
1206
1213
  const p = new Path2D();
1207
1214
  p.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
@@ -1222,10 +1229,10 @@ function buildPath(child) {
1222
1229
  return void 0;
1223
1230
  }
1224
1231
  }
1225
- function buildClipPath(shapes) {
1232
+ function buildClipPath(shapes, vbW, vbH) {
1226
1233
  let combined;
1227
1234
  for (const shape of shapes) {
1228
- const p = buildPath(shape);
1235
+ const p = buildPath(shape, vbW, vbH);
1229
1236
  if (!p) continue;
1230
1237
  if (!combined) {
1231
1238
  combined = p;
@@ -1239,10 +1246,14 @@ function drawSvgContainer(ctx, node, x, y, width, height) {
1239
1246
  ctx.save();
1240
1247
  ctx.translate(x, y);
1241
1248
  const viewBox = node.props.viewBox;
1249
+ let vbW = width;
1250
+ let vbH = height;
1242
1251
  if (viewBox) {
1243
1252
  const parts = viewBox.split(/[\s,]+/).map(Number);
1244
1253
  if (parts.length === 4) {
1245
- const [vbX, vbY, vbW, vbH] = parts;
1254
+ const [vbX, vbY, parsedW, parsedH] = parts;
1255
+ vbW = parsedW;
1256
+ vbH = parsedH;
1246
1257
  const scaleX = width / vbW;
1247
1258
  const scaleY = height / vbH;
1248
1259
  ctx.scale(scaleX, scaleY);
@@ -1260,7 +1271,7 @@ function drawSvgContainer(ctx, node, x, y, width, height) {
1260
1271
  );
1261
1272
  const defs = collectDefs(svgChildren);
1262
1273
  for (const child of svgChildren) {
1263
- drawSvgChild(ctx, child, inheritedFill, color, defs);
1274
+ drawSvgChild(ctx, child, inheritedFill, color, defs, vbW, vbH);
1264
1275
  }
1265
1276
  }
1266
1277
  ctx.restore();
@@ -1269,13 +1280,13 @@ var EMPTY_DEFS = {
1269
1280
  clips: /* @__PURE__ */ new Map(),
1270
1281
  gradients: /* @__PURE__ */ new Map()
1271
1282
  };
1272
- function drawSvgChild(ctx, child, inheritedFill, color, defs = EMPTY_DEFS) {
1283
+ function drawSvgChild(ctx, child, inheritedFill, color, defs = EMPTY_DEFS, vbW = 0, vbH = 0) {
1273
1284
  const { type } = child;
1274
1285
  const props = mergeStyleIntoProps(child.props);
1275
1286
  if (type === "defs" || type === "clipPath") return;
1276
1287
  const clipRef = parseUrlRef(props.clipPath ?? props["clip-path"]);
1277
1288
  const clipShapes = clipRef ? defs.clips.get(clipRef) : void 0;
1278
- const clipPath = clipShapes ? buildClipPath(clipShapes) : void 0;
1289
+ const clipPath = clipShapes ? buildClipPath(clipShapes, vbW, vbH) : void 0;
1279
1290
  if (clipPath) ctx.save();
1280
1291
  if (clipPath) ctx.clip(clipPath);
1281
1292
  switch (type) {
@@ -1283,16 +1294,16 @@ function drawSvgChild(ctx, child, inheritedFill, color, defs = EMPTY_DEFS) {
1283
1294
  drawPath(ctx, props, inheritedFill, color, defs);
1284
1295
  break;
1285
1296
  case "circle":
1286
- drawCircle(ctx, props, inheritedFill, color, defs);
1297
+ drawCircle(ctx, props, inheritedFill, color, defs, vbW, vbH);
1287
1298
  break;
1288
1299
  case "rect":
1289
- drawSvgRect(ctx, props, inheritedFill, color, defs);
1300
+ drawSvgRect(ctx, props, inheritedFill, color, defs, vbW, vbH);
1290
1301
  break;
1291
1302
  case "line":
1292
- drawLine(ctx, props, color, defs);
1303
+ drawLine(ctx, props, color, defs, vbW, vbH);
1293
1304
  break;
1294
1305
  case "ellipse":
1295
- drawEllipse(ctx, props, inheritedFill, color, defs);
1306
+ drawEllipse(ctx, props, inheritedFill, color, defs, vbW, vbH);
1296
1307
  break;
1297
1308
  case "polygon":
1298
1309
  drawPolygon(ctx, props, inheritedFill, color, defs);
@@ -1301,7 +1312,7 @@ function drawSvgChild(ctx, child, inheritedFill, color, defs = EMPTY_DEFS) {
1301
1312
  drawPolyline(ctx, props, inheritedFill, color, defs);
1302
1313
  break;
1303
1314
  case "g":
1304
- drawGroup(ctx, child, inheritedFill, color, defs);
1315
+ drawGroup(ctx, child, inheritedFill, color, defs, vbW, vbH);
1305
1316
  break;
1306
1317
  }
1307
1318
  if (clipPath) ctx.restore();
@@ -1313,32 +1324,32 @@ function drawPath(ctx, props, inheritedFill, color, defs) {
1313
1324
  const bbox = pathBBox(d);
1314
1325
  applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox);
1315
1326
  }
1316
- function drawCircle(ctx, props, inheritedFill, color, defs) {
1317
- const cx = Number(props.cx ?? 0);
1318
- const cy = Number(props.cy ?? 0);
1319
- const r = Number(props.r ?? 0);
1327
+ function drawCircle(ctx, props, inheritedFill, color, defs, vbW = 0, vbH = 0) {
1328
+ const cx = svgLength(props.cx, vbW);
1329
+ const cy = svgLength(props.cy, vbH);
1330
+ const r = svgLength(props.r, Math.min(vbW, vbH));
1320
1331
  if (r <= 0) return;
1321
1332
  const path = new Path2D();
1322
1333
  path.arc(cx, cy, r, 0, Math.PI * 2);
1323
1334
  const bbox = { x: cx - r, y: cy - r, width: 2 * r, height: 2 * r };
1324
1335
  applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox);
1325
1336
  }
1326
- function drawSvgRect(ctx, props, inheritedFill, color, defs) {
1327
- const rx = Number(props.x ?? 0);
1328
- const ry = Number(props.y ?? 0);
1329
- const w = Number(props.width ?? 0);
1330
- const h = Number(props.height ?? 0);
1337
+ function drawSvgRect(ctx, props, inheritedFill, color, defs, vbW = 0, vbH = 0) {
1338
+ const rx = svgLength(props.x, vbW);
1339
+ const ry = svgLength(props.y, vbH);
1340
+ const w = svgLength(props.width, vbW);
1341
+ const h = svgLength(props.height, vbH);
1331
1342
  if (w <= 0 || h <= 0) return;
1332
1343
  const path = new Path2D();
1333
1344
  path.rect(rx, ry, w, h);
1334
1345
  const bbox = { x: rx, y: ry, width: w, height: h };
1335
1346
  applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox);
1336
1347
  }
1337
- function drawLine(ctx, props, color, defs) {
1338
- const x1 = Number(props.x1 ?? 0);
1339
- const y1 = Number(props.y1 ?? 0);
1340
- const x2 = Number(props.x2 ?? 0);
1341
- const y2 = Number(props.y2 ?? 0);
1348
+ function drawLine(ctx, props, color, defs, vbW = 0, vbH = 0) {
1349
+ const x1 = svgLength(props.x1, vbW);
1350
+ const y1 = svgLength(props.y1, vbH);
1351
+ const x2 = svgLength(props.x2, vbW);
1352
+ const y2 = svgLength(props.y2, vbH);
1342
1353
  const path = new Path2D();
1343
1354
  path.moveTo(x1, y1);
1344
1355
  path.lineTo(x2, y2);
@@ -1350,11 +1361,11 @@ function drawLine(ctx, props, color, defs) {
1350
1361
  };
1351
1362
  applyStroke(ctx, props, path, color, defs, bbox);
1352
1363
  }
1353
- function drawEllipse(ctx, props, inheritedFill, color, defs) {
1354
- const cx = Number(props.cx ?? 0);
1355
- const cy = Number(props.cy ?? 0);
1356
- const rx = Number(props.rx ?? 0);
1357
- const ry = Number(props.ry ?? 0);
1364
+ function drawEllipse(ctx, props, inheritedFill, color, defs, vbW = 0, vbH = 0) {
1365
+ const cx = svgLength(props.cx, vbW);
1366
+ const cy = svgLength(props.cy, vbH);
1367
+ const rx = svgLength(props.rx, vbW);
1368
+ const ry = svgLength(props.ry, vbH);
1358
1369
  if (rx <= 0 || ry <= 0) return;
1359
1370
  const path = new Path2D();
1360
1371
  path.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
@@ -1450,7 +1461,7 @@ function applySvgTransform(ctx, transform) {
1450
1461
  }
1451
1462
  }
1452
1463
  }
1453
- function drawGroup(ctx, node, inheritedFill, color, defs = EMPTY_DEFS) {
1464
+ function drawGroup(ctx, node, inheritedFill, color, defs = EMPTY_DEFS, vbW = 0, vbH = 0) {
1454
1465
  const children = normalizeChildren(node);
1455
1466
  if (children.length === 0) return;
1456
1467
  const merged = mergeStyleIntoProps(node.props);
@@ -1462,7 +1473,7 @@ function drawGroup(ctx, node, inheritedFill, color, defs = EMPTY_DEFS) {
1462
1473
  }
1463
1474
  for (const child of children) {
1464
1475
  if (child != null && typeof child === "object") {
1465
- drawSvgChild(ctx, child, groupFill, color, defs);
1476
+ drawSvgChild(ctx, child, groupFill, color, defs, vbW, vbH);
1466
1477
  }
1467
1478
  }
1468
1479
  if (transform) {
@@ -1951,12 +1962,12 @@ async function drawNode(ctx, node, parentX, parentY, debug, emojiStyle) {
1951
1962
  ctx.strokeRect(x, y, width, height);
1952
1963
  }
1953
1964
  if (node.textContent !== void 0 && node.textContent !== "") {
1954
- const paddingTop = toNumber(style.paddingTop);
1955
- const paddingLeft = toNumber(style.paddingLeft);
1956
- const paddingRight = toNumber(style.paddingRight);
1957
- const borderTopW = toNumber(style.borderTopWidth);
1958
- const borderLeftW = toNumber(style.borderLeftWidth);
1959
- const borderRightW = toNumber(style.borderRightWidth);
1965
+ const paddingTop = resolveBoxValue(style.paddingTop, width);
1966
+ const paddingLeft = resolveBoxValue(style.paddingLeft, width);
1967
+ const paddingRight = resolveBoxValue(style.paddingRight, width);
1968
+ const borderTopW = resolveBoxValue(style.borderTopWidth, width);
1969
+ const borderLeftW = resolveBoxValue(style.borderLeftWidth, width);
1970
+ const borderRightW = resolveBoxValue(style.borderRightWidth, width);
1960
1971
  const contentX = x + paddingLeft + borderLeftW;
1961
1972
  const contentY = y + paddingTop + borderTopW;
1962
1973
  const contentWidth = width - paddingLeft - paddingRight - borderLeftW - borderRightW;
@@ -1977,10 +1988,10 @@ async function drawNode(ctx, node, parentX, parentY, debug, emojiStyle) {
1977
1988
  );
1978
1989
  }
1979
1990
  if (node.type === "img" && node.props.src) {
1980
- const paddingTop = toNumber(style.paddingTop);
1981
- const paddingLeft = toNumber(style.paddingLeft);
1982
- const paddingRight = toNumber(style.paddingRight);
1983
- const paddingBottom = toNumber(style.paddingBottom);
1991
+ const paddingTop = resolveBoxValue(style.paddingTop, width);
1992
+ const paddingLeft = resolveBoxValue(style.paddingLeft, width);
1993
+ const paddingRight = resolveBoxValue(style.paddingRight, width);
1994
+ const paddingBottom = resolveBoxValue(style.paddingBottom, width);
1984
1995
  const imgX = x + paddingLeft;
1985
1996
  const imgY = y + paddingTop;
1986
1997
  const imgW = width - paddingLeft - paddingRight;
@@ -2146,12 +2157,12 @@ async function drawNodeInner(ctx, node, parentX, parentY, offsetX, offsetY, debu
2146
2157
  ctx.strokeRect(x, y, width, height);
2147
2158
  }
2148
2159
  if (node.textContent !== void 0 && node.textContent !== "") {
2149
- const paddingTop = toNumber(style.paddingTop);
2150
- const paddingLeft = toNumber(style.paddingLeft);
2151
- const paddingRight = toNumber(style.paddingRight);
2152
- const borderTopW = toNumber(style.borderTopWidth);
2153
- const borderLeftW = toNumber(style.borderLeftWidth);
2154
- const borderRightW = toNumber(style.borderRightWidth);
2160
+ const paddingTop = resolveBoxValue(style.paddingTop, width);
2161
+ const paddingLeft = resolveBoxValue(style.paddingLeft, width);
2162
+ const paddingRight = resolveBoxValue(style.paddingRight, width);
2163
+ const borderTopW = resolveBoxValue(style.borderTopWidth, width);
2164
+ const borderLeftW = resolveBoxValue(style.borderLeftWidth, width);
2165
+ const borderRightW = resolveBoxValue(style.borderRightWidth, width);
2155
2166
  const contentX = x + paddingLeft + borderLeftW;
2156
2167
  const contentY = y + paddingTop + borderTopW;
2157
2168
  const contentWidth = width - paddingLeft - paddingRight - borderLeftW - borderRightW;
@@ -2172,10 +2183,10 @@ async function drawNodeInner(ctx, node, parentX, parentY, offsetX, offsetY, debu
2172
2183
  );
2173
2184
  }
2174
2185
  if (node.type === "img" && node.props.src) {
2175
- const paddingTop = toNumber(style.paddingTop);
2176
- const paddingLeft = toNumber(style.paddingLeft);
2177
- const paddingRight = toNumber(style.paddingRight);
2178
- const paddingBottom = toNumber(style.paddingBottom);
2186
+ const paddingTop = resolveBoxValue(style.paddingTop, width);
2187
+ const paddingLeft = resolveBoxValue(style.paddingLeft, width);
2188
+ const paddingRight = resolveBoxValue(style.paddingRight, width);
2189
+ const paddingBottom = resolveBoxValue(style.paddingBottom, width);
2179
2190
  const imgX = x + paddingLeft;
2180
2191
  const imgY = y + paddingTop;
2181
2192
  const imgW = width - paddingLeft - paddingRight;
@@ -2280,6 +2291,14 @@ function toNumber(v) {
2280
2291
  const n = parseFloat(String(v));
2281
2292
  return isNaN(n) ? 0 : n;
2282
2293
  }
2294
+ function resolveBoxValue(v, referenceWidth) {
2295
+ if (typeof v === "number") return v;
2296
+ if (v === void 0 || v === null) return 0;
2297
+ const s = String(v);
2298
+ if (s.endsWith("%")) return parseFloat(s) / 100 * referenceWidth;
2299
+ const n = parseFloat(s);
2300
+ return isNaN(n) ? 0 : n;
2301
+ }
2283
2302
 
2284
2303
  // src/jsx/font.ts
2285
2304
  import { GlobalFonts } from "@napi-rs/canvas";
@@ -2888,24 +2907,36 @@ function applyEdgeValue(node, setter, edge, value) {
2888
2907
 
2889
2908
  // src/jsx/layout.ts
2890
2909
  async function buildLayoutTree(element, containerWidth, containerHeight, ctx, emojiEnabled, fontFamilies) {
2891
- const rootYogaNode = createYogaNode();
2910
+ const elementYogaNode = createYogaNode();
2892
2911
  const rootStyle = fontFamilies?.length ? { ...DEFAULT_STYLE, fontFamily: fontFamilies.join(", ") } : DEFAULT_STYLE;
2893
- const rootNode = await buildNode(
2912
+ const elementNode = await buildNode(
2894
2913
  element,
2895
2914
  rootStyle,
2896
- rootYogaNode,
2915
+ elementYogaNode,
2897
2916
  containerWidth,
2898
2917
  containerHeight,
2899
2918
  ctx,
2900
2919
  emojiEnabled,
2901
2920
  fontFamilies
2902
2921
  );
2922
+ const rootYogaNode = createYogaNode();
2903
2923
  rootYogaNode.setWidth(containerWidth);
2904
2924
  rootYogaNode.setHeight(containerHeight);
2925
+ rootYogaNode.setFlexDirection(FlexDirection.Row);
2926
+ rootYogaNode.insertChild(elementYogaNode, 0);
2905
2927
  rootYogaNode.calculateLayout(containerWidth, containerHeight);
2906
- const layoutTree = extractLayout(rootNode, rootYogaNode);
2928
+ const elementLayout = extractLayout(elementNode, elementYogaNode);
2907
2929
  freeYogaNode(rootYogaNode);
2908
- return layoutTree;
2930
+ return {
2931
+ type: "div",
2932
+ style: rootStyle,
2933
+ children: [elementLayout],
2934
+ props: {},
2935
+ x: 0,
2936
+ y: 0,
2937
+ width: containerWidth,
2938
+ height: containerHeight
2939
+ };
2909
2940
  }
2910
2941
  async function buildNode(element, parentStyle, yogaNode, viewportWidth, viewportHeight, ctx, emojiEnabled, fontFamilies) {
2911
2942
  if (element === null || element === void 0 || typeof element === "boolean") {
@@ -2999,15 +3030,15 @@ async function buildNode(element, parentStyle, yogaNode, viewportWidth, viewport
2999
3030
  if (parts.length === 4) {
3000
3031
  const [, , vbW, vbH] = parts;
3001
3032
  if (vbW > 0 && vbH > 0) {
3002
- const w = typeof style.width === "number" ? style.width : void 0;
3003
- const h = typeof style.height === "number" ? style.height : void 0;
3004
- if (w !== void 0 && h === void 0) {
3005
- style.height = w * (vbH / vbW);
3006
- } else if (h !== void 0 && w === void 0) {
3007
- style.width = h * (vbW / vbH);
3008
- } else if (w === void 0 && h === void 0) {
3033
+ const wSet = style.width !== void 0;
3034
+ const hSet = style.height !== void 0;
3035
+ if (!wSet && !hSet) {
3009
3036
  style.width = vbW;
3010
3037
  style.height = vbH;
3038
+ } else if (!hSet && typeof style.width === "number") {
3039
+ style.height = style.width * (vbH / vbW);
3040
+ } else if (!wSet && typeof style.height === "number") {
3041
+ style.width = style.height * (vbW / vbH);
3011
3042
  }
3012
3043
  }
3013
3044
  }
@@ -3028,12 +3059,12 @@ async function buildNode(element, parentStyle, yogaNode, viewportWidth, viewport
3028
3059
  const image = await loadImage4(src);
3029
3060
  const naturalW = image.width;
3030
3061
  const naturalH = image.height;
3031
- const w = typeof style.width === "number" ? style.width : void 0;
3032
- const h = typeof style.height === "number" ? style.height : void 0;
3033
- if (w !== void 0 && h === void 0 && naturalW > 0) {
3034
- style.height = w * (naturalH / naturalW);
3035
- } else if (h !== void 0 && w === void 0 && naturalH > 0) {
3036
- style.width = h * (naturalW / naturalH);
3062
+ const wSet = style.width !== void 0;
3063
+ const hSet = style.height !== void 0;
3064
+ if (wSet && !hSet && typeof style.width === "number" && naturalW > 0) {
3065
+ style.height = style.width * (naturalH / naturalW);
3066
+ } else if (hSet && !wSet && typeof style.height === "number" && naturalH > 0) {
3067
+ style.width = style.height * (naturalW / naturalH);
3037
3068
  }
3038
3069
  props.__loadedImage = image;
3039
3070
  } catch {