@effing/canvas 0.18.6 → 0.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -110,6 +110,54 @@ function measureText(text, fontSize, fontFamily, fontWeight = 400, fontStyle = "
110
110
  height: ascent + descent
111
111
  };
112
112
  }
113
+ function measureTrimMetrics(fontSize, fontFamily, fontWeight, fontStyle, lineHeight, edge, ctx) {
114
+ const c = ctx ?? getScratchCtx();
115
+ setFont(c, fontSize, fontFamily, fontWeight, fontStyle);
116
+ const refMetrics = c.measureText("M");
117
+ const fontAscent = refMetrics.fontBoundingBoxAscent ?? refMetrics.actualBoundingBoxAscent ?? fontSize * 0.8;
118
+ const fontDescent = refMetrics.fontBoundingBoxDescent ?? refMetrics.actualBoundingBoxDescent ?? fontSize * 0.2;
119
+ const parts = edge.trim().split(/\s+/);
120
+ const overEdge = parts[0] ?? "text";
121
+ const underEdge = parts.length > 1 ? parts[1] : overEdge;
122
+ let targetAscent;
123
+ switch (overEdge) {
124
+ case "cap": {
125
+ const capMetrics = c.measureText("H");
126
+ targetAscent = capMetrics.actualBoundingBoxAscent ?? fontSize * 0.7;
127
+ break;
128
+ }
129
+ case "ex": {
130
+ const exMetrics = c.measureText("x");
131
+ targetAscent = exMetrics.actualBoundingBoxAscent ?? fontSize * 0.5;
132
+ break;
133
+ }
134
+ case "ideographic":
135
+ case "ideographic-ink":
136
+ case "text":
137
+ default:
138
+ targetAscent = fontAscent;
139
+ break;
140
+ }
141
+ let targetDescent;
142
+ switch (underEdge) {
143
+ case "alphabetic":
144
+ targetDescent = 0;
145
+ break;
146
+ case "ideographic":
147
+ case "ideographic-ink":
148
+ case "text":
149
+ default:
150
+ targetDescent = fontDescent;
151
+ break;
152
+ }
153
+ const halfLeading = (lineHeight - (fontAscent + fontDescent)) / 2;
154
+ const overTrim = halfLeading + (fontAscent - targetAscent);
155
+ const underTrim = halfLeading + (fontDescent - targetDescent);
156
+ return {
157
+ overTrim: Math.max(0, overTrim),
158
+ underTrim: Math.max(0, underTrim)
159
+ };
160
+ }
113
161
  function measureWord(word, fontSize, fontFamily, fontWeight = 400, fontStyle = "normal", ctx, letterSpacing = 0) {
114
162
  const base = measureText(
115
163
  word,
@@ -295,6 +343,28 @@ function layoutText(text, style, maxWidth, ctx, emojiEnabled) {
295
343
  totalHeight += lineHeightPx;
296
344
  maxLineWidth = Math.max(maxLineWidth, lineWidth);
297
345
  }
346
+ const textBoxTrim = style.textBoxTrim;
347
+ if (textBoxTrim && textBoxTrim !== "none" && segments.length > 0) {
348
+ const textBoxEdge = style.textBoxEdge ?? "text";
349
+ const trimMetrics = measureTrimMetrics(
350
+ fontSize,
351
+ fontFamily,
352
+ fontWeight,
353
+ fontStyle,
354
+ lineHeightPx,
355
+ textBoxEdge,
356
+ ctx
357
+ );
358
+ if (textBoxTrim === "trim-start" || textBoxTrim === "trim-both") {
359
+ for (const seg of segments) {
360
+ seg.y -= trimMetrics.overTrim;
361
+ }
362
+ totalHeight -= trimMetrics.overTrim;
363
+ }
364
+ if (textBoxTrim === "trim-end" || textBoxTrim === "trim-both") {
365
+ totalHeight -= trimMetrics.underTrim;
366
+ }
367
+ }
298
368
  return {
299
369
  segments,
300
370
  width: maxLineWidth,
@@ -714,10 +784,10 @@ function getBorderRadiusFromStyle(style, width, height) {
714
784
  }
715
785
  function drawBorders(ctx, x, y, width, height, style, borderRadius) {
716
786
  const hasRoundedCorners = borderRadius.topLeft > 0 || borderRadius.topRight > 0 || borderRadius.bottomRight > 0 || borderRadius.bottomLeft > 0;
717
- const tw = toNumber(style.borderTopWidth);
718
- const rw = toNumber(style.borderRightWidth);
719
- const bw = toNumber(style.borderBottomWidth);
720
- 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);
721
791
  if (tw === 0 && rw === 0 && bw === 0 && lw === 0) return;
722
792
  const allSameWidth = tw === rw && rw === bw && bw === lw && tw > 0;
723
793
  const tc = style.borderTopColor ?? "black";
@@ -823,20 +893,19 @@ function mergeStyleIntoProps(props) {
823
893
  return { ...props, ...style };
824
894
  }
825
895
  function collectDefs(children) {
826
- const defs = /* @__PURE__ */ new Map();
896
+ const clips = /* @__PURE__ */ new Map();
897
+ const gradients = /* @__PURE__ */ new Map();
827
898
  for (const child of children) {
828
899
  if (child.type !== "defs") continue;
829
- const defsChildren = normalizeChildren(child);
830
- for (const def of defsChildren) {
831
- if (def.type === "clipPath") {
832
- const id = def.props.id;
833
- if (id) {
834
- defs.set(id, normalizeChildren(def));
835
- }
836
- }
900
+ for (const def of normalizeChildren(child)) {
901
+ const id = def.props.id;
902
+ if (!id) continue;
903
+ if (def.type === "clipPath") clips.set(id, normalizeChildren(def));
904
+ else if (def.type === "radialGradient" || def.type === "linearGradient")
905
+ gradients.set(id, def);
837
906
  }
838
907
  }
839
- return defs;
908
+ return { clips, gradients };
840
909
  }
841
910
  function parseUrlRef(value) {
842
911
  if (typeof value !== "string") return void 0;
@@ -848,7 +917,267 @@ function normalizeChildren(node) {
848
917
  if (raw == null) return [];
849
918
  return Array.isArray(raw) ? raw : [raw];
850
919
  }
851
- function buildPath(child) {
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
+ }
927
+ function parseFrac(value, fallback) {
928
+ if (value == null) return fallback;
929
+ const s = String(value);
930
+ if (s.endsWith("%")) return parseFloat(s) / 100;
931
+ return Number(s);
932
+ }
933
+ function applyOpacity(color, opacity) {
934
+ if (opacity >= 1) return color;
935
+ const m = color.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i);
936
+ if (m)
937
+ return `rgba(${parseInt(m[1], 16)}, ${parseInt(m[2], 16)}, ${parseInt(m[3], 16)}, ${opacity})`;
938
+ return color;
939
+ }
940
+ function addGradientStops(gradient, def) {
941
+ for (const stop of normalizeChildren(def)) {
942
+ if (stop.type !== "stop") continue;
943
+ const sp = stop.props;
944
+ const offsetRaw = sp.offset ?? 0;
945
+ const offset = typeof offsetRaw === "string" && offsetRaw.endsWith("%") ? parseFloat(offsetRaw) / 100 : Number(offsetRaw);
946
+ const stopColor = sp.stopColor ?? sp["stop-color"] ?? "black";
947
+ const stopOpacity = Number(sp.stopOpacity ?? sp["stop-opacity"] ?? 1);
948
+ gradient.addColorStop(offset, applyOpacity(stopColor, stopOpacity));
949
+ }
950
+ }
951
+ function fillWithSvgGradient(ctx, def, bbox, path, fillRule) {
952
+ const props = def.props;
953
+ if (def.type === "linearGradient") {
954
+ const x1 = bbox.x + parseFrac(props.x1, 0) * bbox.width;
955
+ const y1 = bbox.y + parseFrac(props.y1, 0) * bbox.height;
956
+ const x2 = bbox.x + parseFrac(props.x2, 1) * bbox.width;
957
+ const y2 = bbox.y + parseFrac(props.y2, 0) * bbox.height;
958
+ const gradient = ctx.createLinearGradient(x1, y1, x2, y2);
959
+ addGradientStops(gradient, def);
960
+ ctx.fillStyle = gradient;
961
+ ctx.fill(path, fillRule);
962
+ return true;
963
+ }
964
+ if (def.type === "radialGradient") {
965
+ const cxF = parseFrac(props.cx, 0.5);
966
+ const cyF = parseFrac(props.cy, 0.5);
967
+ const rF = parseFrac(props.r, 0.5);
968
+ const fxF = parseFrac(props.fx, cxF);
969
+ const fyF = parseFrac(props.fy, cyF);
970
+ ctx.save();
971
+ ctx.translate(bbox.x, bbox.y);
972
+ ctx.scale(bbox.width, bbox.height);
973
+ const gradient = ctx.createRadialGradient(fxF, fyF, 0, cxF, cyF, rF);
974
+ addGradientStops(gradient, def);
975
+ ctx.fillStyle = gradient;
976
+ const unitPath = new Path2D();
977
+ const invTransform = {
978
+ a: 1 / bbox.width,
979
+ b: 0,
980
+ c: 0,
981
+ d: 1 / bbox.height,
982
+ e: -bbox.x / bbox.width,
983
+ f: -bbox.y / bbox.height
984
+ };
985
+ unitPath.addPath(path, invTransform);
986
+ ctx.fill(unitPath, fillRule);
987
+ ctx.restore();
988
+ return true;
989
+ }
990
+ return false;
991
+ }
992
+ function strokeWithSvgGradient(ctx, def, bbox, path) {
993
+ const props = def.props;
994
+ if (def.type === "linearGradient") {
995
+ const x1 = bbox.x + parseFrac(props.x1, 0) * bbox.width;
996
+ const y1 = bbox.y + parseFrac(props.y1, 0) * bbox.height;
997
+ const x2 = bbox.x + parseFrac(props.x2, 1) * bbox.width;
998
+ const y2 = bbox.y + parseFrac(props.y2, 0) * bbox.height;
999
+ const gradient = ctx.createLinearGradient(x1, y1, x2, y2);
1000
+ addGradientStops(gradient, def);
1001
+ ctx.strokeStyle = gradient;
1002
+ ctx.stroke(path);
1003
+ return true;
1004
+ }
1005
+ if (def.type === "radialGradient") {
1006
+ const cxAbs = bbox.x + parseFrac(props.cx, 0.5) * bbox.width;
1007
+ const cyAbs = bbox.y + parseFrac(props.cy, 0.5) * bbox.height;
1008
+ const rAbs = parseFrac(props.r, 0.5) * Math.sqrt(bbox.width * bbox.height);
1009
+ const fxAbs = bbox.x + parseFrac(props.fx, 0.5) * bbox.width;
1010
+ const fyAbs = bbox.y + parseFrac(props.fy, 0.5) * bbox.height;
1011
+ const gradient = ctx.createRadialGradient(
1012
+ fxAbs,
1013
+ fyAbs,
1014
+ 0,
1015
+ cxAbs,
1016
+ cyAbs,
1017
+ rAbs
1018
+ );
1019
+ addGradientStops(gradient, def);
1020
+ ctx.strokeStyle = gradient;
1021
+ ctx.stroke(path);
1022
+ return true;
1023
+ }
1024
+ return false;
1025
+ }
1026
+ function pathBBox(d) {
1027
+ let minX = Infinity;
1028
+ let minY = Infinity;
1029
+ let maxX = -Infinity;
1030
+ let maxY = -Infinity;
1031
+ let cx = 0;
1032
+ let cy = 0;
1033
+ const update = (x, y) => {
1034
+ if (x < minX) minX = x;
1035
+ if (x > maxX) maxX = x;
1036
+ if (y < minY) minY = y;
1037
+ if (y > maxY) maxY = y;
1038
+ };
1039
+ const tokens = d.match(/[a-zA-Z]|[-+]?(?:\d+\.?\d*|\.\d+)(?:[eE][-+]?\d+)?/g);
1040
+ if (!tokens) return { x: 0, y: 0, width: 0, height: 0 };
1041
+ let cmd = "";
1042
+ let i = 0;
1043
+ const num = () => Number(tokens[i++]);
1044
+ while (i < tokens.length) {
1045
+ const t = tokens[i];
1046
+ if (/[a-zA-Z]/.test(t)) {
1047
+ cmd = t;
1048
+ i++;
1049
+ }
1050
+ switch (cmd) {
1051
+ case "M":
1052
+ cx = num();
1053
+ cy = num();
1054
+ update(cx, cy);
1055
+ cmd = "L";
1056
+ break;
1057
+ case "m":
1058
+ cx += num();
1059
+ cy += num();
1060
+ update(cx, cy);
1061
+ cmd = "l";
1062
+ break;
1063
+ case "L":
1064
+ cx = num();
1065
+ cy = num();
1066
+ update(cx, cy);
1067
+ break;
1068
+ case "l":
1069
+ cx += num();
1070
+ cy += num();
1071
+ update(cx, cy);
1072
+ break;
1073
+ case "H":
1074
+ cx = num();
1075
+ update(cx, cy);
1076
+ break;
1077
+ case "h":
1078
+ cx += num();
1079
+ update(cx, cy);
1080
+ break;
1081
+ case "V":
1082
+ cy = num();
1083
+ update(cx, cy);
1084
+ break;
1085
+ case "v":
1086
+ cy += num();
1087
+ update(cx, cy);
1088
+ break;
1089
+ case "C": {
1090
+ for (let j = 0; j < 3; j++) {
1091
+ const px = num();
1092
+ const py = num();
1093
+ update(px, py);
1094
+ }
1095
+ cx = Number(tokens[i - 2]);
1096
+ cy = Number(tokens[i - 1]);
1097
+ break;
1098
+ }
1099
+ case "c": {
1100
+ for (let j = 0; j < 3; j++) {
1101
+ const dx = num();
1102
+ const dy = num();
1103
+ update(cx + dx, cy + dy);
1104
+ if (j === 2) {
1105
+ cx += dx;
1106
+ cy += dy;
1107
+ }
1108
+ }
1109
+ break;
1110
+ }
1111
+ case "Q": {
1112
+ for (let j = 0; j < 2; j++) {
1113
+ const px = num();
1114
+ const py = num();
1115
+ update(px, py);
1116
+ }
1117
+ cx = Number(tokens[i - 2]);
1118
+ cy = Number(tokens[i - 1]);
1119
+ break;
1120
+ }
1121
+ case "q": {
1122
+ for (let j = 0; j < 2; j++) {
1123
+ const dx = num();
1124
+ const dy = num();
1125
+ update(cx + dx, cy + dy);
1126
+ if (j === 1) {
1127
+ cx += dx;
1128
+ cy += dy;
1129
+ }
1130
+ }
1131
+ break;
1132
+ }
1133
+ case "A": {
1134
+ num();
1135
+ num();
1136
+ num();
1137
+ num();
1138
+ num();
1139
+ cx = num();
1140
+ cy = num();
1141
+ update(cx, cy);
1142
+ break;
1143
+ }
1144
+ case "a": {
1145
+ num();
1146
+ num();
1147
+ num();
1148
+ num();
1149
+ num();
1150
+ cx += num();
1151
+ cy += num();
1152
+ update(cx, cy);
1153
+ break;
1154
+ }
1155
+ case "Z":
1156
+ case "z":
1157
+ break;
1158
+ default:
1159
+ i++;
1160
+ break;
1161
+ }
1162
+ }
1163
+ if (!isFinite(minX)) return { x: 0, y: 0, width: 0, height: 0 };
1164
+ return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
1165
+ }
1166
+ function pointsBBox(points) {
1167
+ let minX = Infinity;
1168
+ let minY = Infinity;
1169
+ let maxX = -Infinity;
1170
+ let maxY = -Infinity;
1171
+ for (const [x, y] of points) {
1172
+ if (x < minX) minX = x;
1173
+ if (x > maxX) maxX = x;
1174
+ if (y < minY) minY = y;
1175
+ if (y > maxY) maxY = y;
1176
+ }
1177
+ if (!isFinite(minX)) return { x: 0, y: 0, width: 0, height: 0 };
1178
+ return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
1179
+ }
1180
+ function buildPath(child, vbW, vbH) {
852
1181
  const props = mergeStyleIntoProps(child.props);
853
1182
  switch (child.type) {
854
1183
  case "path": {
@@ -857,29 +1186,29 @@ function buildPath(child) {
857
1186
  return new Path2D(d);
858
1187
  }
859
1188
  case "circle": {
860
- const cx = Number(props.cx ?? 0);
861
- const cy = Number(props.cy ?? 0);
862
- 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));
863
1192
  if (r <= 0) return void 0;
864
1193
  const p = new Path2D();
865
1194
  p.arc(cx, cy, r, 0, Math.PI * 2);
866
1195
  return p;
867
1196
  }
868
1197
  case "rect": {
869
- const rx = Number(props.x ?? 0);
870
- const ry = Number(props.y ?? 0);
871
- const w = Number(props.width ?? 0);
872
- 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);
873
1202
  if (w <= 0 || h <= 0) return void 0;
874
1203
  const p = new Path2D();
875
1204
  p.rect(rx, ry, w, h);
876
1205
  return p;
877
1206
  }
878
1207
  case "ellipse": {
879
- const cx = Number(props.cx ?? 0);
880
- const cy = Number(props.cy ?? 0);
881
- const rx = Number(props.rx ?? 0);
882
- 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);
883
1212
  if (rx <= 0 || ry <= 0) return void 0;
884
1213
  const p = new Path2D();
885
1214
  p.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
@@ -900,10 +1229,10 @@ function buildPath(child) {
900
1229
  return void 0;
901
1230
  }
902
1231
  }
903
- function buildClipPath(shapes) {
1232
+ function buildClipPath(shapes, vbW, vbH) {
904
1233
  let combined;
905
1234
  for (const shape of shapes) {
906
- const p = buildPath(shape);
1235
+ const p = buildPath(shape, vbW, vbH);
907
1236
  if (!p) continue;
908
1237
  if (!combined) {
909
1238
  combined = p;
@@ -917,10 +1246,14 @@ function drawSvgContainer(ctx, node, x, y, width, height) {
917
1246
  ctx.save();
918
1247
  ctx.translate(x, y);
919
1248
  const viewBox = node.props.viewBox;
1249
+ let vbW = width;
1250
+ let vbH = height;
920
1251
  if (viewBox) {
921
1252
  const parts = viewBox.split(/[\s,]+/).map(Number);
922
1253
  if (parts.length === 4) {
923
- const [vbX, vbY, vbW, vbH] = parts;
1254
+ const [vbX, vbY, parsedW, parsedH] = parts;
1255
+ vbW = parsedW;
1256
+ vbH = parsedH;
924
1257
  const scaleX = width / vbW;
925
1258
  const scaleY = height / vbH;
926
1259
  ctx.scale(scaleX, scaleY);
@@ -938,94 +1271,113 @@ function drawSvgContainer(ctx, node, x, y, width, height) {
938
1271
  );
939
1272
  const defs = collectDefs(svgChildren);
940
1273
  for (const child of svgChildren) {
941
- drawSvgChild(ctx, child, inheritedFill, color, defs);
1274
+ drawSvgChild(ctx, child, inheritedFill, color, defs, vbW, vbH);
942
1275
  }
943
1276
  }
944
1277
  ctx.restore();
945
1278
  }
946
- function drawSvgChild(ctx, child, inheritedFill, color, defs = /* @__PURE__ */ new Map()) {
1279
+ var EMPTY_DEFS = {
1280
+ clips: /* @__PURE__ */ new Map(),
1281
+ gradients: /* @__PURE__ */ new Map()
1282
+ };
1283
+ function drawSvgChild(ctx, child, inheritedFill, color, defs = EMPTY_DEFS, vbW = 0, vbH = 0) {
947
1284
  const { type } = child;
948
1285
  const props = mergeStyleIntoProps(child.props);
949
1286
  if (type === "defs" || type === "clipPath") return;
950
1287
  const clipRef = parseUrlRef(props.clipPath ?? props["clip-path"]);
951
- const clipShapes = clipRef ? defs.get(clipRef) : void 0;
952
- const clipPath = clipShapes ? buildClipPath(clipShapes) : void 0;
1288
+ const clipShapes = clipRef ? defs.clips.get(clipRef) : void 0;
1289
+ const clipPath = clipShapes ? buildClipPath(clipShapes, vbW, vbH) : void 0;
953
1290
  if (clipPath) ctx.save();
954
1291
  if (clipPath) ctx.clip(clipPath);
955
1292
  switch (type) {
956
1293
  case "path":
957
- drawPath(ctx, props, inheritedFill, color);
1294
+ drawPath(ctx, props, inheritedFill, color, defs);
958
1295
  break;
959
1296
  case "circle":
960
- drawCircle(ctx, props, inheritedFill, color);
1297
+ drawCircle(ctx, props, inheritedFill, color, defs, vbW, vbH);
961
1298
  break;
962
1299
  case "rect":
963
- drawSvgRect(ctx, props, inheritedFill, color);
1300
+ drawSvgRect(ctx, props, inheritedFill, color, defs, vbW, vbH);
964
1301
  break;
965
1302
  case "line":
966
- drawLine(ctx, props, color);
1303
+ drawLine(ctx, props, color, defs, vbW, vbH);
967
1304
  break;
968
1305
  case "ellipse":
969
- drawEllipse(ctx, props, inheritedFill, color);
1306
+ drawEllipse(ctx, props, inheritedFill, color, defs, vbW, vbH);
970
1307
  break;
971
1308
  case "polygon":
972
- drawPolygon(ctx, props, inheritedFill, color);
1309
+ drawPolygon(ctx, props, inheritedFill, color, defs);
973
1310
  break;
974
1311
  case "polyline":
975
- drawPolyline(ctx, props, inheritedFill, color);
1312
+ drawPolyline(ctx, props, inheritedFill, color, defs);
976
1313
  break;
977
1314
  case "g":
978
- drawGroup(ctx, child, inheritedFill, color, defs);
1315
+ drawGroup(ctx, child, inheritedFill, color, defs, vbW, vbH);
979
1316
  break;
980
1317
  }
981
1318
  if (clipPath) ctx.restore();
982
1319
  }
983
- function drawPath(ctx, props, inheritedFill, color) {
1320
+ function drawPath(ctx, props, inheritedFill, color, defs) {
984
1321
  const d = props.d;
985
1322
  if (!d) return;
986
1323
  const path = new Path2D(d);
987
- applyFillAndStroke(ctx, props, path, inheritedFill, color);
1324
+ const bbox = pathBBox(d);
1325
+ applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox);
988
1326
  }
989
- function drawCircle(ctx, props, inheritedFill, color) {
990
- const cx = Number(props.cx ?? 0);
991
- const cy = Number(props.cy ?? 0);
992
- 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));
993
1331
  if (r <= 0) return;
994
1332
  const path = new Path2D();
995
1333
  path.arc(cx, cy, r, 0, Math.PI * 2);
996
- applyFillAndStroke(ctx, props, path, inheritedFill, color);
997
- }
998
- function drawSvgRect(ctx, props, inheritedFill, color) {
999
- const rx = Number(props.x ?? 0);
1000
- const ry = Number(props.y ?? 0);
1001
- const w = Number(props.width ?? 0);
1002
- const h = Number(props.height ?? 0);
1334
+ const bbox = { x: cx - r, y: cy - r, width: 2 * r, height: 2 * r };
1335
+ applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox);
1336
+ }
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);
1003
1342
  if (w <= 0 || h <= 0) return;
1004
1343
  const path = new Path2D();
1005
1344
  path.rect(rx, ry, w, h);
1006
- applyFillAndStroke(ctx, props, path, inheritedFill, color);
1007
- }
1008
- function drawLine(ctx, props, color) {
1009
- const x1 = Number(props.x1 ?? 0);
1010
- const y1 = Number(props.y1 ?? 0);
1011
- const x2 = Number(props.x2 ?? 0);
1012
- const y2 = Number(props.y2 ?? 0);
1345
+ const bbox = { x: rx, y: ry, width: w, height: h };
1346
+ applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox);
1347
+ }
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);
1013
1353
  const path = new Path2D();
1014
1354
  path.moveTo(x1, y1);
1015
1355
  path.lineTo(x2, y2);
1016
- applyStroke(ctx, props, path, color);
1356
+ const bbox = {
1357
+ x: Math.min(x1, x2),
1358
+ y: Math.min(y1, y2),
1359
+ width: Math.abs(x2 - x1),
1360
+ height: Math.abs(y2 - y1)
1361
+ };
1362
+ applyStroke(ctx, props, path, color, defs, bbox);
1017
1363
  }
1018
- function drawEllipse(ctx, props, inheritedFill, color) {
1019
- const cx = Number(props.cx ?? 0);
1020
- const cy = Number(props.cy ?? 0);
1021
- const rx = Number(props.rx ?? 0);
1022
- 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);
1023
1369
  if (rx <= 0 || ry <= 0) return;
1024
1370
  const path = new Path2D();
1025
1371
  path.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
1026
- applyFillAndStroke(ctx, props, path, inheritedFill, color);
1372
+ const bbox = {
1373
+ x: cx - rx,
1374
+ y: cy - ry,
1375
+ width: 2 * rx,
1376
+ height: 2 * ry
1377
+ };
1378
+ applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox);
1027
1379
  }
