@effing/canvas 0.18.4 → 0.18.6
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 +416 -200
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -74,8 +74,28 @@ function getScratchCtx() {
|
|
|
74
74
|
}
|
|
75
75
|
return scratchCtx;
|
|
76
76
|
}
|
|
77
|
+
var GENERIC_FAMILIES = /* @__PURE__ */ new Set([
|
|
78
|
+
"serif",
|
|
79
|
+
"sans-serif",
|
|
80
|
+
"monospace",
|
|
81
|
+
"cursive",
|
|
82
|
+
"fantasy",
|
|
83
|
+
"system-ui",
|
|
84
|
+
"ui-serif",
|
|
85
|
+
"ui-sans-serif",
|
|
86
|
+
"ui-monospace",
|
|
87
|
+
"ui-rounded",
|
|
88
|
+
"math",
|
|
89
|
+
"emoji",
|
|
90
|
+
"fangsong"
|
|
91
|
+
]);
|
|
92
|
+
function quoteFontFamily(family) {
|
|
93
|
+
if (!family || GENERIC_FAMILIES.has(family)) return family;
|
|
94
|
+
return `"${family}"`;
|
|
95
|
+
}
|
|
77
96
|
function setFont(ctx, fontSize, fontFamily, fontWeight = 400, fontStyle = "normal") {
|
|
78
|
-
|
|
97
|
+
const quoted = fontFamily.split(",").map((f) => quoteFontFamily(f.trim())).join(", ");
|
|
98
|
+
ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${quoted}`;
|
|
79
99
|
}
|
|
80
100
|
function measureText(text, fontSize, fontFamily, fontWeight = 400, fontStyle = "normal", ctx) {
|
|
81
101
|
const c = ctx ?? getScratchCtx();
|
|
@@ -650,7 +670,7 @@ async function drawImage(ctx, src, x, y, width, height, style, preloadedImage) {
|
|
|
650
670
|
|
|
651
671
|
// src/jsx/draw/rect.ts
|
|
652
672
|
function drawRect(ctx, x, y, width, height, style) {
|
|
653
|
-
const borderRadius = getBorderRadius(style);
|
|
673
|
+
const borderRadius = getBorderRadius(style, width, height);
|
|
654
674
|
const hasRoundedCorners = borderRadius.topLeft > 0 || borderRadius.topRight > 0 || borderRadius.bottomRight > 0 || borderRadius.bottomLeft > 0;
|
|
655
675
|
if (style.boxShadow) {
|
|
656
676
|
drawBoxShadow(ctx, x, y, width, height, style.boxShadow, borderRadius);
|
|
@@ -677,16 +697,20 @@ function drawRect(ctx, x, y, width, height, style) {
|
|
|
677
697
|
}
|
|
678
698
|
drawBorders(ctx, x, y, width, height, style, borderRadius);
|
|
679
699
|
}
|
|
680
|
-
function
|
|
700
|
+
function resolveRadius(v, width, height) {
|
|
701
|
+
if (typeof v === "string") return parseCSSLength(v, Math.min(width, height));
|
|
702
|
+
return toNumber(v);
|
|
703
|
+
}
|
|
704
|
+
function getBorderRadius(style, width, height) {
|
|
681
705
|
return {
|
|
682
|
-
topLeft:
|
|
683
|
-
topRight:
|
|
684
|
-
bottomRight:
|
|
685
|
-
bottomLeft:
|
|
706
|
+
topLeft: resolveRadius(style.borderTopLeftRadius, width, height),
|
|
707
|
+
topRight: resolveRadius(style.borderTopRightRadius, width, height),
|
|
708
|
+
bottomRight: resolveRadius(style.borderBottomRightRadius, width, height),
|
|
709
|
+
bottomLeft: resolveRadius(style.borderBottomLeftRadius, width, height)
|
|
686
710
|
};
|
|
687
711
|
}
|
|
688
|
-
function getBorderRadiusFromStyle(style) {
|
|
689
|
-
return getBorderRadius(style);
|
|
712
|
+
function getBorderRadiusFromStyle(style, width, height) {
|
|
713
|
+
return getBorderRadius(style, width, height);
|
|
690
714
|
}
|
|
691
715
|
function drawBorders(ctx, x, y, width, height, style, borderRadius) {
|
|
692
716
|
const hasRoundedCorners = borderRadius.topLeft > 0 || borderRadius.topRight > 0 || borderRadius.bottomRight > 0 || borderRadius.bottomLeft > 0;
|
|
@@ -786,12 +810,6 @@ function drawBoxShadow(ctx, x, y, width, height, boxShadow, borderRadius) {
|
|
|
786
810
|
ctx.fill();
|
|
787
811
|
ctx.restore();
|
|
788
812
|
}
|
|
789
|
-
function toNumber(v) {
|
|
790
|
-
if (typeof v === "number") return v;
|
|
791
|
-
if (v === void 0 || v === null) return 0;
|
|
792
|
-
const n = parseFloat(String(v));
|
|
793
|
-
return isNaN(n) ? 0 : n;
|
|
794
|
-
}
|
|
795
813
|
|
|
796
814
|
// src/jsx/draw/svg.ts
|
|
797
815
|
import { Path2D } from "@napi-rs/canvas";
|
|
@@ -804,6 +822,97 @@ function mergeStyleIntoProps(props) {
|
|
|
804
822
|
if (!style) return props;
|
|
805
823
|
return { ...props, ...style };
|
|
806
824
|
}
|
|
825
|
+
function collectDefs(children) {
|
|
826
|
+
const defs = /* @__PURE__ */ new Map();
|
|
827
|
+
for (const child of children) {
|
|
828
|
+
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
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
return defs;
|
|
840
|
+
}
|
|
841
|
+
function parseUrlRef(value) {
|
|
842
|
+
if (typeof value !== "string") return void 0;
|
|
843
|
+
const m = value.match(/^url\(#(.+)\)$/);
|
|
844
|
+
return m?.[1];
|
|
845
|
+
}
|
|
846
|
+
function normalizeChildren(node) {
|
|
847
|
+
const raw = node.children ?? node.props.children;
|
|
848
|
+
if (raw == null) return [];
|
|
849
|
+
return Array.isArray(raw) ? raw : [raw];
|
|
850
|
+
}
|
|
851
|
+
function buildPath(child) {
|
|
852
|
+
const props = mergeStyleIntoProps(child.props);
|
|
853
|
+
switch (child.type) {
|
|
854
|
+
case "path": {
|
|
855
|
+
const d = props.d;
|
|
856
|
+
if (!d) return void 0;
|
|
857
|
+
return new Path2D(d);
|
|
858
|
+
}
|
|
859
|
+
case "circle": {
|
|
860
|
+
const cx = Number(props.cx ?? 0);
|
|
861
|
+
const cy = Number(props.cy ?? 0);
|
|
862
|
+
const r = Number(props.r ?? 0);
|
|
863
|
+
if (r <= 0) return void 0;
|
|
864
|
+
const p = new Path2D();
|
|
865
|
+
p.arc(cx, cy, r, 0, Math.PI * 2);
|
|
866
|
+
return p;
|
|
867
|
+
}
|
|
868
|
+
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);
|
|
873
|
+
if (w <= 0 || h <= 0) return void 0;
|
|
874
|
+
const p = new Path2D();
|
|
875
|
+
p.rect(rx, ry, w, h);
|
|
876
|
+
return p;
|
|
877
|
+
}
|
|
878
|
+
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);
|
|
883
|
+
if (rx <= 0 || ry <= 0) return void 0;
|
|
884
|
+
const p = new Path2D();
|
|
885
|
+
p.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
|
|
886
|
+
return p;
|
|
887
|
+
}
|
|
888
|
+
case "polygon": {
|
|
889
|
+
const points = parsePoints(props.points);
|
|
890
|
+
if (points.length < 2) return void 0;
|
|
891
|
+
const p = new Path2D();
|
|
892
|
+
p.moveTo(points[0][0], points[0][1]);
|
|
893
|
+
for (let i = 1; i < points.length; i++) {
|
|
894
|
+
p.lineTo(points[i][0], points[i][1]);
|
|
895
|
+
}
|
|
896
|
+
p.closePath();
|
|
897
|
+
return p;
|
|
898
|
+
}
|
|
899
|
+
default:
|
|
900
|
+
return void 0;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
function buildClipPath(shapes) {
|
|
904
|
+
let combined;
|
|
905
|
+
for (const shape of shapes) {
|
|
906
|
+
const p = buildPath(shape);
|
|
907
|
+
if (!p) continue;
|
|
908
|
+
if (!combined) {
|
|
909
|
+
combined = p;
|
|
910
|
+
} else {
|
|
911
|
+
combined.addPath(p);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
return combined;
|
|
915
|
+
}
|
|
807
916
|
function drawSvgContainer(ctx, node, x, y, width, height) {
|
|
808
917
|
ctx.save();
|
|
809
918
|
ctx.translate(x, y);
|
|
@@ -824,17 +933,25 @@ function drawSvgContainer(ctx, node, x, y, width, height) {
|
|
|
824
933
|
const children = node.props.children;
|
|
825
934
|
if (children != null) {
|
|
826
935
|
const childArray = Array.isArray(children) ? children : [children];
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
936
|
+
const svgChildren = childArray.filter(
|
|
937
|
+
(c) => c != null && typeof c === "object"
|
|
938
|
+
);
|
|
939
|
+
const defs = collectDefs(svgChildren);
|
|
940
|
+
for (const child of svgChildren) {
|
|
941
|
+
drawSvgChild(ctx, child, inheritedFill, color, defs);
|
|
831
942
|
}
|
|
832
943
|
}
|
|
833
944
|
ctx.restore();
|
|
834
945
|
}
|
|
835
|
-
function drawSvgChild(ctx, child, inheritedFill, color) {
|
|
946
|
+
function drawSvgChild(ctx, child, inheritedFill, color, defs = /* @__PURE__ */ new Map()) {
|
|
836
947
|
const { type } = child;
|
|
837
948
|
const props = mergeStyleIntoProps(child.props);
|
|
949
|
+
if (type === "defs" || type === "clipPath") return;
|
|
950
|
+
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;
|
|
953
|
+
if (clipPath) ctx.save();
|
|
954
|
+
if (clipPath) ctx.clip(clipPath);
|
|
838
955
|
switch (type) {
|
|
839
956
|
case "path":
|
|
840
957
|
drawPath(ctx, props, inheritedFill, color);
|
|
@@ -858,9 +975,10 @@ function drawSvgChild(ctx, child, inheritedFill, color) {
|
|
|
858
975
|
drawPolyline(ctx, props, inheritedFill, color);
|
|
859
976
|
break;
|
|
860
977
|
case "g":
|
|
861
|
-
drawGroup(ctx, child, inheritedFill, color);
|
|
978
|
+
drawGroup(ctx, child, inheritedFill, color, defs);
|
|
862
979
|
break;
|
|
863
980
|
}
|
|
981
|
+
if (clipPath) ctx.restore();
|
|
864
982
|
}
|
|
865
983
|
function drawPath(ctx, props, inheritedFill, color) {
|
|
866
984
|
const d = props.d;
|
|
@@ -928,15 +1046,14 @@ function drawPolyline(ctx, props, inheritedFill, color) {
|
|
|
928
1046
|
}
|
|
929
1047
|
applyFillAndStroke(ctx, props, path, inheritedFill, color);
|
|
930
1048
|
}
|
|
931
|
-
function drawGroup(ctx, node, inheritedFill, color) {
|
|
932
|
-
const children = node
|
|
933
|
-
if (children
|
|
1049
|
+
function drawGroup(ctx, node, inheritedFill, color, defs = /* @__PURE__ */ new Map()) {
|
|
1050
|
+
const children = normalizeChildren(node);
|
|
1051
|
+
if (children.length === 0) return;
|
|
934
1052
|
const merged = mergeStyleIntoProps(node.props);
|
|
935
1053
|
const groupFill = resolveCurrentColor(merged.fill, color) ?? inheritedFill;
|
|
936
|
-
const
|
|
937
|
-
for (const child of childArray) {
|
|
1054
|
+
for (const child of children) {
|
|
938
1055
|
if (child != null && typeof child === "object") {
|
|
939
|
-
drawSvgChild(ctx, child, groupFill, color);
|
|
1056
|
+
drawSvgChild(ctx, child, groupFill, color, defs);
|
|
940
1057
|
}
|
|
941
1058
|
}
|
|
942
1059
|
}
|
|
@@ -951,9 +1068,14 @@ function parsePoints(value) {
|
|
|
951
1068
|
}
|
|
952
1069
|
function applyFillAndStroke(ctx, props, path, inheritedFill, color) {
|
|
953
1070
|
const fill = resolveCurrentColor(props.fill, color) ?? inheritedFill;
|
|
1071
|
+
const fillRule = props.fillRule ?? props["fill-rule"];
|
|
1072
|
+
const clipRule = props.clipRule ?? props["clip-rule"];
|
|
1073
|
+
if (clipRule) {
|
|
1074
|
+
ctx.clip(path, clipRule);
|
|
1075
|
+
}
|
|
954
1076
|
if (fill !== "none") {
|
|
955
1077
|
ctx.fillStyle = fill;
|
|
956
|
-
ctx.fill(path);
|
|
1078
|
+
ctx.fill(path, fillRule ?? "nonzero");
|
|
957
1079
|
}
|
|
958
1080
|
applyStroke(ctx, props, path, color);
|
|
959
1081
|
}
|
|
@@ -1014,7 +1136,7 @@ async function loadEmoji(type, code) {
|
|
|
1014
1136
|
}
|
|
1015
1137
|
|
|
1016
1138
|
// src/jsx/text/emoji-split.ts
|
|
1017
|
-
function splitTextIntoRuns(text, measureText2, emojiSize) {
|
|
1139
|
+
function splitTextIntoRuns(text, measureText2, emojiSize, letterSpacing = 0) {
|
|
1018
1140
|
const runs = [];
|
|
1019
1141
|
const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
1020
1142
|
let currentText = "";
|
|
@@ -1024,13 +1146,18 @@ function splitTextIntoRuns(text, measureText2, emojiSize) {
|
|
|
1024
1146
|
if (isEmojiGrapheme(segment)) {
|
|
1025
1147
|
if (currentText) {
|
|
1026
1148
|
const textWidth = measureText2(currentText);
|
|
1149
|
+
const graphemeCount = [
|
|
1150
|
+
...new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(
|
|
1151
|
+
currentText
|
|
1152
|
+
)
|
|
1153
|
+
].length;
|
|
1027
1154
|
runs.push({
|
|
1028
1155
|
kind: "text",
|
|
1029
1156
|
text: currentText,
|
|
1030
1157
|
x: textStartX,
|
|
1031
|
-
width: textWidth
|
|
1158
|
+
width: textWidth + letterSpacing * graphemeCount
|
|
1032
1159
|
});
|
|
1033
|
-
currentX = textStartX + textWidth;
|
|
1160
|
+
currentX = textStartX + textWidth + letterSpacing * graphemeCount;
|
|
1034
1161
|
currentText = "";
|
|
1035
1162
|
}
|
|
1036
1163
|
runs.push({
|
|
@@ -1039,7 +1166,7 @@ function splitTextIntoRuns(text, measureText2, emojiSize) {
|
|
|
1039
1166
|
x: currentX,
|
|
1040
1167
|
width: emojiSize
|
|
1041
1168
|
});
|
|
1042
|
-
currentX += emojiSize;
|
|
1169
|
+
currentX += emojiSize + letterSpacing;
|
|
1043
1170
|
textStartX = currentX;
|
|
1044
1171
|
} else {
|
|
1045
1172
|
if (!currentText) textStartX = currentX;
|
|
@@ -1048,11 +1175,16 @@ function splitTextIntoRuns(text, measureText2, emojiSize) {
|
|
|
1048
1175
|
}
|
|
1049
1176
|
if (currentText) {
|
|
1050
1177
|
const textWidth = measureText2(currentText);
|
|
1178
|
+
const graphemeCount = [
|
|
1179
|
+
...new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(
|
|
1180
|
+
currentText
|
|
1181
|
+
)
|
|
1182
|
+
].length;
|
|
1051
1183
|
runs.push({
|
|
1052
1184
|
kind: "text",
|
|
1053
1185
|
text: currentText,
|
|
1054
1186
|
x: textStartX,
|
|
1055
|
-
width: textWidth
|
|
1187
|
+
width: textWidth + letterSpacing * graphemeCount
|
|
1056
1188
|
});
|
|
1057
1189
|
}
|
|
1058
1190
|
return runs;
|
|
@@ -1103,20 +1235,29 @@ async function drawText(ctx, segments, offsetX, offsetY, textShadow, emojiStyle)
|
|
|
1103
1235
|
}
|
|
1104
1236
|
}
|
|
1105
1237
|
async function drawSegmentWithEmoji(ctx, seg, x, y, textShadow, emojiStyle) {
|
|
1238
|
+
const letterSpacing = seg.letterSpacing ?? 0;
|
|
1106
1239
|
const runs = splitTextIntoRuns(
|
|
1107
1240
|
seg.text,
|
|
1108
1241
|
(text) => {
|
|
1109
1242
|
setFont(ctx, seg.fontSize, seg.fontFamily, seg.fontWeight, seg.fontStyle);
|
|
1110
1243
|
return ctx.measureText(text).width;
|
|
1111
1244
|
},
|
|
1112
|
-
seg.fontSize
|
|
1245
|
+
seg.fontSize,
|
|
1246
|
+
letterSpacing
|
|
1113
1247
|
);
|
|
1114
1248
|
for (const run of runs) {
|
|
1115
1249
|
if (run.kind === "text") {
|
|
1116
|
-
if (
|
|
1117
|
-
|
|
1250
|
+
if (letterSpacing !== 0) {
|
|
1251
|
+
if (textShadow) {
|
|
1252
|
+
drawTextShadow(ctx, run.text, x + run.x, y, textShadow);
|
|
1253
|
+
}
|
|
1254
|
+
drawTextWithLetterSpacing(ctx, run.text, x + run.x, y, letterSpacing);
|
|
1255
|
+
} else {
|
|
1256
|
+
if (textShadow) {
|
|
1257
|
+
drawTextShadow(ctx, run.text, x + run.x, y, textShadow);
|
|
1258
|
+
}
|
|
1259
|
+
ctx.fillText(run.text, x + run.x, y);
|
|
1118
1260
|
}
|
|
1119
|
-
ctx.fillText(run.text, x + run.x, y);
|
|
1120
1261
|
} else {
|
|
1121
1262
|
const img = await loadEmojiImage(emojiStyle, run.char);
|
|
1122
1263
|
if (img) {
|
|
@@ -1291,84 +1432,91 @@ async function drawNode(ctx, node, parentX, parentY, debug, emojiStyle) {
|
|
|
1291
1432
|
}
|
|
1292
1433
|
const isClipped = style.overflow === "hidden" || style.overflowX === "hidden" || style.overflowY === "hidden";
|
|
1293
1434
|
if (isClipped) {
|
|
1294
|
-
const borderRadius = getBorderRadiusFromStyle(style);
|
|
1435
|
+
const borderRadius = getBorderRadiusFromStyle(style, width, height);
|
|
1295
1436
|
applyClip(ctx, x, y, width, height, borderRadius);
|
|
1296
1437
|
}
|
|
1297
1438
|
if (style.backgroundColor || style.borderTopWidth || style.borderRightWidth || style.borderBottomWidth || style.borderLeftWidth || style.boxShadow) {
|
|
1298
1439
|
drawRect(ctx, x, y, width, height, style);
|
|
1299
1440
|
}
|
|
1300
1441
|
if (style.backgroundImage) {
|
|
1301
|
-
const
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
x,
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
if (borderRadius.topLeft > 0 || borderRadius.topRight > 0 || borderRadius.bottomRight > 0 || borderRadius.bottomLeft > 0) {
|
|
1313
|
-
ctx.beginPath();
|
|
1314
|
-
roundedRect(
|
|
1315
|
-
ctx,
|
|
1316
|
-
x,
|
|
1317
|
-
y,
|
|
1318
|
-
width,
|
|
1319
|
-
height,
|
|
1320
|
-
borderRadius.topLeft,
|
|
1321
|
-
borderRadius.topRight,
|
|
1322
|
-
borderRadius.bottomRight,
|
|
1323
|
-
borderRadius.bottomLeft
|
|
1324
|
-
);
|
|
1325
|
-
ctx.fill();
|
|
1326
|
-
} else {
|
|
1327
|
-
ctx.fillRect(x, y, width, height);
|
|
1328
|
-
}
|
|
1329
|
-
} else {
|
|
1330
|
-
const urlMatch = style.backgroundImage.match(/url\(["']?(.*?)["']?\)/);
|
|
1331
|
-
if (urlMatch) {
|
|
1332
|
-
const borderRadius = getBorderRadiusFromStyle(style);
|
|
1333
|
-
const hasRadius2 = borderRadius.topLeft > 0 || borderRadius.topRight > 0 || borderRadius.bottomRight > 0 || borderRadius.bottomLeft > 0;
|
|
1334
|
-
if (hasRadius2) {
|
|
1335
|
-
applyClip(ctx, x, y, width, height, borderRadius);
|
|
1336
|
-
}
|
|
1337
|
-
const image = await loadImage3(urlMatch[1]);
|
|
1338
|
-
const bgSize = style.backgroundSize;
|
|
1339
|
-
if (bgSize === "cover") {
|
|
1340
|
-
const r = computeCover(
|
|
1341
|
-
image.width,
|
|
1342
|
-
image.height,
|
|
1442
|
+
const layers = splitGradientArgs(style.backgroundImage);
|
|
1443
|
+
for (let i = layers.length - 1; i >= 0; i--) {
|
|
1444
|
+
const layer = layers[i].trim();
|
|
1445
|
+
const gradient = createGradientFromCSS(ctx, layer, x, y, width, height);
|
|
1446
|
+
if (gradient) {
|
|
1447
|
+
ctx.fillStyle = gradient;
|
|
1448
|
+
const borderRadius = getBorderRadiusFromStyle(style, width, height);
|
|
1449
|
+
if (borderRadius.topLeft > 0 || borderRadius.topRight > 0 || borderRadius.bottomRight > 0 || borderRadius.bottomLeft > 0) {
|
|
1450
|
+
ctx.beginPath();
|
|
1451
|
+
roundedRect(
|
|
1452
|
+
ctx,
|
|
1343
1453
|
x,
|
|
1344
1454
|
y,
|
|
1345
1455
|
width,
|
|
1346
|
-
height
|
|
1456
|
+
height,
|
|
1457
|
+
borderRadius.topLeft,
|
|
1458
|
+
borderRadius.topRight,
|
|
1459
|
+
borderRadius.bottomRight,
|
|
1460
|
+
borderRadius.bottomLeft
|
|
1347
1461
|
);
|
|
1348
|
-
ctx.
|
|
1462
|
+
ctx.fill();
|
|
1349
1463
|
} else {
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1464
|
+
ctx.fillRect(x, y, width, height);
|
|
1465
|
+
}
|
|
1466
|
+
} else {
|
|
1467
|
+
const urlMatch = layer.match(/url\(["']?(.*?)["']?\)/);
|
|
1468
|
+
if (urlMatch) {
|
|
1469
|
+
const borderRadius = getBorderRadiusFromStyle(style, width, height);
|
|
1470
|
+
const hasRadius2 = borderRadius.topLeft > 0 || borderRadius.topRight > 0 || borderRadius.bottomRight > 0 || borderRadius.bottomLeft > 0;
|
|
1471
|
+
if (hasRadius2) {
|
|
1472
|
+
applyClip(ctx, x, y, width, height, borderRadius);
|
|
1473
|
+
}
|
|
1474
|
+
const image = await loadImage3(urlMatch[1]);
|
|
1475
|
+
const bgSize = style.backgroundSize;
|
|
1476
|
+
if (bgSize === "cover") {
|
|
1477
|
+
const r = computeCover(
|
|
1353
1478
|
image.width,
|
|
1354
1479
|
image.height,
|
|
1355
|
-
|
|
1356
|
-
|
|
1480
|
+
x,
|
|
1481
|
+
y,
|
|
1357
1482
|
width,
|
|
1358
1483
|
height
|
|
1359
1484
|
);
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1485
|
+
ctx.drawImage(
|
|
1486
|
+
image,
|
|
1487
|
+
r.sx,
|
|
1488
|
+
r.sy,
|
|
1489
|
+
r.sw,
|
|
1490
|
+
r.sh,
|
|
1491
|
+
r.dx,
|
|
1492
|
+
r.dy,
|
|
1493
|
+
r.dw,
|
|
1494
|
+
r.dh
|
|
1495
|
+
);
|
|
1365
1496
|
} else {
|
|
1366
|
-
tileW
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1497
|
+
let tileW, tileH;
|
|
1498
|
+
if (bgSize === "contain") {
|
|
1499
|
+
const r = computeContain(
|
|
1500
|
+
image.width,
|
|
1501
|
+
image.height,
|
|
1502
|
+
0,
|
|
1503
|
+
0,
|
|
1504
|
+
width,
|
|
1505
|
+
height
|
|
1506
|
+
);
|
|
1507
|
+
tileW = r.dw;
|
|
1508
|
+
tileH = r.dh;
|
|
1509
|
+
} else if (bgSize === "100% 100%") {
|
|
1510
|
+
tileW = width;
|
|
1511
|
+
tileH = height;
|
|
1512
|
+
} else {
|
|
1513
|
+
tileW = image.width;
|
|
1514
|
+
tileH = image.height;
|
|
1515
|
+
}
|
|
1516
|
+
for (let ty = y; ty < y + height; ty += tileH) {
|
|
1517
|
+
for (let tx = x; tx < x + width; tx += tileW) {
|
|
1518
|
+
ctx.drawImage(image, tx, ty, tileW, tileH);
|
|
1519
|
+
}
|
|
1372
1520
|
}
|
|
1373
1521
|
}
|
|
1374
1522
|
}
|
|
@@ -1381,12 +1529,12 @@ async function drawNode(ctx, node, parentX, parentY, debug, emojiStyle) {
|
|
|
1381
1529
|
ctx.strokeRect(x, y, width, height);
|
|
1382
1530
|
}
|
|
1383
1531
|
if (node.textContent !== void 0 && node.textContent !== "") {
|
|
1384
|
-
const paddingTop =
|
|
1385
|
-
const paddingLeft =
|
|
1386
|
-
const paddingRight =
|
|
1387
|
-
const borderTopW =
|
|
1388
|
-
const borderLeftW =
|
|
1389
|
-
const borderRightW =
|
|
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);
|
|
1390
1538
|
const contentX = x + paddingLeft + borderLeftW;
|
|
1391
1539
|
const contentY = y + paddingTop + borderTopW;
|
|
1392
1540
|
const contentWidth = width - paddingLeft - paddingRight - borderLeftW - borderRightW;
|
|
@@ -1407,16 +1555,16 @@ async function drawNode(ctx, node, parentX, parentY, debug, emojiStyle) {
|
|
|
1407
1555
|
);
|
|
1408
1556
|
}
|
|
1409
1557
|
if (node.type === "img" && node.props.src) {
|
|
1410
|
-
const paddingTop =
|
|
1411
|
-
const paddingLeft =
|
|
1412
|
-
const paddingRight =
|
|
1413
|
-
const paddingBottom =
|
|
1558
|
+
const paddingTop = toNumber(style.paddingTop);
|
|
1559
|
+
const paddingLeft = toNumber(style.paddingLeft);
|
|
1560
|
+
const paddingRight = toNumber(style.paddingRight);
|
|
1561
|
+
const paddingBottom = toNumber(style.paddingBottom);
|
|
1414
1562
|
const imgX = x + paddingLeft;
|
|
1415
1563
|
const imgY = y + paddingTop;
|
|
1416
1564
|
const imgW = width - paddingLeft - paddingRight;
|
|
1417
1565
|
const imgH = height - paddingTop - paddingBottom;
|
|
1418
1566
|
if (!isClipped) {
|
|
1419
|
-
const borderRadius = getBorderRadiusFromStyle(style);
|
|
1567
|
+
const borderRadius = getBorderRadiusFromStyle(style, width, height);
|
|
1420
1568
|
if (borderRadius.topLeft > 0 || borderRadius.topRight > 0 || borderRadius.bottomRight > 0 || borderRadius.bottomLeft > 0) {
|
|
1421
1569
|
applyClip(ctx, imgX, imgY, imgW, imgH, borderRadius);
|
|
1422
1570
|
}
|
|
@@ -1479,84 +1627,91 @@ async function drawNodeInner(ctx, node, parentX, parentY, offsetX, offsetY, debu
|
|
|
1479
1627
|
}
|
|
1480
1628
|
const isClipped = style.overflow === "hidden" || style.overflowX === "hidden" || style.overflowY === "hidden";
|
|
1481
1629
|
if (isClipped) {
|
|
1482
|
-
const borderRadius = getBorderRadiusFromStyle(style);
|
|
1630
|
+
const borderRadius = getBorderRadiusFromStyle(style, width, height);
|
|
1483
1631
|
applyClip(ctx, x, y, width, height, borderRadius);
|
|
1484
1632
|
}
|
|
1485
1633
|
if (style.backgroundColor || style.borderTopWidth || style.borderRightWidth || style.borderBottomWidth || style.borderLeftWidth || style.boxShadow) {
|
|
1486
1634
|
drawRect(ctx, x, y, width, height, style);
|
|
1487
1635
|
}
|
|
1488
1636
|
if (style.backgroundImage) {
|
|
1489
|
-
const
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
x,
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
if (borderRadius.topLeft > 0 || borderRadius.topRight > 0 || borderRadius.bottomRight > 0 || borderRadius.bottomLeft > 0) {
|
|
1501
|
-
ctx.beginPath();
|
|
1502
|
-
roundedRect(
|
|
1503
|
-
ctx,
|
|
1504
|
-
x,
|
|
1505
|
-
y,
|
|
1506
|
-
width,
|
|
1507
|
-
height,
|
|
1508
|
-
borderRadius.topLeft,
|
|
1509
|
-
borderRadius.topRight,
|
|
1510
|
-
borderRadius.bottomRight,
|
|
1511
|
-
borderRadius.bottomLeft
|
|
1512
|
-
);
|
|
1513
|
-
ctx.fill();
|
|
1514
|
-
} else {
|
|
1515
|
-
ctx.fillRect(x, y, width, height);
|
|
1516
|
-
}
|
|
1517
|
-
} else {
|
|
1518
|
-
const urlMatch = style.backgroundImage.match(/url\(["']?(.*?)["']?\)/);
|
|
1519
|
-
if (urlMatch) {
|
|
1520
|
-
const borderRadius = getBorderRadiusFromStyle(style);
|
|
1521
|
-
const hasRadius2 = borderRadius.topLeft > 0 || borderRadius.topRight > 0 || borderRadius.bottomRight > 0 || borderRadius.bottomLeft > 0;
|
|
1522
|
-
if (hasRadius2) {
|
|
1523
|
-
applyClip(ctx, x, y, width, height, borderRadius);
|
|
1524
|
-
}
|
|
1525
|
-
const image = await loadImage3(urlMatch[1]);
|
|
1526
|
-
const bgSize = style.backgroundSize;
|
|
1527
|
-
if (bgSize === "cover") {
|
|
1528
|
-
const r = computeCover(
|
|
1529
|
-
image.width,
|
|
1530
|
-
image.height,
|
|
1637
|
+
const layers = splitGradientArgs(style.backgroundImage);
|
|
1638
|
+
for (let i = layers.length - 1; i >= 0; i--) {
|
|
1639
|
+
const layer = layers[i].trim();
|
|
1640
|
+
const gradient = createGradientFromCSS(ctx, layer, x, y, width, height);
|
|
1641
|
+
if (gradient) {
|
|
1642
|
+
ctx.fillStyle = gradient;
|
|
1643
|
+
const borderRadius = getBorderRadiusFromStyle(style, width, height);
|
|
1644
|
+
if (borderRadius.topLeft > 0 || borderRadius.topRight > 0 || borderRadius.bottomRight > 0 || borderRadius.bottomLeft > 0) {
|
|
1645
|
+
ctx.beginPath();
|
|
1646
|
+
roundedRect(
|
|
1647
|
+
ctx,
|
|
1531
1648
|
x,
|
|
1532
1649
|
y,
|
|
1533
1650
|
width,
|
|
1534
|
-
height
|
|
1651
|
+
height,
|
|
1652
|
+
borderRadius.topLeft,
|
|
1653
|
+
borderRadius.topRight,
|
|
1654
|
+
borderRadius.bottomRight,
|
|
1655
|
+
borderRadius.bottomLeft
|
|
1535
1656
|
);
|
|
1536
|
-
ctx.
|
|
1657
|
+
ctx.fill();
|
|
1537
1658
|
} else {
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1659
|
+
ctx.fillRect(x, y, width, height);
|
|
1660
|
+
}
|
|
1661
|
+
} else {
|
|
1662
|
+
const urlMatch = layer.match(/url\(["']?(.*?)["']?\)/);
|
|
1663
|
+
if (urlMatch) {
|
|
1664
|
+
const borderRadius = getBorderRadiusFromStyle(style, width, height);
|
|
1665
|
+
const hasRadius2 = borderRadius.topLeft > 0 || borderRadius.topRight > 0 || borderRadius.bottomRight > 0 || borderRadius.bottomLeft > 0;
|
|
1666
|
+
if (hasRadius2) {
|
|
1667
|
+
applyClip(ctx, x, y, width, height, borderRadius);
|
|
1668
|
+
}
|
|
1669
|
+
const image = await loadImage3(urlMatch[1]);
|
|
1670
|
+
const bgSize = style.backgroundSize;
|
|
1671
|
+
if (bgSize === "cover") {
|
|
1672
|
+
const r = computeCover(
|
|
1541
1673
|
image.width,
|
|
1542
1674
|
image.height,
|
|
1543
|
-
|
|
1544
|
-
|
|
1675
|
+
x,
|
|
1676
|
+
y,
|
|
1545
1677
|
width,
|
|
1546
1678
|
height
|
|
1547
1679
|
);
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1680
|
+
ctx.drawImage(
|
|
1681
|
+
image,
|
|
1682
|
+
r.sx,
|
|
1683
|
+
r.sy,
|
|
1684
|
+
r.sw,
|
|
1685
|
+
r.sh,
|
|
1686
|
+
r.dx,
|
|
1687
|
+
r.dy,
|
|
1688
|
+
r.dw,
|
|
1689
|
+
r.dh
|
|
1690
|
+
);
|
|
1553
1691
|
} else {
|
|
1554
|
-
tileW
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1692
|
+
let tileW, tileH;
|
|
1693
|
+
if (bgSize === "contain") {
|
|
1694
|
+
const r = computeContain(
|
|
1695
|
+
image.width,
|
|
1696
|
+
image.height,
|
|
1697
|
+
0,
|
|
1698
|
+
0,
|
|
1699
|
+
width,
|
|
1700
|
+
height
|
|
1701
|
+
);
|
|
1702
|
+
tileW = r.dw;
|
|
1703
|
+
tileH = r.dh;
|
|
1704
|
+
} else if (bgSize === "100% 100%") {
|
|
1705
|
+
tileW = width;
|
|
1706
|
+
tileH = height;
|
|
1707
|
+
} else {
|
|
1708
|
+
tileW = image.width;
|
|
1709
|
+
tileH = image.height;
|
|
1710
|
+
}
|
|
1711
|
+
for (let ty = y; ty < y + height; ty += tileH) {
|
|
1712
|
+
for (let tx = x; tx < x + width; tx += tileW) {
|
|
1713
|
+
ctx.drawImage(image, tx, ty, tileW, tileH);
|
|
1714
|
+
}
|
|
1560
1715
|
}
|
|
1561
1716
|
}
|
|
1562
1717
|
}
|
|
@@ -1569,12 +1724,12 @@ async function drawNodeInner(ctx, node, parentX, parentY, offsetX, offsetY, debu
|
|
|
1569
1724
|
ctx.strokeRect(x, y, width, height);
|
|
1570
1725
|
}
|
|
1571
1726
|
if (node.textContent !== void 0 && node.textContent !== "") {
|
|
1572
|
-
const paddingTop =
|
|
1573
|
-
const paddingLeft =
|
|
1574
|
-
const paddingRight =
|
|
1575
|
-
const borderTopW =
|
|
1576
|
-
const borderLeftW =
|
|
1577
|
-
const borderRightW =
|
|
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);
|
|
1578
1733
|
const contentX = x + paddingLeft + borderLeftW;
|
|
1579
1734
|
const contentY = y + paddingTop + borderTopW;
|
|
1580
1735
|
const contentWidth = width - paddingLeft - paddingRight - borderLeftW - borderRightW;
|
|
@@ -1595,16 +1750,16 @@ async function drawNodeInner(ctx, node, parentX, parentY, offsetX, offsetY, debu
|
|
|
1595
1750
|
);
|
|
1596
1751
|
}
|
|
1597
1752
|
if (node.type === "img" && node.props.src) {
|
|
1598
|
-
const paddingTop =
|
|
1599
|
-
const paddingLeft =
|
|
1600
|
-
const paddingRight =
|
|
1601
|
-
const paddingBottom =
|
|
1753
|
+
const paddingTop = toNumber(style.paddingTop);
|
|
1754
|
+
const paddingLeft = toNumber(style.paddingLeft);
|
|
1755
|
+
const paddingRight = toNumber(style.paddingRight);
|
|
1756
|
+
const paddingBottom = toNumber(style.paddingBottom);
|
|
1602
1757
|
const imgX = x + paddingLeft;
|
|
1603
1758
|
const imgY = y + paddingTop;
|
|
1604
1759
|
const imgW = width - paddingLeft - paddingRight;
|
|
1605
1760
|
const imgH = height - paddingTop - paddingBottom;
|
|
1606
1761
|
if (!isClipped) {
|
|
1607
|
-
const borderRadius = getBorderRadiusFromStyle(style);
|
|
1762
|
+
const borderRadius = getBorderRadiusFromStyle(style, width, height);
|
|
1608
1763
|
if (borderRadius.topLeft > 0 || borderRadius.topRight > 0 || borderRadius.bottomRight > 0 || borderRadius.bottomLeft > 0) {
|
|
1609
1764
|
applyClip(ctx, imgX, imgY, imgW, imgH, borderRadius);
|
|
1610
1765
|
}
|
|
@@ -1629,6 +1784,10 @@ async function drawNodeInner(ctx, node, parentX, parentY, offsetX, offsetY, debu
|
|
|
1629
1784
|
}
|
|
1630
1785
|
ctx.restore();
|
|
1631
1786
|
}
|
|
1787
|
+
function parseCSSLength(value, referenceSize) {
|
|
1788
|
+
if (value.endsWith("%")) return parseFloat(value) / 100 * referenceSize;
|
|
1789
|
+
return parseFloat(value);
|
|
1790
|
+
}
|
|
1632
1791
|
function applyTransform(ctx, transform, x, y, width, height, transformOrigin) {
|
|
1633
1792
|
let ox = x + width / 2;
|
|
1634
1793
|
let oy = y + height / 2;
|
|
@@ -1645,8 +1804,11 @@ function applyTransform(ctx, transform, x, y, width, height, transformOrigin) {
|
|
|
1645
1804
|
case "translate":
|
|
1646
1805
|
case "translateX":
|
|
1647
1806
|
case "translateY": {
|
|
1648
|
-
const tx = name === "translateY" ? 0 :
|
|
1649
|
-
const ty = name === "translateX" ? 0 :
|
|
1807
|
+
const tx = name === "translateY" ? 0 : parseCSSLength(values[0], width);
|
|
1808
|
+
const ty = name === "translateX" ? 0 : parseCSSLength(
|
|
1809
|
+
values[name === "translate" ? 1 : 0] ?? "0",
|
|
1810
|
+
height
|
|
1811
|
+
);
|
|
1650
1812
|
ctx.translate(tx, ty);
|
|
1651
1813
|
break;
|
|
1652
1814
|
}
|
|
@@ -1682,8 +1844,7 @@ function resolveOrigin(value, base, size) {
|
|
|
1682
1844
|
if (value === "left" || value === "top") return base;
|
|
1683
1845
|
if (value === "right" || value === "bottom") return base + size;
|
|
1684
1846
|
if (value === "center") return base + size / 2;
|
|
1685
|
-
|
|
1686
|
-
return base + parseFloat(value);
|
|
1847
|
+
return base + parseCSSLength(value, size);
|
|
1687
1848
|
}
|
|
1688
1849
|
function parseAngle(value) {
|
|
1689
1850
|
if (value.endsWith("deg")) return parseFloat(value) * Math.PI / 180;
|
|
@@ -1691,7 +1852,7 @@ function parseAngle(value) {
|
|
|
1691
1852
|
if (value.endsWith("turn")) return parseFloat(value) * 2 * Math.PI;
|
|
1692
1853
|
return parseFloat(value);
|
|
1693
1854
|
}
|
|
1694
|
-
function
|
|
1855
|
+
function toNumber(v) {
|
|
1695
1856
|
if (typeof v === "number") return v;
|
|
1696
1857
|
if (v === void 0 || v === null) return 0;
|
|
1697
1858
|
const n = parseFloat(String(v));
|
|
@@ -1742,11 +1903,11 @@ function parseValue(v) {
|
|
|
1742
1903
|
if (v === void 0 || v === null) return void 0;
|
|
1743
1904
|
const s = String(v);
|
|
1744
1905
|
if (s === "auto") return "auto";
|
|
1745
|
-
const n =
|
|
1746
|
-
if (!isNaN(n)) return n;
|
|
1906
|
+
const n = Number(s);
|
|
1907
|
+
if (s !== "" && !isNaN(n)) return n;
|
|
1747
1908
|
return s;
|
|
1748
1909
|
}
|
|
1749
|
-
function expandStyle(raw) {
|
|
1910
|
+
function expandStyle(raw, fontFamilies) {
|
|
1750
1911
|
const style = { ...raw };
|
|
1751
1912
|
if (style.margin !== void 0) {
|
|
1752
1913
|
const sides = parseSides(String(style.margin));
|
|
@@ -1859,7 +2020,14 @@ function expandStyle(raw) {
|
|
|
1859
2020
|
if (style.overflowY === void 0) style.overflowY = style.overflow;
|
|
1860
2021
|
}
|
|
1861
2022
|
if (typeof style.fontFamily === "string") {
|
|
1862
|
-
|
|
2023
|
+
const families = style.fontFamily.split(",").map((s) => s.trim().replace(/^['"]|['"]$/g, "")).filter(Boolean);
|
|
2024
|
+
if (fontFamilies) {
|
|
2025
|
+
const present = new Set(families);
|
|
2026
|
+
for (const name of fontFamilies) {
|
|
2027
|
+
if (!present.has(name)) families.push(name);
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
style.fontFamily = families.join(", ");
|
|
1863
2031
|
}
|
|
1864
2032
|
return style;
|
|
1865
2033
|
}
|
|
@@ -1952,7 +2120,15 @@ var DIMENSION_PROPS = [
|
|
|
1952
2120
|
"paddingLeft",
|
|
1953
2121
|
"rowGap",
|
|
1954
2122
|
"columnGap",
|
|
1955
|
-
"flexBasis"
|
|
2123
|
+
"flexBasis",
|
|
2124
|
+
"borderTopWidth",
|
|
2125
|
+
"borderRightWidth",
|
|
2126
|
+
"borderBottomWidth",
|
|
2127
|
+
"borderLeftWidth",
|
|
2128
|
+
"borderTopLeftRadius",
|
|
2129
|
+
"borderTopRightRadius",
|
|
2130
|
+
"borderBottomRightRadius",
|
|
2131
|
+
"borderBottomLeftRadius"
|
|
1956
2132
|
];
|
|
1957
2133
|
function resolveUnit(value, viewportWidth, viewportHeight, fontSize, rootFontSize) {
|
|
1958
2134
|
if (value.endsWith("%") || value === "auto") return value;
|
|
@@ -2022,8 +2198,41 @@ function resolveUnits(style, viewportWidth, viewportHeight, rootFontSize = DEFAU
|
|
|
2022
2198
|
style[prop] = resolved;
|
|
2023
2199
|
}
|
|
2024
2200
|
}
|
|
2201
|
+
if (style.transform) {
|
|
2202
|
+
style.transform = resolveTransformUnits(
|
|
2203
|
+
style.transform,
|
|
2204
|
+
viewportWidth,
|
|
2205
|
+
viewportHeight,
|
|
2206
|
+
fontSize,
|
|
2207
|
+
rootFontSize
|
|
2208
|
+
);
|
|
2209
|
+
}
|
|
2210
|
+
if (style.transformOrigin) {
|
|
2211
|
+
style.transformOrigin = resolveTransformUnits(
|
|
2212
|
+
style.transformOrigin,
|
|
2213
|
+
viewportWidth,
|
|
2214
|
+
viewportHeight,
|
|
2215
|
+
fontSize,
|
|
2216
|
+
rootFontSize
|
|
2217
|
+
);
|
|
2218
|
+
}
|
|
2025
2219
|
return style;
|
|
2026
2220
|
}
|
|
2221
|
+
function resolveTransformUnits(transform, viewportWidth, viewportHeight, fontSize, rootFontSize) {
|
|
2222
|
+
return transform.replace(
|
|
2223
|
+
/(-?\d*\.?\d+)(vw|vh|vmin|vmax|em|rem|px|pt|pc|in|cm|mm)\b/g,
|
|
2224
|
+
(match) => {
|
|
2225
|
+
const resolved = resolveUnit(
|
|
2226
|
+
match,
|
|
2227
|
+
viewportWidth,
|
|
2228
|
+
viewportHeight,
|
|
2229
|
+
fontSize,
|
|
2230
|
+
rootFontSize
|
|
2231
|
+
);
|
|
2232
|
+
return typeof resolved === "number" ? String(resolved) : match;
|
|
2233
|
+
}
|
|
2234
|
+
);
|
|
2235
|
+
}
|
|
2027
2236
|
|
|
2028
2237
|
// src/jsx/yoga.ts
|
|
2029
2238
|
import Yoga, {
|
|
@@ -2212,16 +2421,18 @@ function applyEdgeValue(node, setter, edge, value) {
|
|
|
2212
2421
|
}
|
|
2213
2422
|
|
|
2214
2423
|
// src/jsx/layout.ts
|
|
2215
|
-
async function buildLayoutTree(element, containerWidth, containerHeight, ctx, emojiEnabled) {
|
|
2424
|
+
async function buildLayoutTree(element, containerWidth, containerHeight, ctx, emojiEnabled, fontFamilies) {
|
|
2216
2425
|
const rootYogaNode = createYogaNode();
|
|
2426
|
+
const rootStyle = fontFamilies?.length ? { ...DEFAULT_STYLE, fontFamily: fontFamilies.join(", ") } : DEFAULT_STYLE;
|
|
2217
2427
|
const rootNode = await buildNode(
|
|
2218
2428
|
element,
|
|
2219
|
-
|
|
2429
|
+
rootStyle,
|
|
2220
2430
|
rootYogaNode,
|
|
2221
2431
|
containerWidth,
|
|
2222
2432
|
containerHeight,
|
|
2223
2433
|
ctx,
|
|
2224
|
-
emojiEnabled
|
|
2434
|
+
emojiEnabled,
|
|
2435
|
+
fontFamilies
|
|
2225
2436
|
);
|
|
2226
2437
|
rootYogaNode.setWidth(containerWidth);
|
|
2227
2438
|
rootYogaNode.setHeight(containerHeight);
|
|
@@ -2230,7 +2441,7 @@ async function buildLayoutTree(element, containerWidth, containerHeight, ctx, em
|
|
|
2230
2441
|
freeYogaNode(rootYogaNode);
|
|
2231
2442
|
return layoutTree;
|
|
2232
2443
|
}
|
|
2233
|
-
async function buildNode(element, parentStyle, yogaNode, viewportWidth, viewportHeight, ctx, emojiEnabled) {
|
|
2444
|
+
async function buildNode(element, parentStyle, yogaNode, viewportWidth, viewportHeight, ctx, emojiEnabled, fontFamilies) {
|
|
2234
2445
|
if (element === null || element === void 0 || typeof element === "boolean") {
|
|
2235
2446
|
return {
|
|
2236
2447
|
type: "empty",
|
|
@@ -2271,7 +2482,8 @@ async function buildNode(element, parentStyle, yogaNode, viewportWidth, viewport
|
|
|
2271
2482
|
viewportWidth,
|
|
2272
2483
|
viewportHeight,
|
|
2273
2484
|
ctx,
|
|
2274
|
-
emojiEnabled
|
|
2485
|
+
emojiEnabled,
|
|
2486
|
+
fontFamilies
|
|
2275
2487
|
)
|
|
2276
2488
|
);
|
|
2277
2489
|
}
|
|
@@ -2296,12 +2508,13 @@ async function buildNode(element, parentStyle, yogaNode, viewportWidth, viewport
|
|
|
2296
2508
|
viewportWidth,
|
|
2297
2509
|
viewportHeight,
|
|
2298
2510
|
ctx,
|
|
2299
|
-
emojiEnabled
|
|
2511
|
+
emojiEnabled,
|
|
2512
|
+
fontFamilies
|
|
2300
2513
|
);
|
|
2301
2514
|
}
|
|
2302
2515
|
const props = el.props ?? {};
|
|
2303
2516
|
const rawStyle = props.style ?? {};
|
|
2304
|
-
const expanded = expandStyle(rawStyle);
|
|
2517
|
+
const expanded = expandStyle(rawStyle, fontFamilies);
|
|
2305
2518
|
const style = resolveStyle(expanded, parentStyle);
|
|
2306
2519
|
resolveUnits(style, viewportWidth, viewportHeight);
|
|
2307
2520
|
const tagName = String(type);
|
|
@@ -2419,7 +2632,8 @@ async function buildNode(element, parentStyle, yogaNode, viewportWidth, viewport
|
|
|
2419
2632
|
viewportWidth,
|
|
2420
2633
|
viewportHeight,
|
|
2421
2634
|
ctx,
|
|
2422
|
-
emojiEnabled
|
|
2635
|
+
emojiEnabled,
|
|
2636
|
+
fontFamilies
|
|
2423
2637
|
)
|
|
2424
2638
|
);
|
|
2425
2639
|
}
|
|
@@ -2487,12 +2701,14 @@ async function renderReactElement(ctx, element, options) {
|
|
|
2487
2701
|
const width = ctx.canvas.width;
|
|
2488
2702
|
const height = ctx.canvas.height;
|
|
2489
2703
|
const emojiStyle = options.emoji === "none" ? void 0 : options.emoji ?? "twemoji";
|
|
2704
|
+
const fontFamilies = [...new Set(options.fonts.map((f) => f.name))];
|
|
2490
2705
|
const layoutTree = await buildLayoutTree(
|
|
2491
2706
|
element,
|
|
2492
2707
|
width,
|
|
2493
2708
|
height,
|
|
2494
2709
|
ctx,
|
|
2495
|
-
!!emojiStyle
|
|
2710
|
+
!!emojiStyle,
|
|
2711
|
+
fontFamilies
|
|
2496
2712
|
);
|
|
2497
2713
|
await drawNode(ctx, layoutTree, 0, 0, options.debug ?? false, emojiStyle);
|
|
2498
2714
|
}
|