@effing/canvas 0.22.2 → 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.js +93 -30
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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;
|
|
@@ -2524,28 +2609,6 @@ function parseAngle(value) {
|
|
|
2524
2609
|
return parseFloat(value);
|
|
2525
2610
|
}
|
|
2526
2611
|
|
|
2527
|
-
// src/jsx/font.ts
|
|
2528
|
-
import { GlobalFonts } from "@napi-rs/canvas";
|
|
2529
|
-
var registeredFonts = /* @__PURE__ */ new Set();
|
|
2530
|
-
function registerFont(font) {
|
|
2531
|
-
const key = `${font.name}:${font.weight}:${font.style}`;
|
|
2532
|
-
if (registeredFonts.has(key)) return;
|
|
2533
|
-
const buffer = Buffer.isBuffer(font.data) ? font.data : Buffer.from(font.data);
|
|
2534
|
-
GlobalFonts.register(buffer, font.name);
|
|
2535
|
-
registeredFonts.add(key);
|
|
2536
|
-
}
|
|
2537
|
-
function registerFontFromPath(path, nameAlias) {
|
|
2538
|
-
GlobalFonts.registerFromPath(path, nameAlias ?? "");
|
|
2539
|
-
}
|
|
2540
|
-
function registeredFamilies() {
|
|
2541
|
-
return GlobalFonts.families.map((f) => f.family);
|
|
2542
|
-
}
|
|
2543
|
-
function ensureFontsRegistered(fonts) {
|
|
2544
|
-
for (const font of fonts) {
|
|
2545
|
-
registerFont(font);
|
|
2546
|
-
}
|
|
2547
|
-
}
|
|
2548
|
-
|
|
2549
2612
|
// src/jsx/layout.ts
|
|
2550
2613
|
import { loadImage as loadImage4 } from "@napi-rs/canvas";
|
|
2551
2614
|
|