@effing/canvas 0.24.5 → 0.24.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 CHANGED
@@ -267,22 +267,21 @@ function parseFontMetrics(data) {
267
267
  const numTables = view.getUint16(4);
268
268
  if (buf.byteLength < 12 + numTables * 16) return null;
269
269
  let headOffset = -1;
270
- let os2Offset = -1;
270
+ let hheaOffset = -1;
271
271
  for (let i = 0; i < numTables; i++) {
272
272
  const recordOffset = 12 + i * 16;
273
273
  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));
274
274
  const offset = view.getUint32(recordOffset + 8);
275
275
  if (tag === "head") headOffset = offset;
276
- else if (tag === "OS/2") os2Offset = offset;
276
+ else if (tag === "hhea") hheaOffset = offset;
277
277
  }
278
- if (headOffset < 0 || os2Offset < 0) return null;
278
+ if (headOffset < 0 || hheaOffset < 0) return null;
279
279
  if (buf.byteLength < headOffset + 20) return null;
280
280
  const unitsPerEm = view.getUint16(headOffset + 18);
281
- if (buf.byteLength < os2Offset + 74) return null;
282
- const sTypoAscender = view.getInt16(os2Offset + 68);
283
- const sTypoDescender = view.getInt16(os2Offset + 70);
284
- const sTypoLineGap = view.getInt16(os2Offset + 72);
285
- return { unitsPerEm, sTypoAscender, sTypoDescender, sTypoLineGap };
281
+ if (buf.byteLength < hheaOffset + 8) return null;
282
+ const ascender = view.getInt16(hheaOffset + 4);
283
+ const descender = view.getInt16(hheaOffset + 6);
284
+ return { unitsPerEm, ascender, descender };
286
285
  }
287
286
 
288
287
  // src/jsx/font.ts
