@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 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
- const fontAscent = refMetrics.fontBoundingBoxAscent ?? refMetrics.actualBoundingBoxAscent ?? fontSize * 0.8;
118
- const fontDescent = refMetrics.fontBoundingBoxDescent ?? refMetrics.actualBoundingBoxDescent ?? fontSize * 0.2;
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: totalHeight + (lineHeightPx + metrics.ascent - metrics.descent) / 2,
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, metrics) {
469
+ function resolveLineHeight(lineHeight, fontSize, canvasMetrics, fontMetrics) {
389
470
  if (lineHeight === void 0 || lineHeight === "normal") {
390
- return metrics ? metrics.ascent + metrics.descent : fontSize * 1.2;
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