@shotstack/shotstack-canvas 2.1.3 → 2.1.5
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 +653 -575
- package/dist/entry.node.d.cts +41 -6
- package/dist/entry.node.d.ts +41 -6
- package/dist/entry.node.js +653 -575
- package/dist/entry.web.d.ts +41 -6
- package/dist/entry.web.js +2946 -2868
- package/package.json +2 -2
package/dist/entry.node.cjs
CHANGED
|
@@ -590,19 +590,28 @@ var richCaptionFontSchema = import_zod.z.object({
|
|
|
590
590
|
weight: import_zod.z.union([import_zod.z.string(), import_zod.z.number()]).default("400"),
|
|
591
591
|
color: import_zod.z.string().regex(HEX6).default("#ffffff"),
|
|
592
592
|
opacity: import_zod.z.number().min(0).max(1).default(1),
|
|
593
|
-
background: import_zod.z.string().regex(HEX6).optional()
|
|
593
|
+
background: import_zod.z.string().regex(HEX6).optional(),
|
|
594
|
+
textDecoration: import_zod.z.enum(["none", "underline", "line-through"]).default("none")
|
|
594
595
|
});
|
|
595
596
|
var richCaptionActiveSchema = import_zod2.richCaptionActiveSchema.extend({
|
|
596
597
|
font: import_zod.z.object({
|
|
597
|
-
color: import_zod.z.string().regex(HEX6).
|
|
598
|
+
color: import_zod.z.string().regex(HEX6).optional(),
|
|
598
599
|
background: import_zod.z.string().regex(HEX6).optional(),
|
|
599
|
-
opacity: import_zod.z.number().min(0).max(1).
|
|
600
|
+
opacity: import_zod.z.number().min(0).max(1).optional(),
|
|
601
|
+
textDecoration: import_zod.z.enum(["none", "underline", "line-through"]).optional()
|
|
600
602
|
}).optional(),
|
|
601
603
|
stroke: import_zod.z.object({
|
|
602
604
|
width: import_zod.z.number().min(0).optional(),
|
|
603
605
|
color: import_zod.z.string().regex(HEX6).optional(),
|
|
604
606
|
opacity: import_zod.z.number().min(0).max(1).optional()
|
|
605
607
|
}).optional(),
|
|
608
|
+
shadow: import_zod.z.object({
|
|
609
|
+
offsetX: import_zod.z.number().optional(),
|
|
610
|
+
offsetY: import_zod.z.number().optional(),
|
|
611
|
+
blur: import_zod.z.number().min(0).optional(),
|
|
612
|
+
color: import_zod.z.string().regex(HEX6).optional(),
|
|
613
|
+
opacity: import_zod.z.number().min(0).max(1).optional()
|
|
614
|
+
}).optional(),
|
|
606
615
|
scale: import_zod.z.number().min(0.5).max(2).default(1)
|
|
607
616
|
});
|
|
608
617
|
var richCaptionWordAnimationSchema = import_zod2.richCaptionWordAnimationSchema.extend({
|
|
@@ -2619,234 +2628,598 @@ function parseHex6(hex, alpha = 1) {
|
|
|
2619
2628
|
return { r, g, b, a: alpha };
|
|
2620
2629
|
}
|
|
2621
2630
|
|
|
2622
|
-
// src/core/rich-caption-
|
|
2623
|
-
var
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
slide: 250,
|
|
2629
|
-
bounce: 400,
|
|
2630
|
-
typewriter: 0,
|
|
2631
|
-
none: 0
|
|
2632
|
-
};
|
|
2633
|
-
var DEFAULT_ANIMATION_STATE = {
|
|
2634
|
-
opacity: 1,
|
|
2635
|
-
scale: 1,
|
|
2636
|
-
translateX: 0,
|
|
2637
|
-
translateY: 0,
|
|
2638
|
-
fillProgress: 1,
|
|
2639
|
-
isActive: false,
|
|
2640
|
-
visibleCharacters: -1
|
|
2641
|
-
};
|
|
2642
|
-
function easeOutQuad2(t) {
|
|
2643
|
-
return t * (2 - t);
|
|
2644
|
-
}
|
|
2645
|
-
function easeInOutQuad(t) {
|
|
2646
|
-
return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
|
|
2647
|
-
}
|
|
2648
|
-
function easeOutBack(t) {
|
|
2649
|
-
const c1 = 1.70158;
|
|
2650
|
-
const c3 = c1 + 1;
|
|
2651
|
-
return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
|
|
2652
|
-
}
|
|
2653
|
-
function easeOutCirc(t) {
|
|
2654
|
-
return Math.sqrt(1 - Math.pow(t - 1, 2));
|
|
2631
|
+
// src/core/rich-caption-layout.ts
|
|
2632
|
+
var import_lru_cache = require("lru-cache");
|
|
2633
|
+
var ASCENT_RATIO = 0.8;
|
|
2634
|
+
var DESCENT_RATIO = 0.2;
|
|
2635
|
+
function isRTLText(text) {
|
|
2636
|
+
return containsRTLCharacters(text);
|
|
2655
2637
|
}
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2638
|
+
var WordTimingStore = class {
|
|
2639
|
+
startTimes;
|
|
2640
|
+
endTimes;
|
|
2641
|
+
xPositions;
|
|
2642
|
+
yPositions;
|
|
2643
|
+
widths;
|
|
2644
|
+
words;
|
|
2645
|
+
length;
|
|
2646
|
+
constructor(words) {
|
|
2647
|
+
this.length = words.length;
|
|
2648
|
+
this.startTimes = new Uint32Array(this.length);
|
|
2649
|
+
this.endTimes = new Uint32Array(this.length);
|
|
2650
|
+
this.xPositions = new Float32Array(this.length);
|
|
2651
|
+
this.yPositions = new Float32Array(this.length);
|
|
2652
|
+
this.widths = new Float32Array(this.length);
|
|
2653
|
+
this.words = new Array(this.length);
|
|
2654
|
+
for (let i = 0; i < this.length; i++) {
|
|
2655
|
+
this.startTimes[i] = Math.floor(words[i].start);
|
|
2656
|
+
this.endTimes[i] = Math.floor(words[i].end);
|
|
2657
|
+
this.words[i] = words[i].text;
|
|
2658
|
+
}
|
|
2664
2659
|
}
|
|
2665
|
-
|
|
2666
|
-
|
|
2660
|
+
};
|
|
2661
|
+
function findWordAtTime(store, timeMs) {
|
|
2662
|
+
let left = 0;
|
|
2663
|
+
let right = store.length - 1;
|
|
2664
|
+
while (left <= right) {
|
|
2665
|
+
const mid = left + right >>> 1;
|
|
2666
|
+
const start = store.startTimes[mid];
|
|
2667
|
+
const end = store.endTimes[mid];
|
|
2668
|
+
if (timeMs >= start && timeMs < end) {
|
|
2669
|
+
return mid;
|
|
2670
|
+
}
|
|
2671
|
+
if (timeMs < start) {
|
|
2672
|
+
right = mid - 1;
|
|
2673
|
+
} else {
|
|
2674
|
+
left = mid + 1;
|
|
2675
|
+
}
|
|
2667
2676
|
}
|
|
2668
|
-
return
|
|
2669
|
-
}
|
|
2670
|
-
function clamp(value, min, max) {
|
|
2671
|
-
return Math.min(Math.max(value, min), max);
|
|
2677
|
+
return -1;
|
|
2672
2678
|
}
|
|
2673
|
-
function
|
|
2674
|
-
if (
|
|
2675
|
-
return
|
|
2679
|
+
function groupWordsByPause(store, pauseThreshold = 500) {
|
|
2680
|
+
if (store.length === 0) {
|
|
2681
|
+
return [];
|
|
2676
2682
|
}
|
|
2677
|
-
const
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2683
|
+
const groups = [];
|
|
2684
|
+
let currentGroup = [];
|
|
2685
|
+
for (let i = 0; i < store.length; i++) {
|
|
2686
|
+
if (currentGroup.length === 0) {
|
|
2687
|
+
currentGroup.push(i);
|
|
2688
|
+
continue;
|
|
2689
|
+
}
|
|
2690
|
+
const prevEnd = store.endTimes[currentGroup[currentGroup.length - 1]];
|
|
2691
|
+
const currStart = store.startTimes[i];
|
|
2692
|
+
const gap = currStart - prevEnd;
|
|
2693
|
+
const prevText = store.words[currentGroup[currentGroup.length - 1]];
|
|
2694
|
+
const endsWithPunctuation = /[.!?]$/.test(prevText);
|
|
2695
|
+
if (gap >= pauseThreshold || endsWithPunctuation) {
|
|
2696
|
+
groups.push(currentGroup);
|
|
2697
|
+
currentGroup = [i];
|
|
2698
|
+
} else {
|
|
2699
|
+
currentGroup.push(i);
|
|
2700
|
+
}
|
|
2684
2701
|
}
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
}
|
|
2688
|
-
|
|
2689
|
-
return ctx.currentTime >= ctx.wordStart && ctx.currentTime < ctx.wordEnd;
|
|
2702
|
+
if (currentGroup.length > 0) {
|
|
2703
|
+
groups.push(currentGroup);
|
|
2704
|
+
}
|
|
2705
|
+
return groups;
|
|
2690
2706
|
}
|
|
2691
|
-
function
|
|
2692
|
-
const
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2707
|
+
function breakIntoLines(wordWidths, maxWidth, spaceWidth) {
|
|
2708
|
+
const lines = [];
|
|
2709
|
+
let currentLine = [];
|
|
2710
|
+
let currentWidth = 0;
|
|
2711
|
+
for (let i = 0; i < wordWidths.length; i++) {
|
|
2712
|
+
const wordWidth = wordWidths[i];
|
|
2713
|
+
const spaceNeeded = currentLine.length > 0 ? spaceWidth : 0;
|
|
2714
|
+
if (currentWidth + spaceNeeded + wordWidth <= maxWidth) {
|
|
2715
|
+
currentLine.push(i);
|
|
2716
|
+
currentWidth += spaceNeeded + wordWidth;
|
|
2717
|
+
} else {
|
|
2718
|
+
if (currentLine.length > 0) {
|
|
2719
|
+
lines.push(currentLine);
|
|
2720
|
+
}
|
|
2721
|
+
currentLine = [i];
|
|
2722
|
+
currentWidth = wordWidth;
|
|
2723
|
+
}
|
|
2703
2724
|
}
|
|
2704
|
-
if (
|
|
2705
|
-
|
|
2706
|
-
fillProgress: 1,
|
|
2707
|
-
isActive: false,
|
|
2708
|
-
opacity: 1
|
|
2709
|
-
};
|
|
2725
|
+
if (currentLine.length > 0) {
|
|
2726
|
+
lines.push(currentLine);
|
|
2710
2727
|
}
|
|
2711
|
-
return
|
|
2712
|
-
fillProgress: calculateWordProgress(adjustedCtx),
|
|
2713
|
-
isActive,
|
|
2714
|
-
opacity: 1
|
|
2715
|
-
};
|
|
2728
|
+
return lines;
|
|
2716
2729
|
}
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
return {
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2730
|
+
var GLYPH_SIZE_ESTIMATE = 64;
|
|
2731
|
+
function createShapedWordCache() {
|
|
2732
|
+
return new import_lru_cache.LRUCache({
|
|
2733
|
+
max: 5e4,
|
|
2734
|
+
maxSize: 50 * 1024 * 1024,
|
|
2735
|
+
maxEntrySize: 100 * 1024,
|
|
2736
|
+
sizeCalculation: (value, key) => {
|
|
2737
|
+
const keySize = key.length * 2;
|
|
2738
|
+
const glyphsSize = value.glyphs.length * GLYPH_SIZE_ESTIMATE;
|
|
2739
|
+
return keySize + glyphsSize + 100;
|
|
2740
|
+
}
|
|
2741
|
+
});
|
|
2724
2742
|
}
|
|
2725
|
-
function
|
|
2726
|
-
|
|
2727
|
-
return {
|
|
2728
|
-
scale: 0.5,
|
|
2729
|
-
opacity: 0,
|
|
2730
|
-
isActive: false
|
|
2731
|
-
};
|
|
2732
|
-
}
|
|
2733
|
-
const adjustedDuration = ctx.animationDuration / speed;
|
|
2734
|
-
const adjustedCtx = { ...ctx, animationDuration: adjustedDuration };
|
|
2735
|
-
const progress = calculateAnimationProgress(adjustedCtx);
|
|
2736
|
-
const easedProgress = easeOutBack(progress);
|
|
2737
|
-
const startScale = 0.5;
|
|
2738
|
-
const endScale = isWordActive(ctx) ? activeScale : 1;
|
|
2739
|
-
const scale = startScale + (endScale - startScale) * easedProgress;
|
|
2740
|
-
return {
|
|
2741
|
-
scale: Math.min(scale, activeScale),
|
|
2742
|
-
opacity: easedProgress,
|
|
2743
|
-
isActive: isWordActive(ctx)
|
|
2744
|
-
};
|
|
2743
|
+
function makeShapingKey(text, fontFamily, fontSize, fontWeight, letterSpacing = 0) {
|
|
2744
|
+
return `${text}\0${fontFamily}\0${fontSize}\0${fontWeight}\0${letterSpacing}`;
|
|
2745
2745
|
}
|
|
2746
|
-
function
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2746
|
+
function transformText(text, transform) {
|
|
2747
|
+
switch (transform) {
|
|
2748
|
+
case "uppercase":
|
|
2749
|
+
return text.toUpperCase();
|
|
2750
|
+
case "lowercase":
|
|
2751
|
+
return text.toLowerCase();
|
|
2752
|
+
case "capitalize":
|
|
2753
|
+
return text.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
2754
|
+
default:
|
|
2755
|
+
return text;
|
|
2752
2756
|
}
|
|
2753
|
-
const adjustedDuration = ctx.animationDuration / speed;
|
|
2754
|
-
const adjustedCtx = { ...ctx, animationDuration: adjustedDuration };
|
|
2755
|
-
const progress = calculateAnimationProgress(adjustedCtx);
|
|
2756
|
-
const easedProgress = easeInOutQuad(progress);
|
|
2757
|
-
return {
|
|
2758
|
-
opacity: easedProgress,
|
|
2759
|
-
isActive: isWordActive(ctx)
|
|
2760
|
-
};
|
|
2761
2757
|
}
|
|
2762
|
-
function
|
|
2763
|
-
const
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
return {
|
|
2767
|
-
translateX: offset2.x,
|
|
2768
|
-
translateY: offset2.y,
|
|
2769
|
-
opacity: 0,
|
|
2770
|
-
isActive: false
|
|
2771
|
-
};
|
|
2758
|
+
function splitIntoChunks(arr, chunkSize) {
|
|
2759
|
+
const chunks = [];
|
|
2760
|
+
for (let i = 0; i < arr.length; i += chunkSize) {
|
|
2761
|
+
chunks.push(arr.slice(i, i + chunkSize));
|
|
2772
2762
|
}
|
|
2773
|
-
|
|
2774
|
-
const adjustedCtx = { ...ctx, animationDuration: adjustedDuration };
|
|
2775
|
-
const progress = calculateAnimationProgress(adjustedCtx);
|
|
2776
|
-
const easedProgress = easeOutCirc(progress);
|
|
2777
|
-
const offset = getDirectionOffset(direction, slideDistance);
|
|
2778
|
-
const translateX = offset.x * (1 - easedProgress);
|
|
2779
|
-
const translateY = offset.y * (1 - easedProgress);
|
|
2780
|
-
return {
|
|
2781
|
-
translateX,
|
|
2782
|
-
translateY,
|
|
2783
|
-
opacity: easeOutQuad2(progress),
|
|
2784
|
-
isActive: isWordActive(ctx)
|
|
2785
|
-
};
|
|
2763
|
+
return chunks;
|
|
2786
2764
|
}
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
case "down":
|
|
2796
|
-
return { x: 0, y: distance };
|
|
2765
|
+
var CaptionLayoutEngine = class {
|
|
2766
|
+
fontRegistry;
|
|
2767
|
+
cache;
|
|
2768
|
+
layoutEngine;
|
|
2769
|
+
constructor(fontRegistry) {
|
|
2770
|
+
this.fontRegistry = fontRegistry;
|
|
2771
|
+
this.cache = createShapedWordCache();
|
|
2772
|
+
this.layoutEngine = new LayoutEngine(fontRegistry);
|
|
2797
2773
|
}
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2774
|
+
async measureWord(text, config) {
|
|
2775
|
+
const transformedText = transformText(text, config.textTransform);
|
|
2776
|
+
const cacheKey = makeShapingKey(
|
|
2777
|
+
transformedText,
|
|
2778
|
+
config.fontFamily,
|
|
2779
|
+
config.fontSize,
|
|
2780
|
+
config.fontWeight,
|
|
2781
|
+
config.letterSpacing
|
|
2782
|
+
);
|
|
2783
|
+
const cached = this.cache.get(cacheKey);
|
|
2784
|
+
if (cached) {
|
|
2785
|
+
return cached;
|
|
2786
|
+
}
|
|
2787
|
+
const lines = await this.layoutEngine.layout({
|
|
2788
|
+
text: transformedText,
|
|
2789
|
+
width: 1e5,
|
|
2790
|
+
letterSpacing: config.letterSpacing,
|
|
2791
|
+
fontSize: config.fontSize,
|
|
2792
|
+
lineHeight: 1,
|
|
2793
|
+
desc: { family: config.fontFamily, weight: config.fontWeight },
|
|
2794
|
+
textTransform: "none"
|
|
2795
|
+
});
|
|
2796
|
+
const width = lines[0]?.width ?? 0;
|
|
2797
|
+
const glyphs = lines[0]?.glyphs ?? [];
|
|
2798
|
+
const isRTL = isRTLText(transformedText);
|
|
2799
|
+
const shaped = {
|
|
2800
|
+
text: transformedText,
|
|
2801
|
+
width,
|
|
2802
|
+
glyphs: glyphs.map((g) => ({
|
|
2803
|
+
id: g.id,
|
|
2804
|
+
xAdvance: g.xAdvance,
|
|
2805
|
+
xOffset: g.xOffset,
|
|
2806
|
+
yOffset: g.yOffset,
|
|
2807
|
+
cluster: g.cluster
|
|
2808
|
+
})),
|
|
2809
|
+
isRTL
|
|
2810
|
+
};
|
|
2811
|
+
this.cache.set(cacheKey, shaped);
|
|
2812
|
+
return shaped;
|
|
2813
|
+
}
|
|
2814
|
+
async layoutCaption(words, config) {
|
|
2815
|
+
const store = new WordTimingStore(words);
|
|
2816
|
+
const measurementConfig = {
|
|
2817
|
+
fontFamily: config.fontFamily,
|
|
2818
|
+
fontSize: config.fontSize,
|
|
2819
|
+
fontWeight: config.fontWeight,
|
|
2820
|
+
letterSpacing: config.letterSpacing,
|
|
2821
|
+
textTransform: config.textTransform
|
|
2822
|
+
};
|
|
2823
|
+
const shapedWords = await Promise.all(
|
|
2824
|
+
words.map((w) => this.measureWord(w.text, measurementConfig))
|
|
2825
|
+
);
|
|
2826
|
+
if (config.measureTextWidth) {
|
|
2827
|
+
const fontString = `${config.fontWeight} ${config.fontSize}px "${config.fontFamily}"`;
|
|
2828
|
+
for (let i = 0; i < shapedWords.length; i++) {
|
|
2829
|
+
store.widths[i] = config.measureTextWidth(shapedWords[i].text, fontString);
|
|
2830
|
+
}
|
|
2831
|
+
} else {
|
|
2832
|
+
for (let i = 0; i < shapedWords.length; i++) {
|
|
2833
|
+
store.widths[i] = shapedWords[i].width;
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
if (config.textTransform !== "none") {
|
|
2837
|
+
for (let i = 0; i < shapedWords.length; i++) {
|
|
2838
|
+
store.words[i] = shapedWords[i].text;
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2841
|
+
const wordGroups = groupWordsByPause(store, config.pauseThreshold);
|
|
2842
|
+
const pixelMaxWidth = config.availableWidth;
|
|
2843
|
+
let spaceWidth;
|
|
2844
|
+
if (config.measureTextWidth) {
|
|
2845
|
+
const fontString = `${config.fontWeight} ${config.fontSize}px "${config.fontFamily}"`;
|
|
2846
|
+
spaceWidth = config.measureTextWidth(" ", fontString) + config.wordSpacing;
|
|
2847
|
+
} else {
|
|
2848
|
+
const spaceWord = await this.measureWord(" ", measurementConfig);
|
|
2849
|
+
spaceWidth = spaceWord.width + config.wordSpacing;
|
|
2850
|
+
}
|
|
2851
|
+
const groups = wordGroups.flatMap((indices) => {
|
|
2852
|
+
const groupWidths = indices.map((i) => store.widths[i]);
|
|
2853
|
+
const allLines = breakIntoLines(
|
|
2854
|
+
groupWidths,
|
|
2855
|
+
pixelMaxWidth,
|
|
2856
|
+
spaceWidth
|
|
2857
|
+
);
|
|
2858
|
+
const lineChunks = splitIntoChunks(allLines, config.maxLines);
|
|
2859
|
+
return lineChunks.map((chunkLines) => {
|
|
2860
|
+
const lines = chunkLines.map((lineWordIndices, lineIndex) => {
|
|
2861
|
+
const actualIndices = lineWordIndices.map((i) => indices[i]);
|
|
2862
|
+
const lineWidth = actualIndices.reduce((sum, idx) => sum + store.widths[idx], 0) + (actualIndices.length - 1) * spaceWidth;
|
|
2863
|
+
return {
|
|
2864
|
+
wordIndices: actualIndices,
|
|
2865
|
+
x: 0,
|
|
2866
|
+
y: lineIndex * config.fontSize * config.lineHeight,
|
|
2867
|
+
width: lineWidth,
|
|
2868
|
+
height: config.fontSize
|
|
2869
|
+
};
|
|
2870
|
+
});
|
|
2871
|
+
const allWordIndices = lines.flatMap((l) => l.wordIndices);
|
|
2872
|
+
if (allWordIndices.length === 0) {
|
|
2873
|
+
return null;
|
|
2874
|
+
}
|
|
2875
|
+
return {
|
|
2876
|
+
wordIndices: allWordIndices,
|
|
2877
|
+
startTime: store.startTimes[allWordIndices[0]],
|
|
2878
|
+
endTime: store.endTimes[allWordIndices[allWordIndices.length - 1]],
|
|
2879
|
+
lines
|
|
2880
|
+
};
|
|
2881
|
+
}).filter((g) => g !== null);
|
|
2882
|
+
});
|
|
2883
|
+
const contentHeight = config.frameHeight - config.padding.top - config.padding.bottom;
|
|
2884
|
+
const contentWidth = config.frameWidth - config.padding.left - config.padding.right;
|
|
2885
|
+
const calculateGroupY = (group) => {
|
|
2886
|
+
const totalHeight = group.lines.length * config.fontSize * config.lineHeight;
|
|
2887
|
+
switch (config.verticalAlign) {
|
|
2888
|
+
case "top":
|
|
2889
|
+
return config.padding.top + config.fontSize * ASCENT_RATIO;
|
|
2890
|
+
case "bottom":
|
|
2891
|
+
return config.frameHeight - config.padding.bottom - totalHeight + config.fontSize * ASCENT_RATIO;
|
|
2892
|
+
case "middle":
|
|
2893
|
+
default:
|
|
2894
|
+
return config.padding.top + (contentHeight - totalHeight) / 2 + config.fontSize * ASCENT_RATIO;
|
|
2895
|
+
}
|
|
2896
|
+
};
|
|
2897
|
+
const allWordTexts = store.words.slice(0, store.length);
|
|
2898
|
+
const paragraphDirection = detectParagraphDirectionFromWords(allWordTexts);
|
|
2899
|
+
const calculateLineX = (lineWidth) => {
|
|
2900
|
+
switch (config.horizontalAlign) {
|
|
2901
|
+
case "left":
|
|
2902
|
+
return config.padding.left;
|
|
2903
|
+
case "right":
|
|
2904
|
+
return config.frameWidth - lineWidth - config.padding.right;
|
|
2905
|
+
case "center":
|
|
2906
|
+
default:
|
|
2907
|
+
return config.padding.left + (contentWidth - lineWidth) / 2;
|
|
2908
|
+
}
|
|
2909
|
+
};
|
|
2910
|
+
for (const group of groups) {
|
|
2911
|
+
const baseY = calculateGroupY(group);
|
|
2912
|
+
for (let lineIdx = 0; lineIdx < group.lines.length; lineIdx++) {
|
|
2913
|
+
const line = group.lines[lineIdx];
|
|
2914
|
+
line.x = calculateLineX(line.width);
|
|
2915
|
+
line.y = baseY + lineIdx * config.fontSize * config.lineHeight;
|
|
2916
|
+
const lineWordTexts = line.wordIndices.map((idx) => store.words[idx]);
|
|
2917
|
+
const visualOrder = reorderWordsForLine(lineWordTexts, paragraphDirection);
|
|
2918
|
+
let xCursor = line.x;
|
|
2919
|
+
for (const visualIdx of visualOrder) {
|
|
2920
|
+
const wordIdx = line.wordIndices[visualIdx];
|
|
2921
|
+
store.xPositions[wordIdx] = xCursor;
|
|
2922
|
+
store.yPositions[wordIdx] = line.y;
|
|
2923
|
+
xCursor += store.widths[wordIdx] + spaceWidth;
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2802
2927
|
return {
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2928
|
+
store,
|
|
2929
|
+
groups,
|
|
2930
|
+
shapedWords,
|
|
2931
|
+
paragraphDirection
|
|
2806
2932
|
};
|
|
2807
2933
|
}
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2934
|
+
getVisibleWordsAtTime(layout, timeMs) {
|
|
2935
|
+
const activeGroup = layout.groups.find(
|
|
2936
|
+
(g) => timeMs >= g.startTime && timeMs <= g.endTime
|
|
2937
|
+
);
|
|
2938
|
+
if (!activeGroup) {
|
|
2939
|
+
return [];
|
|
2940
|
+
}
|
|
2941
|
+
return activeGroup.wordIndices.map((idx) => ({
|
|
2942
|
+
wordIndex: idx,
|
|
2943
|
+
text: layout.store.words[idx],
|
|
2944
|
+
x: layout.store.xPositions[idx],
|
|
2945
|
+
y: layout.store.yPositions[idx],
|
|
2946
|
+
width: layout.store.widths[idx],
|
|
2947
|
+
startTime: layout.store.startTimes[idx],
|
|
2948
|
+
endTime: layout.store.endTimes[idx],
|
|
2949
|
+
isRTL: layout.shapedWords[idx].isRTL
|
|
2950
|
+
}));
|
|
2951
|
+
}
|
|
2952
|
+
getActiveWordAtTime(layout, timeMs) {
|
|
2953
|
+
const wordIndex = findWordAtTime(layout.store, timeMs);
|
|
2954
|
+
if (wordIndex === -1) {
|
|
2955
|
+
return null;
|
|
2956
|
+
}
|
|
2824
2957
|
return {
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2958
|
+
wordIndex,
|
|
2959
|
+
text: layout.store.words[wordIndex],
|
|
2960
|
+
x: layout.store.xPositions[wordIndex],
|
|
2961
|
+
y: layout.store.yPositions[wordIndex],
|
|
2962
|
+
width: layout.store.widths[wordIndex],
|
|
2963
|
+
startTime: layout.store.startTimes[wordIndex],
|
|
2964
|
+
endTime: layout.store.endTimes[wordIndex],
|
|
2965
|
+
isRTL: layout.shapedWords[wordIndex].isRTL
|
|
2828
2966
|
};
|
|
2829
2967
|
}
|
|
2830
|
-
|
|
2968
|
+
clearCache() {
|
|
2969
|
+
this.cache.clear();
|
|
2970
|
+
}
|
|
2971
|
+
getCacheStats() {
|
|
2831
2972
|
return {
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
isActive: false
|
|
2973
|
+
size: this.cache.size,
|
|
2974
|
+
calculatedSize: this.cache.calculatedSize
|
|
2835
2975
|
};
|
|
2836
2976
|
}
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2977
|
+
};
|
|
2978
|
+
|
|
2979
|
+
// src/core/rich-caption-animator.ts
|
|
2980
|
+
var ANIMATION_DURATIONS = {
|
|
2981
|
+
karaoke: 0,
|
|
2982
|
+
highlight: 0,
|
|
2983
|
+
pop: 200,
|
|
2984
|
+
fade: 150,
|
|
2985
|
+
slide: 250,
|
|
2986
|
+
bounce: 400,
|
|
2987
|
+
typewriter: 0,
|
|
2988
|
+
none: 0
|
|
2989
|
+
};
|
|
2990
|
+
var DEFAULT_ANIMATION_STATE = {
|
|
2991
|
+
opacity: 1,
|
|
2992
|
+
scale: 1,
|
|
2993
|
+
translateX: 0,
|
|
2994
|
+
translateY: 0,
|
|
2995
|
+
fillProgress: 1,
|
|
2996
|
+
isActive: false,
|
|
2997
|
+
visibleCharacters: -1
|
|
2998
|
+
};
|
|
2999
|
+
function easeOutQuad2(t) {
|
|
3000
|
+
return t * (2 - t);
|
|
3001
|
+
}
|
|
3002
|
+
function easeInOutQuad(t) {
|
|
3003
|
+
return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
|
|
3004
|
+
}
|
|
3005
|
+
function easeOutBack(t) {
|
|
3006
|
+
const c1 = 1.70158;
|
|
3007
|
+
const c3 = c1 + 1;
|
|
3008
|
+
return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
|
|
3009
|
+
}
|
|
3010
|
+
function easeOutCirc(t) {
|
|
3011
|
+
return Math.sqrt(1 - Math.pow(t - 1, 2));
|
|
3012
|
+
}
|
|
3013
|
+
function easeOutBounce(t) {
|
|
3014
|
+
const n1 = 7.5625;
|
|
3015
|
+
const d1 = 2.75;
|
|
3016
|
+
if (t < 1 / d1) {
|
|
3017
|
+
return n1 * t * t;
|
|
3018
|
+
}
|
|
3019
|
+
if (t < 2 / d1) {
|
|
3020
|
+
return n1 * (t -= 1.5 / d1) * t + 0.75;
|
|
3021
|
+
}
|
|
3022
|
+
if (t < 2.5 / d1) {
|
|
3023
|
+
return n1 * (t -= 2.25 / d1) * t + 0.9375;
|
|
3024
|
+
}
|
|
3025
|
+
return n1 * (t -= 2.625 / d1) * t + 0.984375;
|
|
3026
|
+
}
|
|
3027
|
+
function clamp(value, min, max) {
|
|
3028
|
+
return Math.min(Math.max(value, min), max);
|
|
3029
|
+
}
|
|
3030
|
+
function calculateAnimationProgress(ctx) {
|
|
3031
|
+
if (ctx.animationDuration <= 0) {
|
|
3032
|
+
return ctx.currentTime >= ctx.wordStart ? 1 : 0;
|
|
3033
|
+
}
|
|
3034
|
+
const elapsed = ctx.currentTime - ctx.wordStart;
|
|
3035
|
+
return clamp(elapsed / ctx.animationDuration, 0, 1);
|
|
3036
|
+
}
|
|
3037
|
+
function calculateWordProgress(ctx) {
|
|
3038
|
+
const duration = ctx.wordEnd - ctx.wordStart;
|
|
3039
|
+
if (duration <= 0) {
|
|
3040
|
+
return ctx.currentTime >= ctx.wordStart ? 1 : 0;
|
|
3041
|
+
}
|
|
3042
|
+
const elapsed = ctx.currentTime - ctx.wordStart;
|
|
3043
|
+
return clamp(elapsed / duration, 0, 1);
|
|
3044
|
+
}
|
|
3045
|
+
function isWordActive(ctx) {
|
|
3046
|
+
return ctx.currentTime >= ctx.wordStart && ctx.currentTime < ctx.wordEnd;
|
|
3047
|
+
}
|
|
3048
|
+
function calculateKaraokeState(ctx, speed) {
|
|
3049
|
+
const isActive = isWordActive(ctx);
|
|
3050
|
+
const wordDuration = ctx.wordEnd - ctx.wordStart;
|
|
3051
|
+
const adjustedDuration = wordDuration / speed;
|
|
3052
|
+
const adjustedEnd = ctx.wordStart + adjustedDuration;
|
|
3053
|
+
const adjustedCtx = { ...ctx, wordEnd: adjustedEnd };
|
|
3054
|
+
if (ctx.currentTime < ctx.wordStart) {
|
|
3055
|
+
return {
|
|
3056
|
+
fillProgress: 0,
|
|
3057
|
+
isActive: false,
|
|
3058
|
+
opacity: 1
|
|
3059
|
+
};
|
|
3060
|
+
}
|
|
3061
|
+
if (ctx.currentTime >= adjustedEnd) {
|
|
3062
|
+
return {
|
|
3063
|
+
fillProgress: 1,
|
|
3064
|
+
isActive: false,
|
|
3065
|
+
opacity: 1
|
|
3066
|
+
};
|
|
3067
|
+
}
|
|
3068
|
+
return {
|
|
3069
|
+
fillProgress: calculateWordProgress(adjustedCtx),
|
|
3070
|
+
isActive,
|
|
3071
|
+
opacity: 1
|
|
3072
|
+
};
|
|
3073
|
+
}
|
|
3074
|
+
function calculateHighlightState(ctx) {
|
|
3075
|
+
const isActive = isWordActive(ctx);
|
|
3076
|
+
return {
|
|
3077
|
+
isActive,
|
|
3078
|
+
fillProgress: isActive ? 1 : 0,
|
|
3079
|
+
opacity: 1
|
|
3080
|
+
};
|
|
3081
|
+
}
|
|
3082
|
+
function calculatePopState(ctx, activeScale, speed) {
|
|
3083
|
+
if (ctx.currentTime < ctx.wordStart) {
|
|
3084
|
+
return {
|
|
3085
|
+
scale: 0.5,
|
|
3086
|
+
opacity: 0,
|
|
3087
|
+
isActive: false,
|
|
3088
|
+
fillProgress: 0
|
|
3089
|
+
};
|
|
3090
|
+
}
|
|
3091
|
+
const adjustedDuration = ctx.animationDuration / speed;
|
|
3092
|
+
const adjustedCtx = { ...ctx, animationDuration: adjustedDuration };
|
|
3093
|
+
const progress = calculateAnimationProgress(adjustedCtx);
|
|
3094
|
+
const easedProgress = easeOutBack(progress);
|
|
3095
|
+
const startScale = 0.5;
|
|
3096
|
+
const isActive = isWordActive(ctx);
|
|
3097
|
+
const endScale = isActive ? activeScale : 1;
|
|
3098
|
+
const scale = startScale + (endScale - startScale) * easedProgress;
|
|
3099
|
+
return {
|
|
3100
|
+
scale: Math.min(scale, activeScale),
|
|
3101
|
+
opacity: easedProgress,
|
|
3102
|
+
isActive,
|
|
3103
|
+
fillProgress: isActive ? 1 : 0
|
|
3104
|
+
};
|
|
3105
|
+
}
|
|
3106
|
+
function calculateFadeState(ctx, speed) {
|
|
3107
|
+
if (ctx.currentTime < ctx.wordStart) {
|
|
3108
|
+
return {
|
|
3109
|
+
opacity: 0,
|
|
3110
|
+
isActive: false,
|
|
3111
|
+
fillProgress: 0
|
|
3112
|
+
};
|
|
3113
|
+
}
|
|
3114
|
+
const adjustedDuration = ctx.animationDuration / speed;
|
|
3115
|
+
const adjustedCtx = { ...ctx, animationDuration: adjustedDuration };
|
|
3116
|
+
const progress = calculateAnimationProgress(adjustedCtx);
|
|
3117
|
+
const easedProgress = easeInOutQuad(progress);
|
|
3118
|
+
const isActive = isWordActive(ctx);
|
|
3119
|
+
return {
|
|
3120
|
+
opacity: easedProgress,
|
|
3121
|
+
isActive,
|
|
3122
|
+
fillProgress: isActive ? 1 : 0
|
|
3123
|
+
};
|
|
3124
|
+
}
|
|
3125
|
+
function calculateSlideState(ctx, direction, speed, fontSize) {
|
|
3126
|
+
const slideDistance = fontSize * 1.5;
|
|
3127
|
+
if (ctx.currentTime < ctx.wordStart) {
|
|
3128
|
+
const offset2 = getDirectionOffset(direction, slideDistance);
|
|
3129
|
+
return {
|
|
3130
|
+
translateX: offset2.x,
|
|
3131
|
+
translateY: offset2.y,
|
|
3132
|
+
opacity: 0,
|
|
3133
|
+
isActive: false,
|
|
3134
|
+
fillProgress: 0
|
|
3135
|
+
};
|
|
3136
|
+
}
|
|
3137
|
+
const adjustedDuration = ctx.animationDuration / speed;
|
|
3138
|
+
const adjustedCtx = { ...ctx, animationDuration: adjustedDuration };
|
|
3139
|
+
const progress = calculateAnimationProgress(adjustedCtx);
|
|
3140
|
+
const easedProgress = easeOutCirc(progress);
|
|
3141
|
+
const offset = getDirectionOffset(direction, slideDistance);
|
|
3142
|
+
const translateX = offset.x * (1 - easedProgress);
|
|
3143
|
+
const translateY = offset.y * (1 - easedProgress);
|
|
3144
|
+
const isActive = isWordActive(ctx);
|
|
3145
|
+
return {
|
|
3146
|
+
translateX,
|
|
3147
|
+
translateY,
|
|
3148
|
+
opacity: easeOutQuad2(progress),
|
|
3149
|
+
isActive,
|
|
3150
|
+
fillProgress: isActive ? 1 : 0
|
|
3151
|
+
};
|
|
3152
|
+
}
|
|
3153
|
+
function getDirectionOffset(direction, distance) {
|
|
3154
|
+
switch (direction) {
|
|
3155
|
+
case "left":
|
|
3156
|
+
return { x: -distance, y: 0 };
|
|
3157
|
+
case "right":
|
|
3158
|
+
return { x: distance, y: 0 };
|
|
3159
|
+
case "up":
|
|
3160
|
+
return { x: 0, y: distance };
|
|
3161
|
+
case "down":
|
|
3162
|
+
return { x: 0, y: -distance };
|
|
3163
|
+
}
|
|
3164
|
+
}
|
|
3165
|
+
function calculateBounceState(ctx, speed, fontSize) {
|
|
3166
|
+
const bounceDistance = fontSize * 0.8;
|
|
3167
|
+
if (ctx.currentTime < ctx.wordStart) {
|
|
3168
|
+
return {
|
|
3169
|
+
translateY: -bounceDistance,
|
|
3170
|
+
opacity: 0,
|
|
3171
|
+
isActive: false,
|
|
3172
|
+
fillProgress: 0
|
|
3173
|
+
};
|
|
3174
|
+
}
|
|
3175
|
+
const adjustedDuration = ctx.animationDuration / speed;
|
|
3176
|
+
const adjustedCtx = { ...ctx, animationDuration: adjustedDuration };
|
|
3177
|
+
const progress = calculateAnimationProgress(adjustedCtx);
|
|
3178
|
+
const easedProgress = easeOutBounce(progress);
|
|
3179
|
+
const isActive = isWordActive(ctx);
|
|
3180
|
+
return {
|
|
3181
|
+
translateY: -bounceDistance * (1 - easedProgress),
|
|
3182
|
+
opacity: easeOutQuad2(progress),
|
|
3183
|
+
isActive,
|
|
3184
|
+
fillProgress: isActive ? 1 : 0
|
|
3185
|
+
};
|
|
3186
|
+
}
|
|
3187
|
+
function calculateTypewriterState(ctx, charCount, speed) {
|
|
3188
|
+
const wordDuration = ctx.wordEnd - ctx.wordStart;
|
|
3189
|
+
const adjustedDuration = wordDuration / speed;
|
|
3190
|
+
const adjustedEnd = ctx.wordStart + adjustedDuration;
|
|
3191
|
+
const adjustedCtx = { ...ctx, wordEnd: adjustedEnd };
|
|
3192
|
+
if (ctx.currentTime < ctx.wordStart) {
|
|
3193
|
+
return {
|
|
3194
|
+
visibleCharacters: 0,
|
|
3195
|
+
opacity: 1,
|
|
3196
|
+
isActive: false,
|
|
3197
|
+
fillProgress: 0
|
|
3198
|
+
};
|
|
3199
|
+
}
|
|
3200
|
+
if (ctx.currentTime >= adjustedEnd) {
|
|
3201
|
+
return {
|
|
3202
|
+
visibleCharacters: charCount,
|
|
3203
|
+
opacity: 1,
|
|
3204
|
+
isActive: false,
|
|
3205
|
+
fillProgress: 0
|
|
3206
|
+
};
|
|
3207
|
+
}
|
|
3208
|
+
const progress = calculateWordProgress(adjustedCtx);
|
|
3209
|
+
const visibleCharacters = Math.ceil(progress * charCount);
|
|
3210
|
+
const isActive = isWordActive(ctx);
|
|
3211
|
+
return {
|
|
3212
|
+
visibleCharacters: clamp(visibleCharacters, 0, charCount),
|
|
3213
|
+
opacity: 1,
|
|
3214
|
+
isActive,
|
|
3215
|
+
fillProgress: isActive ? 1 : 0
|
|
3216
|
+
};
|
|
3217
|
+
}
|
|
3218
|
+
function calculateNoneState(_ctx) {
|
|
3219
|
+
return {
|
|
3220
|
+
opacity: 1,
|
|
3221
|
+
isActive: false,
|
|
3222
|
+
fillProgress: 0
|
|
2850
3223
|
};
|
|
2851
3224
|
}
|
|
2852
3225
|
function calculateWordAnimationState(wordStart, wordEnd, currentTime, config, activeScale = 1, charCount = 0, fontSize = 48, isRTL = false) {
|
|
@@ -2920,28 +3293,35 @@ function getDefaultAnimationConfig() {
|
|
|
2920
3293
|
}
|
|
2921
3294
|
|
|
2922
3295
|
// src/core/rich-caption-generator.ts
|
|
2923
|
-
var ASCENT_RATIO = 0.8;
|
|
2924
|
-
var DESCENT_RATIO = 0.2;
|
|
2925
3296
|
var WORD_BG_OPACITY = 1;
|
|
2926
3297
|
var WORD_BG_BORDER_RADIUS = 4;
|
|
2927
3298
|
var WORD_BG_PADDING_RATIO = 0.12;
|
|
2928
3299
|
function extractFontConfig(asset) {
|
|
2929
3300
|
const font = asset.font;
|
|
2930
3301
|
const active = asset.active?.font;
|
|
3302
|
+
const hasExplicitActiveColor = active?.color !== void 0;
|
|
2931
3303
|
const baseColor = font?.color ?? "#ffffff";
|
|
2932
|
-
const
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
3304
|
+
const baseOpacity = font?.opacity ?? 1;
|
|
3305
|
+
let activeColor;
|
|
3306
|
+
let activeOpacity;
|
|
3307
|
+
if (!hasExplicitActiveColor) {
|
|
3308
|
+
activeColor = baseColor;
|
|
3309
|
+
activeOpacity = active?.opacity ?? baseOpacity;
|
|
3310
|
+
} else {
|
|
3311
|
+
const animStyle = asset.wordAnimation?.style ?? "highlight";
|
|
3312
|
+
const isFillAnimation = animStyle === "karaoke" || animStyle === "highlight";
|
|
3313
|
+
const DEFAULT_ACTIVE_COLOR = "#ffff00";
|
|
3314
|
+
activeColor = active.color ?? (isFillAnimation ? DEFAULT_ACTIVE_COLOR : baseColor);
|
|
3315
|
+
activeOpacity = active?.opacity ?? baseOpacity;
|
|
3316
|
+
}
|
|
2937
3317
|
return {
|
|
2938
3318
|
family: font?.family ?? "Roboto",
|
|
2939
3319
|
size: font?.size ?? 24,
|
|
2940
3320
|
weight: String(font?.weight ?? "400"),
|
|
2941
3321
|
baseColor,
|
|
2942
3322
|
activeColor,
|
|
2943
|
-
baseOpacity
|
|
2944
|
-
activeOpacity
|
|
3323
|
+
baseOpacity,
|
|
3324
|
+
activeOpacity,
|
|
2945
3325
|
letterSpacing: asset.style?.letterSpacing ?? 0
|
|
2946
3326
|
};
|
|
2947
3327
|
}
|
|
@@ -2971,20 +3351,33 @@ function extractStrokeConfig(asset, isActive) {
|
|
|
2971
3351
|
return void 0;
|
|
2972
3352
|
}
|
|
2973
3353
|
function extractShadowConfig(asset, isActive) {
|
|
2974
|
-
|
|
3354
|
+
const baseShadow = asset.shadow;
|
|
3355
|
+
const activeShadow = asset.active?.shadow;
|
|
3356
|
+
if (!baseShadow && !activeShadow) {
|
|
2975
3357
|
return void 0;
|
|
2976
3358
|
}
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
3359
|
+
if (isActive) {
|
|
3360
|
+
if (!activeShadow) {
|
|
3361
|
+
return void 0;
|
|
3362
|
+
}
|
|
3363
|
+
return {
|
|
3364
|
+
offsetX: activeShadow.offsetX ?? baseShadow?.offsetX ?? 0,
|
|
3365
|
+
offsetY: activeShadow.offsetY ?? baseShadow?.offsetY ?? 0,
|
|
3366
|
+
blur: activeShadow.blur ?? baseShadow?.blur ?? 0,
|
|
3367
|
+
color: activeShadow.color ?? baseShadow?.color ?? "#000000",
|
|
3368
|
+
opacity: activeShadow.opacity ?? baseShadow?.opacity ?? 0.5
|
|
3369
|
+
};
|
|
2980
3370
|
}
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
3371
|
+
if (baseShadow) {
|
|
3372
|
+
return {
|
|
3373
|
+
offsetX: baseShadow.offsetX ?? 0,
|
|
3374
|
+
offsetY: baseShadow.offsetY ?? 0,
|
|
3375
|
+
blur: baseShadow.blur ?? 0,
|
|
3376
|
+
color: baseShadow.color ?? "#000000",
|
|
3377
|
+
opacity: baseShadow.opacity ?? 0.5
|
|
3378
|
+
};
|
|
3379
|
+
}
|
|
3380
|
+
return void 0;
|
|
2988
3381
|
}
|
|
2989
3382
|
function extractBackgroundConfig(asset, isActive, fontSize) {
|
|
2990
3383
|
const fontBackground = asset.font?.background;
|
|
@@ -3039,6 +3432,17 @@ function extractCaptionBorder(asset) {
|
|
|
3039
3432
|
radius: border.radius ?? 0
|
|
3040
3433
|
};
|
|
3041
3434
|
}
|
|
3435
|
+
function extractTextDecoration(asset, isActive) {
|
|
3436
|
+
const baseDecoration = asset.font?.textDecoration;
|
|
3437
|
+
const activeDecoration = asset.active?.font?.textDecoration;
|
|
3438
|
+
if (isActive && activeDecoration !== void 0) {
|
|
3439
|
+
return activeDecoration === "none" ? void 0 : activeDecoration;
|
|
3440
|
+
}
|
|
3441
|
+
if (!baseDecoration || baseDecoration === "none") {
|
|
3442
|
+
return void 0;
|
|
3443
|
+
}
|
|
3444
|
+
return baseDecoration;
|
|
3445
|
+
}
|
|
3042
3446
|
function extractAnimationConfig(asset) {
|
|
3043
3447
|
const wordAnim = asset.wordAnimation;
|
|
3044
3448
|
if (!wordAnim) {
|
|
@@ -3081,7 +3485,8 @@ function createDrawCaptionWordOp(word, animState, asset, fontConfig) {
|
|
|
3081
3485
|
letterSpacing: fontConfig.letterSpacing > 0 ? fontConfig.letterSpacing : void 0,
|
|
3082
3486
|
stroke: extractStrokeConfig(asset, isActive),
|
|
3083
3487
|
shadow: extractShadowConfig(asset, isActive),
|
|
3084
|
-
background: extractBackgroundConfig(asset, isActive, fontConfig.size)
|
|
3488
|
+
background: extractBackgroundConfig(asset, isActive, fontConfig.size),
|
|
3489
|
+
textDecoration: extractTextDecoration(asset, isActive)
|
|
3085
3490
|
};
|
|
3086
3491
|
}
|
|
3087
3492
|
function generateRichCaptionDrawOps(asset, layout, frameTimeMs, layoutEngine, config) {
|
|
@@ -3585,7 +3990,8 @@ async function createNodePainter(opts) {
|
|
|
3585
3990
|
context.lineCap = "round";
|
|
3586
3991
|
context.strokeText(displayText, 0, 0);
|
|
3587
3992
|
}
|
|
3588
|
-
|
|
3993
|
+
const sameColor = wordOp.activeColor === wordOp.baseColor && wordOp.activeOpacity === wordOp.baseOpacity;
|
|
3994
|
+
if (wordOp.fillProgress <= 0 || sameColor) {
|
|
3589
3995
|
const baseC = parseHex6(wordOp.baseColor, wordOp.baseOpacity);
|
|
3590
3996
|
context.fillStyle = `rgba(${baseC.r},${baseC.g},${baseC.b},${baseC.a})`;
|
|
3591
3997
|
context.fillText(displayText, 0, 0);
|
|
@@ -3612,7 +4018,26 @@ async function createNodePainter(opts) {
|
|
|
3612
4018
|
context.fillText(displayText, 0, 0);
|
|
3613
4019
|
context.restore();
|
|
3614
4020
|
}
|
|
3615
|
-
|
|
4021
|
+
if (wordOp.textDecoration) {
|
|
4022
|
+
const geo = decorationGeometry(wordOp.textDecoration, {
|
|
4023
|
+
baselineY: 0,
|
|
4024
|
+
fontSize: wordOp.fontSize,
|
|
4025
|
+
lineWidth: textWidth,
|
|
4026
|
+
xStart: 0
|
|
4027
|
+
});
|
|
4028
|
+
const sameC = wordOp.activeColor === wordOp.baseColor && wordOp.activeOpacity === wordOp.baseOpacity;
|
|
4029
|
+
const decoIsActive = wordOp.fillProgress >= 1 && !sameC;
|
|
4030
|
+
const decoColor = decoIsActive ? wordOp.activeColor : wordOp.baseColor;
|
|
4031
|
+
const decoOpacity = decoIsActive ? wordOp.activeOpacity : wordOp.baseOpacity;
|
|
4032
|
+
const dc = parseHex6(decoColor, decoOpacity);
|
|
4033
|
+
context.strokeStyle = `rgba(${dc.r},${dc.g},${dc.b},${dc.a})`;
|
|
4034
|
+
context.lineWidth = geo.width;
|
|
4035
|
+
context.beginPath();
|
|
4036
|
+
context.moveTo(geo.x1, geo.y);
|
|
4037
|
+
context.lineTo(geo.x2, geo.y);
|
|
4038
|
+
context.stroke();
|
|
4039
|
+
}
|
|
4040
|
+
context.restore();
|
|
3616
4041
|
}
|
|
3617
4042
|
});
|
|
3618
4043
|
continue;
|
|
@@ -5071,353 +5496,6 @@ function extractSvgDimensions(svgString) {
|
|
|
5071
5496
|
return { width, height };
|
|
5072
5497
|
}
|
|
5073
5498
|
|
|
5074
|
-
// src/core/rich-caption-layout.ts
|
|
5075
|
-
var import_lru_cache = require("lru-cache");
|
|
5076
|
-
function isRTLText(text) {
|
|
5077
|
-
return containsRTLCharacters(text);
|
|
5078
|
-
}
|
|
5079
|
-
var WordTimingStore = class {
|
|
5080
|
-
startTimes;
|
|
5081
|
-
endTimes;
|
|
5082
|
-
xPositions;
|
|
5083
|
-
yPositions;
|
|
5084
|
-
widths;
|
|
5085
|
-
words;
|
|
5086
|
-
length;
|
|
5087
|
-
constructor(words) {
|
|
5088
|
-
this.length = words.length;
|
|
5089
|
-
this.startTimes = new Uint32Array(this.length);
|
|
5090
|
-
this.endTimes = new Uint32Array(this.length);
|
|
5091
|
-
this.xPositions = new Float32Array(this.length);
|
|
5092
|
-
this.yPositions = new Float32Array(this.length);
|
|
5093
|
-
this.widths = new Float32Array(this.length);
|
|
5094
|
-
this.words = new Array(this.length);
|
|
5095
|
-
for (let i = 0; i < this.length; i++) {
|
|
5096
|
-
this.startTimes[i] = Math.floor(words[i].start);
|
|
5097
|
-
this.endTimes[i] = Math.floor(words[i].end);
|
|
5098
|
-
this.words[i] = words[i].text;
|
|
5099
|
-
}
|
|
5100
|
-
}
|
|
5101
|
-
};
|
|
5102
|
-
function findWordAtTime(store, timeMs) {
|
|
5103
|
-
let left = 0;
|
|
5104
|
-
let right = store.length - 1;
|
|
5105
|
-
while (left <= right) {
|
|
5106
|
-
const mid = left + right >>> 1;
|
|
5107
|
-
const start = store.startTimes[mid];
|
|
5108
|
-
const end = store.endTimes[mid];
|
|
5109
|
-
if (timeMs >= start && timeMs < end) {
|
|
5110
|
-
return mid;
|
|
5111
|
-
}
|
|
5112
|
-
if (timeMs < start) {
|
|
5113
|
-
right = mid - 1;
|
|
5114
|
-
} else {
|
|
5115
|
-
left = mid + 1;
|
|
5116
|
-
}
|
|
5117
|
-
}
|
|
5118
|
-
return -1;
|
|
5119
|
-
}
|
|
5120
|
-
function groupWordsByPause(store, pauseThreshold = 500) {
|
|
5121
|
-
if (store.length === 0) {
|
|
5122
|
-
return [];
|
|
5123
|
-
}
|
|
5124
|
-
const groups = [];
|
|
5125
|
-
let currentGroup = [];
|
|
5126
|
-
for (let i = 0; i < store.length; i++) {
|
|
5127
|
-
if (currentGroup.length === 0) {
|
|
5128
|
-
currentGroup.push(i);
|
|
5129
|
-
continue;
|
|
5130
|
-
}
|
|
5131
|
-
const prevEnd = store.endTimes[currentGroup[currentGroup.length - 1]];
|
|
5132
|
-
const currStart = store.startTimes[i];
|
|
5133
|
-
const gap = currStart - prevEnd;
|
|
5134
|
-
const prevText = store.words[currentGroup[currentGroup.length - 1]];
|
|
5135
|
-
const endsWithPunctuation = /[.!?]$/.test(prevText);
|
|
5136
|
-
if (gap >= pauseThreshold || endsWithPunctuation) {
|
|
5137
|
-
groups.push(currentGroup);
|
|
5138
|
-
currentGroup = [i];
|
|
5139
|
-
} else {
|
|
5140
|
-
currentGroup.push(i);
|
|
5141
|
-
}
|
|
5142
|
-
}
|
|
5143
|
-
if (currentGroup.length > 0) {
|
|
5144
|
-
groups.push(currentGroup);
|
|
5145
|
-
}
|
|
5146
|
-
return groups;
|
|
5147
|
-
}
|
|
5148
|
-
function breakIntoLines(wordWidths, maxWidth, spaceWidth) {
|
|
5149
|
-
const lines = [];
|
|
5150
|
-
let currentLine = [];
|
|
5151
|
-
let currentWidth = 0;
|
|
5152
|
-
for (let i = 0; i < wordWidths.length; i++) {
|
|
5153
|
-
const wordWidth = wordWidths[i];
|
|
5154
|
-
const spaceNeeded = currentLine.length > 0 ? spaceWidth : 0;
|
|
5155
|
-
if (currentWidth + spaceNeeded + wordWidth <= maxWidth) {
|
|
5156
|
-
currentLine.push(i);
|
|
5157
|
-
currentWidth += spaceNeeded + wordWidth;
|
|
5158
|
-
} else {
|
|
5159
|
-
if (currentLine.length > 0) {
|
|
5160
|
-
lines.push(currentLine);
|
|
5161
|
-
}
|
|
5162
|
-
currentLine = [i];
|
|
5163
|
-
currentWidth = wordWidth;
|
|
5164
|
-
}
|
|
5165
|
-
}
|
|
5166
|
-
if (currentLine.length > 0) {
|
|
5167
|
-
lines.push(currentLine);
|
|
5168
|
-
}
|
|
5169
|
-
return lines;
|
|
5170
|
-
}
|
|
5171
|
-
var GLYPH_SIZE_ESTIMATE = 64;
|
|
5172
|
-
function createShapedWordCache() {
|
|
5173
|
-
return new import_lru_cache.LRUCache({
|
|
5174
|
-
max: 5e4,
|
|
5175
|
-
maxSize: 50 * 1024 * 1024,
|
|
5176
|
-
maxEntrySize: 100 * 1024,
|
|
5177
|
-
sizeCalculation: (value, key) => {
|
|
5178
|
-
const keySize = key.length * 2;
|
|
5179
|
-
const glyphsSize = value.glyphs.length * GLYPH_SIZE_ESTIMATE;
|
|
5180
|
-
return keySize + glyphsSize + 100;
|
|
5181
|
-
}
|
|
5182
|
-
});
|
|
5183
|
-
}
|
|
5184
|
-
function makeShapingKey(text, fontFamily, fontSize, fontWeight, letterSpacing = 0) {
|
|
5185
|
-
return `${text}\0${fontFamily}\0${fontSize}\0${fontWeight}\0${letterSpacing}`;
|
|
5186
|
-
}
|
|
5187
|
-
function transformText(text, transform) {
|
|
5188
|
-
switch (transform) {
|
|
5189
|
-
case "uppercase":
|
|
5190
|
-
return text.toUpperCase();
|
|
5191
|
-
case "lowercase":
|
|
5192
|
-
return text.toLowerCase();
|
|
5193
|
-
case "capitalize":
|
|
5194
|
-
return text.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
5195
|
-
default:
|
|
5196
|
-
return text;
|
|
5197
|
-
}
|
|
5198
|
-
}
|
|
5199
|
-
function splitIntoChunks(arr, chunkSize) {
|
|
5200
|
-
const chunks = [];
|
|
5201
|
-
for (let i = 0; i < arr.length; i += chunkSize) {
|
|
5202
|
-
chunks.push(arr.slice(i, i + chunkSize));
|
|
5203
|
-
}
|
|
5204
|
-
return chunks;
|
|
5205
|
-
}
|
|
5206
|
-
var CaptionLayoutEngine = class {
|
|
5207
|
-
fontRegistry;
|
|
5208
|
-
cache;
|
|
5209
|
-
layoutEngine;
|
|
5210
|
-
constructor(fontRegistry) {
|
|
5211
|
-
this.fontRegistry = fontRegistry;
|
|
5212
|
-
this.cache = createShapedWordCache();
|
|
5213
|
-
this.layoutEngine = new LayoutEngine(fontRegistry);
|
|
5214
|
-
}
|
|
5215
|
-
async measureWord(text, config) {
|
|
5216
|
-
const transformedText = transformText(text, config.textTransform);
|
|
5217
|
-
const cacheKey = makeShapingKey(
|
|
5218
|
-
transformedText,
|
|
5219
|
-
config.fontFamily,
|
|
5220
|
-
config.fontSize,
|
|
5221
|
-
config.fontWeight,
|
|
5222
|
-
config.letterSpacing
|
|
5223
|
-
);
|
|
5224
|
-
const cached = this.cache.get(cacheKey);
|
|
5225
|
-
if (cached) {
|
|
5226
|
-
return cached;
|
|
5227
|
-
}
|
|
5228
|
-
const lines = await this.layoutEngine.layout({
|
|
5229
|
-
text: transformedText,
|
|
5230
|
-
width: 1e5,
|
|
5231
|
-
letterSpacing: config.letterSpacing,
|
|
5232
|
-
fontSize: config.fontSize,
|
|
5233
|
-
lineHeight: 1,
|
|
5234
|
-
desc: { family: config.fontFamily, weight: config.fontWeight },
|
|
5235
|
-
textTransform: "none"
|
|
5236
|
-
});
|
|
5237
|
-
const width = lines[0]?.width ?? 0;
|
|
5238
|
-
const glyphs = lines[0]?.glyphs ?? [];
|
|
5239
|
-
const isRTL = isRTLText(transformedText);
|
|
5240
|
-
const shaped = {
|
|
5241
|
-
text: transformedText,
|
|
5242
|
-
width,
|
|
5243
|
-
glyphs: glyphs.map((g) => ({
|
|
5244
|
-
id: g.id,
|
|
5245
|
-
xAdvance: g.xAdvance,
|
|
5246
|
-
xOffset: g.xOffset,
|
|
5247
|
-
yOffset: g.yOffset,
|
|
5248
|
-
cluster: g.cluster
|
|
5249
|
-
})),
|
|
5250
|
-
isRTL
|
|
5251
|
-
};
|
|
5252
|
-
this.cache.set(cacheKey, shaped);
|
|
5253
|
-
return shaped;
|
|
5254
|
-
}
|
|
5255
|
-
async layoutCaption(words, config) {
|
|
5256
|
-
const store = new WordTimingStore(words);
|
|
5257
|
-
const measurementConfig = {
|
|
5258
|
-
fontFamily: config.fontFamily,
|
|
5259
|
-
fontSize: config.fontSize,
|
|
5260
|
-
fontWeight: config.fontWeight,
|
|
5261
|
-
letterSpacing: config.letterSpacing,
|
|
5262
|
-
textTransform: config.textTransform
|
|
5263
|
-
};
|
|
5264
|
-
const shapedWords = await Promise.all(
|
|
5265
|
-
words.map((w) => this.measureWord(w.text, measurementConfig))
|
|
5266
|
-
);
|
|
5267
|
-
if (config.measureTextWidth) {
|
|
5268
|
-
const fontString = `${config.fontWeight} ${config.fontSize}px "${config.fontFamily}"`;
|
|
5269
|
-
for (let i = 0; i < shapedWords.length; i++) {
|
|
5270
|
-
store.widths[i] = config.measureTextWidth(shapedWords[i].text, fontString);
|
|
5271
|
-
}
|
|
5272
|
-
} else {
|
|
5273
|
-
for (let i = 0; i < shapedWords.length; i++) {
|
|
5274
|
-
store.widths[i] = shapedWords[i].width;
|
|
5275
|
-
}
|
|
5276
|
-
}
|
|
5277
|
-
if (config.textTransform !== "none") {
|
|
5278
|
-
for (let i = 0; i < shapedWords.length; i++) {
|
|
5279
|
-
store.words[i] = shapedWords[i].text;
|
|
5280
|
-
}
|
|
5281
|
-
}
|
|
5282
|
-
const wordGroups = groupWordsByPause(store, config.pauseThreshold);
|
|
5283
|
-
const pixelMaxWidth = config.availableWidth;
|
|
5284
|
-
let spaceWidth;
|
|
5285
|
-
if (config.measureTextWidth) {
|
|
5286
|
-
const fontString = `${config.fontWeight} ${config.fontSize}px "${config.fontFamily}"`;
|
|
5287
|
-
spaceWidth = config.measureTextWidth(" ", fontString) + config.wordSpacing;
|
|
5288
|
-
} else {
|
|
5289
|
-
const spaceWord = await this.measureWord(" ", measurementConfig);
|
|
5290
|
-
spaceWidth = spaceWord.width + config.wordSpacing;
|
|
5291
|
-
}
|
|
5292
|
-
const groups = wordGroups.flatMap((indices) => {
|
|
5293
|
-
const groupWidths = indices.map((i) => store.widths[i]);
|
|
5294
|
-
const allLines = breakIntoLines(
|
|
5295
|
-
groupWidths,
|
|
5296
|
-
pixelMaxWidth,
|
|
5297
|
-
spaceWidth
|
|
5298
|
-
);
|
|
5299
|
-
const lineChunks = splitIntoChunks(allLines, config.maxLines);
|
|
5300
|
-
return lineChunks.map((chunkLines) => {
|
|
5301
|
-
const lines = chunkLines.map((lineWordIndices, lineIndex) => {
|
|
5302
|
-
const actualIndices = lineWordIndices.map((i) => indices[i]);
|
|
5303
|
-
const lineWidth = actualIndices.reduce((sum, idx) => sum + store.widths[idx], 0) + (actualIndices.length - 1) * spaceWidth;
|
|
5304
|
-
return {
|
|
5305
|
-
wordIndices: actualIndices,
|
|
5306
|
-
x: 0,
|
|
5307
|
-
y: lineIndex * config.fontSize * config.lineHeight,
|
|
5308
|
-
width: lineWidth,
|
|
5309
|
-
height: config.fontSize
|
|
5310
|
-
};
|
|
5311
|
-
});
|
|
5312
|
-
const allWordIndices = lines.flatMap((l) => l.wordIndices);
|
|
5313
|
-
if (allWordIndices.length === 0) {
|
|
5314
|
-
return null;
|
|
5315
|
-
}
|
|
5316
|
-
return {
|
|
5317
|
-
wordIndices: allWordIndices,
|
|
5318
|
-
startTime: store.startTimes[allWordIndices[0]],
|
|
5319
|
-
endTime: store.endTimes[allWordIndices[allWordIndices.length - 1]],
|
|
5320
|
-
lines
|
|
5321
|
-
};
|
|
5322
|
-
}).filter((g) => g !== null);
|
|
5323
|
-
});
|
|
5324
|
-
const contentHeight = config.frameHeight - config.padding.top - config.padding.bottom;
|
|
5325
|
-
const contentWidth = config.frameWidth - config.padding.left - config.padding.right;
|
|
5326
|
-
const ASCENT_RATIO2 = 0.8;
|
|
5327
|
-
const calculateGroupY = (group) => {
|
|
5328
|
-
const totalHeight = group.lines.length * config.fontSize * config.lineHeight;
|
|
5329
|
-
switch (config.verticalAlign) {
|
|
5330
|
-
case "top":
|
|
5331
|
-
return config.padding.top + config.fontSize * ASCENT_RATIO2;
|
|
5332
|
-
case "bottom":
|
|
5333
|
-
return config.frameHeight - config.padding.bottom - totalHeight + config.fontSize * ASCENT_RATIO2;
|
|
5334
|
-
case "middle":
|
|
5335
|
-
default:
|
|
5336
|
-
return config.padding.top + (contentHeight - totalHeight) / 2 + config.fontSize * ASCENT_RATIO2;
|
|
5337
|
-
}
|
|
5338
|
-
};
|
|
5339
|
-
const allWordTexts = store.words.slice(0, store.length);
|
|
5340
|
-
const paragraphDirection = detectParagraphDirectionFromWords(allWordTexts);
|
|
5341
|
-
const calculateLineX = (lineWidth) => {
|
|
5342
|
-
switch (config.horizontalAlign) {
|
|
5343
|
-
case "left":
|
|
5344
|
-
return config.padding.left;
|
|
5345
|
-
case "right":
|
|
5346
|
-
return config.frameWidth - lineWidth - config.padding.right;
|
|
5347
|
-
case "center":
|
|
5348
|
-
default:
|
|
5349
|
-
return config.padding.left + (contentWidth - lineWidth) / 2;
|
|
5350
|
-
}
|
|
5351
|
-
};
|
|
5352
|
-
for (const group of groups) {
|
|
5353
|
-
const baseY = calculateGroupY(group);
|
|
5354
|
-
for (let lineIdx = 0; lineIdx < group.lines.length; lineIdx++) {
|
|
5355
|
-
const line = group.lines[lineIdx];
|
|
5356
|
-
line.x = calculateLineX(line.width);
|
|
5357
|
-
line.y = baseY + lineIdx * config.fontSize * config.lineHeight;
|
|
5358
|
-
const lineWordTexts = line.wordIndices.map((idx) => store.words[idx]);
|
|
5359
|
-
const visualOrder = reorderWordsForLine(lineWordTexts, paragraphDirection);
|
|
5360
|
-
let xCursor = line.x;
|
|
5361
|
-
for (const visualIdx of visualOrder) {
|
|
5362
|
-
const wordIdx = line.wordIndices[visualIdx];
|
|
5363
|
-
store.xPositions[wordIdx] = xCursor;
|
|
5364
|
-
store.yPositions[wordIdx] = line.y;
|
|
5365
|
-
xCursor += store.widths[wordIdx] + spaceWidth;
|
|
5366
|
-
}
|
|
5367
|
-
}
|
|
5368
|
-
}
|
|
5369
|
-
return {
|
|
5370
|
-
store,
|
|
5371
|
-
groups,
|
|
5372
|
-
shapedWords,
|
|
5373
|
-
paragraphDirection
|
|
5374
|
-
};
|
|
5375
|
-
}
|
|
5376
|
-
getVisibleWordsAtTime(layout, timeMs) {
|
|
5377
|
-
const activeGroup = layout.groups.find(
|
|
5378
|
-
(g) => timeMs >= g.startTime && timeMs <= g.endTime
|
|
5379
|
-
);
|
|
5380
|
-
if (!activeGroup) {
|
|
5381
|
-
return [];
|
|
5382
|
-
}
|
|
5383
|
-
return activeGroup.wordIndices.map((idx) => ({
|
|
5384
|
-
wordIndex: idx,
|
|
5385
|
-
text: layout.store.words[idx],
|
|
5386
|
-
x: layout.store.xPositions[idx],
|
|
5387
|
-
y: layout.store.yPositions[idx],
|
|
5388
|
-
width: layout.store.widths[idx],
|
|
5389
|
-
startTime: layout.store.startTimes[idx],
|
|
5390
|
-
endTime: layout.store.endTimes[idx],
|
|
5391
|
-
isRTL: layout.shapedWords[idx].isRTL
|
|
5392
|
-
}));
|
|
5393
|
-
}
|
|
5394
|
-
getActiveWordAtTime(layout, timeMs) {
|
|
5395
|
-
const wordIndex = findWordAtTime(layout.store, timeMs);
|
|
5396
|
-
if (wordIndex === -1) {
|
|
5397
|
-
return null;
|
|
5398
|
-
}
|
|
5399
|
-
return {
|
|
5400
|
-
wordIndex,
|
|
5401
|
-
text: layout.store.words[wordIndex],
|
|
5402
|
-
x: layout.store.xPositions[wordIndex],
|
|
5403
|
-
y: layout.store.yPositions[wordIndex],
|
|
5404
|
-
width: layout.store.widths[wordIndex],
|
|
5405
|
-
startTime: layout.store.startTimes[wordIndex],
|
|
5406
|
-
endTime: layout.store.endTimes[wordIndex],
|
|
5407
|
-
isRTL: layout.shapedWords[wordIndex].isRTL
|
|
5408
|
-
};
|
|
5409
|
-
}
|
|
5410
|
-
clearCache() {
|
|
5411
|
-
this.cache.clear();
|
|
5412
|
-
}
|
|
5413
|
-
getCacheStats() {
|
|
5414
|
-
return {
|
|
5415
|
-
size: this.cache.size,
|
|
5416
|
-
calculatedSize: this.cache.calculatedSize
|
|
5417
|
-
};
|
|
5418
|
-
}
|
|
5419
|
-
};
|
|
5420
|
-
|
|
5421
5499
|
// src/core/canvas-text-measurer.ts
|
|
5422
5500
|
async function createCanvasTextMeasurer() {
|
|
5423
5501
|
const canvasMod = await import("canvas");
|