@@ -413,8 +412,8 @@ function measureTrimMetrics(fontSize, fontFamily, fontWeight, fontStyle, lineHei
413
412
  let fontAscent;
414
413
  let fontDescent;
415
414
  if (fontMetrics) {
416
- fontAscent = fontMetrics.sTypoAscender / fontMetrics.unitsPerEm * fontSize;
417
- fontDescent = -fontMetrics.sTypoDescender / fontMetrics.unitsPerEm * fontSize;
415
+ fontAscent = fontMetrics.ascender / fontMetrics.unitsPerEm * fontSize;
416
+ fontDescent = -fontMetrics.descender / fontMetrics.unitsPerEm * fontSize;
418
417
  } else {
419
418
  fontAscent = refMetrics.fontBoundingBoxAscent ?? refMetrics.actualBoundingBoxAscent ?? fontSize * 0.8;
420
419
  fontDescent = refMetrics.fontBoundingBoxDescent ?? refMetrics.actualBoundingBoxDescent ?? fontSize * 0.2;
@@ -727,8 +726,8 @@ function layoutText(text, style, maxWidth, ctx, emojiEnabled) {
727
726
  function resolveLineHeight(lineHeight, fontSize, canvasMetrics, fontMetrics) {
728
727
  if (lineHeight === void 0 || lineHeight === "normal") {
729
728
  if (fontMetrics) {
730
- const { sTypoAscender, sTypoDescender, sTypoLineGap, unitsPerEm } = fontMetrics;
731
- return (sTypoAscender - sTypoDescender + sTypoLineGap) / unitsPerEm * fontSize;
729
+ const { ascender, descender, unitsPerEm } = fontMetrics;
730
+ return (ascender - descender) / unitsPerEm * fontSize;
732
731
  }
733
732
  return canvasMetrics ? canvasMetrics.ascent + canvasMetrics.descent : fontSize * 1.2;
734
733
  }
@@ -2325,6 +2324,7 @@ function drawSvgContainer(ctx, node, x, y, width, height) {
2325
2324
 
2326
2325
  // src/jsx/draw/text.ts
2327
2326
  import { loadImage as loadImage2 } from "@napi-rs/canvas";
2327
+ import parseCssColor2 from "parse-css-color";
2328
2328
 
2329
2329
  // src/jsx/emoji.ts
2330
2330
  var emojiApis = {
@@ -2440,12 +2440,14 @@ function loadEmojiImage(style, char) {
2440
2440
  return cached;
2441
2441
  }
2442
2442
  async function drawText(ctx, segments, offsetX, offsetY, textShadow, emojiStyle) {
2443
+ const shadow = textShadow ? parseShadow(textShadow) : null;
2443
2444
  for (const seg of segments) {
2444
2445
  if (!seg.text) continue;
2445
2446
  setFont(ctx, seg.fontSize, seg.fontFamily, seg.fontWeight, seg.fontStyle);
2446
2447
  ctx.fillStyle = seg.color;
2447
2448
  const x = offsetX + seg.x;
2448
2449
  const y = offsetY + seg.y;
2450
+ const textAlpha = shadow ? getColorAlpha(seg.color) : 1;
2449
2451
  const hasStroke = seg.textStrokeWidth !== void 0 && seg.textStrokeWidth > 0;
2450
2452
  if (emojiStyle) {
2451
2453
  await drawSegmentWithEmoji(
@@ -2458,6 +2460,14 @@ async function drawText(ctx, segments, offsetX, offsetY, textShadow, emojiStyle)
2458
2460
  hasStroke
2459
2461
  );
2460
2462
  } else if (seg.letterSpacing && seg.letterSpacing !== 0) {
2463
+ if (shadow) {
2464
+ drawShadowPass(
2465
+ ctx,
2466
+ shadow,
2467
+ textAlpha,
2468
+ () => drawTextWithLetterSpacing(ctx, seg.text, x, y, seg.letterSpacing)
2469
+ );
2470
+ }
2461
2471
  if (hasStroke) {
2462
2472
  drawStrokeWithLetterSpacing(
2463
2473
  ctx,
@@ -2471,8 +2481,13 @@ async function drawText(ctx, segments, offsetX, offsetY, textShadow, emojiStyle)
2471
2481
  }
2472
2482
  drawTextWithLetterSpacing(ctx, seg.text, x, y, seg.letterSpacing);
2473
2483
  } else {
2474
- if (textShadow) {
2475
- drawTextShadow(ctx, seg.text, x, y, textShadow);
2484
+ if (shadow) {
2485
+ drawShadowPass(
2486
+ ctx,
2487
+ shadow,
2488
+ textAlpha,
2489
+ () => ctx.fillText(seg.text, x, y)
2490
+ );
2476
2491
  }
2477
2492
  if (hasStroke) {
2478
2493
  ctx.save();
@@ -2500,10 +2515,32 @@ async function drawSegmentWithEmoji(ctx, seg, x, y, textShadow, emojiStyle, hasS
2500
2515
  seg.fontSize,
2501
2516
  letterSpacing
2502
2517
  );
2518
+ const shadow = textShadow ? parseShadow(textShadow) : null;
2519
+ const textAlpha = shadow ? getColorAlpha(seg.color) : 1;
2503
2520
  for (const run of runs) {
2504
2521
  if (run.kind === "text") {
2505
- if (textShadow) {
2506
- drawTextShadow(ctx, run.text, x + run.x, y, textShadow);
2522
+ if (shadow) {
2523
+ if (letterSpacing !== 0) {
2524
+ drawShadowPass(
2525
+ ctx,
2526
+ shadow,
2527
+ textAlpha,
2528
+ () => drawTextWithLetterSpacing(
2529
+ ctx,
2530
+ run.text,
2531
+ x + run.x,
2532
+ y,
2533
+ letterSpacing
2534
+ )
2535
+ );
2536
+ } else {
2537
+ drawShadowPass(
2538
+ ctx,
2539
+ shadow,
2540
+ textAlpha,
2541
+ () => ctx.fillText(run.text, x + run.x, y)
2542
+ );
2543
+ }
2507
2544
  }
2508
2545
  if (hasStroke) {
2509
2546
  if (letterSpacing !== 0) {
@@ -2563,17 +2600,29 @@ function drawStrokeWithLetterSpacing(ctx, text, x, y, letterSpacing, strokeWidth
2563
2600
  }
2564
2601
  ctx.restore();
2565
2602
  }
2566
- function drawTextShadow(ctx, text, x, y, shadow) {
2603
+ function parseShadow(shadow) {
2567
2604
  const parts = shadow.match(
2568
2605
  /(-?\d+(?:\.\d+)?)\s*(?:px)?\s+(-?\d+(?:\.\d+)?)\s*(?:px)?\s+(-?\d+(?:\.\d+)?)\s*(?:px)?\s+(.*)/
2569
2606
  );
2570
- if (!parts) return;
2607
+ if (!parts) return null;
2608
+ return {
2609
+ offsetX: parseFloat(parts[1]),
2610
+ offsetY: parseFloat(parts[2]),
2611
+ blur: parseFloat(parts[3]),
2612
+ color: parts[4].trim()
2613
+ };
2614
+ }
2615
+ function getColorAlpha(color) {
2616
+ const parsed = parseCssColor2(color);
2617
+ return parsed ? parsed.alpha : 1;
2618
+ }
2619
+ function drawShadowPass(ctx, shadow, textAlpha, drawFn) {
2571
2620
  ctx.save();
2572
- ctx.shadowOffsetX = parseFloat(parts[1]);
2573
- ctx.shadowOffsetY = parseFloat(parts[2]);
2574
- ctx.shadowBlur = parseFloat(parts[3]);
2575
- ctx.shadowColor = parts[4].trim();
2576
- ctx.fillText(text, x, y);
2621
+ ctx.fillStyle = shadow.color;
2622
+ if (textAlpha < 1) ctx.globalAlpha *= textAlpha;
2623
+ if (shadow.blur > 0) ctx.filter = `blur(${shadow.blur / 2}px)`;
2624
+ ctx.translate(shadow.offsetX, shadow.offsetY);
2625
+ drawFn();
2577
2626
  ctx.restore();
2578
2627
  }
2579
2628
  function drawTextDecoration(ctx, seg, offsetX, offsetY) {