@effing/canvas 0.18.6 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +517 -51
- 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,
|
|
@@ -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,6 +917,259 @@ function normalizeChildren(node) {
|
|
|
848
917
|
if (raw == null) return [];
|
|
849
918
|
return Array.isArray(raw) ? raw : [raw];
|
|
850
919
|
}
|
|
920
|
+
function parseFrac(value, fallback) {
|
|
921
|
+
if (value == null) return fallback;
|
|
922
|
+
const s = String(value);
|
|
923
|
+
if (s.endsWith("%")) return parseFloat(s) / 100;
|
|
924
|
+
return Number(s);
|
|
925
|
+
}
|
|
926
|
+
function applyOpacity(color, opacity) {
|
|
927
|
+
if (opacity >= 1) return color;
|
|
928
|
+
const m = color.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i);
|
|
929
|
+
if (m)
|
|
930
|
+
return `rgba(${parseInt(m[1], 16)}, ${parseInt(m[2], 16)}, ${parseInt(m[3], 16)}, ${opacity})`;
|
|
931
|
+
return color;
|
|
932
|
+
}
|
|
933
|
+
function addGradientStops(gradient, def) {
|
|
934
|
+
for (const stop of normalizeChildren(def)) {
|
|
935
|
+
if (stop.type !== "stop") continue;
|
|
936
|
+
const sp = stop.props;
|
|
937
|
+
const offsetRaw = sp.offset ?? 0;
|
|
938
|
+
const offset = typeof offsetRaw === "string" && offsetRaw.endsWith("%") ? parseFloat(offsetRaw) / 100 : Number(offsetRaw);
|
|
939
|
+
const stopColor = sp.stopColor ?? sp["stop-color"] ?? "black";
|
|
940
|
+
const stopOpacity = Number(sp.stopOpacity ?? sp["stop-opacity"] ?? 1);
|
|
941
|
+
gradient.addColorStop(offset, applyOpacity(stopColor, stopOpacity));
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
function fillWithSvgGradient(ctx, def, bbox, path, fillRule) {
|
|
945
|
+
const props = def.props;
|
|
946
|
+
if (def.type === "linearGradient") {
|
|
947
|
+
const x1 = bbox.x + parseFrac(props.x1, 0) * bbox.width;
|
|
948
|
+
const y1 = bbox.y + parseFrac(props.y1, 0) * bbox.height;
|
|
949
|
+
const x2 = bbox.x + parseFrac(props.x2, 1) * bbox.width;
|
|
950
|
+
const y2 = bbox.y + parseFrac(props.y2, 0) * bbox.height;
|
|
951
|
+
const gradient = ctx.createLinearGradient(x1, y1, x2, y2);
|
|
952
|
+
addGradientStops(gradient, def);
|
|
953
|
+
ctx.fillStyle = gradient;
|
|
954
|
+
ctx.fill(path, fillRule);
|
|
955
|
+
return true;
|
|
956
|
+
}
|
|
957
|
+
if (def.type === "radialGradient") {
|
|
958
|
+
const cxF = parseFrac(props.cx, 0.5);
|
|
959
|
+
const cyF = parseFrac(props.cy, 0.5);
|
|
960
|
+
const rF = parseFrac(props.r, 0.5);
|
|
961
|
+
const fxF = parseFrac(props.fx, cxF);
|
|
962
|
+
const fyF = parseFrac(props.fy, cyF);
|
|
963
|
+
ctx.save();
|
|
964
|
+
ctx.translate(bbox.x, bbox.y);
|
|
965
|
+
ctx.scale(bbox.width, bbox.height);
|
|
966
|
+
const gradient = ctx.createRadialGradient(fxF, fyF, 0, cxF, cyF, rF);
|
|
967
|
+
addGradientStops(gradient, def);
|
|
968
|
+
ctx.fillStyle = gradient;
|
|
969
|
+
const unitPath = new Path2D();
|
|
970
|
+
const invTransform = {
|
|
971
|
+
a: 1 / bbox.width,
|
|
972
|
+
b: 0,
|
|
973
|
+
c: 0,
|
|
974
|
+
d: 1 / bbox.height,
|
|
975
|
+
e: -bbox.x / bbox.width,
|
|
976
|
+
f: -bbox.y / bbox.height
|
|
977
|
+
};
|
|
978
|
+
unitPath.addPath(path, invTransform);
|
|
979
|
+
ctx.fill(unitPath, fillRule);
|
|
980
|
+
ctx.restore();
|
|
981
|
+
return true;
|
|
982
|
+
}
|
|
983
|
+
return false;
|
|
984
|
+
}
|
|
985
|
+
function strokeWithSvgGradient(ctx, def, bbox, path) {
|
|
986
|
+
const props = def.props;
|
|
987
|
+
if (def.type === "linearGradient") {
|
|
988
|
+
const x1 = bbox.x + parseFrac(props.x1, 0) * bbox.width;
|
|
989
|
+
const y1 = bbox.y + parseFrac(props.y1, 0) * bbox.height;
|
|
990
|
+
const x2 = bbox.x + parseFrac(props.x2, 1) * bbox.width;
|
|
991
|
+
const y2 = bbox.y + parseFrac(props.y2, 0) * bbox.height;
|
|
992
|
+
const gradient = ctx.createLinearGradient(x1, y1, x2, y2);
|
|
993
|
+
addGradientStops(gradient, def);
|
|
994
|
+
ctx.strokeStyle = gradient;
|
|
995
|
+
ctx.stroke(path);
|
|
996
|
+
return true;
|
|
997
|
+
}
|
|
998
|
+
if (def.type === "radialGradient") {
|
|
999
|
+
const cxAbs = bbox.x + parseFrac(props.cx, 0.5) * bbox.width;
|
|
1000
|
+
const cyAbs = bbox.y + parseFrac(props.cy, 0.5) * bbox.height;
|
|
1001
|
+
const rAbs = parseFrac(props.r, 0.5) * Math.sqrt(bbox.width * bbox.height);
|
|
1002
|
+
const fxAbs = bbox.x + parseFrac(props.fx, 0.5) * bbox.width;
|
|
1003
|
+
const fyAbs = bbox.y + parseFrac(props.fy, 0.5) * bbox.height;
|
|
1004
|
+
const gradient = ctx.createRadialGradient(
|
|
1005
|
+
fxAbs,
|
|
1006
|
+
fyAbs,
|
|
1007
|
+
0,
|
|
1008
|
+
cxAbs,
|
|
1009
|
+
cyAbs,
|
|
1010
|
+
rAbs
|
|
1011
|
+
);
|
|
1012
|
+
addGradientStops(gradient, def);
|
|
1013
|
+
ctx.strokeStyle = gradient;
|
|
1014
|
+
ctx.stroke(path);
|
|
1015
|
+
return true;
|
|
1016
|
+
}
|
|
1017
|
+
return false;
|
|
1018
|
+
}
|
|
1019
|
+
function pathBBox(d) {
|
|
1020
|
+
let minX = Infinity;
|
|
1021
|
+
let minY = Infinity;
|
|
1022
|
+
let maxX = -Infinity;
|
|
1023
|
+
let maxY = -Infinity;
|
|
1024
|
+
let cx = 0;
|
|
1025
|
+
let cy = 0;
|
|
1026
|
+
const update = (x, y) => {
|
|
1027
|
+
if (x < minX) minX = x;
|
|
1028
|
+
if (x > maxX) maxX = x;
|
|
1029
|
+
if (y < minY) minY = y;
|
|
1030
|
+
if (y > maxY) maxY = y;
|
|
1031
|
+
};
|
|
1032
|
+
const tokens = d.match(/[a-zA-Z]|[-+]?(?:\d+\.?\d*|\.\d+)(?:[eE][-+]?\d+)?/g);
|
|
1033
|
+
if (!tokens) return { x: 0, y: 0, width: 0, height: 0 };
|
|
1034
|
+
let cmd = "";
|
|
1035
|
+
let i = 0;
|
|
1036
|
+
const num = () => Number(tokens[i++]);
|
|
1037
|
+
while (i < tokens.length) {
|
|
1038
|
+
const t = tokens[i];
|
|
1039
|
+
if (/[a-zA-Z]/.test(t)) {
|
|
1040
|
+
cmd = t;
|
|
1041
|
+
i++;
|
|
1042
|
+
}
|
|
1043
|
+
switch (cmd) {
|
|
1044
|
+
case "M":
|
|
1045
|
+
cx = num();
|
|
1046
|
+
cy = num();
|
|
1047
|
+
update(cx, cy);
|
|
1048
|
+
cmd = "L";
|
|
1049
|
+
break;
|
|
1050
|
+
case "m":
|
|
1051
|
+
cx += num();
|
|
1052
|
+
cy += num();
|
|
1053
|
+
update(cx, cy);
|
|
1054
|
+
cmd = "l";
|
|
1055
|
+
break;
|
|
1056
|
+
case "L":
|
|
1057
|
+
cx = num();
|
|
1058
|
+
cy = num();
|
|
1059
|
+
update(cx, cy);
|
|
1060
|
+
break;
|
|
1061
|
+
case "l":
|
|
1062
|
+
cx += num();
|
|
1063
|
+
cy += num();
|
|
1064
|
+
update(cx, cy);
|
|
1065
|
+
break;
|
|
1066
|
+
case "H":
|
|
1067
|
+
cx = num();
|
|
1068
|
+
update(cx, cy);
|
|
1069
|
+
break;
|
|
1070
|
+
case "h":
|
|
1071
|
+
cx += num();
|
|
1072
|
+
update(cx, cy);
|
|
1073
|
+
break;
|
|
1074
|
+
case "V":
|
|
1075
|
+
cy = num();
|
|
1076
|
+
update(cx, cy);
|
|
1077
|
+
break;
|
|
1078
|
+
case "v":
|
|
1079
|
+
cy += num();
|
|
1080
|
+
update(cx, cy);
|
|
1081
|
+
break;
|
|
1082
|
+
case "C": {
|
|
1083
|
+
for (let j = 0; j < 3; j++) {
|
|
1084
|
+
const px = num();
|
|
1085
|
+
const py = num();
|
|
1086
|
+
update(px, py);
|
|
1087
|
+
}
|
|
1088
|
+
cx = Number(tokens[i - 2]);
|
|
1089
|
+
cy = Number(tokens[i - 1]);
|
|
1090
|
+
break;
|
|
1091
|
+
}
|
|
1092
|
+
case "c": {
|
|
1093
|
+
for (let j = 0; j < 3; j++) {
|
|
1094
|
+
const dx = num();
|
|
1095
|
+
const dy = num();
|
|
1096
|
+
update(cx + dx, cy + dy);
|
|
1097
|
+
if (j === 2) {
|
|
1098
|
+
cx += dx;
|
|
1099
|
+
cy += dy;
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
break;
|
|
1103
|
+
}
|
|
1104
|
+
case "Q": {
|
|
1105
|
+
for (let j = 0; j < 2; j++) {
|
|
1106
|
+
const px = num();
|
|
1107
|
+
const py = num();
|
|
1108
|
+
update(px, py);
|
|
1109
|
+
}
|
|
1110
|
+
cx = Number(tokens[i - 2]);
|
|
1111
|
+
cy = Number(tokens[i - 1]);
|
|
1112
|
+
break;
|
|
1113
|
+
}
|
|
1114
|
+
case "q": {
|
|
1115
|
+
for (let j = 0; j < 2; j++) {
|
|
1116
|
+
const dx = num();
|
|
1117
|
+
const dy = num();
|
|
1118
|
+
update(cx + dx, cy + dy);
|
|
1119
|
+
if (j === 1) {
|
|
1120
|
+
cx += dx;
|
|
1121
|
+
cy += dy;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
break;
|
|
1125
|
+
}
|
|
1126
|
+
case "A": {
|
|
1127
|
+
num();
|
|
1128
|
+
num();
|
|
1129
|
+
num();
|
|
1130
|
+
num();
|
|
1131
|
+
num();
|
|
1132
|
+
cx = num();
|
|
1133
|
+
cy = num();
|
|
1134
|
+
update(cx, cy);
|
|
1135
|
+
break;
|
|
1136
|
+
}
|
|
1137
|
+
case "a": {
|
|
1138
|
+
num();
|
|
1139
|
+
num();
|
|
1140
|
+
num();
|
|
1141
|
+
num();
|
|
1142
|
+
num();
|
|
1143
|
+
cx += num();
|
|
1144
|
+
cy += num();
|
|
1145
|
+
update(cx, cy);
|
|
1146
|
+
break;
|
|
1147
|
+
}
|
|
1148
|
+
case "Z":
|
|
1149
|
+
case "z":
|
|
1150
|
+
break;
|
|
1151
|
+
default:
|
|
1152
|
+
i++;
|
|
1153
|
+
break;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
if (!isFinite(minX)) return { x: 0, y: 0, width: 0, height: 0 };
|
|
1157
|
+
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
|
|
1158
|
+
}
|
|
1159
|
+
function pointsBBox(points) {
|
|
1160
|
+
let minX = Infinity;
|
|
1161
|
+
let minY = Infinity;
|
|
1162
|
+
let maxX = -Infinity;
|
|
1163
|
+
let maxY = -Infinity;
|
|
1164
|
+
for (const [x, y] of points) {
|
|
1165
|
+
if (x < minX) minX = x;
|
|
1166
|
+
if (x > maxX) maxX = x;
|
|
1167
|
+
if (y < minY) minY = y;
|
|
1168
|
+
if (y > maxY) maxY = y;
|
|
1169
|
+
}
|
|
1170
|
+
if (!isFinite(minX)) return { x: 0, y: 0, width: 0, height: 0 };
|
|
1171
|
+
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
|
|
1172
|
+
}
|
|
851
1173
|
function buildPath(child) {
|
|
852
1174
|
const props = mergeStyleIntoProps(child.props);
|
|
853
1175
|
switch (child.type) {
|
|
@@ -943,36 +1265,40 @@ function drawSvgContainer(ctx, node, x, y, width, height) {
|
|
|
943
1265
|
}
|
|
944
1266
|
ctx.restore();
|
|
945
1267
|
}
|
|
946
|
-
|
|
1268
|
+
var EMPTY_DEFS = {
|
|
1269
|
+
clips: /* @__PURE__ */ new Map(),
|
|
1270
|
+
gradients: /* @__PURE__ */ new Map()
|
|
1271
|
+
};
|
|
1272
|
+
function drawSvgChild(ctx, child, inheritedFill, color, defs = EMPTY_DEFS) {
|
|
947
1273
|
const { type } = child;
|
|
948
1274
|
const props = mergeStyleIntoProps(child.props);
|
|
949
1275
|
if (type === "defs" || type === "clipPath") return;
|
|
950
1276
|
const clipRef = parseUrlRef(props.clipPath ?? props["clip-path"]);
|
|
951
|
-
const clipShapes = clipRef ? defs.get(clipRef) : void 0;
|
|
1277
|
+
const clipShapes = clipRef ? defs.clips.get(clipRef) : void 0;
|
|
952
1278
|
const clipPath = clipShapes ? buildClipPath(clipShapes) : void 0;
|
|
953
1279
|
if (clipPath) ctx.save();
|
|
954
1280
|
if (clipPath) ctx.clip(clipPath);
|
|
955
1281
|
switch (type) {
|
|
956
1282
|
case "path":
|
|
957
|
-
drawPath(ctx, props, inheritedFill, color);
|
|
1283
|
+
drawPath(ctx, props, inheritedFill, color, defs);
|
|
958
1284
|
break;
|
|
959
1285
|
case "circle":
|
|
960
|
-
drawCircle(ctx, props, inheritedFill, color);
|
|
1286
|
+
drawCircle(ctx, props, inheritedFill, color, defs);
|
|
961
1287
|
break;
|
|
962
1288
|
case "rect":
|
|
963
|
-
drawSvgRect(ctx, props, inheritedFill, color);
|
|
1289
|
+
drawSvgRect(ctx, props, inheritedFill, color, defs);
|
|
964
1290
|
break;
|
|
965
1291
|
case "line":
|
|
966
|
-
drawLine(ctx, props, color);
|
|
1292
|
+
drawLine(ctx, props, color, defs);
|
|
967
1293
|
break;
|
|
968
1294
|
case "ellipse":
|
|
969
|
-
drawEllipse(ctx, props, inheritedFill, color);
|
|
1295
|
+
drawEllipse(ctx, props, inheritedFill, color, defs);
|
|
970
1296
|
break;
|
|
971
1297
|
case "polygon":
|
|
972
|
-
drawPolygon(ctx, props, inheritedFill, color);
|
|
1298
|
+
drawPolygon(ctx, props, inheritedFill, color, defs);
|
|
973
1299
|
break;
|
|
974
1300
|
case "polyline":
|
|
975
|
-
drawPolyline(ctx, props, inheritedFill, color);
|
|
1301
|
+
drawPolyline(ctx, props, inheritedFill, color, defs);
|
|
976
1302
|
break;
|
|
977
1303
|
case "g":
|
|
978
1304
|
drawGroup(ctx, child, inheritedFill, color, defs);
|
|
@@ -980,22 +1306,24 @@ function drawSvgChild(ctx, child, inheritedFill, color, defs = /* @__PURE__ */ n
|
|
|
980
1306
|
}
|
|
981
1307
|
if (clipPath) ctx.restore();
|
|
982
1308
|
}
|
|
983
|
-
function drawPath(ctx, props, inheritedFill, color) {
|
|
1309
|
+
function drawPath(ctx, props, inheritedFill, color, defs) {
|
|
984
1310
|
const d = props.d;
|
|
985
1311
|
if (!d) return;
|
|
986
1312
|
const path = new Path2D(d);
|
|
987
|
-
|
|
1313
|
+
const bbox = pathBBox(d);
|
|
1314
|
+
applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox);
|
|
988
1315
|
}
|
|
989
|
-
function drawCircle(ctx, props, inheritedFill, color) {
|
|
1316
|
+
function drawCircle(ctx, props, inheritedFill, color, defs) {
|
|
990
1317
|
const cx = Number(props.cx ?? 0);
|
|
991
1318
|
const cy = Number(props.cy ?? 0);
|
|
992
1319
|
const r = Number(props.r ?? 0);
|
|
993
1320
|
if (r <= 0) return;
|
|
994
1321
|
const path = new Path2D();
|
|
995
1322
|
path.arc(cx, cy, r, 0, Math.PI * 2);
|
|
996
|
-
|
|
1323
|
+
const bbox = { x: cx - r, y: cy - r, width: 2 * r, height: 2 * r };
|
|
1324
|
+
applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox);
|
|
997
1325
|
}
|
|
998
|
-
function drawSvgRect(ctx, props, inheritedFill, color) {
|
|
1326
|
+
function drawSvgRect(ctx, props, inheritedFill, color, defs) {
|
|
999
1327
|
const rx = Number(props.x ?? 0);
|
|
1000
1328
|
const ry = Number(props.y ?? 0);
|
|
1001
1329
|
const w = Number(props.width ?? 0);
|
|
@@ -1003,9 +1331,10 @@ function drawSvgRect(ctx, props, inheritedFill, color) {
|
|
|
1003
1331
|
if (w <= 0 || h <= 0) return;
|
|
1004
1332
|
const path = new Path2D();
|
|
1005
1333
|
path.rect(rx, ry, w, h);
|
|
1006
|
-
|
|
1334
|
+
const bbox = { x: rx, y: ry, width: w, height: h };
|
|
1335
|
+
applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox);
|
|
1007
1336
|
}
|
|
1008
|
-
function drawLine(ctx, props, color) {
|
|
1337
|
+
function drawLine(ctx, props, color, defs) {
|
|
1009
1338
|
const x1 = Number(props.x1 ?? 0);
|
|
1010
1339
|
const y1 = Number(props.y1 ?? 0);
|
|
1011
1340
|
const x2 = Number(props.x2 ?? 0);
|
|
@@ -1013,9 +1342,15 @@ function drawLine(ctx, props, color) {
|
|
|
1013
1342
|
const path = new Path2D();
|
|
1014
1343
|
path.moveTo(x1, y1);
|
|
1015
1344
|
path.lineTo(x2, y2);
|
|
1016
|
-
|
|
1345
|
+
const bbox = {
|
|
1346
|
+
x: Math.min(x1, x2),
|
|
1347
|
+
y: Math.min(y1, y2),
|
|
1348
|
+
width: Math.abs(x2 - x1),
|
|
1349
|
+
height: Math.abs(y2 - y1)
|
|
1350
|
+
};
|
|
1351
|
+
applyStroke(ctx, props, path, color, defs, bbox);
|
|
1017
1352
|
}
|
|
1018
|
-
function drawEllipse(ctx, props, inheritedFill, color) {
|
|
1353
|
+
function drawEllipse(ctx, props, inheritedFill, color, defs) {
|
|
1019
1354
|
const cx = Number(props.cx ?? 0);
|
|
1020
1355
|
const cy = Number(props.cy ?? 0);
|
|
1021
1356
|
const rx = Number(props.rx ?? 0);
|
|
@@ -1023,9 +1358,15 @@ function drawEllipse(ctx, props, inheritedFill, color) {
|
|
|
1023
1358
|
if (rx <= 0 || ry <= 0) return;
|
|
1024
1359
|
const path = new Path2D();
|
|
1025
1360
|
path.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
|
|
1026
|
-
|
|
1361
|
+
const bbox = {
|
|
1362
|
+
x: cx - rx,
|
|
1363
|
+
y: cy - ry,
|
|
1364
|
+
width: 2 * rx,
|
|
1365
|
+
height: 2 * ry
|
|
1366
|
+
};
|
|
1367
|
+
applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox);
|
|
1027
1368
|
}
|
|
1028
|
-
function drawPolygon(ctx, props, inheritedFill, color) {
|
|
1369
|
+
function drawPolygon(ctx, props, inheritedFill, color, defs) {
|
|
1029
1370
|
const points = parsePoints(props.points);
|
|
1030
1371
|
if (points.length < 2) return;
|
|
1031
1372
|
const path = new Path2D();
|
|
@@ -1034,9 +1375,10 @@ function drawPolygon(ctx, props, inheritedFill, color) {
|
|
|
1034
1375
|
path.lineTo(points[i][0], points[i][1]);
|
|
1035
1376
|
}
|
|
1036
1377
|
path.closePath();
|
|
1037
|
-
|
|
1378
|
+
const bbox = pointsBBox(points);
|
|
1379
|
+
applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox);
|
|
1038
1380
|
}
|
|
1039
|
-
function drawPolyline(ctx, props, inheritedFill, color) {
|
|
1381
|
+
function drawPolyline(ctx, props, inheritedFill, color, defs) {
|
|
1040
1382
|
const points = parsePoints(props.points);
|
|
1041
1383
|
if (points.length < 2) return;
|
|
1042
1384
|
const path = new Path2D();
|
|
@@ -1044,18 +1386,88 @@ function drawPolyline(ctx, props, inheritedFill, color) {
|
|
|
1044
1386
|
for (let i = 1; i < points.length; i++) {
|
|
1045
1387
|
path.lineTo(points[i][0], points[i][1]);
|
|
1046
1388
|
}
|
|
1047
|
-
|
|
1389
|
+
const bbox = pointsBBox(points);
|
|
1390
|
+
applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox);
|
|
1048
1391
|
}
|
|
1049
|
-
function
|
|
1392
|
+
function applySvgTransform(ctx, transform) {
|
|
1393
|
+
const re = /\b(translate|scale|rotate|skewX|skewY|matrix)\s*\(([^)]*)\)/g;
|
|
1394
|
+
let match;
|
|
1395
|
+
while ((match = re.exec(transform)) !== null) {
|
|
1396
|
+
const fn = match[1];
|
|
1397
|
+
const args = match[2].split(/[\s,]+/).filter(Boolean).map(Number);
|
|
1398
|
+
switch (fn) {
|
|
1399
|
+
case "translate":
|
|
1400
|
+
ctx.translate(args[0] ?? 0, args[1] ?? 0);
|
|
1401
|
+
break;
|
|
1402
|
+
case "scale": {
|
|
1403
|
+
const sx = args[0] ?? 1;
|
|
1404
|
+
ctx.scale(sx, args[1] ?? sx);
|
|
1405
|
+
break;
|
|
1406
|
+
}
|
|
1407
|
+
case "rotate": {
|
|
1408
|
+
const angle = (args[0] ?? 0) * Math.PI / 180;
|
|
1409
|
+
if (args.length >= 3) {
|
|
1410
|
+
const cx = args[1];
|
|
1411
|
+
const cy = args[2];
|
|
1412
|
+
ctx.translate(cx, cy);
|
|
1413
|
+
ctx.rotate(angle);
|
|
1414
|
+
ctx.translate(-cx, -cy);
|
|
1415
|
+
} else {
|
|
1416
|
+
ctx.rotate(angle);
|
|
1417
|
+
}
|
|
1418
|
+
break;
|
|
1419
|
+
}
|
|
1420
|
+
case "skewX":
|
|
1421
|
+
ctx.transform(
|
|
1422
|
+
1,
|
|
1423
|
+
0,
|
|
1424
|
+
Math.tan((args[0] ?? 0) * Math.PI / 180),
|
|
1425
|
+
1,
|
|
1426
|
+
0,
|
|
1427
|
+
0
|
|
1428
|
+
);
|
|
1429
|
+
break;
|
|
1430
|
+
case "skewY":
|
|
1431
|
+
ctx.transform(
|
|
1432
|
+
1,
|
|
1433
|
+
Math.tan((args[0] ?? 0) * Math.PI / 180),
|
|
1434
|
+
0,
|
|
1435
|
+
1,
|
|
1436
|
+
0,
|
|
1437
|
+
0
|
|
1438
|
+
);
|
|
1439
|
+
break;
|
|
1440
|
+
case "matrix":
|
|
1441
|
+
ctx.transform(
|
|
1442
|
+
args[0] ?? 1,
|
|
1443
|
+
args[1] ?? 0,
|
|
1444
|
+
args[2] ?? 0,
|
|
1445
|
+
args[3] ?? 1,
|
|
1446
|
+
args[4] ?? 0,
|
|
1447
|
+
args[5] ?? 0
|
|
1448
|
+
);
|
|
1449
|
+
break;
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
function drawGroup(ctx, node, inheritedFill, color, defs = EMPTY_DEFS) {
|
|
1050
1454
|
const children = normalizeChildren(node);
|
|
1051
1455
|
if (children.length === 0) return;
|
|
1052
1456
|
const merged = mergeStyleIntoProps(node.props);
|
|
1053
1457
|
const groupFill = resolveCurrentColor(merged.fill, color) ?? inheritedFill;
|
|
1458
|
+
const transform = merged.transform;
|
|
1459
|
+
if (transform) {
|
|
1460
|
+
ctx.save();
|
|
1461
|
+
applySvgTransform(ctx, transform);
|
|
1462
|
+
}
|
|
1054
1463
|
for (const child of children) {
|
|
1055
1464
|
if (child != null && typeof child === "object") {
|
|
1056
1465
|
drawSvgChild(ctx, child, groupFill, color, defs);
|
|
1057
1466
|
}
|
|
1058
1467
|
}
|
|
1468
|
+
if (transform) {
|
|
1469
|
+
ctx.restore();
|
|
1470
|
+
}
|
|
1059
1471
|
}
|
|
1060
1472
|
function parsePoints(value) {
|
|
1061
1473
|
if (!value) return [];
|
|
@@ -1066,26 +1478,36 @@ function parsePoints(value) {
|
|
|
1066
1478
|
}
|
|
1067
1479
|
return result;
|
|
1068
1480
|
}
|
|
1069
|
-
function applyFillAndStroke(ctx, props, path, inheritedFill, color) {
|
|
1481
|
+
function applyFillAndStroke(ctx, props, path, inheritedFill, color, defs, bbox) {
|
|
1070
1482
|
const fill = resolveCurrentColor(props.fill, color) ?? inheritedFill;
|
|
1071
1483
|
const fillRule = props.fillRule ?? props["fill-rule"];
|
|
1072
|
-
const
|
|
1073
|
-
if (
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1484
|
+
const fillRef = parseUrlRef(fill);
|
|
1485
|
+
if (fillRef) {
|
|
1486
|
+
const gradientDef = defs.gradients.get(fillRef);
|
|
1487
|
+
if (gradientDef) {
|
|
1488
|
+
fillWithSvgGradient(ctx, gradientDef, bbox, path, fillRule ?? "nonzero");
|
|
1489
|
+
}
|
|
1490
|
+
} else if (fill !== "none") {
|
|
1077
1491
|
ctx.fillStyle = fill;
|
|
1078
1492
|
ctx.fill(path, fillRule ?? "nonzero");
|
|
1079
1493
|
}
|
|
1080
|
-
applyStroke(ctx, props, path, color);
|
|
1494
|
+
applyStroke(ctx, props, path, color, defs, bbox);
|
|
1081
1495
|
}
|
|
1082
|
-
function applyStroke(ctx, props, path, color) {
|
|
1496
|
+
function applyStroke(ctx, props, path, color, defs, bbox) {
|
|
1083
1497
|
const stroke = resolveCurrentColor(props.stroke, color);
|
|
1084
1498
|
if (!stroke || stroke === "none") return;
|
|
1085
|
-
ctx.strokeStyle = stroke;
|
|
1086
1499
|
ctx.lineWidth = Number(props.strokeWidth ?? props["stroke-width"] ?? 1);
|
|
1087
1500
|
ctx.lineCap = props.strokeLinecap ?? props["stroke-linecap"] ?? "butt";
|
|
1088
1501
|
ctx.lineJoin = props.strokeLinejoin ?? props["stroke-linejoin"] ?? "miter";
|
|
1502
|
+
const strokeRef = parseUrlRef(stroke);
|
|
1503
|
+
if (strokeRef) {
|
|
1504
|
+
const gradientDef = defs.gradients.get(strokeRef);
|
|
1505
|
+
if (gradientDef) {
|
|
1506
|
+
strokeWithSvgGradient(ctx, gradientDef, bbox, path);
|
|
1507
|
+
return;
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
ctx.strokeStyle = stroke;
|
|
1089
1511
|
ctx.stroke(path);
|
|
1090
1512
|
}
|
|
1091
1513
|
|
|
@@ -1978,6 +2400,20 @@ function expandStyle(raw, fontFamilies) {
|
|
|
1978
2400
|
}
|
|
1979
2401
|
delete style.border;
|
|
1980
2402
|
}
|
|
2403
|
+
for (const side of SIDES) {
|
|
2404
|
+
const key = `border${side}`;
|
|
2405
|
+
if (style[key] !== void 0) {
|
|
2406
|
+
const parts = String(style[key]).split(/\s+/);
|
|
2407
|
+
const width = parseValue(parts[0]);
|
|
2408
|
+
const borderStyle = parts[1] ?? "solid";
|
|
2409
|
+
const color = parts[2] ?? "black";
|
|
2410
|
+
if (style[`${key}Width`] === void 0) style[`${key}Width`] = width;
|
|
2411
|
+
if (style[`${key}Style`] === void 0)
|
|
2412
|
+
style[`${key}Style`] = borderStyle;
|
|
2413
|
+
if (style[`${key}Color`] === void 0) style[`${key}Color`] = color;
|
|
2414
|
+
delete style[key];
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
1981
2417
|
if (style.flex !== void 0) {
|
|
1982
2418
|
const val = String(style.flex);
|
|
1983
2419
|
const parts = val.split(/\s+/);
|
|
@@ -2019,6 +2455,19 @@ function expandStyle(raw, fontFamilies) {
|
|
|
2019
2455
|
if (style.overflowX === void 0) style.overflowX = style.overflow;
|
|
2020
2456
|
if (style.overflowY === void 0) style.overflowY = style.overflow;
|
|
2021
2457
|
}
|
|
2458
|
+
if (style.textBox !== void 0) {
|
|
2459
|
+
const val = String(style.textBox);
|
|
2460
|
+
if (val === "normal" || val === "none") {
|
|
2461
|
+
style.textBoxTrim ??= "none";
|
|
2462
|
+
} else {
|
|
2463
|
+
const parts = val.split(/\s+/);
|
|
2464
|
+
style.textBoxTrim ??= parts[0];
|
|
2465
|
+
if (parts.length > 1) {
|
|
2466
|
+
style.textBoxEdge ??= parts.slice(1).join(" ");
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
delete style.textBox;
|
|
2470
|
+
}
|
|
2022
2471
|
if (typeof style.fontFamily === "string") {
|
|
2023
2472
|
const families = style.fontFamily.split(",").map((s) => s.trim().replace(/^['"]|['"]$/g, "")).filter(Boolean);
|
|
2024
2473
|
if (fontFamilies) {
|
|
@@ -2033,6 +2482,7 @@ function expandStyle(raw, fontFamilies) {
|
|
|
2033
2482
|
}
|
|
2034
2483
|
|
|
2035
2484
|
// src/jsx/style/compute.ts
|
|
2485
|
+
var ROOT_FONT_SIZE = 16;
|
|
2036
2486
|
var DEFAULT_STYLE = {
|
|
2037
2487
|
display: "flex",
|
|
2038
2488
|
flexDirection: "row",
|
|
@@ -2042,7 +2492,7 @@ var DEFAULT_STYLE = {
|
|
|
2042
2492
|
alignItems: "stretch",
|
|
2043
2493
|
justifyContent: "flex-start",
|
|
2044
2494
|
position: "relative",
|
|
2045
|
-
fontSize:
|
|
2495
|
+
fontSize: ROOT_FONT_SIZE,
|
|
2046
2496
|
fontWeight: 400,
|
|
2047
2497
|
fontStyle: "normal",
|
|
2048
2498
|
color: "black",
|
|
@@ -2067,7 +2517,9 @@ var INHERITABLE_PROPS = [
|
|
|
2067
2517
|
"letterSpacing",
|
|
2068
2518
|
"whiteSpace",
|
|
2069
2519
|
"wordBreak",
|
|
2070
|
-
"textOverflow"
|
|
2520
|
+
"textOverflow",
|
|
2521
|
+
"textBoxTrim",
|
|
2522
|
+
"textBoxEdge"
|
|
2071
2523
|
];
|
|
2072
2524
|
function resolveStyle(rawStyle, parentStyle) {
|
|
2073
2525
|
const style = { ...rawStyle };
|
|
@@ -2077,8 +2529,15 @@ function resolveStyle(rawStyle, parentStyle) {
|
|
|
2077
2529
|
}
|
|
2078
2530
|
}
|
|
2079
2531
|
if (typeof style.fontSize === "string") {
|
|
2080
|
-
const
|
|
2081
|
-
|
|
2532
|
+
const parentFontSize = typeof parentStyle.fontSize === "number" ? parentStyle.fontSize : ROOT_FONT_SIZE;
|
|
2533
|
+
const resolved = resolveUnit(
|
|
2534
|
+
style.fontSize,
|
|
2535
|
+
0,
|
|
2536
|
+
0,
|
|
2537
|
+
parentFontSize,
|
|
2538
|
+
ROOT_FONT_SIZE
|
|
2539
|
+
);
|
|
2540
|
+
style.fontSize = typeof resolved === "number" ? resolved : parentFontSize;
|
|
2082
2541
|
}
|
|
2083
2542
|
if (typeof style.lineHeight === "string") {
|
|
2084
2543
|
const str = style.lineHeight;
|
|
@@ -2092,9 +2551,16 @@ function resolveStyle(rawStyle, parentStyle) {
|
|
|
2092
2551
|
}
|
|
2093
2552
|
}
|
|
2094
2553
|
if (typeof style.letterSpacing === "string") {
|
|
2095
|
-
const
|
|
2096
|
-
|
|
2097
|
-
style.letterSpacing
|
|
2554
|
+
const currentFontSize = typeof style.fontSize === "number" ? style.fontSize : typeof parentStyle.fontSize === "number" ? parentStyle.fontSize : ROOT_FONT_SIZE;
|
|
2555
|
+
const resolved = resolveUnit(
|
|
2556
|
+
style.letterSpacing,
|
|
2557
|
+
0,
|
|
2558
|
+
0,
|
|
2559
|
+
currentFontSize,
|
|
2560
|
+
ROOT_FONT_SIZE
|
|
2561
|
+
);
|
|
2562
|
+
if (typeof resolved === "number") {
|
|
2563
|
+
style.letterSpacing = resolved;
|
|
2098
2564
|
}
|
|
2099
2565
|
}
|
|
2100
2566
|
return style;
|
|
@@ -2182,7 +2648,7 @@ function resolveUnit(value, viewportWidth, viewportHeight, fontSize, rootFontSiz
|
|
|
2182
2648
|
}
|
|
2183
2649
|
return value;
|
|
2184
2650
|
}
|
|
2185
|
-
function resolveUnits(style, viewportWidth, viewportHeight, rootFontSize =
|
|
2651
|
+
function resolveUnits(style, viewportWidth, viewportHeight, rootFontSize = ROOT_FONT_SIZE) {
|
|
2186
2652
|
const fontSize = typeof style.fontSize === "number" ? style.fontSize : rootFontSize;
|
|
2187
2653
|
for (const prop of DIMENSION_PROPS) {
|
|
2188
2654
|
const value = style[prop];
|