1028
- function drawPolygon(ctx, props, inheritedFill, color) {
1380
+ function drawPolygon(ctx, props, inheritedFill, color, defs) {
1029
1381
  const points = parsePoints(props.points);
1030
1382
  if (points.length < 2) return;
1031
1383
  const path = new Path2D();
@@ -1034,9 +1386,10 @@ function drawPolygon(ctx, props, inheritedFill, color) {
1034
1386
  path.lineTo(points[i][0], points[i][1]);
1035
1387
  }
1036
1388
  path.closePath();
1037
- applyFillAndStroke(ctx, props, path, inheritedFill, color);
1389
+ const bbox = pointsBBox(points);
1390
+ applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox);
1038
1391
  }
1039
- function drawPolyline(ctx, props, inheritedFill, color) {
1392
+ function drawPolyline(ctx, props, inheritedFill, color, defs) {
1040
1393
  const points = parsePoints(props.points);
1041
1394
  if (points.length < 2) return;
1042
1395
  const path = new Path2D();
@@ -1044,18 +1397,88 @@ function drawPolyline(ctx, props, inheritedFill, color) {
1044
1397
  for (let i = 1; i < points.length; i++) {
1045
1398
  path.lineTo(points[i][0], points[i][1]);
1046
1399
  }
1047
- applyFillAndStroke(ctx, props, path, inheritedFill, color);
1400
+ const bbox = pointsBBox(points);
1401
+ applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox);
1402
+ }
1403
+ function applySvgTransform(ctx, transform) {
1404
+ const re = /\b(translate|scale|rotate|skewX|skewY|matrix)\s*\(([^)]*)\)/g;
1405
+ let match;
1406
+ while ((match = re.exec(transform)) !== null) {
1407
+ const fn = match[1];
1408
+ const args = match[2].split(/[\s,]+/).filter(Boolean).map(Number);
1409
+ switch (fn) {
1410
+ case "translate":
1411
+ ctx.translate(args[0] ?? 0, args[1] ?? 0);
1412
+ break;
1413
+ case "scale": {
1414
+ const sx = args[0] ?? 1;
1415
+ ctx.scale(sx, args[1] ?? sx);
1416
+ break;
1417
+ }
1418
+ case "rotate": {
1419
+ const angle = (args[0] ?? 0) * Math.PI / 180;
1420
+ if (args.length >= 3) {
1421
+ const cx = args[1];
1422
+ const cy = args[2];
1423
+ ctx.translate(cx, cy);
1424
+ ctx.rotate(angle);
1425
+ ctx.translate(-cx, -cy);
1426
+ } else {
1427
+ ctx.rotate(angle);
1428
+ }
1429
+ break;
1430
+ }
1431
+ case "skewX":
1432
+ ctx.transform(
1433
+ 1,
1434
+ 0,
1435
+ Math.tan((args[0] ?? 0) * Math.PI / 180),
1436
+ 1,
1437
+ 0,
1438
+ 0
1439
+ );
1440
+ break;
1441
+ case "skewY":
1442
+ ctx.transform(
1443
+ 1,
1444
+ Math.tan((args[0] ?? 0) * Math.PI / 180),
1445
+ 0,
1446
+ 1,
1447
+ 0,
1448
+ 0
1449
+ );
1450
+ break;
1451
+ case "matrix":
1452
+ ctx.transform(
1453
+ args[0] ?? 1,
1454
+ args[1] ?? 0,
1455
+ args[2] ?? 0,
1456
+ args[3] ?? 1,
1457
+ args[4] ?? 0,
1458
+ args[5] ?? 0
1459
+ );
1460
+ break;
1461
+ }
1462
+ }
1048
1463
  }
