@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 +609 -124
- package/dist/index.js.map +1 -1
- package/package.json +6 -2
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 =
|
|
718
|
-
const rw =
|
|
719
|
-
const bw =
|
|
720
|
-
const lw =
|
|
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
|
|
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
|
|
830
|
-
|
|
831
|
-
if (
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
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
|
|
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
|
|
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 =
|
|
861
|
-
const cy =
|
|
862
|
-
const r =
|
|
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 =
|
|
870
|
-
const ry =
|
|
871
|
-
const w =
|
|
872
|
-
const h =
|
|
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 =
|
|
880
|
-
const cy =
|
|
881
|
-
const rx =
|
|
882
|
-
const ry =
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
991
|
-
const cy =
|
|
992
|
-
const r =
|
|
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
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
const
|
|
1001
|
-
const
|
|
1002
|
-
const
|
|
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
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
const
|
|
1011
|
-
const
|
|
1012
|
-
const
|
|
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
|
-
|
|
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 =
|
|
1020
|
-
const cy =
|
|
1021
|
-
const rx =
|
|
1022
|
-
const ry =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
1073
|
-
if (
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
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 =
|
|
1533
|
-
const paddingLeft =
|
|
1534
|
-
const paddingRight =
|
|
1535
|
-
const borderTopW =
|
|
1536
|
-
const borderLeftW =
|
|
1537
|
-
const borderRightW =
|
|
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 =
|
|
1559
|
-
const paddingLeft =
|
|
1560
|
-
const paddingRight =
|
|
1561
|
-
const 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 =
|
|
1728
|
-
const paddingLeft =
|
|
1729
|
-
const paddingRight =
|
|
1730
|
-
const borderTopW =
|
|
1731
|
-
const borderLeftW =
|
|
1732
|
-
const borderRightW =
|
|
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 =
|
|
1754
|
-
const paddingLeft =
|
|
1755
|
-
const paddingRight =
|
|
1756
|
-
const 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:
|
|
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
|
|
2081
|
-
|
|
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
|
|
2096
|
-
|
|
2097
|
-
style.letterSpacing
|
|
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 =
|
|
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
|
|
2537
|
-
const
|
|
2538
|
-
if (
|
|
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
|
|
2566
|
-
const
|
|
2567
|
-
if (
|
|
2568
|
-
style.height =
|
|
2569
|
-
} else if (
|
|
2570
|
-
style.width =
|
|
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 {
|