@effing/canvas 0.24.6 → 0.24.8
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 +96 -24
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -260,12 +260,67 @@ function resolveTransformUnits(transform, viewportWidth, viewportHeight, fontSiz
|
|
|
260
260
|
import { GlobalFonts } from "@napi-rs/canvas";
|
|
261
261
|
|
|
262
262
|
// src/jsx/font-metrics.ts
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
263
|
+
import { inflateSync } from "zlib";
|
|
264
|
+
function fontMetricsToPx(metrics, fontSize) {
|
|
265
|
+
return {
|
|
266
|
+
ascent: metrics.ascender / metrics.unitsPerEm * fontSize,
|
|
267
|
+
descent: -metrics.descender / metrics.unitsPerEm * fontSize
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
var WOFF_SIGNATURE = 2001684038;
|
|
271
|
+
function readWoffTable(view, entry) {
|
|
272
|
+
const compressed = entry.compLength < entry.origLength;
|
|
273
|
+
if (compressed) {
|
|
274
|
+
try {
|
|
275
|
+
const compData = new Uint8Array(
|
|
276
|
+
view.buffer,
|
|
277
|
+
view.byteOffset + entry.offset,
|
|
278
|
+
entry.compLength
|
|
279
|
+
);
|
|
280
|
+
const decompressed = inflateSync(compData);
|
|
281
|
+
return new DataView(
|
|
282
|
+
decompressed.buffer,
|
|
283
|
+
decompressed.byteOffset,
|
|
284
|
+
decompressed.byteLength
|
|
285
|
+
);
|
|
286
|
+
} catch {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return new DataView(
|
|
291
|
+
view.buffer,
|
|
292
|
+
view.byteOffset + entry.offset,
|
|
293
|
+
entry.origLength
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
function parseWoff(view) {
|
|
297
|
+
if (view.byteLength < 44) return null;
|
|
298
|
+
const numTables = view.getUint16(12);
|
|
299
|
+
if (view.byteLength < 44 + numTables * 20) return null;
|
|
300
|
+
let headEntry = null;
|
|
301
|
+
let hheaEntry = null;
|
|
302
|
+
for (let i = 0; i < numTables; i++) {
|
|
303
|
+
const r = 44 + i * 20;
|
|
304
|
+
const tag = String.fromCharCode(view.getUint8(r)) + String.fromCharCode(view.getUint8(r + 1)) + String.fromCharCode(view.getUint8(r + 2)) + String.fromCharCode(view.getUint8(r + 3));
|
|
305
|
+
const offset = view.getUint32(r + 4);
|
|
306
|
+
const compLength = view.getUint32(r + 8);
|
|
307
|
+
const origLength = view.getUint32(r + 12);
|
|
308
|
+
if (tag === "head") headEntry = { offset, compLength, origLength };
|
|
309
|
+
else if (tag === "hhea") hheaEntry = { offset, compLength, origLength };
|
|
310
|
+
}
|
|
311
|
+
if (!headEntry || !hheaEntry) return null;
|
|
312
|
+
const headView = readWoffTable(view, headEntry);
|
|
313
|
+
const hheaView = readWoffTable(view, hheaEntry);
|
|
314
|
+
if (!headView || !hheaView) return null;
|
|
315
|
+
if (headView.byteLength < 20 || hheaView.byteLength < 8) return null;
|
|
316
|
+
const unitsPerEm = headView.getUint16(18);
|
|
317
|
+
const ascender = hheaView.getInt16(4);
|
|
318
|
+
const descender = hheaView.getInt16(6);
|
|
319
|
+
return { unitsPerEm, ascender, descender };
|
|
320
|
+
}
|
|
321
|
+
function parseSfnt(view) {
|
|
267
322
|
const numTables = view.getUint16(4);
|
|
268
|
-
if (
|
|
323
|
+
if (view.byteLength < 12 + numTables * 16) return null;
|
|
269
324
|
let headOffset = -1;
|
|
270
325
|
let hheaOffset = -1;
|
|
271
326
|
for (let i = 0; i < numTables; i++) {
|
|
@@ -276,13 +331,23 @@ function parseFontMetrics(data) {
|
|
|
276
331
|
else if (tag === "hhea") hheaOffset = offset;
|
|
277
332
|
}
|
|
278
333
|
if (headOffset < 0 || hheaOffset < 0) return null;
|
|
279
|
-
if (
|
|
334
|
+
if (view.byteLength < headOffset + 20) return null;
|
|
280
335
|
const unitsPerEm = view.getUint16(headOffset + 18);
|
|
281
|
-
if (
|
|
336
|
+
if (view.byteLength < hheaOffset + 8) return null;
|
|
282
337
|
const ascender = view.getInt16(hheaOffset + 4);
|
|
283
338
|
const descender = view.getInt16(hheaOffset + 6);
|
|
284
339
|
return { unitsPerEm, ascender, descender };
|
|
285
340
|
}
|
|
341
|
+
function parseFontMetrics(data) {
|
|
342
|
+
const buf = data instanceof ArrayBuffer ? data : data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
343
|
+
const view = new DataView(buf);
|
|
344
|
+
if (buf.byteLength < 12) return null;
|
|
345
|
+
const signature = view.getUint32(0);
|
|
346
|
+
if (signature === WOFF_SIGNATURE) {
|
|
347
|
+
return parseWoff(view);
|
|
348
|
+
}
|
|
349
|
+
return parseSfnt(view);
|
|
350
|
+
}
|
|
286
351
|
|
|
287
352
|
// src/jsx/font.ts
|
|
288
353
|
var registeredFonts = /* @__PURE__ */ new Set();
|
|
@@ -299,10 +364,13 @@ function registerFont(font) {
|
|
|
299
364
|
registeredFonts.add(key);
|
|
300
365
|
}
|
|
301
366
|
function getFontMetrics(family, weight, style) {
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
if (
|
|
367
|
+
const families = family.split(",").map((f) => f.trim().replace(/^['"]|['"]$/g, ""));
|
|
368
|
+
for (const name of families) {
|
|
369
|
+
const exact = metricsCache.get(`${name}:${weight}:${style}`);
|
|
370
|
+
if (exact) return exact;
|
|
371
|
+
for (const [key, metrics] of metricsCache) {
|
|
372
|
+
if (key.startsWith(`${name}:`)) return metrics;
|
|
373
|
+
}
|
|
306
374
|
}
|
|
307
375
|
return null;
|
|
308
376
|
}
|
|
@@ -412,8 +480,9 @@ function measureTrimMetrics(fontSize, fontFamily, fontWeight, fontStyle, lineHei
|
|
|
412
480
|
let fontAscent;
|
|
413
481
|
let fontDescent;
|
|
414
482
|
if (fontMetrics) {
|
|
415
|
-
|
|
416
|
-
|
|
483
|
+
const px = fontMetricsToPx(fontMetrics, fontSize);
|
|
484
|
+
fontAscent = px.ascent;
|
|
485
|
+
fontDescent = px.descent;
|
|
417
486
|
} else {
|
|
418
487
|
fontAscent = refMetrics.fontBoundingBoxAscent ?? refMetrics.actualBoundingBoxAscent ?? fontSize * 0.8;
|
|
419
488
|
fontDescent = refMetrics.fontBoundingBoxDescent ?? refMetrics.actualBoundingBoxDescent ?? fontSize * 0.2;
|
|
@@ -637,7 +706,8 @@ function layoutText(text, style, maxWidth, ctx, emojiEnabled) {
|
|
|
637
706
|
let totalHeight = 0;
|
|
638
707
|
let maxLineWidth = 0;
|
|
639
708
|
const isAutoLineHeight = style.lineHeight === void 0 || style.lineHeight === "normal";
|
|
640
|
-
|
|
709
|
+
const fontPx = fontMetrics ? fontMetricsToPx(fontMetrics, fontSize) : null;
|
|
710
|
+
let lastPositioningDescent;
|
|
641
711
|
for (let i = 0; i < lines.length; i++) {
|
|
642
712
|
const line = lines[i];
|
|
643
713
|
const lineWidth = measure(line);
|
|
@@ -655,14 +725,16 @@ function layoutText(text, style, maxWidth, ctx, emojiEnabled) {
|
|
|
655
725
|
fontStyle,
|
|
656
726
|
ctx
|
|
657
727
|
);
|
|
658
|
-
|
|
659
|
-
const
|
|
660
|
-
|
|
661
|
-
|
|
728
|
+
const positioningAscent = fontPx?.ascent ?? metrics.ascent;
|
|
729
|
+
const positioningDescent = fontPx?.descent ?? metrics.descent;
|
|
730
|
+
lastPositioningDescent = positioningDescent;
|
|
731
|
+
const contentHeight = positioningAscent + positioningDescent;
|
|
732
|
+
let effectiveAscent = positioningAscent;
|
|
733
|
+
let effectiveDescent = positioningDescent;
|
|
662
734
|
if (isAutoLineHeight && contentHeight > lineHeightPx) {
|
|
663
735
|
const scale = lineHeightPx / contentHeight;
|
|
664
|
-
effectiveAscent =
|
|
665
|
-
effectiveDescent =
|
|
736
|
+
effectiveAscent = positioningAscent * scale;
|
|
737
|
+
effectiveDescent = positioningDescent * scale;
|
|
666
738
|
}
|
|
667
739
|
const baselineY = totalHeight + (lineHeightPx + effectiveAscent - effectiveDescent) / 2;
|
|
668
740
|
segments.push({
|
|
@@ -686,9 +758,9 @@ function layoutText(text, style, maxWidth, ctx, emojiEnabled) {
|
|
|
686
758
|
totalHeight += lineHeightPx;
|
|
687
759
|
maxLineWidth = Math.max(maxLineWidth, lineWidth);
|
|
688
760
|
}
|
|
689
|
-
if (isAutoLineHeight &&
|
|
761
|
+
if (isAutoLineHeight && lastPositioningDescent !== void 0 && segments.length > 0) {
|
|
690
762
|
const lastSeg = segments[segments.length - 1];
|
|
691
|
-
const descentOverflow = lastSeg.y +
|
|
763
|
+
const descentOverflow = lastSeg.y + lastPositioningDescent - totalHeight;
|
|
692
764
|
if (descentOverflow > 0) {
|
|
693
765
|
totalHeight += descentOverflow;
|
|
694
766
|
}
|
|
@@ -726,8 +798,8 @@ function layoutText(text, style, maxWidth, ctx, emojiEnabled) {
|
|
|
726
798
|
function resolveLineHeight(lineHeight, fontSize, canvasMetrics, fontMetrics) {
|
|
727
799
|
if (lineHeight === void 0 || lineHeight === "normal") {
|
|
728
800
|
if (fontMetrics) {
|
|
729
|
-
const
|
|
730
|
-
return
|
|
801
|
+
const px = fontMetricsToPx(fontMetrics, fontSize);
|
|
802
|
+
return px.ascent + px.descent;
|
|
731
803
|
}
|
|
732
804
|
return canvasMetrics ? canvasMetrics.ascent + canvasMetrics.descent : fontSize * 1.2;
|
|
733
805
|
}
|