1049
- function drawGroup(ctx, node, inheritedFill, color, defs = /* @__PURE__ */ new Map()) {
1464
+ function drawGroup(ctx, node, inheritedFill, color, defs = EMPTY_DEFS, vbW = 0, vbH = 0) {
1050
1465
  const children = normalizeChildren(node);
1051
1466
  if (children.length === 0) return;
1052
1467
  const merged = mergeStyleIntoProps(node.props);
1053
1468
  const groupFill = resolveCurrentColor(merged.fill, color) ?? inheritedFill;
1469
+ const transform = merged.transform;
1470
+ if (transform) {
1471
+ ctx.save();
1472
+ applySvgTransform(ctx, transform);
1473
+ }
1054
1474
  for (const child of children) {
1055
1475
  if (child != null && typeof child === "object") {
1056
- drawSvgChild(ctx, child, groupFill, color, defs);
1476
+ drawSvgChild(ctx, child, groupFill, color, defs, vbW, vbH);
1057
1477
  }
1058
1478
  }
1479
+ if (transform) {
1480
+ ctx.restore();
1481
+ }
1059
1482
  }
1060
1483
  function parsePoints(value) {
1061
1484
  if (!value) return [];
@@ -1066,26 +1489,36 @@ function parsePoints(value) {
1066
1489
  }
1067
1490
  return result;
1068
1491
  }
1069
- function applyFillAndStroke(ctx, props, path, inheritedFill, color) {
1492
+ function applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox) {
1070
1493
  const fill = resolveCurrentColor(props.fill, color) ?? inheritedFill;
1071
1494
  const fillRule = props.fillRule ?? props["fill-rule"];
1072
- const clipRule = props.clipRule ?? props["clip-rule"];
1073
- if (clipRule) {
1074
- ctx.clip(path, clipRule);
1075
- }
1076
- if (fill !== "none") {
1495
+ const fillRef = parseUrlRef(fill);
1496
+ if (fillRef) {
1497
+ const gradientDef = defs.gradients.get(fillRef);
1498
+ if (gradientDef) {
1499
+ fillWithSvgGradient(ctx, gradientDef, bbox, path, fillRule ?? "nonzero");
1500
+ }
1501
+ } else if (fill !== "none") {
1077
1502
  ctx.fillStyle = fill;
1078
1503
  ctx.fill(path, fillRule ?? "nonzero");
1079
1504
  }
1080
- applyStroke(ctx, props, path, color);
1505
+ applyStroke(ctx, props, path, color, defs, bbox);
1081
1506
  }
1082
- function applyStroke(ctx, props, path, color) {
1507
+ function applyStroke(ctx, props, path, color, defs, bbox) {
1083
1508
  const stroke = resolveCurrentColor(props.stroke, color);
1084
1509
  if (!stroke || stroke === "none") return;
1085
- ctx.strokeStyle = stroke;
1086
1510
  ctx.lineWidth = Number(props.strokeWidth ?? props["stroke-width"] ?? 1);
1087
1511
  ctx.lineCap = props.strokeLinecap ?? props["stroke-linecap"] ?? "butt";
1088
1512
  ctx.lineJoin = props.strokeLinejoin ?? props["stroke-linejoin"] ?? "miter";
1513
+ const strokeRef = parseUrlRef(stroke);
1514
+ if (strokeRef) {
1515
+ const gradientDef = defs.gradients.get(strokeRef);
1516
+ if (gradientDef) {
1517
+ strokeWithSvgGradient(ctx, gradientDef, bbox, path);
1518
+ return;
1519
+ }
1520
+ }
1521
+ ctx.strokeStyle = stroke;
1089
1522
  ctx.stroke(path);
1090
1523
  }
1091
1524
 
@@ -1529,12 +1962,12 @@ async function drawNode(ctx, node, parentX, parentY, debug, emojiStyle) {
1529
1962
  ctx.strokeRect(x, y, width, height);
1530
1963
  }
1531
1964
  if (node.textContent !== void 0 && node.textContent !== "") {
1532
- const paddingTop = toNumber(style.paddingTop);
1533
- const paddingLeft = toNumber(style.paddingLeft);
1534
- const paddingRight = toNumber(style.paddingRight);
1535
- const borderTopW = toNumber(style.borderTopWidth);
1536
- const borderLeftW = toNumber(style.borderLeftWidth);
1537
- 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);
1538
1971
  const contentX = x + paddingLeft + borderLeftW;
1539
1972
  const contentY = y + paddingTop + borderTopW;
1540
1973
  const contentWidth = width - paddingLeft - paddingRight - borderLeftW - borderRightW;
@@ -1555,10 +1988,10 @@ async function drawNode(ctx, node, parentX, parentY, debug, emojiStyle) {
1555
1988
  );
1556
1989
  }
1557
1990
  if (node.type === "img" && node.props.src) {
1558
- const paddingTop = toNumber(style.paddingTop);
1559
- const paddingLeft = toNumber(style.paddingLeft);
1560
- const paddingRight = toNumber(style.paddingRight);
1561
- 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);
1562
1995
  const imgX = x + paddingLeft;
1563
1996
  const imgY = y + paddingTop;
1564
1997
  const imgW = width - paddingLeft - paddingRight;
@@ -1724,12 +2157,12 @@ async function drawNodeInner(ctx, node, parentX, parentY, offsetX, offsetY, debu
1724
2157
  ctx.strokeRect(x, y, width, height);
1725
2158
  }
1726
2159
  if (node.textContent !== void 0 && node.textContent !== "") {
1727
- const paddingTop = toNumber(style.paddingTop);
1728
- const paddingLeft = toNumber(style.paddingLeft);
1729
- const paddingRight = toNumber(style.paddingRight);
1730
- const borderTopW = toNumber(style.borderTopWidth);
1731
- const borderLeftW = toNumber(style.borderLeftWidth);
1732
- 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);
1733
2166
  const contentX = x + paddingLeft + borderLeftW;
1734
2167
  const contentY = y + paddingTop + borderTopW;
1735
2168
  const contentWidth = width - paddingLeft - paddingRight - borderLeftW - borderRightW;
@@ -1750,10 +2183,10 @@ async function drawNodeInner(ctx, node, parentX, parentY, offsetX, offsetY, debu
1750
2183
  );
1751
2184
  }
1752
2185
  if (node.type === "img" && node.props.src) {
1753
- const paddingTop = toNumber(style.paddingTop);
1754
- const paddingLeft = toNumber(style.paddingLeft);
1755
- const paddingRight = toNumber(style.paddingRight);
1756
- 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);
1757
2190
  const imgX = x + paddingLeft;
1758
2191
  const imgY = y + paddingTop;
1759
2192
  const imgW = width - paddingLeft - paddingRight;
@@ -1858,6 +2291,14 @@ function toNumber(v) {
1858
2291
  const n = parseFloat(String(v));
1859
2292
  return isNaN(n) ? 0 : n;
1860
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
+ }
1861
2302
 
1862
2303
  // src/jsx/font.ts
1863
2304
  import { GlobalFonts } from "@napi-rs/canvas";
@@ -1978,6 +2419,20 @@ function expandStyle(raw, fontFamilies) {
1978
2419
  }
1979
2420
  delete style.border;
1980
2421
  }
