@effing/canvas 0.42.0 → 0.43.0
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/README.md +31 -24
- package/dist/index.d.ts +8 -6
- package/dist/index.js +500 -1035
- package/dist/index.js.map +1 -1
- package/package.json +3 -4
package/dist/index.js
CHANGED
|
@@ -130,6 +130,7 @@ var INHERITABLE_PROPS = [
|
|
|
130
130
|
"letterSpacing",
|
|
131
131
|
"whiteSpace",
|
|
132
132
|
"wordBreak",
|
|
133
|
+
"overflowWrap",
|
|
133
134
|
"textOverflow",
|
|
134
135
|
"lineClamp",
|
|
135
136
|
"textBoxTrim",
|
|
@@ -320,176 +321,35 @@ function resolveLengthUnits(transform, viewportWidth, viewportHeight, fontSize,
|
|
|
320
321
|
);
|
|
321
322
|
}
|
|
322
323
|
|
|
323
|
-
// src/jsx/
|
|
324
|
-
import {
|
|
325
|
-
|
|
326
|
-
// src/jsx/font-metrics.ts
|
|
327
|
-
import { inflateSync } from "zlib";
|
|
328
|
-
function fontMetricsToPx(metrics, fontSize) {
|
|
329
|
-
return {
|
|
330
|
-
ascent: metrics.ascender / metrics.unitsPerEm * fontSize,
|
|
331
|
-
descent: -metrics.descender / metrics.unitsPerEm * fontSize
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
var WOFF_SIGNATURE = 2001684038;
|
|
335
|
-
function readWoffTable(view, entry) {
|
|
336
|
-
const compressed = entry.compLength < entry.origLength;
|
|
337
|
-
if (compressed) {
|
|
338
|
-
try {
|
|
339
|
-
const compData = new Uint8Array(
|
|
340
|
-
view.buffer,
|
|
341
|
-
view.byteOffset + entry.offset,
|
|
342
|
-
entry.compLength
|
|
343
|
-
);
|
|
344
|
-
const decompressed = inflateSync(compData);
|
|
345
|
-
return new DataView(
|
|
346
|
-
decompressed.buffer,
|
|
347
|
-
decompressed.byteOffset,
|
|
348
|
-
decompressed.byteLength
|
|
349
|
-
);
|
|
350
|
-
} catch {
|
|
351
|
-
return null;
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
return new DataView(
|
|
355
|
-
view.buffer,
|
|
356
|
-
view.byteOffset + entry.offset,
|
|
357
|
-
entry.origLength
|
|
358
|
-
);
|
|
359
|
-
}
|
|
360
|
-
function parseWoff(view) {
|
|
361
|
-
if (view.byteLength < 44) return null;
|
|
362
|
-
const numTables = view.getUint16(12);
|
|
363
|
-
if (view.byteLength < 44 + numTables * 20) return null;
|
|
364
|
-
let headEntry = null;
|
|
365
|
-
let hheaEntry = null;
|
|
366
|
-
for (let i = 0; i < numTables; i++) {
|
|
367
|
-
const r = 44 + i * 20;
|
|
368
|
-
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));
|
|
369
|
-
const offset = view.getUint32(r + 4);
|
|
370
|
-
const compLength = view.getUint32(r + 8);
|
|
371
|
-
const origLength = view.getUint32(r + 12);
|
|
372
|
-
if (tag === "head") headEntry = { offset, compLength, origLength };
|
|
373
|
-
else if (tag === "hhea") hheaEntry = { offset, compLength, origLength };
|
|
374
|
-
}
|
|
375
|
-
if (!headEntry || !hheaEntry) return null;
|
|
376
|
-
const headView = readWoffTable(view, headEntry);
|
|
377
|
-
const hheaView = readWoffTable(view, hheaEntry);
|
|
378
|
-
if (!headView || !hheaView) return null;
|
|
379
|
-
if (headView.byteLength < 20 || hheaView.byteLength < 8) return null;
|
|
380
|
-
const unitsPerEm = headView.getUint16(18);
|
|
381
|
-
const ascender = hheaView.getInt16(4);
|
|
382
|
-
const descender = hheaView.getInt16(6);
|
|
383
|
-
return { unitsPerEm, ascender, descender };
|
|
384
|
-
}
|
|
385
|
-
function parseSfnt(view) {
|
|
386
|
-
const numTables = view.getUint16(4);
|
|
387
|
-
if (view.byteLength < 12 + numTables * 16) return null;
|
|
388
|
-
let headOffset = -1;
|
|
389
|
-
let hheaOffset = -1;
|
|
390
|
-
for (let i = 0; i < numTables; i++) {
|
|
391
|
-
const recordOffset = 12 + i * 16;
|
|
392
|
-
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));
|
|
393
|
-
const offset = view.getUint32(recordOffset + 8);
|
|
394
|
-
if (tag === "head") headOffset = offset;
|
|
395
|
-
else if (tag === "hhea") hheaOffset = offset;
|
|
396
|
-
}
|
|
397
|
-
if (headOffset < 0 || hheaOffset < 0) return null;
|
|
398
|
-
if (view.byteLength < headOffset + 20) return null;
|
|
399
|
-
const unitsPerEm = view.getUint16(headOffset + 18);
|
|
400
|
-
if (view.byteLength < hheaOffset + 8) return null;
|
|
401
|
-
const ascender = view.getInt16(hheaOffset + 4);
|
|
402
|
-
const descender = view.getInt16(hheaOffset + 6);
|
|
403
|
-
return { unitsPerEm, ascender, descender };
|
|
404
|
-
}
|
|
405
|
-
function parseFontMetrics(data) {
|
|
406
|
-
const buf = data instanceof ArrayBuffer ? data : data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
407
|
-
const view = new DataView(buf);
|
|
408
|
-
if (buf.byteLength < 12) return null;
|
|
409
|
-
const signature = view.getUint32(0);
|
|
410
|
-
if (signature === WOFF_SIGNATURE) {
|
|
411
|
-
return parseWoff(view);
|
|
412
|
-
}
|
|
413
|
-
return parseSfnt(view);
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// src/jsx/font.ts
|
|
417
|
-
var registeredFonts = /* @__PURE__ */ new Set();
|
|
418
|
-
var metricsCache = /* @__PURE__ */ new Map();
|
|
419
|
-
function registerFont(font) {
|
|
420
|
-
const key = `${font.name}:${font.weight}:${font.style}`;
|
|
421
|
-
if (registeredFonts.has(key)) return;
|
|
422
|
-
const buffer = Buffer.isBuffer(font.data) ? font.data : Buffer.from(font.data);
|
|
423
|
-
GlobalFonts.register(buffer, font.name);
|
|
424
|
-
const metrics = parseFontMetrics(font.data);
|
|
425
|
-
if (metrics) {
|
|
426
|
-
metricsCache.set(key, metrics);
|
|
427
|
-
}
|
|
428
|
-
registeredFonts.add(key);
|
|
429
|
-
}
|
|
430
|
-
function getFontMetrics(family, weight, style) {
|
|
431
|
-
const families = family.split(",").map((f) => f.trim().replace(/^['"]|['"]$/g, ""));
|
|
432
|
-
for (const name of families) {
|
|
433
|
-
const exact = metricsCache.get(`${name}:${weight}:${style}`);
|
|
434
|
-
if (exact) return exact;
|
|
435
|
-
for (const [key, metrics] of metricsCache) {
|
|
436
|
-
if (key.startsWith(`${name}:`)) return metrics;
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
return null;
|
|
440
|
-
}
|
|
441
|
-
function registerFontFromPath(path, nameAlias) {
|
|
442
|
-
GlobalFonts.registerFromPath(path, nameAlias ?? "");
|
|
443
|
-
}
|
|
444
|
-
function registeredFamilies() {
|
|
445
|
-
return GlobalFonts.families.map((f) => f.family);
|
|
446
|
-
}
|
|
447
|
-
function ensureFontsRegistered(fonts) {
|
|
448
|
-
for (const font of fonts) {
|
|
449
|
-
registerFont(font);
|
|
450
|
-
}
|
|
451
|
-
}
|
|
324
|
+
// src/jsx/text/native.ts
|
|
325
|
+
import { Paragraph } from "@effing/skia/extensions";
|
|
452
326
|
|
|
453
327
|
// src/jsx/language.ts
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
// src/jsx/text/linebreak.ts
|
|
480
|
-
import LineBreaker from "linebreak";
|
|
481
|
-
function findBreakOpportunities(text) {
|
|
482
|
-
const breaker = new LineBreaker(text);
|
|
483
|
-
const opportunities = [];
|
|
484
|
-
let bk = breaker.nextBreak();
|
|
485
|
-
while (bk) {
|
|
486
|
-
opportunities.push({
|
|
487
|
-
position: bk.position,
|
|
488
|
-
required: bk.required ?? false
|
|
489
|
-
});
|
|
490
|
-
bk = breaker.nextBreak();
|
|
491
|
-
}
|
|
492
|
-
return opportunities;
|
|
328
|
+
var UNASSIGNED_PICTOGRAPHIC = String.raw`(?=\p{Cn})\p{Extended_Pictographic}`;
|
|
329
|
+
var EMOJI_CHAR = String.raw`[\p{Emoji}\p{Extended_Pictographic}]`;
|
|
330
|
+
var KEYCAP_BASE = "[0-9#*]";
|
|
331
|
+
var TEXT_EMOJI_CHAR = String.raw`[0-9#*\u00A9\u00AE\u2122]`;
|
|
332
|
+
var TAGS = String.raw`[\u{E0020}-\u{E007E}]+\u{E007F}`;
|
|
333
|
+
var PRESENTED_EMOJI = [
|
|
334
|
+
// Keycap: 1️⃣, and the unqualified 1⃣.
|
|
335
|
+
String.raw`${KEYCAP_BASE}\uFE0F?\u20E3`,
|
|
336
|
+
// Flag: a pair of regional indicators.
|
|
337
|
+
String.raw`\p{Regional_Indicator}{2}`,
|
|
338
|
+
// Emoji presentation sequence (©️), modifier sequence (👍🏽, ☝🏽) or tag
|
|
339
|
+
// sequence (🏴).
|
|
340
|
+
String.raw`(?!${KEYCAP_BASE})${EMOJI_CHAR}(?:\uFE0F|\p{Emoji_Modifier}|${TAGS})`,
|
|
341
|
+
// An emoji character on its own, emoji presentation by default (🌍, ⭐) or
|
|
342
|
+
// text (❤, ☎, 🕵), but not one that stays text.
|
|
343
|
+
String.raw`(?!${TEXT_EMOJI_CHAR})\p{Emoji}`,
|
|
344
|
+
UNASSIGNED_PICTOGRAPHIC
|
|
345
|
+
].join("|");
|
|
346
|
+
var ZWJ_ELEMENT = `(?:${PRESENTED_EMOJI}|(?!${KEYCAP_BASE})${EMOJI_CHAR})`;
|
|
347
|
+
var EMOJI_RE = new RegExp(
|
|
348
|
+
`^(?:${PRESENTED_EMOJI}|${ZWJ_ELEMENT}(?:\\u200D${ZWJ_ELEMENT})+)$`,
|
|
349
|
+
"u"
|
|
350
|
+
);
|
|
351
|
+
function isEmoji(grapheme) {
|
|
352
|
+
return EMOJI_RE.test(grapheme);
|
|
493
353
|
}
|
|
494
354
|
|
|
495
355
|
// src/jsx/text/measure.ts
|
|
@@ -501,16 +361,6 @@ function getScratchCtx() {
|
|
|
501
361
|
}
|
|
502
362
|
return scratchCtx;
|
|
503
363
|
}
|
|
504
|
-
function withUnsnappedText(ctx, fn) {
|
|
505
|
-
const previous = ctx.textRendering;
|
|
506
|
-
if (previous === "geometricPrecision") return fn();
|
|
507
|
-
ctx.textRendering = "geometricPrecision";
|
|
508
|
-
try {
|
|
509
|
-
return fn();
|
|
510
|
-
} finally {
|
|
511
|
-
ctx.textRendering = previous;
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
364
|
var GENERIC_FAMILIES = /* @__PURE__ */ new Set([
|
|
515
365
|
"serif",
|
|
516
366
|
"sans-serif",
|
|
@@ -536,34 +386,18 @@ function quoteFontFamilies(fontFamily) {
|
|
|
536
386
|
function setFont(ctx, fontSize, fontFamily, fontWeight = 400, fontStyle = "normal") {
|
|
537
387
|
ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${quoteFontFamilies(fontFamily)}`;
|
|
538
388
|
}
|
|
539
|
-
function
|
|
540
|
-
const c =
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
389
|
+
function measureTrimMetrics(fontSize, fontFamily, fontWeight, fontStyle, lineHeight, edge, fontAscent, fontDescent) {
|
|
390
|
+
const c = getScratchCtx();
|
|
391
|
+
const measure = (text) => {
|
|
392
|
+
setFont(c, fontSize, fontFamily, fontWeight, fontStyle);
|
|
393
|
+
const previous = c.textRendering;
|
|
394
|
+
c.textRendering = "geometricPrecision";
|
|
395
|
+
try {
|
|
396
|
+
return c.measureText(text);
|
|
397
|
+
} finally {
|
|
398
|
+
c.textRendering = previous;
|
|
399
|
+
}
|
|
550
400
|
};
|
|
551
|
-
}
|
|
552
|
-
function measureTrimMetrics(fontSize, fontFamily, fontWeight, fontStyle, lineHeight, edge, ctx, fontMetrics) {
|
|
553
|
-
const c = ctx ?? getScratchCtx();
|
|
554
|
-
setFont(c, fontSize, fontFamily, fontWeight, fontStyle);
|
|
555
|
-
const measure = (text) => withUnsnappedText(c, () => c.measureText(text));
|
|
556
|
-
const refMetrics = measure("M");
|
|
557
|
-
let fontAscent;
|
|
558
|
-
let fontDescent;
|
|
559
|
-
if (fontMetrics) {
|
|
560
|
-
const px = fontMetricsToPx(fontMetrics, fontSize);
|
|
561
|
-
fontAscent = px.ascent;
|
|
562
|
-
fontDescent = px.descent;
|
|
563
|
-
} else {
|
|
564
|
-
fontAscent = refMetrics.fontBoundingBoxAscent ?? refMetrics.actualBoundingBoxAscent ?? fontSize * 0.8;
|
|
565
|
-
fontDescent = refMetrics.fontBoundingBoxDescent ?? refMetrics.actualBoundingBoxDescent ?? fontSize * 0.2;
|
|
566
|
-
}
|
|
567
401
|
const parts = edge.trim().split(/\s+/);
|
|
568
402
|
const overEdge = parts[0] ?? "text";
|
|
569
403
|
const underEdge = parts.length > 1 ? parts[1] : overEdge;
|
|
@@ -606,32 +440,8 @@ function measureTrimMetrics(fontSize, fontFamily, fontWeight, fontStyle, lineHei
|
|
|
606
440
|
underTrim: Math.max(0, underTrim)
|
|
607
441
|
};
|
|
608
442
|
}
|
|
609
|
-
function measureWord(word, fontSize, fontFamily, fontWeight = 400, fontStyle = "normal", ctx, letterSpacing = 0) {
|
|
610
|
-
const base = measureText(
|
|
611
|
-
word,
|
|
612
|
-
fontSize,
|
|
613
|
-
fontFamily,
|
|
614
|
-
fontWeight,
|
|
615
|
-
fontStyle,
|
|
616
|
-
ctx
|
|
617
|
-
).width;
|
|
618
|
-
if (letterSpacing === 0 || word.length === 0) return base;
|
|
619
|
-
return base + letterSpacing * word.length;
|
|
620
|
-
}
|
|
621
443
|
|
|
622
444
|
// src/jsx/text/native.ts
|
|
623
|
-
import { Paragraph } from "@effing/skia/extensions";
|
|
624
|
-
function canLayoutNatively(text, style, emojiEnabled) {
|
|
625
|
-
if (text === "") return false;
|
|
626
|
-
if (style.wordBreak === "break-all") return false;
|
|
627
|
-
if (style.whiteSpace === "pre" && /[ \t](?:\n|$)/.test(text)) return false;
|
|
628
|
-
if (emojiEnabled) {
|
|
629
|
-
for (const char of text) {
|
|
630
|
-
if (isEmoji(char)) return false;
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
return true;
|
|
634
|
-
}
|
|
635
445
|
function toWeight(weight) {
|
|
636
446
|
if (typeof weight === "number") return weight;
|
|
637
447
|
if (weight === "bold") return 700;
|
|
@@ -654,7 +464,17 @@ var TEXT_ALIGNS = /* @__PURE__ */ new Set([
|
|
|
654
464
|
function toTextAlign(textAlign) {
|
|
655
465
|
return textAlign !== void 0 && TEXT_ALIGNS.has(textAlign) ? textAlign : "left";
|
|
656
466
|
}
|
|
467
|
+
function toWordBreak(style) {
|
|
468
|
+
const { wordBreak } = style;
|
|
469
|
+
return wordBreak === "break-all" || wordBreak === "keep-all" ? wordBreak : "normal";
|
|
470
|
+
}
|
|
471
|
+
function toOverflowWrap(style) {
|
|
472
|
+
const { overflowWrap, wordBreak } = style;
|
|
473
|
+
return overflowWrap === "break-word" || overflowWrap === "anywhere" || wordBreak === "break-word" ? "break-word" : "normal";
|
|
474
|
+
}
|
|
657
475
|
var MIN_WIDTH = 0.01;
|
|
476
|
+
var MIN_FONT_SIZE = 1e-3;
|
|
477
|
+
var EMOJI_DROP = 0.1;
|
|
658
478
|
var MAX_PENDING_PARAGRAPHS = 2e3;
|
|
659
479
|
var pendingParagraphs = 0;
|
|
660
480
|
var turnWatched = false;
|
|
@@ -671,35 +491,104 @@ async function releaseParagraphs() {
|
|
|
671
491
|
if (pendingParagraphs < MAX_PENDING_PARAGRAPHS) return;
|
|
672
492
|
await new Promise((resolve) => setImmediate(resolve));
|
|
673
493
|
}
|
|
674
|
-
|
|
675
|
-
|
|
494
|
+
var graphemeSegmenter = new Intl.Segmenter(void 0, {
|
|
495
|
+
granularity: "grapheme"
|
|
496
|
+
});
|
|
497
|
+
function splitEmoji(text, fontSize, letterSpacing) {
|
|
498
|
+
const items = [];
|
|
499
|
+
const emoji = [];
|
|
500
|
+
const marks = [];
|
|
501
|
+
let run = "";
|
|
502
|
+
let paragraphIndex = 0;
|
|
503
|
+
let textIndex = 0;
|
|
504
|
+
for (const { segment } of graphemeSegmenter.segment(text)) {
|
|
505
|
+
if (!isEmoji(segment)) {
|
|
506
|
+
run += segment;
|
|
507
|
+
paragraphIndex += segment.length;
|
|
508
|
+
textIndex += segment.length;
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
if (run) items.push(run);
|
|
512
|
+
run = "";
|
|
513
|
+
items.push({
|
|
514
|
+
// Letter spacing follows every character, an emoji too, but a
|
|
515
|
+
// paragraph doesn't add it to a placeholder.
|
|
516
|
+
width: Math.max(0, fontSize + letterSpacing),
|
|
517
|
+
height: fontSize,
|
|
518
|
+
verticalAlign: "baseline",
|
|
519
|
+
baselineOffset: fontSize * (1 - EMOJI_DROP),
|
|
520
|
+
// Lines break around it as around an emoji, not an inline-block: it
|
|
521
|
+
// stays with the punctuation next to it, so "Hi 🎉! ok" breaks as
|
|
522
|
+
// "Hi | 🎉! | ok" and "(🎉)" stays whole, as in browsers.
|
|
523
|
+
lineBreak: "emoji"
|
|
524
|
+
});
|
|
525
|
+
emoji.push(segment);
|
|
526
|
+
marks.push([paragraphIndex, textIndex]);
|
|
527
|
+
paragraphIndex += 1;
|
|
528
|
+
textIndex += segment.length;
|
|
529
|
+
marks.push([paragraphIndex, textIndex]);
|
|
530
|
+
}
|
|
531
|
+
if (run) items.push(run);
|
|
532
|
+
if (emoji.length === 0) return { items: [text], emoji };
|
|
533
|
+
return {
|
|
534
|
+
items,
|
|
535
|
+
emoji,
|
|
536
|
+
toTextIndex: (index) => {
|
|
537
|
+
let mark = [0, 0];
|
|
538
|
+
for (const m of marks) {
|
|
539
|
+
if (m[0] > index) break;
|
|
540
|
+
mark = m;
|
|
541
|
+
}
|
|
542
|
+
return mark[1] + (index - mark[0]);
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
function layoutTextNative(text, style, maxWidth, lineHeight, emojiEnabled) {
|
|
547
|
+
const styledFontSize = style.fontSize ?? 16;
|
|
548
|
+
const fontSize = Number.isFinite(styledFontSize) ? Math.max(styledFontSize, MIN_FONT_SIZE) : 16;
|
|
676
549
|
const fontFamily = style.fontFamily ?? DEFAULT_FONT_FAMILY;
|
|
677
550
|
const fontWeight = style.fontWeight ?? 400;
|
|
678
551
|
const fontStyle = style.fontStyle ?? "normal";
|
|
679
|
-
const letterSpacing = typeof style.letterSpacing === "number" ? style.letterSpacing : 0;
|
|
552
|
+
const letterSpacing = typeof style.letterSpacing === "number" && Number.isFinite(style.letterSpacing) ? style.letterSpacing : 0;
|
|
680
553
|
const whiteSpace = style.whiteSpace ?? "normal";
|
|
681
554
|
const noWrap = whiteSpace === "nowrap" || whiteSpace === "pre";
|
|
682
|
-
const lineClamp = style.lineClamp && style.lineClamp
|
|
555
|
+
const lineClamp = style.lineClamp && style.lineClamp >= 1 ? Math.floor(style.lineClamp) : void 0;
|
|
683
556
|
const ellipsis = lineClamp !== void 0 || noWrap && style.textOverflow === "ellipsis" ? "\u2026" : void 0;
|
|
684
557
|
const width = maxWidth > 0 ? maxWidth : MIN_WIDTH;
|
|
685
|
-
const
|
|
558
|
+
const content = emojiEnabled ? splitEmoji(text, fontSize, letterSpacing) : { items: [text], emoji: [] };
|
|
559
|
+
const paragraph = new Paragraph(content.items, {
|
|
686
560
|
fontFamily: quoteFontFamilies(fontFamily),
|
|
687
561
|
fontSize,
|
|
688
562
|
fontWeight: toWeight(fontWeight),
|
|
689
563
|
fontStyle: toFontStyle(fontStyle),
|
|
690
564
|
letterSpacing,
|
|
691
|
-
lineHeight
|
|
565
|
+
lineHeight,
|
|
692
566
|
textAlign: toTextAlign(style.textAlign),
|
|
693
567
|
noWrap,
|
|
694
568
|
maxLines: lineClamp,
|
|
695
|
-
ellipsis
|
|
569
|
+
ellipsis,
|
|
570
|
+
// `pre` and `pre-wrap` keep the spaces before a line break in the line.
|
|
571
|
+
keepTrailingWhitespace: whiteSpace === "pre" || whiteSpace === "pre-wrap",
|
|
572
|
+
wordBreak: toWordBreak(style),
|
|
573
|
+
overflowWrap: toOverflowWrap(style)
|
|
696
574
|
});
|
|
697
575
|
countParagraph();
|
|
698
576
|
const layout = paragraph.layout(width);
|
|
699
|
-
|
|
700
|
-
|
|
577
|
+
const lines = layout.lines.length > 0 ? layout.lines : [
|
|
578
|
+
{
|
|
579
|
+
left: 0,
|
|
580
|
+
width: 0,
|
|
581
|
+
// As Skia places a baseline: by half-leading in the line box.
|
|
582
|
+
baseline: (layout.lineHeight + layout.ascent - layout.descent) / 2,
|
|
583
|
+
startIndex: 0,
|
|
584
|
+
endIndex: 0,
|
|
585
|
+
hardBreak: true
|
|
586
|
+
}
|
|
587
|
+
];
|
|
588
|
+
const toTextIndex = content.toTextIndex ?? ((index) => index);
|
|
589
|
+
const segments = lines.map((line, i) => ({
|
|
701
590
|
// A line that ends the text at a newline reports the newline as its text.
|
|
702
|
-
text: text.slice(line.startIndex, line.endIndex).replace(/\n/g, ""),
|
|
591
|
+
text: text.slice(toTextIndex(line.startIndex), toTextIndex(line.endIndex)).replace(/\n/g, ""),
|
|
703
592
|
x: line.left,
|
|
704
593
|
y: line.baseline,
|
|
705
594
|
width: line.width,
|
|
@@ -714,18 +603,25 @@ function layoutTextNative(text, style, maxWidth, lineHeight) {
|
|
|
714
603
|
letterSpacing,
|
|
715
604
|
lineIndex: i
|
|
716
605
|
}));
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
606
|
+
const emoji = [];
|
|
607
|
+
layout.placeholders.forEach((box, i) => {
|
|
608
|
+
if (!box) return;
|
|
609
|
+
emoji.push({
|
|
610
|
+
grapheme: content.emoji[i],
|
|
611
|
+
x: box.x,
|
|
612
|
+
y: box.y,
|
|
613
|
+
size: fontSize,
|
|
614
|
+
baseline: lines[box.line].baseline
|
|
615
|
+
});
|
|
616
|
+
});
|
|
617
|
+
let height = lines.length * layout.lineHeight;
|
|
618
|
+
if (lineHeight === void 0) {
|
|
619
|
+
const noFontSize = Number.isFinite(styledFontSize) && styledFontSize <= 0;
|
|
620
|
+
height = noFontSize ? 0 : Math.ceil(height);
|
|
720
621
|
}
|
|
721
622
|
let paragraphOffsetY = 0;
|
|
722
623
|
const textBoxTrim = style.textBoxTrim;
|
|
723
|
-
if (textBoxTrim && textBoxTrim !== "none"
|
|
724
|
-
const fontMetrics = {
|
|
725
|
-
unitsPerEm: fontSize,
|
|
726
|
-
ascender: layout.ascent,
|
|
727
|
-
descender: -layout.descent
|
|
728
|
-
};
|
|
624
|
+
if (textBoxTrim && textBoxTrim !== "none") {
|
|
729
625
|
const trim = measureTrimMetrics(
|
|
730
626
|
fontSize,
|
|
731
627
|
fontFamily,
|
|
@@ -733,11 +629,15 @@ function layoutTextNative(text, style, maxWidth, lineHeight) {
|
|
|
733
629
|
fontStyle,
|
|
734
630
|
layout.lineHeight,
|
|
735
631
|
style.textBoxEdge ?? "text",
|
|
736
|
-
|
|
737
|
-
|
|
632
|
+
layout.ascent,
|
|
633
|
+
layout.descent
|
|
738
634
|
);
|
|
739
635
|
if (textBoxTrim === "trim-start" || textBoxTrim === "trim-both") {
|
|
740
636
|
for (const seg of segments) seg.y -= trim.overTrim;
|
|
637
|
+
for (const e of emoji) {
|
|
638
|
+
e.y -= trim.overTrim;
|
|
639
|
+
e.baseline -= trim.overTrim;
|
|
640
|
+
}
|
|
741
641
|
height -= trim.overTrim;
|
|
742
642
|
paragraphOffsetY = -trim.overTrim;
|
|
743
643
|
}
|
|
@@ -750,270 +650,46 @@ function layoutTextNative(text, style, maxWidth, lineHeight) {
|
|
|
750
650
|
width: segments.reduce((w, s) => Math.max(w, s.width), 0),
|
|
751
651
|
height,
|
|
752
652
|
paragraph,
|
|
753
|
-
paragraphOffsetY
|
|
653
|
+
paragraphOffsetY,
|
|
654
|
+
emoji
|
|
754
655
|
};
|
|
755
656
|
}
|
|
756
657
|
|
|
658
|
+
// src/jsx/text/white-space.ts
|
|
659
|
+
function isWhiteSpaceOnly(text) {
|
|
660
|
+
return /^[ \t\n\r\f\v]*$/.test(text);
|
|
661
|
+
}
|
|
662
|
+
function collapseWhiteSpace(text, whiteSpace) {
|
|
663
|
+
let result = text.replace(/[\v\f]/g, "").replace(/\r\n/g, "\n");
|
|
664
|
+
if (whiteSpace === "pre" || whiteSpace === "pre-wrap") {
|
|
665
|
+
result = result.replace(/\r/g, "");
|
|
666
|
+
} else if (/[\t\n\r]| {2}|^ | $/.test(result)) {
|
|
667
|
+
result = result.replace(/[\t\r]/g, " ").replace(/ *\n */g, "\n");
|
|
668
|
+
if (whiteSpace !== "pre-line") result = result.replace(/\n+/g, " ");
|
|
669
|
+
result = result.replace(/ {2,}/g, " ").replace(/^ | $/g, "");
|
|
670
|
+
}
|
|
671
|
+
return result.replace(FINAL_BREAK, "").replace(SEPARATORS, SEPARATOR_SPACE);
|
|
672
|
+
}
|
|
673
|
+
var FINAL_BREAK = /\n$/;
|
|
674
|
+
var SEPARATORS = /[\u2028\u2029]/g;
|
|
675
|
+
var SEPARATOR_SPACE = "\xA0\u200B";
|
|
676
|
+
|
|
757
677
|
// src/jsx/text/index.ts
|
|
758
|
-
function
|
|
759
|
-
const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
760
|
-
let totalWidth = 0;
|
|
761
|
-
let textBuffer = "";
|
|
762
|
-
for (const { segment } of segmenter.segment(word)) {
|
|
763
|
-
let isEmojiSegment = false;
|
|
764
|
-
for (const char of segment) {
|
|
765
|
-
if (isEmoji(char)) {
|
|
766
|
-
isEmojiSegment = true;
|
|
767
|
-
break;
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
if (isEmojiSegment) {
|
|
771
|
-
if (textBuffer) {
|
|
772
|
-
totalWidth += measureWord(
|
|
773
|
-
textBuffer,
|
|
774
|
-
fontSize,
|
|
775
|
-
fontFamily,
|
|
776
|
-
fontWeight,
|
|
777
|
-
fontStyle,
|
|
778
|
-
ctx,
|
|
779
|
-
letterSpacing
|
|
780
|
-
);
|
|
781
|
-
textBuffer = "";
|
|
782
|
-
}
|
|
783
|
-
totalWidth += fontSize;
|
|
784
|
-
} else {
|
|
785
|
-
textBuffer += segment;
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
if (textBuffer) {
|
|
789
|
-
totalWidth += measureWord(
|
|
790
|
-
textBuffer,
|
|
791
|
-
fontSize,
|
|
792
|
-
fontFamily,
|
|
793
|
-
fontWeight,
|
|
794
|
-
fontStyle,
|
|
795
|
-
ctx,
|
|
796
|
-
letterSpacing
|
|
797
|
-
);
|
|
798
|
-
}
|
|
799
|
-
return totalWidth;
|
|
800
|
-
}
|
|
801
|
-
function layoutText(text, style, maxWidth, ctx, emojiEnabled) {
|
|
678
|
+
function layoutText(text, style, maxWidth, emojiEnabled) {
|
|
802
679
|
const fontSize = style.fontSize ?? 16;
|
|
803
|
-
const
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
style,
|
|
810
|
-
maxWidth,
|
|
811
|
-
lineHeight
|
|
812
|
-
);
|
|
813
|
-
if (result) {
|
|
814
|
-
const textStrokeWidth = resolveTextStrokeWidth(style, fontSize);
|
|
815
|
-
for (const seg of result.segments) {
|
|
816
|
-
seg.textStrokeWidth = textStrokeWidth;
|
|
817
|
-
seg.textStrokeColor = style.WebkitTextStrokeColor;
|
|
818
|
-
}
|
|
819
|
-
return result;
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
return layoutTextFallback(text, style, maxWidth, ctx, emojiEnabled);
|
|
823
|
-
}
|
|
824
|
-
function layoutTextFallback(text, style, maxWidth, ctx, emojiEnabled) {
|
|
825
|
-
const fontSize = style.fontSize ?? 16;
|
|
826
|
-
const fontFamily = style.fontFamily ?? DEFAULT_FONT_FAMILY;
|
|
827
|
-
const fontWeight = style.fontWeight ?? 400;
|
|
828
|
-
const fontStyle = style.fontStyle ?? "normal";
|
|
829
|
-
const color = style.color ?? "black";
|
|
830
|
-
const textAlign = style.textAlign ?? "left";
|
|
831
|
-
const refMetrics = measureText(
|
|
832
|
-
"M",
|
|
833
|
-
fontSize,
|
|
834
|
-
fontFamily,
|
|
835
|
-
fontWeight,
|
|
836
|
-
fontStyle,
|
|
837
|
-
ctx
|
|
838
|
-
);
|
|
839
|
-
const fontMetrics = getFontMetrics(fontFamily, fontWeight, fontStyle);
|
|
840
|
-
const lineHeightPx = resolveLineHeight(
|
|
841
|
-
style.lineHeight,
|
|
842
|
-
fontSize,
|
|
843
|
-
refMetrics,
|
|
844
|
-
fontMetrics
|
|
680
|
+
const result = layoutTextNative(
|
|
681
|
+
applyTextTransform(collapseWhiteSpace(text, style.whiteSpace), style),
|
|
682
|
+
style,
|
|
683
|
+
maxWidth,
|
|
684
|
+
resolveLineHeight(style.lineHeight, fontSize),
|
|
685
|
+
emojiEnabled
|
|
845
686
|
);
|
|
846
|
-
const letterSpacing = typeof style.letterSpacing === "number" ? style.letterSpacing : 0;
|
|
847
|
-
const whiteSpace = style.whiteSpace ?? "normal";
|
|
848
|
-
const wordBreak = style.wordBreak ?? "normal";
|
|
849
|
-
const textOverflow = style.textOverflow ?? "clip";
|
|
850
|
-
const textDecoration = style.textDecoration;
|
|
851
687
|
const textStrokeWidth = resolveTextStrokeWidth(style, fontSize);
|
|
852
|
-
const
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
fontSize,
|
|
856
|
-
fontFamily,
|
|
857
|
-
fontWeight,
|
|
858
|
-
fontStyle,
|
|
859
|
-
ctx,
|
|
860
|
-
ls ?? letterSpacing
|
|
861
|
-
) : (word, ls) => measureWord(
|
|
862
|
-
word,
|
|
863
|
-
fontSize,
|
|
864
|
-
fontFamily,
|
|
865
|
-
fontWeight,
|
|
866
|
-
fontStyle,
|
|
867
|
-
ctx,
|
|
868
|
-
ls ?? letterSpacing
|
|
869
|
-
);
|
|
870
|
-
const processedText = applyTextTransform(text, style);
|
|
871
|
-
const noWrap = whiteSpace === "nowrap" || whiteSpace === "pre";
|
|
872
|
-
const paragraphs = processedText.split("\n");
|
|
873
|
-
const lines = [];
|
|
874
|
-
for (const paragraph of paragraphs) {
|
|
875
|
-
if (noWrap) {
|
|
876
|
-
lines.push(paragraph);
|
|
877
|
-
continue;
|
|
878
|
-
}
|
|
879
|
-
const wrapped = wrapText(
|
|
880
|
-
paragraph,
|
|
881
|
-
maxWidth,
|
|
882
|
-
fontSize,
|
|
883
|
-
fontFamily,
|
|
884
|
-
fontWeight,
|
|
885
|
-
fontStyle,
|
|
886
|
-
letterSpacing,
|
|
887
|
-
wordBreak,
|
|
888
|
-
ctx,
|
|
889
|
-
measure
|
|
890
|
-
);
|
|
891
|
-
lines.push(...wrapped);
|
|
892
|
-
}
|
|
893
|
-
if (textOverflow === "ellipsis" && noWrap && lines.length === 1) {
|
|
894
|
-
const line = lines[0];
|
|
895
|
-
const lineWidth = measure(line);
|
|
896
|
-
if (lineWidth > maxWidth) {
|
|
897
|
-
lines[0] = truncateWithEllipsis(
|
|
898
|
-
line,
|
|
899
|
-
maxWidth,
|
|
900
|
-
fontSize,
|
|
901
|
-
fontFamily,
|
|
902
|
-
fontWeight,
|
|
903
|
-
fontStyle,
|
|
904
|
-
ctx,
|
|
905
|
-
letterSpacing
|
|
906
|
-
);
|
|
907
|
-
}
|
|
688
|
+
for (const seg of result.segments) {
|
|
689
|
+
seg.textStrokeWidth = textStrokeWidth;
|
|
690
|
+
seg.textStrokeColor = style.WebkitTextStrokeColor;
|
|
908
691
|
}
|
|
909
|
-
|
|
910
|
-
if (lineClamp && lineClamp > 0 && lines.length > lineClamp) {
|
|
911
|
-
const lastLineText = lines.slice(lineClamp - 1).join(" ");
|
|
912
|
-
lines.length = lineClamp;
|
|
913
|
-
lines[lineClamp - 1] = truncateWithEllipsis(
|
|
914
|
-
lastLineText,
|
|
915
|
-
maxWidth,
|
|
916
|
-
fontSize,
|
|
917
|
-
fontFamily,
|
|
918
|
-
fontWeight,
|
|
919
|
-
fontStyle,
|
|
920
|
-
ctx,
|
|
921
|
-
letterSpacing
|
|
922
|
-
);
|
|
923
|
-
}
|
|
924
|
-
const segments = [];
|
|
925
|
-
let totalHeight = 0;
|
|
926
|
-
let maxLineWidth = 0;
|
|
927
|
-
const isAutoLineHeight = style.lineHeight === void 0 || style.lineHeight === "normal";
|
|
928
|
-
const fontPx = fontMetrics ? fontMetricsToPx(fontMetrics, fontSize) : null;
|
|
929
|
-
let lastPositioningDescent;
|
|
930
|
-
for (let i = 0; i < lines.length; i++) {
|
|
931
|
-
const line = lines[i];
|
|
932
|
-
const lineWidth = measure(line);
|
|
933
|
-
const slack = Math.max(0, maxWidth - lineWidth);
|
|
934
|
-
let x = 0;
|
|
935
|
-
if (textAlign === "center") {
|
|
936
|
-
x = slack / 2;
|
|
937
|
-
} else if (textAlign === "right") {
|
|
938
|
-
x = slack;
|
|
939
|
-
}
|
|
940
|
-
const metrics = measureText(
|
|
941
|
-
line || "M",
|
|
942
|
-
fontSize,
|
|
943
|
-
fontFamily,
|
|
944
|
-
fontWeight,
|
|
945
|
-
fontStyle,
|
|
946
|
-
ctx
|
|
947
|
-
);
|
|
948
|
-
const positioningAscent = fontPx?.ascent ?? metrics.ascent;
|
|
949
|
-
const positioningDescent = fontPx?.descent ?? metrics.descent;
|
|
950
|
-
lastPositioningDescent = positioningDescent;
|
|
951
|
-
const contentHeight = positioningAscent + positioningDescent;
|
|
952
|
-
let effectiveAscent = positioningAscent;
|
|
953
|
-
let effectiveDescent = positioningDescent;
|
|
954
|
-
if (isAutoLineHeight && contentHeight > lineHeightPx) {
|
|
955
|
-
const scale = lineHeightPx / contentHeight;
|
|
956
|
-
effectiveAscent = positioningAscent * scale;
|
|
957
|
-
effectiveDescent = positioningDescent * scale;
|
|
958
|
-
}
|
|
959
|
-
const baselineY = totalHeight + (lineHeightPx + effectiveAscent - effectiveDescent) / 2;
|
|
960
|
-
segments.push({
|
|
961
|
-
text: line,
|
|
962
|
-
x,
|
|
963
|
-
y: baselineY,
|
|
964
|
-
width: lineWidth,
|
|
965
|
-
height: lineHeightPx,
|
|
966
|
-
fontSize,
|
|
967
|
-
fontFamily,
|
|
968
|
-
fontWeight,
|
|
969
|
-
fontStyle,
|
|
970
|
-
color,
|
|
971
|
-
ascent: metrics.ascent,
|
|
972
|
-
textDecoration,
|
|
973
|
-
letterSpacing,
|
|
974
|
-
lineIndex: i,
|
|
975
|
-
textStrokeWidth,
|
|
976
|
-
textStrokeColor
|
|
977
|
-
});
|
|
978
|
-
totalHeight += lineHeightPx;
|
|
979
|
-
maxLineWidth = Math.max(maxLineWidth, lineWidth);
|
|
980
|
-
}
|
|
981
|
-
if (isAutoLineHeight && lastPositioningDescent !== void 0 && segments.length > 0) {
|
|
982
|
-
const lastSeg = segments[segments.length - 1];
|
|
983
|
-
const descentOverflow = lastSeg.y + lastPositioningDescent - totalHeight;
|
|
984
|
-
if (descentOverflow > 0) {
|
|
985
|
-
totalHeight += descentOverflow;
|
|
986
|
-
}
|
|
987
|
-
totalHeight = Math.ceil(totalHeight);
|
|
988
|
-
}
|
|
989
|
-
const textBoxTrim = style.textBoxTrim;
|
|
990
|
-
if (textBoxTrim && textBoxTrim !== "none" && segments.length > 0) {
|
|
991
|
-
const textBoxEdge = style.textBoxEdge ?? "text";
|
|
992
|
-
const trimMetrics = measureTrimMetrics(
|
|
993
|
-
fontSize,
|
|
994
|
-
fontFamily,
|
|
995
|
-
fontWeight,
|
|
996
|
-
fontStyle,
|
|
997
|
-
lineHeightPx,
|
|
998
|
-
textBoxEdge,
|
|
999
|
-
ctx,
|
|
1000
|
-
fontMetrics
|
|
1001
|
-
);
|
|
1002
|
-
if (textBoxTrim === "trim-start" || textBoxTrim === "trim-both") {
|
|
1003
|
-
for (const seg of segments) {
|
|
1004
|
-
seg.y -= trimMetrics.overTrim;
|
|
1005
|
-
}
|
|
1006
|
-
totalHeight -= trimMetrics.overTrim;
|
|
1007
|
-
}
|
|
1008
|
-
if (textBoxTrim === "trim-end" || textBoxTrim === "trim-both") {
|
|
1009
|
-
totalHeight -= trimMetrics.underTrim;
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
return {
|
|
1013
|
-
segments,
|
|
1014
|
-
width: maxLineWidth,
|
|
1015
|
-
height: totalHeight
|
|
1016
|
-
};
|
|
692
|
+
return result;
|
|
1017
693
|
}
|
|
1018
694
|
function resolveTextStrokeWidth(style, fontSize) {
|
|
1019
695
|
const raw = style.WebkitTextStrokeWidth;
|
|
@@ -1030,157 +706,98 @@ function applyTextTransform(text, style) {
|
|
|
1030
706
|
}
|
|
1031
707
|
return text;
|
|
1032
708
|
}
|
|
1033
|
-
function resolveLineHeight(lineHeight, fontSize
|
|
1034
|
-
if (lineHeight === void 0 || lineHeight === "normal")
|
|
1035
|
-
|
|
1036
|
-
const px = fontMetricsToPx(fontMetrics, fontSize);
|
|
1037
|
-
return px.ascent + px.descent;
|
|
1038
|
-
}
|
|
1039
|
-
return canvasMetrics ? canvasMetrics.ascent + canvasMetrics.descent : fontSize * 1.2;
|
|
1040
|
-
}
|
|
709
|
+
function resolveLineHeight(lineHeight, fontSize) {
|
|
710
|
+
if (lineHeight === void 0 || lineHeight === "normal") return void 0;
|
|
711
|
+
let px;
|
|
1041
712
|
if (typeof lineHeight === "number") {
|
|
1042
|
-
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
713
|
+
px = lineHeight > 5 ? lineHeight : lineHeight * fontSize;
|
|
714
|
+
} else {
|
|
715
|
+
px = parseFloat(lineHeight);
|
|
716
|
+
}
|
|
717
|
+
return Number.isFinite(px) && px >= 0 ? px : void 0;
|
|
718
|
+
}
|
|
719
|
+
var TextMeasure = class {
|
|
720
|
+
constructor(text, style, emojiEnabled) {
|
|
721
|
+
this.text = text;
|
|
722
|
+
this.style = style;
|
|
723
|
+
this.emojiEnabled = emojiEnabled;
|
|
724
|
+
this.measureStyle = { ...style, textOverflow: "clip" };
|
|
725
|
+
}
|
|
726
|
+
text;
|
|
727
|
+
style;
|
|
728
|
+
emojiEnabled;
|
|
729
|
+
/** Yoga's measurements by width (Infinity for unbounded). */
|
|
730
|
+
sizes = /* @__PURE__ */ new Map();
|
|
731
|
+
/** The heights reported to Yoga since the last `settle`. */
|
|
732
|
+
reported = /* @__PURE__ */ new Set();
|
|
733
|
+
pinnedHeight;
|
|
734
|
+
settled;
|
|
735
|
+
measureStyle;
|
|
736
|
+
/**
|
|
737
|
+
* Measure the text for Yoga.
|
|
738
|
+
*
|
|
739
|
+
* @param maxWidth - The width available, Infinity for unbounded
|
|
740
|
+
*/
|
|
741
|
+
measure(maxWidth) {
|
|
742
|
+
let size = this.sizes.get(maxWidth);
|
|
743
|
+
if (!size) {
|
|
744
|
+
const result = layoutText(
|
|
745
|
+
this.text,
|
|
746
|
+
this.measureStyle,
|
|
1074
747
|
maxWidth,
|
|
1075
|
-
|
|
1076
|
-
fontFamily,
|
|
1077
|
-
fontWeight,
|
|
1078
|
-
fontStyle,
|
|
1079
|
-
ctx,
|
|
1080
|
-
letterSpacing,
|
|
1081
|
-
measureFn
|
|
748
|
+
this.emojiEnabled
|
|
1082
749
|
);
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
lineStart = opp.position;
|
|
750
|
+
const wrapped = result.segments.length > 1 && maxWidth < Infinity;
|
|
751
|
+
size = {
|
|
752
|
+
width: wrapped ? maxWidth : Math.min(result.width, maxWidth),
|
|
753
|
+
height: result.height
|
|
754
|
+
};
|
|
755
|
+
this.sizes.set(maxWidth, size);
|
|
1090
756
|
}
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
lines.push(...broken.lines);
|
|
1112
|
-
} else {
|
|
1113
|
-
lines.push(remaining);
|
|
1114
|
-
}
|
|
757
|
+
const height = this.pinnedHeight ?? size.height;
|
|
758
|
+
this.reported.add(height);
|
|
759
|
+
return { width: size.width, height };
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Lay the text out for drawing at the node's final content width, and
|
|
763
|
+
* check it against the heights Yoga was given since the last call.
|
|
764
|
+
*
|
|
765
|
+
* @param pin - Whether to pin later measurements to the drawn height when
|
|
766
|
+
* they disagree
|
|
767
|
+
* @returns Whether Yoga sized the node from another height. If `pin`, the
|
|
768
|
+
* node's measurements are now pinned to the drawn height, and the caller
|
|
769
|
+
* should mark the node dirty and compute the layout again.
|
|
770
|
+
*/
|
|
771
|
+
settle(width, pin) {
|
|
772
|
+
if (this.settled?.width !== width) {
|
|
773
|
+
this.settled = {
|
|
774
|
+
width,
|
|
775
|
+
result: layoutText(this.text, this.style, width, this.emojiEnabled)
|
|
776
|
+
};
|
|
1115
777
|
}
|
|
778
|
+
const { height } = this.settled.result;
|
|
779
|
+
const agrees = this.reported.size === 0 || this.reported.size === 1 && this.reported.has(height);
|
|
780
|
+
this.reported.clear();
|
|
781
|
+
if (agrees) return false;
|
|
782
|
+
if (pin) this.pinnedHeight = height;
|
|
783
|
+
return true;
|
|
1116
784
|
}
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
while (pos < end) {
|
|
1132
|
-
let breakPos = pos + 1;
|
|
1133
|
-
while (breakPos < end) {
|
|
1134
|
-
const chunk = text.slice(pos, breakPos + 1);
|
|
1135
|
-
const w = mw(chunk);
|
|
1136
|
-
if (w > maxWidth) break;
|
|
1137
|
-
breakPos++;
|
|
1138
|
-
}
|
|
1139
|
-
const line = text.slice(pos, breakPos);
|
|
1140
|
-
if (line.trim()) lines.push(line);
|
|
1141
|
-
pos = breakPos;
|
|
785
|
+
/**
|
|
786
|
+
* Drop the pinned height, so that Yoga sees the measured heights again.
|
|
787
|
+
*
|
|
788
|
+
* @returns Whether there was a pin
|
|
789
|
+
*/
|
|
790
|
+
unpin() {
|
|
791
|
+
const pinned = this.pinnedHeight !== void 0;
|
|
792
|
+
this.pinnedHeight = void 0;
|
|
793
|
+
this.reported.clear();
|
|
794
|
+
return pinned;
|
|
795
|
+
}
|
|
796
|
+
/** The text as laid out by the last `settle`. */
|
|
797
|
+
get layout() {
|
|
798
|
+
return this.settled?.result;
|
|
1142
799
|
}
|
|
1143
|
-
|
|
1144
|
-
}
|
|
1145
|
-
function truncateWithEllipsis(text, maxWidth, fontSize, fontFamily, fontWeight, fontStyle, ctx, letterSpacing = 0) {
|
|
1146
|
-
const ellipsis = "\u2026";
|
|
1147
|
-
const ellipsisWidth = measureWord(
|
|
1148
|
-
ellipsis,
|
|
1149
|
-
fontSize,
|
|
1150
|
-
fontFamily,
|
|
1151
|
-
fontWeight,
|
|
1152
|
-
fontStyle,
|
|
1153
|
-
ctx,
|
|
1154
|
-
letterSpacing
|
|
1155
|
-
);
|
|
1156
|
-
const availWidth = maxWidth - ellipsisWidth;
|
|
1157
|
-
for (let i = text.length; i > 0; i--) {
|
|
1158
|
-
const truncated = text.slice(0, i);
|
|
1159
|
-
const w = measureWord(
|
|
1160
|
-
truncated,
|
|
1161
|
-
fontSize,
|
|
1162
|
-
fontFamily,
|
|
1163
|
-
fontWeight,
|
|
1164
|
-
fontStyle,
|
|
1165
|
-
ctx,
|
|
1166
|
-
letterSpacing
|
|
1167
|
-
);
|
|
1168
|
-
if (w <= availWidth) {
|
|
1169
|
-
return truncated + ellipsis;
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
return ellipsis;
|
|
1173
|
-
}
|
|
1174
|
-
function createTextMeasureFunc(text, style, ctx, emojiEnabled) {
|
|
1175
|
-
const measureStyle = { ...style, textOverflow: "clip" };
|
|
1176
|
-
return (width, _widthMode, _height, _heightMode) => {
|
|
1177
|
-
const maxWidth = width > 0 ? width : Infinity;
|
|
1178
|
-
const result = layoutText(text, measureStyle, maxWidth, ctx, emojiEnabled);
|
|
1179
|
-
const wrapped = result.segments.length > 1;
|
|
1180
|
-
const reportedWidth = wrapped ? Math.min(maxWidth, width > 0 ? width : result.width) : result.width;
|
|
1181
|
-
return { width: Math.min(reportedWidth, maxWidth), height: result.height };
|
|
1182
|
-
};
|
|
1183
|
-
}
|
|
800
|
+
};
|
|
1184
801
|
|
|
1185
802
|
// src/jsx/draw/clip.ts
|
|
1186
803
|
function applyClip(ctx, x, y, width, height, borderRadius) {
|
|
@@ -3408,106 +3025,70 @@ import { fillParagraph, strokeParagraph } from "@effing/skia/extensions";
|
|
|
3408
3025
|
import parseCssColor2 from "parse-css-color";
|
|
3409
3026
|
|
|
3410
3027
|
// src/jsx/emoji.ts
|
|
3411
|
-
var
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
var
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
c = unicodeSurrogates.charCodeAt(i++);
|
|
3429
|
-
if (p) {
|
|
3430
|
-
r.push((65536 + (p - 55296 << 10) + (c - 56320)).toString(16));
|
|
3431
|
-
p = 0;
|
|
3432
|
-
} else if (55296 <= c && c <= 56319) {
|
|
3433
|
-
p = c;
|
|
3434
|
-
} else {
|
|
3435
|
-
r.push(c.toString(16));
|
|
3028
|
+
var FE0F = 65039;
|
|
3029
|
+
var ZWJ = 8205;
|
|
3030
|
+
var KEYCAP = 8419;
|
|
3031
|
+
var EMOJI_CHAR2 = /[\p{Emoji}\p{Extended_Pictographic}]/u;
|
|
3032
|
+
var EMOJI_PRESENTATION = new RegExp(
|
|
3033
|
+
String.raw`\p{Emoji_Presentation}|${UNASSIGNED_PICTOGRAPHIC}`,
|
|
3034
|
+
"u"
|
|
3035
|
+
);
|
|
3036
|
+
var EMOJI_MODIFIER = /\p{Emoji_Modifier}/u;
|
|
3037
|
+
function qualifiedCodePoints(emoji) {
|
|
3038
|
+
const chars = [...emoji].filter((c) => c.codePointAt(0) !== FE0F);
|
|
3039
|
+
const codePoints = [];
|
|
3040
|
+
chars.forEach((char, i) => {
|
|
3041
|
+
codePoints.push(char.codePointAt(0));
|
|
3042
|
+
const startsElement = i === 0 || chars[i - 1] === "\u200D";
|
|
3043
|
+
if (startsElement && EMOJI_CHAR2.test(char) && !EMOJI_PRESENTATION.test(char) && !EMOJI_MODIFIER.test(chars[i + 1] ?? "")) {
|
|
3044
|
+
codePoints.push(FE0F);
|
|
3436
3045
|
}
|
|
3437
|
-
}
|
|
3438
|
-
return
|
|
3046
|
+
});
|
|
3047
|
+
return codePoints;
|
|
3439
3048
|
}
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
const key = type + ":" + code;
|
|
3443
|
-
if (key in emojiCache) return emojiCache[key];
|
|
3444
|
-
const api = emojiApis[type];
|
|
3445
|
-
const url = typeof api === "function" ? api(code) : `${api}${code.toUpperCase()}.svg`;
|
|
3446
|
-
return emojiCache[key] = fetch(url).then((r) => r.text());
|
|
3049
|
+
function hex(codePoints, pad = 0) {
|
|
3050
|
+
return codePoints.map((cp) => cp.toString(16).padStart(pad, "0")).join("-");
|
|
3447
3051
|
}
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
x: currentX,
|
|
3475
|
-
width: emojiSize
|
|
3476
|
-
});
|
|
3477
|
-
currentX += emojiSize + letterSpacing;
|
|
3478
|
-
textStartX = currentX;
|
|
3479
|
-
} else {
|
|
3480
|
-
if (!currentText) textStartX = currentX;
|
|
3481
|
-
currentText += segment;
|
|
3482
|
-
currentGraphemeCount++;
|
|
3483
|
-
}
|
|
3484
|
-
}
|
|
3485
|
-
if (currentText) {
|
|
3486
|
-
const textWidth = measureText2(currentText);
|
|
3487
|
-
runs.push({
|
|
3488
|
-
kind: "text",
|
|
3489
|
-
text: currentText,
|
|
3490
|
-
x: textStartX,
|
|
3491
|
-
width: textWidth + letterSpacing * currentGraphemeCount
|
|
3492
|
-
});
|
|
3493
|
-
}
|
|
3494
|
-
return runs;
|
|
3052
|
+
function twemojiCode(codePoints) {
|
|
3053
|
+
const code = hex(
|
|
3054
|
+
codePoints.includes(ZWJ) ? codePoints : codePoints.filter((cp) => cp !== FE0F)
|
|
3055
|
+
);
|
|
3056
|
+
return code === "1f441-fe0f-200d-1f5e8-fe0f" ? "1f441-200d-1f5e8" : code;
|
|
3057
|
+
}
|
|
3058
|
+
function svgmojiCode(codePoints) {
|
|
3059
|
+
const keepFE0F = codePoints.includes(ZWJ) || codePoints.includes(KEYCAP);
|
|
3060
|
+
return hex(
|
|
3061
|
+
keepFE0F ? codePoints : codePoints.filter((cp) => cp !== FE0F),
|
|
3062
|
+
4
|
|
3063
|
+
).toUpperCase();
|
|
3064
|
+
}
|
|
3065
|
+
function fluentCode(codePoints) {
|
|
3066
|
+
return encodeURIComponent(String.fromCodePoint(...codePoints));
|
|
3067
|
+
}
|
|
3068
|
+
var emojiUrls = {
|
|
3069
|
+
twemoji: (cps) => `https://cdnjs.cloudflare.com/ajax/libs/twemoji/16.0.1/svg/${twemojiCode(cps)}.svg`,
|
|
3070
|
+
openmoji: (cps) => `https://cdn.jsdelivr.net/npm/@svgmoji/openmoji@2.0.0/svg/${svgmojiCode(cps)}.svg`,
|
|
3071
|
+
blobmoji: (cps) => `https://cdn.jsdelivr.net/npm/@svgmoji/blob@2.0.0/svg/${svgmojiCode(cps)}.svg`,
|
|
3072
|
+
noto: (cps) => `https://cdn.jsdelivr.net/gh/svgmoji/svgmoji/packages/svgmoji__noto/svg/${svgmojiCode(cps)}.svg`,
|
|
3073
|
+
fluent: (cps) => `https://cdn.jsdelivr.net/gh/shuding/fluentui-emoji-unicode/assets/${fluentCode(cps)}_color.svg`,
|
|
3074
|
+
fluentFlat: (cps) => `https://cdn.jsdelivr.net/gh/shuding/fluentui-emoji-unicode/assets/${fluentCode(cps)}_flat.svg`
|
|
3075
|
+
};
|
|
3076
|
+
function emojiUrl(style, emoji) {
|
|
3077
|
+
return emojiUrls[style](qualifiedCodePoints(emoji));
|
|
3495
3078
|
}
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
return false;
|
|
3079
|
+
var emojiCache = {};
|
|
3080
|
+
async function loadEmoji(style, emoji) {
|
|
3081
|
+
const url = emojiUrl(style, emoji);
|
|
3082
|
+
return emojiCache[url] ??= fetch(url).then((r) => r.text());
|
|
3501
3083
|
}
|
|
3502
3084
|
|
|
3503
3085
|
// src/jsx/draw/text.ts
|
|
3504
3086
|
var emojiImageCache = /* @__PURE__ */ new Map();
|
|
3505
|
-
function loadEmojiImage(style,
|
|
3506
|
-
const
|
|
3507
|
-
const key = style + ":" + code;
|
|
3087
|
+
function loadEmojiImage(style, emoji) {
|
|
3088
|
+
const key = emojiUrl(style, emoji);
|
|
3508
3089
|
let cached = emojiImageCache.get(key);
|
|
3509
3090
|
if (!cached) {
|
|
3510
|
-
cached = loadEmoji(style,
|
|
3091
|
+
cached = loadEmoji(style, emoji).then((svgText) => {
|
|
3511
3092
|
if (!svgText || !svgText.includes("<svg")) return null;
|
|
3512
3093
|
const dataUri = "data:image/svg+xml;base64," + Buffer.from(svgText).toString("base64");
|
|
3513
3094
|
return loadImage2(dataUri);
|
|
@@ -3516,32 +3097,12 @@ function loadEmojiImage(style, char) {
|
|
|
3516
3097
|
}
|
|
3517
3098
|
return cached;
|
|
3518
3099
|
}
|
|
3519
|
-
async function drawText(ctx,
|
|
3520
|
-
|
|
3521
|
-
drawParagraph(
|
|
3522
|
-
ctx,
|
|
3523
|
-
native.paragraph,
|
|
3524
|
-
segments,
|
|
3525
|
-
offsetX,
|
|
3526
|
-
offsetY,
|
|
3527
|
-
native.offsetY,
|
|
3528
|
-
textShadow
|
|
3529
|
-
);
|
|
3530
|
-
return;
|
|
3531
|
-
}
|
|
3532
|
-
const textRendering = ctx.textRendering;
|
|
3533
|
-
ctx.textRendering = "geometricPrecision";
|
|
3534
|
-
try {
|
|
3535
|
-
await drawSegments(ctx, segments, offsetX, offsetY, textShadow, emojiStyle);
|
|
3536
|
-
} finally {
|
|
3537
|
-
ctx.textRendering = textRendering;
|
|
3538
|
-
}
|
|
3539
|
-
}
|
|
3540
|
-
function drawParagraph(ctx, paragraph, segments, offsetX, offsetY, paragraphOffsetY, textShadow) {
|
|
3100
|
+
async function drawText(ctx, layout, offsetX, offsetY, textShadow, emojiStyle) {
|
|
3101
|
+
const { paragraph, segments, emoji } = layout;
|
|
3541
3102
|
const first = segments[0];
|
|
3542
3103
|
if (!first) return;
|
|
3543
3104
|
const x = offsetX;
|
|
3544
|
-
const y = offsetY + paragraphOffsetY;
|
|
3105
|
+
const y = offsetY + layout.paragraphOffsetY;
|
|
3545
3106
|
const shadow = textShadow ? parseShadow(textShadow) : null;
|
|
3546
3107
|
ctx.fillStyle = first.color;
|
|
3547
3108
|
if (shadow) {
|
|
@@ -3561,172 +3122,37 @@ function drawParagraph(ctx, paragraph, segments, offsetX, offsetY, paragraphOffs
|
|
|
3561
3122
|
ctx.restore();
|
|
3562
3123
|
}
|
|
3563
3124
|
fillParagraph(ctx, paragraph, x, y);
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
drawTextDecoration(ctx, seg, offsetX, offsetY);
|
|
3567
|
-
}
|
|
3125
|
+
if (emojiStyle && emoji.length > 0) {
|
|
3126
|
+
await drawEmoji(ctx, emoji, first, offsetX, offsetY, emojiStyle);
|
|
3568
3127
|
}
|
|
3569
|
-
}
|
|
3570
|
-
async function drawSegments(ctx, segments, offsetX, offsetY, textShadow, emojiStyle) {
|
|
3571
|
-
const shadow = textShadow ? parseShadow(textShadow) : null;
|
|
3572
3128
|
for (const seg of segments) {
|
|
3573
|
-
if (!seg.text) continue;
|
|
3574
|
-
setFont(ctx, seg.fontSize, seg.fontFamily, seg.fontWeight, seg.fontStyle);
|
|
3575
|
-
ctx.fillStyle = seg.color;
|
|
3576
|
-
const x = offsetX + seg.x;
|
|
3577
|
-
const y = offsetY + seg.y;
|
|
3578
|
-
const textAlpha = shadow ? getColorAlpha(seg.color) : 1;
|
|
3579
|
-
const hasStroke = seg.textStrokeWidth !== void 0 && seg.textStrokeWidth > 0;
|
|
3580
|
-
if (emojiStyle) {
|
|
3581
|
-
await drawSegmentWithEmoji(
|
|
3582
|
-
ctx,
|
|
3583
|
-
seg,
|
|
3584
|
-
x,
|
|
3585
|
-
y,
|
|
3586
|
-
textShadow,
|
|
3587
|
-
emojiStyle,
|
|
3588
|
-
hasStroke
|
|
3589
|
-
);
|
|
3590
|
-
} else if (seg.letterSpacing && seg.letterSpacing !== 0) {
|
|
3591
|
-
if (shadow) {
|
|
3592
|
-
drawShadowPass(
|
|
3593
|
-
ctx,
|
|
3594
|
-
shadow,
|
|
3595
|
-
textAlpha,
|
|
3596
|
-
() => drawTextWithLetterSpacing(ctx, seg.text, x, y, seg.letterSpacing)
|
|
3597
|
-
);
|
|
3598
|
-
}
|
|
3599
|
-
if (hasStroke) {
|
|
3600
|
-
drawStrokeWithLetterSpacing(
|
|
3601
|
-
ctx,
|
|
3602
|
-
seg.text,
|
|
3603
|
-
x,
|
|
3604
|
-
y,
|
|
3605
|
-
seg.letterSpacing,
|
|
3606
|
-
seg.textStrokeWidth,
|
|
3607
|
-
seg.textStrokeColor ?? seg.color
|
|
3608
|
-
);
|
|
3609
|
-
}
|
|
3610
|
-
drawTextWithLetterSpacing(ctx, seg.text, x, y, seg.letterSpacing);
|
|
3611
|
-
} else {
|
|
3612
|
-
if (shadow) {
|
|
3613
|
-
drawShadowPass(
|
|
3614
|
-
ctx,
|
|
3615
|
-
shadow,
|
|
3616
|
-
textAlpha,
|
|
3617
|
-
() => ctx.fillText(seg.text, x, y)
|
|
3618
|
-
);
|
|
3619
|
-
}
|
|
3620
|
-
if (hasStroke) {
|
|
3621
|
-
ctx.save();
|
|
3622
|
-
ctx.lineWidth = seg.textStrokeWidth;
|
|
3623
|
-
ctx.strokeStyle = seg.textStrokeColor ?? seg.color;
|
|
3624
|
-
ctx.lineJoin = "round";
|
|
3625
|
-
ctx.strokeText(seg.text, x, y);
|
|
3626
|
-
ctx.restore();
|
|
3627
|
-
}
|
|
3628
|
-
ctx.fillText(seg.text, x, y);
|
|
3629
|
-
}
|
|
3630
3129
|
if (seg.textDecoration) {
|
|
3631
3130
|
drawTextDecoration(ctx, seg, offsetX, offsetY);
|
|
3632
3131
|
}
|
|
3633
3132
|
}
|
|
3634
3133
|
}
|
|
3635
|
-
async function
|
|
3636
|
-
const
|
|
3637
|
-
|
|
3638
|
-
seg.text,
|
|
3639
|
-
(text) => {
|
|
3640
|
-
setFont(ctx, seg.fontSize, seg.fontFamily, seg.fontWeight, seg.fontStyle);
|
|
3641
|
-
return ctx.measureText(text).width;
|
|
3642
|
-
},
|
|
3643
|
-
seg.fontSize,
|
|
3644
|
-
letterSpacing
|
|
3134
|
+
async function drawEmoji(ctx, emoji, font, offsetX, offsetY, emojiStyle) {
|
|
3135
|
+
const images = await Promise.all(
|
|
3136
|
+
emoji.map((e) => loadEmojiImage(emojiStyle, e.grapheme))
|
|
3645
3137
|
);
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
if (letterSpacing !== 0) {
|
|
3652
|
-
drawShadowPass(
|
|
3653
|
-
ctx,
|
|
3654
|
-
shadow,
|
|
3655
|
-
textAlpha,
|
|
3656
|
-
() => drawTextWithLetterSpacing(
|
|
3657
|
-
ctx,
|
|
3658
|
-
run.text,
|
|
3659
|
-
x + run.x,
|
|
3660
|
-
y,
|
|
3661
|
-
letterSpacing
|
|
3662
|
-
)
|
|
3663
|
-
);
|
|
3664
|
-
} else {
|
|
3665
|
-
drawShadowPass(
|
|
3666
|
-
ctx,
|
|
3667
|
-
shadow,
|
|
3668
|
-
textAlpha,
|
|
3669
|
-
() => ctx.fillText(run.text, x + run.x, y)
|
|
3670
|
-
);
|
|
3671
|
-
}
|
|
3672
|
-
}
|
|
3673
|
-
if (hasStroke) {
|
|
3674
|
-
if (letterSpacing !== 0) {
|
|
3675
|
-
drawStrokeWithLetterSpacing(
|
|
3676
|
-
ctx,
|
|
3677
|
-
run.text,
|
|
3678
|
-
x + run.x,
|
|
3679
|
-
y,
|
|
3680
|
-
letterSpacing,
|
|
3681
|
-
seg.textStrokeWidth,
|
|
3682
|
-
seg.textStrokeColor ?? seg.color
|
|
3683
|
-
);
|
|
3684
|
-
} else {
|
|
3685
|
-
ctx.save();
|
|
3686
|
-
ctx.lineWidth = seg.textStrokeWidth;
|
|
3687
|
-
ctx.strokeStyle = seg.textStrokeColor ?? seg.color;
|
|
3688
|
-
ctx.lineJoin = "round";
|
|
3689
|
-
ctx.strokeText(run.text, x + run.x, y);
|
|
3690
|
-
ctx.restore();
|
|
3691
|
-
}
|
|
3692
|
-
}
|
|
3693
|
-
if (letterSpacing !== 0) {
|
|
3694
|
-
drawTextWithLetterSpacing(ctx, run.text, x + run.x, y, letterSpacing);
|
|
3695
|
-
} else {
|
|
3696
|
-
ctx.fillText(run.text, x + run.x, y);
|
|
3697
|
-
}
|
|
3698
|
-
} else {
|
|
3699
|
-
const img = await loadEmojiImage(emojiStyle, run.char);
|
|
3700
|
-
if (img) {
|
|
3701
|
-
const emojiSize = seg.fontSize;
|
|
3702
|
-
const emojiY = y - seg.ascent + (seg.height - seg.fontSize) / 2;
|
|
3703
|
-
ctx.drawImage(img, x + run.x, emojiY, emojiSize, emojiSize);
|
|
3704
|
-
} else {
|
|
3705
|
-
ctx.fillText(run.char, x + run.x, y);
|
|
3706
|
-
}
|
|
3138
|
+
emoji.forEach((e, i) => {
|
|
3139
|
+
const img = images[i];
|
|
3140
|
+
if (img) {
|
|
3141
|
+
ctx.drawImage(img, offsetX + e.x, offsetY + e.y, e.size, e.size);
|
|
3142
|
+
return;
|
|
3707
3143
|
}
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
ctx.lineWidth = strokeWidth;
|
|
3721
|
-
ctx.strokeStyle = strokeColor;
|
|
3722
|
-
ctx.lineJoin = "round";
|
|
3723
|
-
let currentX = x;
|
|
3724
|
-
for (const char of text) {
|
|
3725
|
-
ctx.strokeText(char, currentX, y);
|
|
3726
|
-
const metrics = ctx.measureText(char);
|
|
3727
|
-
currentX += metrics.width + letterSpacing;
|
|
3728
|
-
}
|
|
3729
|
-
ctx.restore();
|
|
3144
|
+
setFont(
|
|
3145
|
+
ctx,
|
|
3146
|
+
font.fontSize,
|
|
3147
|
+
font.fontFamily,
|
|
3148
|
+
font.fontWeight,
|
|
3149
|
+
font.fontStyle
|
|
3150
|
+
);
|
|
3151
|
+
const textRendering = ctx.textRendering;
|
|
3152
|
+
ctx.textRendering = "geometricPrecision";
|
|
3153
|
+
ctx.fillText(e.grapheme, offsetX + e.x, offsetY + e.baseline);
|
|
3154
|
+
ctx.textRendering = textRendering;
|
|
3155
|
+
});
|
|
3730
3156
|
}
|
|
3731
3157
|
function parseShadow(shadow) {
|
|
3732
3158
|
const parts = shadow.match(
|
|
@@ -3949,24 +3375,14 @@ async function paintNode(ctx, node, x, y, opacity, renderContext, emojiStyle, gr
|
|
|
3949
3375
|
const contentX = x + paddingLeft + borderLeftW;
|
|
3950
3376
|
const contentY = y + paddingTop + borderTopW;
|
|
3951
3377
|
const contentWidth = width - paddingLeft - paddingRight - borderLeftW - borderRightW;
|
|
3952
|
-
const textLayout = layoutText(
|
|
3953
|
-
node.textContent,
|
|
3954
|
-
style,
|
|
3955
|
-
contentWidth,
|
|
3956
|
-
ctx,
|
|
3957
|
-
!!emojiStyle
|
|
3958
|
-
);
|
|
3378
|
+
const textLayout = node.textLayout && contentWidth === node.width ? node.textLayout : layoutText(node.textContent, style, contentWidth, !!emojiStyle);
|
|
3959
3379
|
await drawText(
|
|
3960
3380
|
ctx,
|
|
3961
|
-
textLayout
|
|
3381
|
+
textLayout,
|
|
3962
3382
|
contentX,
|
|
3963
3383
|
contentY,
|
|
3964
3384
|
style.textShadow,
|
|
3965
|
-
emojiStyle
|
|
3966
|
-
textLayout.paragraph && {
|
|
3967
|
-
paragraph: textLayout.paragraph,
|
|
3968
|
-
offsetY: textLayout.paragraphOffsetY ?? 0
|
|
3969
|
-
}
|
|
3385
|
+
emojiStyle
|
|
3970
3386
|
);
|
|
3971
3387
|
}
|
|
3972
3388
|
if (node.type === "img" && node.props.src) {
|
|
@@ -4071,6 +3487,28 @@ function parseAngle(value) {
|
|
|
4071
3487
|
return parseFloat(value);
|
|
4072
3488
|
}
|
|
4073
3489
|
|
|
3490
|
+
// src/jsx/font.ts
|
|
3491
|
+
import { GlobalFonts } from "@effing/skia";
|
|
3492
|
+
var registeredFonts = /* @__PURE__ */ new Set();
|
|
3493
|
+
function registerFont(font) {
|
|
3494
|
+
const key = `${font.name}:${font.weight}:${font.style}`;
|
|
3495
|
+
if (registeredFonts.has(key)) return;
|
|
3496
|
+
const buffer = Buffer.isBuffer(font.data) ? font.data : Buffer.from(font.data);
|
|
3497
|
+
GlobalFonts.register(buffer, font.name);
|
|
3498
|
+
registeredFonts.add(key);
|
|
3499
|
+
}
|
|
3500
|
+
function registerFontFromPath(path, nameAlias) {
|
|
3501
|
+
GlobalFonts.registerFromPath(path, nameAlias ?? "");
|
|
3502
|
+
}
|
|
3503
|
+
function registeredFamilies() {
|
|
3504
|
+
return GlobalFonts.families.map((f) => f.family);
|
|
3505
|
+
}
|
|
3506
|
+
function ensureFontsRegistered(fonts) {
|
|
3507
|
+
for (const font of fonts) {
|
|
3508
|
+
registerFont(font);
|
|
3509
|
+
}
|
|
3510
|
+
}
|
|
3511
|
+
|
|
4074
3512
|
// src/jsx/style/expand.ts
|
|
4075
3513
|
var SIDES = ["Top", "Right", "Bottom", "Left"];
|
|
4076
3514
|
function parseSides(value) {
|
|
@@ -4264,6 +3702,10 @@ function expandStyle(raw, fontFamilies) {
|
|
|
4264
3702
|
style.backdropFilter = style.WebkitBackdropFilter;
|
|
4265
3703
|
delete style.WebkitBackdropFilter;
|
|
4266
3704
|
}
|
|
3705
|
+
if (style.wordWrap !== void 0) {
|
|
3706
|
+
if (style.overflowWrap === void 0) style.overflowWrap = style.wordWrap;
|
|
3707
|
+
delete style.wordWrap;
|
|
3708
|
+
}
|
|
4267
3709
|
if (style.WebkitClipPath !== void 0) {
|
|
4268
3710
|
if (style.clipPath === void 0) style.clipPath = style.WebkitClipPath;
|
|
4269
3711
|
delete style.WebkitClipPath;
|
|
@@ -4388,6 +3830,7 @@ import Yoga, {
|
|
|
4388
3830
|
FlexDirection,
|
|
4389
3831
|
Gutter,
|
|
4390
3832
|
Justify,
|
|
3833
|
+
MeasureMode,
|
|
4391
3834
|
Overflow,
|
|
4392
3835
|
PositionType,
|
|
4393
3836
|
Wrap
|
|
@@ -4395,6 +3838,14 @@ import Yoga, {
|
|
|
4395
3838
|
function createYogaNode() {
|
|
4396
3839
|
return Yoga.Node.create();
|
|
4397
3840
|
}
|
|
3841
|
+
var textConfig;
|
|
3842
|
+
function createTextYogaNode() {
|
|
3843
|
+
if (!textConfig) {
|
|
3844
|
+
textConfig = Yoga.Config.create();
|
|
3845
|
+
textConfig.setPointScaleFactor(0);
|
|
3846
|
+
}
|
|
3847
|
+
return Yoga.Node.create(textConfig);
|
|
3848
|
+
}
|
|
4398
3849
|
function freeYogaNode(node) {
|
|
4399
3850
|
node.freeRecursive();
|
|
4400
3851
|
}
|
|
@@ -4567,20 +4018,20 @@ function applyEdgeValue(node, setter, edge, value) {
|
|
|
4567
4018
|
}
|
|
4568
4019
|
|
|
4569
4020
|
// src/jsx/layout.ts
|
|
4570
|
-
async function buildLayoutTree(element, containerWidth, containerHeight,
|
|
4021
|
+
async function buildLayoutTree(element, containerWidth, containerHeight, emojiEnabled, fontFamilies, context) {
|
|
4571
4022
|
const renderContext = context ?? {
|
|
4572
4023
|
imageCache: /* @__PURE__ */ new Map(),
|
|
4573
4024
|
debug: false
|
|
4574
4025
|
};
|
|
4575
|
-
const
|
|
4026
|
+
const rootElement = renderComponents(element);
|
|
4027
|
+
const elementYogaNode = isText(rootElement) ? createTextYogaNode() : createYogaNode();
|
|
4576
4028
|
const rootStyle = fontFamilies?.length ? { ...DEFAULT_STYLE, fontFamily: fontFamilies.join(", ") } : DEFAULT_STYLE;
|
|
4577
4029
|
const elementNode = await buildNode(
|
|
4578
|
-
|
|
4030
|
+
rootElement,
|
|
4579
4031
|
rootStyle,
|
|
4580
4032
|
elementYogaNode,
|
|
4581
4033
|
containerWidth,
|
|
4582
4034
|
containerHeight,
|
|
4583
|
-
ctx,
|
|
4584
4035
|
emojiEnabled,
|
|
4585
4036
|
fontFamilies,
|
|
4586
4037
|
renderContext
|
|
@@ -4591,6 +4042,11 @@ async function buildLayoutTree(element, containerWidth, containerHeight, ctx, em
|
|
|
4591
4042
|
rootYogaNode.setFlexDirection(FlexDirection.Row);
|
|
4592
4043
|
rootYogaNode.insertChild(elementYogaNode, 0);
|
|
4593
4044
|
rootYogaNode.calculateLayout(containerWidth, containerHeight);
|
|
4045
|
+
settleText(
|
|
4046
|
+
elementNode,
|
|
4047
|
+
() => rootYogaNode.calculateLayout(containerWidth, containerHeight),
|
|
4048
|
+
renderContext.debug
|
|
4049
|
+
);
|
|
4594
4050
|
const elementLayout = extractLayout(elementNode, elementYogaNode);
|
|
4595
4051
|
freeYogaNode(rootYogaNode);
|
|
4596
4052
|
const tree = {
|
|
@@ -4605,7 +4061,7 @@ async function buildLayoutTree(element, containerWidth, containerHeight, ctx, em
|
|
|
4605
4061
|
};
|
|
4606
4062
|
return { tree, imageCache: renderContext.imageCache };
|
|
4607
4063
|
}
|
|
4608
|
-
async function buildNode(element, parentStyle, yogaNode, viewportWidth, viewportHeight,
|
|
4064
|
+
async function buildNode(element, parentStyle, yogaNode, viewportWidth, viewportHeight, emojiEnabled, fontFamilies, context) {
|
|
4609
4065
|
if (element === null || element === void 0 || typeof element === "boolean") {
|
|
4610
4066
|
return {
|
|
4611
4067
|
type: "empty",
|
|
@@ -4618,35 +4074,20 @@ async function buildNode(element, parentStyle, yogaNode, viewportWidth, viewport
|
|
|
4618
4074
|
if (typeof element === "string" || typeof element === "number") {
|
|
4619
4075
|
const text = String(element);
|
|
4620
4076
|
const style2 = resolveStyle(void 0, parentStyle);
|
|
4621
|
-
const
|
|
4622
|
-
yogaNode
|
|
4077
|
+
const textMeasure = new TextMeasure(text, style2, emojiEnabled);
|
|
4078
|
+
setTextMeasure(yogaNode, textMeasure);
|
|
4623
4079
|
return {
|
|
4624
4080
|
type: "text",
|
|
4625
4081
|
style: style2,
|
|
4626
4082
|
children: [],
|
|
4627
4083
|
textContent: text,
|
|
4084
|
+
textMeasure,
|
|
4628
4085
|
props: {},
|
|
4629
4086
|
yogaNode
|
|
4630
4087
|
};
|
|
4631
4088
|
}
|
|
4632
4089
|
const el = element;
|
|
4633
4090
|
const type = el.type;
|
|
4634
|
-
if (typeof type === "function") {
|
|
4635
|
-
const rendered = type(
|
|
4636
|
-
el.props ?? {}
|
|
4637
|
-
);
|
|
4638
|
-
return await buildNode(
|
|
4639
|
-
rendered,
|
|
4640
|
-
parentStyle,
|
|
4641
|
-
yogaNode,
|
|
4642
|
-
viewportWidth,
|
|
4643
|
-
viewportHeight,
|
|
4644
|
-
ctx,
|
|
4645
|
-
emojiEnabled,
|
|
4646
|
-
fontFamilies,
|
|
4647
|
-
context
|
|
4648
|
-
);
|
|
4649
|
-
}
|
|
4650
4091
|
const props = el.props ?? {};
|
|
4651
4092
|
const rawStyle = props.style ?? {};
|
|
4652
4093
|
const expanded = expandStyle(rawStyle, fontFamilies);
|
|
@@ -4740,17 +4181,14 @@ async function buildNode(element, parentStyle, yogaNode, viewportWidth, viewport
|
|
|
4740
4181
|
yogaNode
|
|
4741
4182
|
};
|
|
4742
4183
|
}
|
|
4743
|
-
const
|
|
4744
|
-
|
|
4184
|
+
const items = flattenChildren(props.children);
|
|
4185
|
+
const onlyText = items.length === 1 && isText(items[0]) ? items[0] : null;
|
|
4186
|
+
if (onlyText !== null && !isWhiteSpaceOnly(String(onlyText))) {
|
|
4187
|
+
const textContent = String(onlyText);
|
|
4745
4188
|
const childStyle = resolveStyle(void 0, style);
|
|
4746
|
-
const childYogaNode =
|
|
4747
|
-
const
|
|
4748
|
-
|
|
4749
|
-
childStyle,
|
|
4750
|
-
ctx,
|
|
4751
|
-
emojiEnabled
|
|
4752
|
-
);
|
|
4753
|
-
childYogaNode.setMeasureFunc(measureFunc);
|
|
4189
|
+
const childYogaNode = createTextYogaNode();
|
|
4190
|
+
const textMeasure = new TextMeasure(textContent, childStyle, emojiEnabled);
|
|
4191
|
+
setTextMeasure(childYogaNode, textMeasure);
|
|
4754
4192
|
const jc = style.justifyContent;
|
|
4755
4193
|
if (!jc || jc === "flex-start") {
|
|
4756
4194
|
childYogaNode.setFlexGrow(1);
|
|
@@ -4769,6 +4207,7 @@ async function buildNode(element, parentStyle, yogaNode, viewportWidth, viewport
|
|
|
4769
4207
|
style: childStyle,
|
|
4770
4208
|
children: [],
|
|
4771
4209
|
textContent,
|
|
4210
|
+
textMeasure,
|
|
4772
4211
|
props: {},
|
|
4773
4212
|
yogaNode: childYogaNode
|
|
4774
4213
|
}
|
|
@@ -4778,36 +4217,22 @@ async function buildNode(element, parentStyle, yogaNode, viewportWidth, viewport
|
|
|
4778
4217
|
};
|
|
4779
4218
|
}
|
|
4780
4219
|
const children = [];
|
|
4781
|
-
const
|
|
4782
|
-
|
|
4783
|
-
const
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
children.push(
|
|
4798
|
-
await buildNode(
|
|
4799
|
-
processedChild,
|
|
4800
|
-
style,
|
|
4801
|
-
childYogaNode,
|
|
4802
|
-
viewportWidth,
|
|
4803
|
-
viewportHeight,
|
|
4804
|
-
ctx,
|
|
4805
|
-
emojiEnabled,
|
|
4806
|
-
fontFamilies,
|
|
4807
|
-
context
|
|
4808
|
-
)
|
|
4809
|
-
);
|
|
4810
|
-
}
|
|
4220
|
+
for (const rendered of items) {
|
|
4221
|
+
if (typeof rendered === "string" && isWhiteSpaceOnly(rendered)) continue;
|
|
4222
|
+
const childYogaNode = isText(rendered) ? createTextYogaNode() : createYogaNode();
|
|
4223
|
+
yogaNode.insertChild(childYogaNode, children.length);
|
|
4224
|
+
children.push(
|
|
4225
|
+
await buildNode(
|
|
4226
|
+
rendered,
|
|
4227
|
+
style,
|
|
4228
|
+
childYogaNode,
|
|
4229
|
+
viewportWidth,
|
|
4230
|
+
viewportHeight,
|
|
4231
|
+
emojiEnabled,
|
|
4232
|
+
fontFamilies,
|
|
4233
|
+
context
|
|
4234
|
+
)
|
|
4235
|
+
);
|
|
4811
4236
|
}
|
|
4812
4237
|
return {
|
|
4813
4238
|
type: tagName,
|
|
@@ -4817,8 +4242,84 @@ async function buildNode(element, parentStyle, yogaNode, viewportWidth, viewport
|
|
|
4817
4242
|
yogaNode
|
|
4818
4243
|
};
|
|
4819
4244
|
}
|
|
4245
|
+
var MAX_TEXT_RELAYOUTS = 2;
|
|
4246
|
+
function settleText(root, relayout, debug) {
|
|
4247
|
+
const textNodes = [];
|
|
4248
|
+
const collect = (node) => {
|
|
4249
|
+
if (node.style.display === "none") return;
|
|
4250
|
+
if (node.textMeasure) textNodes.push(node);
|
|
4251
|
+
node.children.forEach(collect);
|
|
4252
|
+
};
|
|
4253
|
+
collect(root);
|
|
4254
|
+
const settle = (node, pin) => node.textMeasure.settle(node.yogaNode.getComputedWidth(), pin);
|
|
4255
|
+
for (let pass = 0; pass < MAX_TEXT_RELAYOUTS; pass++) {
|
|
4256
|
+
let unsettled2 = false;
|
|
4257
|
+
for (const node of textNodes) {
|
|
4258
|
+
if (settle(node, true)) {
|
|
4259
|
+
node.yogaNode.markDirty();
|
|
4260
|
+
unsettled2 = true;
|
|
4261
|
+
}
|
|
4262
|
+
}
|
|
4263
|
+
if (!unsettled2) return;
|
|
4264
|
+
relayout();
|
|
4265
|
+
}
|
|
4266
|
+
if (!textNodes.some((node) => settle(node, false))) return;
|
|
4267
|
+
for (const node of textNodes) {
|
|
4268
|
+
if (node.textMeasure.unpin()) node.yogaNode.markDirty();
|
|
4269
|
+
}
|
|
4270
|
+
relayout();
|
|
4271
|
+
const unsettled = textNodes.filter((node) => settle(node, false)).length;
|
|
4272
|
+
if (debug && unsettled > 0) {
|
|
4273
|
+
console.warn(
|
|
4274
|
+
`[@effing/canvas] ${unsettled} text node(s) sized for other lines than drawn: the layout didn't settle in ${MAX_TEXT_RELAYOUTS} relayouts`
|
|
4275
|
+
);
|
|
4276
|
+
}
|
|
4277
|
+
}
|
|
4278
|
+
function setTextMeasure(yogaNode, textMeasure) {
|
|
4279
|
+
yogaNode.setMeasureFunc(
|
|
4280
|
+
(width, widthMode) => textMeasure.measure(
|
|
4281
|
+
widthMode === MeasureMode.Undefined || Number.isNaN(width) ? Infinity : width
|
|
4282
|
+
)
|
|
4283
|
+
);
|
|
4284
|
+
}
|
|
4285
|
+
var isText = (node) => typeof node === "string" || typeof node === "number";
|
|
4286
|
+
function renderComponents(node) {
|
|
4287
|
+
while (node !== null && typeof node === "object" && "type" in node && typeof node.type === "function") {
|
|
4288
|
+
const el = node;
|
|
4289
|
+
node = el.type(
|
|
4290
|
+
el.props ?? {}
|
|
4291
|
+
);
|
|
4292
|
+
}
|
|
4293
|
+
return node;
|
|
4294
|
+
}
|
|
4295
|
+
function flattenChildren(node, items = []) {
|
|
4296
|
+
const rendered = renderComponents(node);
|
|
4297
|
+
if (rendered === null || rendered === void 0 || typeof rendered === "boolean") {
|
|
4298
|
+
return items;
|
|
4299
|
+
}
|
|
4300
|
+
if (Array.isArray(rendered)) {
|
|
4301
|
+
for (const child of rendered) flattenChildren(child, items);
|
|
4302
|
+
return items;
|
|
4303
|
+
}
|
|
4304
|
+
const el = rendered;
|
|
4305
|
+
if (typeof rendered === "object" && el.type === REACT_FRAGMENT) {
|
|
4306
|
+
return flattenChildren(el.props?.children, items);
|
|
4307
|
+
}
|
|
4308
|
+
const last = items[items.length - 1];
|
|
4309
|
+
if (isText(rendered) && items.length > 0 && isText(last)) {
|
|
4310
|
+
items[items.length - 1] = `${last}${rendered}`;
|
|
4311
|
+
} else {
|
|
4312
|
+
items.push(rendered);
|
|
4313
|
+
}
|
|
4314
|
+
return items;
|
|
4315
|
+
}
|
|
4316
|
+
function floorToPixel(value) {
|
|
4317
|
+
const rounded = Math.round(value);
|
|
4318
|
+
return Math.abs(value - rounded) < 1e-4 ? rounded : Math.floor(value);
|
|
4319
|
+
}
|
|
4820
4320
|
function extractLayout(node, yogaNode) {
|
|
4821
4321
|
const layout = yogaNode.getComputedLayout();
|
|
4322
|
+
const place = node.textMeasure ? floorToPixel : (v) => v;
|
|
4822
4323
|
return {
|
|
4823
4324
|
type: node.type,
|
|
4824
4325
|
style: node.style,
|
|
@@ -4827,37 +4328,14 @@ function extractLayout(node, yogaNode) {
|
|
|
4827
4328
|
return extractLayout(child, childYoga);
|
|
4828
4329
|
}),
|
|
4829
4330
|
textContent: node.textContent,
|
|
4331
|
+
textLayout: node.textMeasure?.layout,
|
|
4830
4332
|
props: node.props,
|
|
4831
|
-
x: layout.left,
|
|
4832
|
-
y: layout.top,
|
|
4333
|
+
x: place(layout.left),
|
|
4334
|
+
y: place(layout.top),
|
|
4833
4335
|
width: layout.width,
|
|
4834
4336
|
height: layout.height
|
|
4835
4337
|
};
|
|
4836
4338
|
}
|
|
4837
|
-
function isBrElement(child) {
|
|
4838
|
-
if (child === null || child === void 0 || typeof child !== "object")
|
|
4839
|
-
return false;
|
|
4840
|
-
return child.type === "br";
|
|
4841
|
-
}
|
|
4842
|
-
function extractTextContent(children) {
|
|
4843
|
-
if (children === void 0 || children === null) return void 0;
|
|
4844
|
-
if (typeof children === "string") return children;
|
|
4845
|
-
if (typeof children === "number") return String(children);
|
|
4846
|
-
if (Array.isArray(children)) {
|
|
4847
|
-
const parts = [];
|
|
4848
|
-
for (const child of children) {
|
|
4849
|
-
if (typeof child === "string") {
|
|
4850
|
-
parts.push(child);
|
|
4851
|
-
} else if (typeof child === "number") {
|
|
4852
|
-
parts.push(String(child));
|
|
4853
|
-
} else if (child !== null && child !== void 0 && typeof child !== "boolean") {
|
|
4854
|
-
return void 0;
|
|
4855
|
-
}
|
|
4856
|
-
}
|
|
4857
|
-
return parts.join("");
|
|
4858
|
-
}
|
|
4859
|
-
return void 0;
|
|
4860
|
-
}
|
|
4861
4339
|
var REACT_FRAGMENT = /* @__PURE__ */ Symbol.for("react.fragment");
|
|
4862
4340
|
function resolveSvgTree(node) {
|
|
4863
4341
|
if (node === null || node === void 0 || typeof node === "boolean")
|
|
@@ -4901,18 +4379,6 @@ function resolveSvgTree(node) {
|
|
|
4901
4379
|
}
|
|
4902
4380
|
return el;
|
|
4903
4381
|
}
|
|
4904
|
-
function hasElementChildren(children) {
|
|
4905
|
-
if (children === void 0 || children === null || typeof children === "boolean")
|
|
4906
|
-
return false;
|
|
4907
|
-
if (typeof children === "string" || typeof children === "number")
|
|
4908
|
-
return false;
|
|
4909
|
-
if (Array.isArray(children)) {
|
|
4910
|
-
return children.some(
|
|
4911
|
-
(child) => child !== null && child !== void 0 && typeof child !== "boolean" && typeof child !== "string" && typeof child !== "number"
|
|
4912
|
-
);
|
|
4913
|
-
}
|
|
4914
|
-
return typeof children === "object";
|
|
4915
|
-
}
|
|
4916
4382
|
|
|
4917
4383
|
// src/jsx/index.ts
|
|
4918
4384
|
async function renderReactElement(ctx, element, options = {}) {
|
|
@@ -4931,7 +4397,6 @@ async function renderReactElement(ctx, element, options = {}) {
|
|
|
4931
4397
|
element,
|
|
4932
4398
|
width,
|
|
4933
4399
|
height,
|
|
4934
|
-
ctx,
|
|
4935
4400
|
!!emojiStyle,
|
|
4936
4401
|
fontFamilies,
|
|
4937
4402
|
renderContext
|