@shotstack/shotstack-canvas 1.3.8 → 1.4.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/dist/entry.node.cjs +66 -9
- package/dist/entry.node.d.cts +5 -0
- package/dist/entry.node.d.ts +5 -0
- package/dist/entry.node.js +66 -9
- package/dist/entry.web.d.ts +5 -0
- package/dist/entry.web.js +66 -9
- package/package.json +1 -1
package/dist/entry.node.cjs
CHANGED
|
@@ -370,10 +370,14 @@ var FontRegistry = class _FontRegistry {
|
|
|
370
370
|
fontkitFonts = /* @__PURE__ */ new Map();
|
|
371
371
|
wasmBaseURL;
|
|
372
372
|
initPromise;
|
|
373
|
+
emojiFallbackDesc;
|
|
373
374
|
static fallbackLoader;
|
|
374
375
|
static setFallbackLoader(loader) {
|
|
375
376
|
_FontRegistry.fallbackLoader = loader;
|
|
376
377
|
}
|
|
378
|
+
setEmojiFallback(desc) {
|
|
379
|
+
this.emojiFallbackDesc = desc;
|
|
380
|
+
}
|
|
377
381
|
constructor(wasmBaseURL) {
|
|
378
382
|
this.wasmBaseURL = wasmBaseURL;
|
|
379
383
|
}
|
|
@@ -415,7 +419,8 @@ var FontRegistry = class _FontRegistry {
|
|
|
415
419
|
return this.hb;
|
|
416
420
|
}
|
|
417
421
|
key(desc) {
|
|
418
|
-
|
|
422
|
+
const normalizedStyle = desc.style === "oblique" ? "italic" : desc.style ?? "normal";
|
|
423
|
+
return `${desc.family}__${desc.weight ?? "400"}__${normalizedStyle}`;
|
|
419
424
|
}
|
|
420
425
|
async registerFromBytes(bytes, desc) {
|
|
421
426
|
try {
|
|
@@ -531,7 +536,8 @@ var FontRegistry = class _FontRegistry {
|
|
|
531
536
|
if (fkFont) {
|
|
532
537
|
const glyph = fkFont.getGlyph(glyphId);
|
|
533
538
|
if (glyph && glyph.path) {
|
|
534
|
-
|
|
539
|
+
const path2 = glyph.path.toSVG();
|
|
540
|
+
return path2 && path2 !== "" ? path2 : "M 0 0";
|
|
535
541
|
}
|
|
536
542
|
}
|
|
537
543
|
const font = await this.getFont(desc);
|
|
@@ -587,6 +593,18 @@ var FontRegistry = class _FontRegistry {
|
|
|
587
593
|
};
|
|
588
594
|
|
|
589
595
|
// src/core/layout.ts
|
|
596
|
+
function isEmoji(char) {
|
|
597
|
+
const code = char.codePointAt(0);
|
|
598
|
+
if (!code) return false;
|
|
599
|
+
return code >= 127744 && code <= 129535 || // Emoticons, symbols, pictographs
|
|
600
|
+
code >= 9728 && code <= 9983 || // Miscellaneous symbols
|
|
601
|
+
code >= 9984 && code <= 10175 || // Dingbats
|
|
602
|
+
code >= 65024 && code <= 65039 || // Variation selectors
|
|
603
|
+
code >= 128512 && code <= 128591 || // Emoticons
|
|
604
|
+
code >= 128640 && code <= 128767 || // Transport and map symbols
|
|
605
|
+
code >= 129280 && code <= 129535 || // Supplemental symbols and pictographs
|
|
606
|
+
code >= 129648 && code <= 129791;
|
|
607
|
+
}
|
|
590
608
|
var LayoutEngine = class {
|
|
591
609
|
constructor(fonts) {
|
|
592
610
|
this.fonts = fonts;
|
|
@@ -634,14 +652,43 @@ var LayoutEngine = class {
|
|
|
634
652
|
}
|
|
635
653
|
async layout(params) {
|
|
636
654
|
try {
|
|
637
|
-
const { textTransform, desc, fontSize, letterSpacing, width } = params;
|
|
655
|
+
const { textTransform, desc, fontSize, letterSpacing, width, emojiFallback } = params;
|
|
638
656
|
const input = this.transformText(params.text, textTransform);
|
|
639
657
|
if (!input || input.length === 0) {
|
|
640
658
|
return [];
|
|
641
659
|
}
|
|
642
660
|
let shaped;
|
|
643
661
|
try {
|
|
644
|
-
|
|
662
|
+
if (!emojiFallback) {
|
|
663
|
+
shaped = await this.shapeFull(input, desc);
|
|
664
|
+
} else {
|
|
665
|
+
const chars = Array.from(input);
|
|
666
|
+
const runs = [];
|
|
667
|
+
let currentRun = { text: "", startIndex: 0, isEmoji: false };
|
|
668
|
+
for (let i = 0; i < chars.length; i++) {
|
|
669
|
+
const char = chars[i];
|
|
670
|
+
const charIsEmoji = isEmoji(char);
|
|
671
|
+
if (i === 0) {
|
|
672
|
+
currentRun = { text: char, startIndex: 0, isEmoji: charIsEmoji };
|
|
673
|
+
} else if (currentRun.isEmoji === charIsEmoji) {
|
|
674
|
+
currentRun.text += char;
|
|
675
|
+
} else {
|
|
676
|
+
runs.push(currentRun);
|
|
677
|
+
currentRun = { text: char, startIndex: currentRun.startIndex + currentRun.text.length, isEmoji: charIsEmoji };
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
if (currentRun.text) runs.push(currentRun);
|
|
681
|
+
shaped = [];
|
|
682
|
+
for (const run of runs) {
|
|
683
|
+
const runFont = run.isEmoji ? emojiFallback : desc;
|
|
684
|
+
const runShaped = await this.shapeFull(run.text, runFont);
|
|
685
|
+
for (const glyph of runShaped) {
|
|
686
|
+
glyph.cl += run.startIndex;
|
|
687
|
+
glyph.fontDesc = runFont;
|
|
688
|
+
}
|
|
689
|
+
shaped.push(...runShaped);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
645
692
|
} catch (err) {
|
|
646
693
|
throw new Error(`Text shaping failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
647
694
|
}
|
|
@@ -667,7 +714,9 @@ var LayoutEngine = class {
|
|
|
667
714
|
xOffset: g.dx * scale,
|
|
668
715
|
yOffset: -g.dy * scale,
|
|
669
716
|
cluster: g.cl,
|
|
670
|
-
char
|
|
717
|
+
char,
|
|
718
|
+
fontDesc: g.fontDesc
|
|
719
|
+
// Preserve font descriptor
|
|
671
720
|
};
|
|
672
721
|
});
|
|
673
722
|
const lines = [];
|
|
@@ -815,7 +864,7 @@ async function buildDrawOps(p) {
|
|
|
815
864
|
const lineIndex = p.lines.indexOf(line);
|
|
816
865
|
const baselineY = blockY + lineIndex * lineHeightPx;
|
|
817
866
|
for (const glyph of line.glyphs) {
|
|
818
|
-
const path = await p.glyphPathProvider(glyph.id);
|
|
867
|
+
const path = await p.glyphPathProvider(glyph.id, glyph.fontDesc);
|
|
819
868
|
if (!path || path === "M 0 0") {
|
|
820
869
|
xCursor += glyph.xAdvance;
|
|
821
870
|
continue;
|
|
@@ -2079,6 +2128,13 @@ async function createTextEngine(opts = {}) {
|
|
|
2079
2128
|
const desc = { family: main.family, weight: `${main.weight}`, style: main.style };
|
|
2080
2129
|
let lines;
|
|
2081
2130
|
try {
|
|
2131
|
+
const emojiDesc = { family: "NotoEmoji", weight: "400", style: "normal" };
|
|
2132
|
+
let emojiAvailable = false;
|
|
2133
|
+
try {
|
|
2134
|
+
await fonts.getFace(emojiDesc);
|
|
2135
|
+
emojiAvailable = true;
|
|
2136
|
+
} catch {
|
|
2137
|
+
}
|
|
2082
2138
|
lines = await layout.layout({
|
|
2083
2139
|
text: asset.text,
|
|
2084
2140
|
width: asset.width ?? width,
|
|
@@ -2086,7 +2142,8 @@ async function createTextEngine(opts = {}) {
|
|
|
2086
2142
|
fontSize: main.size,
|
|
2087
2143
|
lineHeight: asset.style?.lineHeight ?? 1.2,
|
|
2088
2144
|
desc,
|
|
2089
|
-
textTransform: asset.style?.textTransform ?? "none"
|
|
2145
|
+
textTransform: asset.style?.textTransform ?? "none",
|
|
2146
|
+
emojiFallback: emojiAvailable ? emojiDesc : void 0
|
|
2090
2147
|
});
|
|
2091
2148
|
} catch (err) {
|
|
2092
2149
|
throw new Error(
|
|
@@ -2128,8 +2185,8 @@ async function createTextEngine(opts = {}) {
|
|
|
2128
2185
|
vertical: asset.align?.vertical ?? "middle"
|
|
2129
2186
|
},
|
|
2130
2187
|
background: asset.background,
|
|
2131
|
-
glyphPathProvider: (gid) => fonts.glyphPath(desc, gid),
|
|
2132
|
-
getUnitsPerEm: () => fonts.getUnitsPerEm(desc)
|
|
2188
|
+
glyphPathProvider: (gid, fontDesc) => fonts.glyphPath(fontDesc || desc, gid),
|
|
2189
|
+
getUnitsPerEm: (fontDesc) => fonts.getUnitsPerEm(fontDesc || desc)
|
|
2133
2190
|
});
|
|
2134
2191
|
} catch (err) {
|
|
2135
2192
|
throw new Error(
|
package/dist/entry.node.d.cts
CHANGED
package/dist/entry.node.d.ts
CHANGED
package/dist/entry.node.js
CHANGED
|
@@ -331,10 +331,14 @@ var FontRegistry = class _FontRegistry {
|
|
|
331
331
|
fontkitFonts = /* @__PURE__ */ new Map();
|
|
332
332
|
wasmBaseURL;
|
|
333
333
|
initPromise;
|
|
334
|
+
emojiFallbackDesc;
|
|
334
335
|
static fallbackLoader;
|
|
335
336
|
static setFallbackLoader(loader) {
|
|
336
337
|
_FontRegistry.fallbackLoader = loader;
|
|
337
338
|
}
|
|
339
|
+
setEmojiFallback(desc) {
|
|
340
|
+
this.emojiFallbackDesc = desc;
|
|
341
|
+
}
|
|
338
342
|
constructor(wasmBaseURL) {
|
|
339
343
|
this.wasmBaseURL = wasmBaseURL;
|
|
340
344
|
}
|
|
@@ -376,7 +380,8 @@ var FontRegistry = class _FontRegistry {
|
|
|
376
380
|
return this.hb;
|
|
377
381
|
}
|
|
378
382
|
key(desc) {
|
|
379
|
-
|
|
383
|
+
const normalizedStyle = desc.style === "oblique" ? "italic" : desc.style ?? "normal";
|
|
384
|
+
return `${desc.family}__${desc.weight ?? "400"}__${normalizedStyle}`;
|
|
380
385
|
}
|
|
381
386
|
async registerFromBytes(bytes, desc) {
|
|
382
387
|
try {
|
|
@@ -492,7 +497,8 @@ var FontRegistry = class _FontRegistry {
|
|
|
492
497
|
if (fkFont) {
|
|
493
498
|
const glyph = fkFont.getGlyph(glyphId);
|
|
494
499
|
if (glyph && glyph.path) {
|
|
495
|
-
|
|
500
|
+
const path2 = glyph.path.toSVG();
|
|
501
|
+
return path2 && path2 !== "" ? path2 : "M 0 0";
|
|
496
502
|
}
|
|
497
503
|
}
|
|
498
504
|
const font = await this.getFont(desc);
|
|
@@ -548,6 +554,18 @@ var FontRegistry = class _FontRegistry {
|
|
|
548
554
|
};
|
|
549
555
|
|
|
550
556
|
// src/core/layout.ts
|
|
557
|
+
function isEmoji(char) {
|
|
558
|
+
const code = char.codePointAt(0);
|
|
559
|
+
if (!code) return false;
|
|
560
|
+
return code >= 127744 && code <= 129535 || // Emoticons, symbols, pictographs
|
|
561
|
+
code >= 9728 && code <= 9983 || // Miscellaneous symbols
|
|
562
|
+
code >= 9984 && code <= 10175 || // Dingbats
|
|
563
|
+
code >= 65024 && code <= 65039 || // Variation selectors
|
|
564
|
+
code >= 128512 && code <= 128591 || // Emoticons
|
|
565
|
+
code >= 128640 && code <= 128767 || // Transport and map symbols
|
|
566
|
+
code >= 129280 && code <= 129535 || // Supplemental symbols and pictographs
|
|
567
|
+
code >= 129648 && code <= 129791;
|
|
568
|
+
}
|
|
551
569
|
var LayoutEngine = class {
|
|
552
570
|
constructor(fonts) {
|
|
553
571
|
this.fonts = fonts;
|
|
@@ -595,14 +613,43 @@ var LayoutEngine = class {
|
|
|
595
613
|
}
|
|
596
614
|
async layout(params) {
|
|
597
615
|
try {
|
|
598
|
-
const { textTransform, desc, fontSize, letterSpacing, width } = params;
|
|
616
|
+
const { textTransform, desc, fontSize, letterSpacing, width, emojiFallback } = params;
|
|
599
617
|
const input = this.transformText(params.text, textTransform);
|
|
600
618
|
if (!input || input.length === 0) {
|
|
601
619
|
return [];
|
|
602
620
|
}
|
|
603
621
|
let shaped;
|
|
604
622
|
try {
|
|
605
|
-
|
|
623
|
+
if (!emojiFallback) {
|
|
624
|
+
shaped = await this.shapeFull(input, desc);
|
|
625
|
+
} else {
|
|
626
|
+
const chars = Array.from(input);
|
|
627
|
+
const runs = [];
|
|
628
|
+
let currentRun = { text: "", startIndex: 0, isEmoji: false };
|
|
629
|
+
for (let i = 0; i < chars.length; i++) {
|
|
630
|
+
const char = chars[i];
|
|
631
|
+
const charIsEmoji = isEmoji(char);
|
|
632
|
+
if (i === 0) {
|
|
633
|
+
currentRun = { text: char, startIndex: 0, isEmoji: charIsEmoji };
|
|
634
|
+
} else if (currentRun.isEmoji === charIsEmoji) {
|
|
635
|
+
currentRun.text += char;
|
|
636
|
+
} else {
|
|
637
|
+
runs.push(currentRun);
|
|
638
|
+
currentRun = { text: char, startIndex: currentRun.startIndex + currentRun.text.length, isEmoji: charIsEmoji };
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
if (currentRun.text) runs.push(currentRun);
|
|
642
|
+
shaped = [];
|
|
643
|
+
for (const run of runs) {
|
|
644
|
+
const runFont = run.isEmoji ? emojiFallback : desc;
|
|
645
|
+
const runShaped = await this.shapeFull(run.text, runFont);
|
|
646
|
+
for (const glyph of runShaped) {
|
|
647
|
+
glyph.cl += run.startIndex;
|
|
648
|
+
glyph.fontDesc = runFont;
|
|
649
|
+
}
|
|
650
|
+
shaped.push(...runShaped);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
606
653
|
} catch (err) {
|
|
607
654
|
throw new Error(`Text shaping failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
608
655
|
}
|
|
@@ -628,7 +675,9 @@ var LayoutEngine = class {
|
|
|
628
675
|
xOffset: g.dx * scale,
|
|
629
676
|
yOffset: -g.dy * scale,
|
|
630
677
|
cluster: g.cl,
|
|
631
|
-
char
|
|
678
|
+
char,
|
|
679
|
+
fontDesc: g.fontDesc
|
|
680
|
+
// Preserve font descriptor
|
|
632
681
|
};
|
|
633
682
|
});
|
|
634
683
|
const lines = [];
|
|
@@ -776,7 +825,7 @@ async function buildDrawOps(p) {
|
|
|
776
825
|
const lineIndex = p.lines.indexOf(line);
|
|
777
826
|
const baselineY = blockY + lineIndex * lineHeightPx;
|
|
778
827
|
for (const glyph of line.glyphs) {
|
|
779
|
-
const path = await p.glyphPathProvider(glyph.id);
|
|
828
|
+
const path = await p.glyphPathProvider(glyph.id, glyph.fontDesc);
|
|
780
829
|
if (!path || path === "M 0 0") {
|
|
781
830
|
xCursor += glyph.xAdvance;
|
|
782
831
|
continue;
|
|
@@ -2040,6 +2089,13 @@ async function createTextEngine(opts = {}) {
|
|
|
2040
2089
|
const desc = { family: main.family, weight: `${main.weight}`, style: main.style };
|
|
2041
2090
|
let lines;
|
|
2042
2091
|
try {
|
|
2092
|
+
const emojiDesc = { family: "NotoEmoji", weight: "400", style: "normal" };
|
|
2093
|
+
let emojiAvailable = false;
|
|
2094
|
+
try {
|
|
2095
|
+
await fonts.getFace(emojiDesc);
|
|
2096
|
+
emojiAvailable = true;
|
|
2097
|
+
} catch {
|
|
2098
|
+
}
|
|
2043
2099
|
lines = await layout.layout({
|
|
2044
2100
|
text: asset.text,
|
|
2045
2101
|
width: asset.width ?? width,
|
|
@@ -2047,7 +2103,8 @@ async function createTextEngine(opts = {}) {
|
|
|
2047
2103
|
fontSize: main.size,
|
|
2048
2104
|
lineHeight: asset.style?.lineHeight ?? 1.2,
|
|
2049
2105
|
desc,
|
|
2050
|
-
textTransform: asset.style?.textTransform ?? "none"
|
|
2106
|
+
textTransform: asset.style?.textTransform ?? "none",
|
|
2107
|
+
emojiFallback: emojiAvailable ? emojiDesc : void 0
|
|
2051
2108
|
});
|
|
2052
2109
|
} catch (err) {
|
|
2053
2110
|
throw new Error(
|
|
@@ -2089,8 +2146,8 @@ async function createTextEngine(opts = {}) {
|
|
|
2089
2146
|
vertical: asset.align?.vertical ?? "middle"
|
|
2090
2147
|
},
|
|
2091
2148
|
background: asset.background,
|
|
2092
|
-
glyphPathProvider: (gid) => fonts.glyphPath(desc, gid),
|
|
2093
|
-
getUnitsPerEm: () => fonts.getUnitsPerEm(desc)
|
|
2149
|
+
glyphPathProvider: (gid, fontDesc) => fonts.glyphPath(fontDesc || desc, gid),
|
|
2150
|
+
getUnitsPerEm: (fontDesc) => fonts.getUnitsPerEm(fontDesc || desc)
|
|
2094
2151
|
});
|
|
2095
2152
|
} catch (err) {
|
|
2096
2153
|
throw new Error(
|
package/dist/entry.web.d.ts
CHANGED
package/dist/entry.web.js
CHANGED
|
@@ -336,11 +336,15 @@ var _FontRegistry = class _FontRegistry {
|
|
|
336
336
|
__publicField(this, "fontkitFonts", /* @__PURE__ */ new Map());
|
|
337
337
|
__publicField(this, "wasmBaseURL");
|
|
338
338
|
__publicField(this, "initPromise");
|
|
339
|
+
__publicField(this, "emojiFallbackDesc");
|
|
339
340
|
this.wasmBaseURL = wasmBaseURL;
|
|
340
341
|
}
|
|
341
342
|
static setFallbackLoader(loader) {
|
|
342
343
|
_FontRegistry.fallbackLoader = loader;
|
|
343
344
|
}
|
|
345
|
+
setEmojiFallback(desc) {
|
|
346
|
+
this.emojiFallbackDesc = desc;
|
|
347
|
+
}
|
|
344
348
|
async init() {
|
|
345
349
|
if (this.initPromise) {
|
|
346
350
|
await this.initPromise;
|
|
@@ -379,7 +383,8 @@ var _FontRegistry = class _FontRegistry {
|
|
|
379
383
|
return this.hb;
|
|
380
384
|
}
|
|
381
385
|
key(desc) {
|
|
382
|
-
|
|
386
|
+
const normalizedStyle = desc.style === "oblique" ? "italic" : desc.style ?? "normal";
|
|
387
|
+
return `${desc.family}__${desc.weight ?? "400"}__${normalizedStyle}`;
|
|
383
388
|
}
|
|
384
389
|
async registerFromBytes(bytes, desc) {
|
|
385
390
|
try {
|
|
@@ -495,7 +500,8 @@ var _FontRegistry = class _FontRegistry {
|
|
|
495
500
|
if (fkFont) {
|
|
496
501
|
const glyph = fkFont.getGlyph(glyphId);
|
|
497
502
|
if (glyph && glyph.path) {
|
|
498
|
-
|
|
503
|
+
const path2 = glyph.path.toSVG();
|
|
504
|
+
return path2 && path2 !== "" ? path2 : "M 0 0";
|
|
499
505
|
}
|
|
500
506
|
}
|
|
501
507
|
const font = await this.getFont(desc);
|
|
@@ -553,6 +559,18 @@ __publicField(_FontRegistry, "fallbackLoader");
|
|
|
553
559
|
var FontRegistry = _FontRegistry;
|
|
554
560
|
|
|
555
561
|
// src/core/layout.ts
|
|
562
|
+
function isEmoji(char) {
|
|
563
|
+
const code = char.codePointAt(0);
|
|
564
|
+
if (!code) return false;
|
|
565
|
+
return code >= 127744 && code <= 129535 || // Emoticons, symbols, pictographs
|
|
566
|
+
code >= 9728 && code <= 9983 || // Miscellaneous symbols
|
|
567
|
+
code >= 9984 && code <= 10175 || // Dingbats
|
|
568
|
+
code >= 65024 && code <= 65039 || // Variation selectors
|
|
569
|
+
code >= 128512 && code <= 128591 || // Emoticons
|
|
570
|
+
code >= 128640 && code <= 128767 || // Transport and map symbols
|
|
571
|
+
code >= 129280 && code <= 129535 || // Supplemental symbols and pictographs
|
|
572
|
+
code >= 129648 && code <= 129791;
|
|
573
|
+
}
|
|
556
574
|
var LayoutEngine = class {
|
|
557
575
|
constructor(fonts) {
|
|
558
576
|
this.fonts = fonts;
|
|
@@ -600,14 +618,43 @@ var LayoutEngine = class {
|
|
|
600
618
|
}
|
|
601
619
|
async layout(params) {
|
|
602
620
|
try {
|
|
603
|
-
const { textTransform, desc, fontSize, letterSpacing, width } = params;
|
|
621
|
+
const { textTransform, desc, fontSize, letterSpacing, width, emojiFallback } = params;
|
|
604
622
|
const input = this.transformText(params.text, textTransform);
|
|
605
623
|
if (!input || input.length === 0) {
|
|
606
624
|
return [];
|
|
607
625
|
}
|
|
608
626
|
let shaped;
|
|
609
627
|
try {
|
|
610
|
-
|
|
628
|
+
if (!emojiFallback) {
|
|
629
|
+
shaped = await this.shapeFull(input, desc);
|
|
630
|
+
} else {
|
|
631
|
+
const chars = Array.from(input);
|
|
632
|
+
const runs = [];
|
|
633
|
+
let currentRun = { text: "", startIndex: 0, isEmoji: false };
|
|
634
|
+
for (let i = 0; i < chars.length; i++) {
|
|
635
|
+
const char = chars[i];
|
|
636
|
+
const charIsEmoji = isEmoji(char);
|
|
637
|
+
if (i === 0) {
|
|
638
|
+
currentRun = { text: char, startIndex: 0, isEmoji: charIsEmoji };
|
|
639
|
+
} else if (currentRun.isEmoji === charIsEmoji) {
|
|
640
|
+
currentRun.text += char;
|
|
641
|
+
} else {
|
|
642
|
+
runs.push(currentRun);
|
|
643
|
+
currentRun = { text: char, startIndex: currentRun.startIndex + currentRun.text.length, isEmoji: charIsEmoji };
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
if (currentRun.text) runs.push(currentRun);
|
|
647
|
+
shaped = [];
|
|
648
|
+
for (const run of runs) {
|
|
649
|
+
const runFont = run.isEmoji ? emojiFallback : desc;
|
|
650
|
+
const runShaped = await this.shapeFull(run.text, runFont);
|
|
651
|
+
for (const glyph of runShaped) {
|
|
652
|
+
glyph.cl += run.startIndex;
|
|
653
|
+
glyph.fontDesc = runFont;
|
|
654
|
+
}
|
|
655
|
+
shaped.push(...runShaped);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
611
658
|
} catch (err) {
|
|
612
659
|
throw new Error(`Text shaping failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
613
660
|
}
|
|
@@ -633,7 +680,9 @@ var LayoutEngine = class {
|
|
|
633
680
|
xOffset: g.dx * scale,
|
|
634
681
|
yOffset: -g.dy * scale,
|
|
635
682
|
cluster: g.cl,
|
|
636
|
-
char
|
|
683
|
+
char,
|
|
684
|
+
fontDesc: g.fontDesc
|
|
685
|
+
// Preserve font descriptor
|
|
637
686
|
};
|
|
638
687
|
});
|
|
639
688
|
const lines = [];
|
|
@@ -781,7 +830,7 @@ async function buildDrawOps(p) {
|
|
|
781
830
|
const lineIndex = p.lines.indexOf(line);
|
|
782
831
|
const baselineY = blockY + lineIndex * lineHeightPx;
|
|
783
832
|
for (const glyph of line.glyphs) {
|
|
784
|
-
const path = await p.glyphPathProvider(glyph.id);
|
|
833
|
+
const path = await p.glyphPathProvider(glyph.id, glyph.fontDesc);
|
|
785
834
|
if (!path || path === "M 0 0") {
|
|
786
835
|
xCursor += glyph.xAdvance;
|
|
787
836
|
continue;
|
|
@@ -1761,6 +1810,13 @@ async function createTextEngine(opts = {}) {
|
|
|
1761
1810
|
const desc = { family: main.family, weight: `${main.weight}`, style: main.style };
|
|
1762
1811
|
let lines;
|
|
1763
1812
|
try {
|
|
1813
|
+
const emojiDesc = { family: "NotoEmoji", weight: "400", style: "normal" };
|
|
1814
|
+
let emojiAvailable = false;
|
|
1815
|
+
try {
|
|
1816
|
+
await fonts.getFace(emojiDesc);
|
|
1817
|
+
emojiAvailable = true;
|
|
1818
|
+
} catch {
|
|
1819
|
+
}
|
|
1764
1820
|
lines = await layout.layout({
|
|
1765
1821
|
text: asset.text,
|
|
1766
1822
|
width: asset.width ?? width,
|
|
@@ -1768,7 +1824,8 @@ async function createTextEngine(opts = {}) {
|
|
|
1768
1824
|
fontSize: main.size,
|
|
1769
1825
|
lineHeight: asset.style?.lineHeight ?? 1.2,
|
|
1770
1826
|
desc,
|
|
1771
|
-
textTransform: asset.style?.textTransform ?? "none"
|
|
1827
|
+
textTransform: asset.style?.textTransform ?? "none",
|
|
1828
|
+
emojiFallback: emojiAvailable ? emojiDesc : void 0
|
|
1772
1829
|
});
|
|
1773
1830
|
} catch (err) {
|
|
1774
1831
|
throw new Error(
|
|
@@ -1810,8 +1867,8 @@ async function createTextEngine(opts = {}) {
|
|
|
1810
1867
|
vertical: asset.align?.vertical ?? "middle"
|
|
1811
1868
|
},
|
|
1812
1869
|
background: asset.background,
|
|
1813
|
-
glyphPathProvider: (gid) => fonts.glyphPath(desc, gid),
|
|
1814
|
-
getUnitsPerEm: () => fonts.getUnitsPerEm(desc)
|
|
1870
|
+
glyphPathProvider: (gid, fontDesc) => fonts.glyphPath(fontDesc || desc, gid),
|
|
1871
|
+
getUnitsPerEm: (fontDesc) => fonts.getUnitsPerEm(fontDesc || desc)
|
|
1815
1872
|
});
|
|
1816
1873
|
} catch (err) {
|
|
1817
1874
|
throw new Error(
|
package/package.json
CHANGED