@effing/canvas 0.22.1 → 0.22.3
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.d.ts +1 -1
- package/dist/index.js +116 -40
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -23,6 +23,69 @@ function renderLottieFrame(ctx, animation, frame) {
|
|
|
23
23
|
// src/jsx/draw/index.ts
|
|
24
24
|
import { loadImage as loadImage3 } from "@napi-rs/canvas";
|
|
25
25
|
|
|
26
|
+
// src/jsx/font.ts
|
|
27
|
+
import { GlobalFonts } from "@napi-rs/canvas";
|
|
28
|
+
|
|
29
|
+
// src/jsx/font-metrics.ts
|
|
30
|
+
function parseFontMetrics(data) {
|
|
31
|
+
const buf = data instanceof ArrayBuffer ? data : data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
32
|
+
const view = new DataView(buf);
|
|
33
|
+
if (buf.byteLength < 12) return null;
|
|
34
|
+
const numTables = view.getUint16(4);
|
|
35
|
+
if (buf.byteLength < 12 + numTables * 16) return null;
|
|
36
|
+
let headOffset = -1;
|
|
37
|
+
let os2Offset = -1;
|
|
38
|
+
for (let i = 0; i < numTables; i++) {
|
|
39
|
+
const recordOffset = 12 + i * 16;
|
|
40
|
+
const tag = String.fromCharCode(view.getUint8(recordOffset)) + String.fromCharCode(view.getUint8(recordOffset + 1)) + String.fromCharCode(view.getUint8(recordOffset + 2)) + String.fromCharCode(view.getUint8(recordOffset + 3));
|
|
41
|
+
const offset = view.getUint32(recordOffset + 8);
|
|
42
|
+
if (tag === "head") headOffset = offset;
|
|
43
|
+
else if (tag === "OS/2") os2Offset = offset;
|
|
44
|
+
}
|
|
45
|
+
if (headOffset < 0 || os2Offset < 0) return null;
|
|
46
|
+
if (buf.byteLength < headOffset + 20) return null;
|
|
47
|
+
const unitsPerEm = view.getUint16(headOffset + 18);
|
|
48
|
+
if (buf.byteLength < os2Offset + 74) return null;
|
|
49
|
+
const sTypoAscender = view.getInt16(os2Offset + 68);
|
|
50
|
+
const sTypoDescender = view.getInt16(os2Offset + 70);
|
|
51
|
+
const sTypoLineGap = view.getInt16(os2Offset + 72);
|
|
52
|
+
return { unitsPerEm, sTypoAscender, sTypoDescender, sTypoLineGap };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/jsx/font.ts
|
|
56
|
+
var registeredFonts = /* @__PURE__ */ new Set();
|
|
57
|
+
var metricsCache = /* @__PURE__ */ new Map();
|
|
58
|
+
function registerFont(font) {
|
|
59
|
+
const key = `${font.name}:${font.weight}:${font.style}`;
|
|
60
|
+
if (registeredFonts.has(key)) return;
|
|
61
|
+
const buffer = Buffer.isBuffer(font.data) ? font.data : Buffer.from(font.data);
|
|
62
|
+
GlobalFonts.register(buffer, font.name);
|
|
63
|
+
const metrics = parseFontMetrics(font.data);
|
|
64
|
+
if (metrics) {
|
|
65
|
+
metricsCache.set(key, metrics);
|
|
66
|
+
}
|
|
67
|
+
registeredFonts.add(key);
|
|
68
|
+
}
|
|
69
|
+
function getFontMetrics(family, weight, style) {
|
|
70
|
+
const exact = metricsCache.get(`${family}:${weight}:${style}`);
|
|
71
|
+
if (exact) return exact;
|
|
72
|
+
for (const [key, metrics] of metricsCache) {
|
|
73
|
+
if (key.startsWith(`${family}:`)) return metrics;
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
function registerFontFromPath(path, nameAlias) {
|
|
78
|
+
GlobalFonts.registerFromPath(path, nameAlias ?? "");
|
|
79
|
+
}
|
|
80
|
+
function registeredFamilies() {
|
|
81
|
+
return GlobalFonts.families.map((f) => f.family);
|
|
82
|
+
}
|
|
83
|
+
function ensureFontsRegistered(fonts) {
|
|
84
|
+
for (const font of fonts) {
|
|
85
|
+
registerFont(font);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
26
89
|
// src/jsx/language.ts
|
|
27
90
|
function isEmoji(char) {
|
|
28
91
|
const cp = char.codePointAt(0);
|
|
@@ -110,12 +173,19 @@ function measureText(text, fontSize, fontFamily, fontWeight = 400, fontStyle = "
|
|
|
110
173
|
height: ascent + descent
|
|
111
174
|
};
|
|
112
175
|
}
|
|
113
|
-
function measureTrimMetrics(fontSize, fontFamily, fontWeight, fontStyle, lineHeight, edge, ctx) {
|
|
176
|
+
function measureTrimMetrics(fontSize, fontFamily, fontWeight, fontStyle, lineHeight, edge, ctx, fontMetrics) {
|
|
114
177
|
const c = ctx ?? getScratchCtx();
|
|
115
178
|
setFont(c, fontSize, fontFamily, fontWeight, fontStyle);
|
|
116
179
|
const refMetrics = c.measureText("M");
|
|
117
|
-
|
|
118
|
-
|
|
180
|
+
let fontAscent;
|
|
181
|
+
let fontDescent;
|
|
182
|
+
if (fontMetrics) {
|
|
183
|
+
fontAscent = fontMetrics.sTypoAscender / fontMetrics.unitsPerEm * fontSize;
|
|
184
|
+
fontDescent = -fontMetrics.sTypoDescender / fontMetrics.unitsPerEm * fontSize;
|
|
185
|
+
} else {
|
|
186
|
+
fontAscent = refMetrics.fontBoundingBoxAscent ?? refMetrics.actualBoundingBoxAscent ?? fontSize * 0.8;
|
|
187
|
+
fontDescent = refMetrics.fontBoundingBoxDescent ?? refMetrics.actualBoundingBoxDescent ?? fontSize * 0.2;
|
|
188
|
+
}
|
|
119
189
|
const parts = edge.trim().split(/\s+/);
|
|
120
190
|
const overEdge = parts[0] ?? "text";
|
|
121
191
|
const underEdge = parts.length > 1 ? parts[1] : overEdge;
|
|
@@ -230,10 +300,12 @@ function layoutText(text, style, maxWidth, ctx, emojiEnabled) {
|
|
|
230
300
|
fontStyle,
|
|
231
301
|
ctx
|
|
232
302
|
);
|
|
303
|
+
const fontMetrics = getFontMetrics(fontFamily, fontWeight, fontStyle);
|
|
233
304
|
const lineHeightPx = resolveLineHeight(
|
|
234
305
|
style.lineHeight,
|
|
235
306
|
fontSize,
|
|
236
|
-
refMetrics
|
|
307
|
+
refMetrics,
|
|
308
|
+
fontMetrics
|
|
237
309
|
);
|
|
238
310
|
const letterSpacing = typeof style.letterSpacing === "number" ? style.letterSpacing : 0;
|
|
239
311
|
const whiteSpace = style.whiteSpace ?? "normal";
|
|
@@ -338,10 +410,18 @@ function layoutText(text, style, maxWidth, ctx, emojiEnabled) {
|
|
|
338
410
|
fontStyle,
|
|
339
411
|
ctx
|
|
340
412
|
);
|
|
413
|
+
let baselineY;
|
|
414
|
+
if (fontMetrics) {
|
|
415
|
+
const typoAscent = fontMetrics.sTypoAscender / fontMetrics.unitsPerEm * fontSize;
|
|
416
|
+
const typoDescent = -fontMetrics.sTypoDescender / fontMetrics.unitsPerEm * fontSize;
|
|
417
|
+
baselineY = totalHeight + (lineHeightPx + typoAscent - typoDescent) / 2;
|
|
418
|
+
} else {
|
|
419
|
+
baselineY = totalHeight + (lineHeightPx + metrics.ascent - metrics.descent) / 2;
|
|
420
|
+
}
|
|
341
421
|
segments.push({
|
|
342
422
|
text: line,
|
|
343
423
|
x,
|
|
344
|
-
y:
|
|
424
|
+
y: baselineY,
|
|
345
425
|
width: lineWidth,
|
|
346
426
|
height: lineHeightPx,
|
|
347
427
|
fontSize,
|
|
@@ -367,7 +447,8 @@ function layoutText(text, style, maxWidth, ctx, emojiEnabled) {
|
|
|
367
447
|
fontStyle,
|
|
368
448
|
lineHeightPx,
|
|
369
449
|
textBoxEdge,
|
|
370
|
-
ctx
|
|
450
|
+
ctx,
|
|
451
|
+
fontMetrics
|
|
371
452
|
);
|
|
372
453
|
if (textBoxTrim === "trim-start" || textBoxTrim === "trim-both") {
|
|
373
454
|
for (const seg of segments) {
|
|
@@ -385,9 +466,13 @@ function layoutText(text, style, maxWidth, ctx, emojiEnabled) {
|
|
|
385
466
|
height: totalHeight
|
|
386
467
|
};
|
|
387
468
|
}
|
|
388
|
-
function resolveLineHeight(lineHeight, fontSize,
|
|
469
|
+
function resolveLineHeight(lineHeight, fontSize, canvasMetrics, fontMetrics) {
|
|
389
470
|
if (lineHeight === void 0 || lineHeight === "normal") {
|
|
390
|
-
|
|
471
|
+
if (fontMetrics) {
|
|
472
|
+
const { sTypoAscender, sTypoDescender, sTypoLineGap, unitsPerEm } = fontMetrics;
|
|
473
|
+
return (sTypoAscender - sTypoDescender + sTypoLineGap) / unitsPerEm * fontSize;
|
|
474
|
+
}
|
|
475
|
+
return canvasMetrics ? canvasMetrics.ascent + canvasMetrics.descent : fontSize * 1.2;
|
|
391
476
|
}
|
|
392
477
|
if (typeof lineHeight === "number") {
|
|
393
478
|
return lineHeight > 5 ? lineHeight : lineHeight * fontSize;
|
|
@@ -764,8 +849,7 @@ function acquireOffscreen(w, h) {
|
|
|
764
849
|
const canvas2 = ref.deref();
|
|
765
850
|
if (canvas2) {
|
|
766
851
|
const ctx = canvas2.getContext("2d");
|
|
767
|
-
ctx.
|
|
768
|
-
ctx.clearRect(0, 0, w, h);
|
|
852
|
+
ctx.reset();
|
|
769
853
|
return [canvas2, ctx];
|
|
770
854
|
}
|
|
771
855
|
}
|
|
@@ -1618,9 +1702,11 @@ function drawSvgChild(ctx, child, inherited, color, defs = EMPTY_DEFS, vbW = 0,
|
|
|
1618
1702
|
if (type === "defs" || type === "clipPath" || type === "mask" || type === "filter")
|
|
1619
1703
|
return;
|
|
1620
1704
|
const transform = type !== "g" ? props.transform : void 0;
|
|
1621
|
-
|
|
1705
|
+
const opacity = type !== "g" ? Number(props.opacity ?? 1) : 1;
|
|
1706
|
+
if (transform || opacity < 1) {
|
|
1622
1707
|
ctx.save();
|
|
1623
|
-
applySvgTransform(ctx, transform);
|
|
1708
|
+
if (transform) applySvgTransform(ctx, transform);
|
|
1709
|
+
if (opacity < 1) ctx.globalAlpha *= opacity;
|
|
1624
1710
|
}
|
|
1625
1711
|
const clipRef = parseUrlRef(props.clipPath ?? props["clip-path"]);
|
|
1626
1712
|
const clipShapes = clipRef ? defs.clips.get(clipRef) : void 0;
|
|
@@ -1701,7 +1787,7 @@ function drawSvgChild(ctx, child, inherited, color, defs = EMPTY_DEFS, vbW = 0,
|
|
|
1701
1787
|
releaseOffscreen(elemCanvas);
|
|
1702
1788
|
}
|
|
1703
1789
|
if (clipPath) ctx.restore();
|
|
1704
|
-
if (transform) ctx.restore();
|
|
1790
|
+
if (transform || opacity < 1) ctx.restore();
|
|
1705
1791
|
}
|
|
1706
1792
|
function drawPath(ctx, props, inherited, color, defs) {
|
|
1707
1793
|
const d = props.d;
|
|
@@ -1869,17 +1955,19 @@ function drawGroup(ctx, node, inherited, color, defs = EMPTY_DEFS, vbW = 0, vbH
|
|
|
1869
1955
|
strokeLinejoin: merged.strokeLinejoin ?? merged["stroke-linejoin"] ?? inherited.strokeLinejoin,
|
|
1870
1956
|
strokeOpacity: merged.strokeOpacity ?? merged["stroke-opacity"] ?? inherited.strokeOpacity
|
|
1871
1957
|
};
|
|
1958
|
+
const opacity = Number(merged.opacity ?? 1);
|
|
1872
1959
|
const transform = merged.transform;
|
|
1873
|
-
if (transform) {
|
|
1960
|
+
if (transform || opacity < 1) {
|
|
1874
1961
|
ctx.save();
|
|
1875
|
-
applySvgTransform(ctx, transform);
|
|
1962
|
+
if (transform) applySvgTransform(ctx, transform);
|
|
1963
|
+
if (opacity < 1) ctx.globalAlpha *= opacity;
|
|
1876
1964
|
}
|
|
1877
1965
|
for (const child of children) {
|
|
1878
1966
|
if (child != null && typeof child === "object") {
|
|
1879
1967
|
drawSvgChild(ctx, child, groupInherited, color, defs, vbW, vbH);
|
|
1880
1968
|
}
|
|
1881
1969
|
}
|
|
1882
|
-
if (transform) {
|
|
1970
|
+
if (transform || opacity < 1) {
|
|
1883
1971
|
ctx.restore();
|
|
1884
1972
|
}
|
|
1885
1973
|
}
|
|
@@ -2521,28 +2609,6 @@ function parseAngle(value) {
|
|
|
2521
2609
|
return parseFloat(value);
|
|
2522
2610
|
}
|
|
2523
2611
|
|
|
2524
|
-
// src/jsx/font.ts
|
|
2525
|
-
import { GlobalFonts } from "@napi-rs/canvas";
|
|
2526
|
-
var registeredFonts = /* @__PURE__ */ new Set();
|
|
2527
|
-
function registerFont(font) {
|
|
2528
|
-
const key = `${font.name}:${font.weight}:${font.style}`;
|
|
2529
|
-
if (registeredFonts.has(key)) return;
|
|
2530
|
-
const buffer = Buffer.isBuffer(font.data) ? font.data : Buffer.from(font.data);
|
|
2531
|
-
GlobalFonts.register(buffer, font.name);
|
|
2532
|
-
registeredFonts.add(key);
|
|
2533
|
-
}
|
|
2534
|
-
function registerFontFromPath(path, nameAlias) {
|
|
2535
|
-
GlobalFonts.registerFromPath(path, nameAlias ?? "");
|
|
2536
|
-
}
|
|
2537
|
-
function registeredFamilies() {
|
|
2538
|
-
return GlobalFonts.families.map((f) => f.family);
|
|
2539
|
-
}
|
|
2540
|
-
function ensureFontsRegistered(fonts) {
|
|
2541
|
-
for (const font of fonts) {
|
|
2542
|
-
registerFont(font);
|
|
2543
|
-
}
|
|
2544
|
-
}
|
|
2545
|
-
|
|
2546
2612
|
// src/jsx/layout.ts
|
|
2547
2613
|
import { loadImage as loadImage4 } from "@napi-rs/canvas";
|
|
2548
2614
|
|
|
@@ -3219,11 +3285,21 @@ async function buildNode(element, parentStyle, yogaNode, viewportWidth, viewport
|
|
|
3219
3285
|
if (tagName === "svg") {
|
|
3220
3286
|
if (props.width != null && style.width === void 0) {
|
|
3221
3287
|
const v = props.width;
|
|
3222
|
-
|
|
3288
|
+
if (typeof v === "string") {
|
|
3289
|
+
const resolved = resolveUnit(v, viewportWidth, viewportHeight, 16, 16);
|
|
3290
|
+
style.width = typeof resolved === "string" && resolved.endsWith("%") ? resolved : Number(resolved);
|
|
3291
|
+
} else {
|
|
3292
|
+
style.width = Number(v);
|
|
3293
|
+
}
|
|
3223
3294
|
}
|
|
3224
3295
|
if (props.height != null && style.height === void 0) {
|
|
3225
3296
|
const v = props.height;
|
|
3226
|
-
|
|
3297
|
+
if (typeof v === "string") {
|
|
3298
|
+
const resolved = resolveUnit(v, viewportWidth, viewportHeight, 16, 16);
|
|
3299
|
+
style.height = typeof resolved === "string" && resolved.endsWith("%") ? resolved : Number(resolved);
|
|
3300
|
+
} else {
|
|
3301
|
+
style.height = Number(v);
|
|
3302
|
+
}
|
|
3227
3303
|
}
|
|
3228
3304
|
const viewBox = props.viewBox;
|
|
3229
3305
|
if (viewBox) {
|