2422
+ for (const side of SIDES) {
2423
+ const key = `border${side}`;
2424
+ if (style[key] !== void 0) {
2425
+ const parts = String(style[key]).split(/\s+/);
2426
+ const width = parseValue(parts[0]);
2427
+ const borderStyle = parts[1] ?? "solid";
2428
+ const color = parts[2] ?? "black";
2429
+ if (style[`${key}Width`] === void 0) style[`${key}Width`] = width;
2430
+ if (style[`${key}Style`] === void 0)
2431
+ style[`${key}Style`] = borderStyle;
2432
+ if (style[`${key}Color`] === void 0) style[`${key}Color`] = color;
2433
+ delete style[key];
2434
+ }
2435
+ }
1981
2436
  if (style.flex !== void 0) {
1982
2437
  const val = String(style.flex);
1983
2438
  const parts = val.split(/\s+/);
@@ -2019,6 +2474,19 @@ function expandStyle(raw, fontFamilies) {
2019
2474
  if (style.overflowX === void 0) style.overflowX = style.overflow;
2020
2475
  if (style.overflowY === void 0) style.overflowY = style.overflow;
2021
2476
  }
2477
+ if (style.textBox !== void 0) {
2478
+ const val = String(style.textBox);
2479
+ if (val === "normal" || val === "none") {
2480
+ style.textBoxTrim ??= "none";
2481
+ } else {
2482
+ const parts = val.split(/\s+/);
2483
+ style.textBoxTrim ??= parts[0];
2484
+ if (parts.length > 1) {
2485
+ style.textBoxEdge ??= parts.slice(1).join(" ");
2486
+ }
2487
+ }
2488
+ delete style.textBox;
2489
+ }
2022
2490
  if (typeof style.fontFamily === "string") {
2023
2491
  const families = style.fontFamily.split(",").map((s) => s.trim().replace(/^['"]|['"]$/g, "")).filter(Boolean);
2024
2492
  if (fontFamilies) {
@@ -2033,6 +2501,7 @@ function expandStyle(raw, fontFamilies) {
2033
2501
  }
2034
2502
 
2035
2503
  // src/jsx/style/compute.ts
2504
+ var ROOT_FONT_SIZE = 16;
2036
2505
  var DEFAULT_STYLE = {
2037
2506
  display: "flex",
2038
2507
  flexDirection: "row",
@@ -2042,7 +2511,7 @@ var DEFAULT_STYLE = {
2042
2511
  alignItems: "stretch",
2043
2512
  justifyContent: "flex-start",
2044
2513
  position: "relative",
2045
- fontSize: 16,
2514
+ fontSize: ROOT_FONT_SIZE,
2046
2515
  fontWeight: 400,
2047
2516
  fontStyle: "normal",
2048
2517
  color: "black",
@@ -2067,7 +2536,9 @@ var INHERITABLE_PROPS = [
2067
2536
  "letterSpacing",
2068
2537
  "whiteSpace",
2069
2538
  "wordBreak",
2070
- "textOverflow"
2539
+ "textOverflow",
2540
+ "textBoxTrim",
2541
+ "textBoxEdge"
2071
2542
  ];
2072
2543
  function resolveStyle(rawStyle, parentStyle) {
2073
2544
  const style = { ...rawStyle };
@@ -2077,8 +2548,15 @@ function resolveStyle(rawStyle, parentStyle) {
2077
2548
  }
2078
2549
  }
2079
2550
  if (typeof style.fontSize === "string") {
2080
- const parsed = parseFloat(style.fontSize);
2081
- style.fontSize = isNaN(parsed) ? parentStyle.fontSize : parsed;
2551
+ const parentFontSize = typeof parentStyle.fontSize === "number" ? parentStyle.fontSize : ROOT_FONT_SIZE;
2552
+ const resolved = resolveUnit(
2553
+ style.fontSize,
2554
+ 0,
2555
+ 0,
2556
+ parentFontSize,
2557
+ ROOT_FONT_SIZE
2558
+ );
2559
+ style.fontSize = typeof resolved === "number" ? resolved : parentFontSize;
2082
2560
  }
2083
2561
  if (typeof style.lineHeight === "string") {
2084
2562
  const str = style.lineHeight;
@@ -2092,9 +2570,16 @@ function resolveStyle(rawStyle, parentStyle) {
2092
2570
  }
2093
2571
  }
2094
2572
  if (typeof style.letterSpacing === "string") {
2095
- const parsed = parseFloat(style.letterSpacing);
2096
- if (!isNaN(parsed)) {
2097
- style.letterSpacing = parsed;
2573
+ const currentFontSize = typeof style.fontSize === "number" ? style.fontSize : typeof parentStyle.fontSize === "number" ? parentStyle.fontSize : ROOT_FONT_SIZE;
2574
+ const resolved = resolveUnit(
2575
+ style.letterSpacing,
2576
+ 0,
2577
+ 0,
2578
+ currentFontSize,
2579
+ ROOT_FONT_SIZE
2580
+ );
2581
+ if (typeof resolved === "number") {
2582
+ style.letterSpacing = resolved;
2098
2583
  }
2099
2584
  }
2100
2585
  return style;
@@ -2182,7 +2667,7 @@ function resolveUnit(value, viewportWidth, viewportHeight, fontSize, rootFontSiz
2182
2667
  }
2183
2668
  return value;
2184
2669
  }
2185
- function resolveUnits(style, viewportWidth, viewportHeight, rootFontSize = DEFAULT_STYLE.fontSize) {
2670
+ function resolveUnits(style, viewportWidth, viewportHeight, rootFontSize = ROOT_FONT_SIZE) {
2186
2671
  const fontSize = typeof style.fontSize === "number" ? style.fontSize : rootFontSize;
2187
2672
  for (const prop of DIMENSION_PROPS) {
2188
2673
  const value = style[prop];
@@ -2533,15 +3018,15 @@ async function buildNode(element, parentStyle, yogaNode, viewportWidth, viewport
2533
3018
  if (parts.length === 4) {
2534
3019
  const [, , vbW, vbH] = parts;
2535
3020
  if (vbW > 0 && vbH > 0) {
2536
- const w = typeof style.width === "number" ? style.width : void 0;
2537
- const h = typeof style.height === "number" ? style.height : void 0;
2538
- if (w !== void 0 && h === void 0) {
2539
- style.height = w * (vbH / vbW);
2540
- } else if (h !== void 0 && w === void 0) {
2541
- style.width = h * (vbW / vbH);
2542
- } else if (w === void 0 && h === void 0) {
3021
+ const wSet = style.width !== void 0;
3022
+ const hSet = style.height !== void 0;
3023
+ if (!wSet && !hSet) {
2543
3024
  style.width = vbW;
2544
3025
  style.height = vbH;
3026
+ } else if (!hSet && typeof style.width === "number") {
3027
+ style.height = style.width * (vbH / vbW);
3028
+ } else if (!wSet && typeof style.height === "number") {
3029
+ style.width = style.height * (vbW / vbH);
2545
3030
  }
2546
3031
  }
2547
3032
  }
@@ -2562,12 +3047,12 @@ async function buildNode(element, parentStyle, yogaNode, viewportWidth, viewport
2562
3047
  const image = await loadImage4(src);
2563
3048
  const naturalW = image.width;
2564
3049
  const naturalH = image.height;
2565
- const w = typeof style.width === "number" ? style.width : void 0;
2566
- const h = typeof style.height === "number" ? style.height : void 0;
2567
- if (w !== void 0 && h === void 0 && naturalW > 0) {
2568
- style.height = w * (naturalH / naturalW);
2569
- } else if (h !== void 0 && w === void 0 && naturalH > 0) {
2570
- style.width = h * (naturalW / naturalH);
3050
+ const wSet = style.width !== void 0;
3051
+ const hSet = style.height !== void 0;
3052
+ if (wSet && !hSet && typeof style.width === "number" && naturalW > 0) {
3053
+ style.height = style.width * (naturalH / naturalW);
3054
+ } else if (hSet && !wSet && typeof style.height === "number" && naturalH > 0) {
3055
+ style.width = style.height * (naturalW / naturalH);
2571
3056
  }
2572
3057
  props.__loadedImage = image;
2573
3058
  } catch {