@applemusic-like-lyrics/core 0.4.0 → 0.4.2
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/amll-core.cjs +339 -66
- package/dist/amll-core.cjs.map +1 -1
- package/dist/amll-core.d.cts +13 -1
- package/dist/amll-core.d.mts +13 -1
- package/dist/amll-core.mjs +337 -64
- package/dist/amll-core.mjs.map +1 -1
- package/dist/style.css +6 -5
- package/package.json +3 -3
package/dist/amll-core.cjs
CHANGED
|
@@ -30,9 +30,9 @@ let _pixi_filter_bulge_pinch = require("@pixi/filter-bulge-pinch");
|
|
|
30
30
|
let _pixi_filter_color_matrix = require("@pixi/filter-color-matrix");
|
|
31
31
|
let _pixi_sprite = require("@pixi/sprite");
|
|
32
32
|
let _ungap_structured_clone = require("@ungap/structured-clone");
|
|
33
|
-
_ungap_structured_clone = __toESM(_ungap_structured_clone);
|
|
33
|
+
_ungap_structured_clone = __toESM(_ungap_structured_clone, 1);
|
|
34
34
|
let bezier_easing = require("bezier-easing");
|
|
35
|
-
bezier_easing = __toESM(bezier_easing);
|
|
35
|
+
bezier_easing = __toESM(bezier_easing, 1);
|
|
36
36
|
//#region src/bg-render/base.ts
|
|
37
37
|
var AbstractBaseRenderer = class {};
|
|
38
38
|
var BaseRenderer = class extends AbstractBaseRenderer {
|
|
@@ -2274,7 +2274,7 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2274
2274
|
bottomLine = new BottomLineEl(this);
|
|
2275
2275
|
enableBlur = true;
|
|
2276
2276
|
enableScale = true;
|
|
2277
|
-
maskObsceneWords =
|
|
2277
|
+
maskObsceneWords = "";
|
|
2278
2278
|
maskObsceneWordChar = "*";
|
|
2279
2279
|
hidePassedLines = false;
|
|
2280
2280
|
scrollBoundary = [0, 0];
|
|
@@ -2564,7 +2564,7 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2564
2564
|
const c = char.charAt(0) || "*";
|
|
2565
2565
|
if (this.maskObsceneWordChar === c) return;
|
|
2566
2566
|
this.maskObsceneWordChar = c;
|
|
2567
|
-
if (this.maskObsceneWords !==
|
|
2567
|
+
if (this.maskObsceneWords !== "") {
|
|
2568
2568
|
this.rebuildLyricLines();
|
|
2569
2569
|
this.calcLayout();
|
|
2570
2570
|
}
|
|
@@ -2579,10 +2579,10 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2579
2579
|
*/
|
|
2580
2580
|
processObsceneWord(word) {
|
|
2581
2581
|
const text = word.word;
|
|
2582
|
-
if (!word.obscene || this.maskObsceneWords ===
|
|
2582
|
+
if (!word.obscene || this.maskObsceneWords === "") return text;
|
|
2583
2583
|
const maskChar = this.maskObsceneWordChar;
|
|
2584
|
-
if (this.maskObsceneWords ===
|
|
2585
|
-
if (this.maskObsceneWords ===
|
|
2584
|
+
if (this.maskObsceneWords === "full-mask") return text.replace(/\S/g, maskChar);
|
|
2585
|
+
if (this.maskObsceneWords === "partial-mask") {
|
|
2586
2586
|
const trimmed = text.trim();
|
|
2587
2587
|
if (trimmed.length <= 2) return text.replace(/\S/g, maskChar);
|
|
2588
2588
|
const startPos = text.indexOf(trimmed);
|
|
@@ -2943,7 +2943,7 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2943
2943
|
let targetScale = 100;
|
|
2944
2944
|
if (!isActive && this.isPlaying) if (line.isBG) targetScale = 75;
|
|
2945
2945
|
else targetScale = SCALE_ASPECT;
|
|
2946
|
-
const renderMode = isActive ?
|
|
2946
|
+
const renderMode = isActive ? 1 : 0;
|
|
2947
2947
|
lineObj.setTransform(curPos, targetScale, targetOpacity, blurLevel, force, delay, renderMode);
|
|
2948
2948
|
if (line.isBG && (isActive || !this.isPlaying)) curPos += this.lyricLinesSize.get(lineObj)?.[1] ?? LINE_HEIGHT_FALLBACK;
|
|
2949
2949
|
else if (!line.isBG) curPos += this.lyricLinesSize.get(lineObj)?.[1] ?? LINE_HEIGHT_FALLBACK;
|
|
@@ -3096,8 +3096,17 @@ var LyricLineBase = class extends EventTarget {
|
|
|
3096
3096
|
posY: new Spring(0),
|
|
3097
3097
|
scale: new Spring(100)
|
|
3098
3098
|
};
|
|
3099
|
+
/**
|
|
3100
|
+
* 用于 CJK 词语边界检测的分词器
|
|
3101
|
+
*/
|
|
3102
|
+
static wordSegmenter = typeof Intl !== "undefined" && Intl.Segmenter ? new Intl.Segmenter(void 0, { granularity: "word" }) : null;
|
|
3103
|
+
/**
|
|
3104
|
+
* Unicode 标准的全局 Grapheme Cluster 分词器
|
|
3105
|
+
* 用于正确处理 emoji、复合字符等
|
|
3106
|
+
*/
|
|
3107
|
+
static graphemeSegmenter = typeof Intl !== "undefined" && Intl.Segmenter ? new Intl.Segmenter(void 0, { granularity: "grapheme" }) : null;
|
|
3099
3108
|
onLineSizeChange(_size) {}
|
|
3100
|
-
setTransform(top = this.top, scale = this.scale, opacity = this.opacity, blur = this.blur, _force = false, delay = 0, _mode =
|
|
3109
|
+
setTransform(top = this.top, scale = this.scale, opacity = this.opacity, blur = this.blur, _force = false, delay = 0, _mode = 0) {
|
|
3101
3110
|
this.top = top;
|
|
3102
3111
|
this.scale = scale;
|
|
3103
3112
|
this.opacity = opacity;
|
|
@@ -3122,8 +3131,277 @@ var LyricLineBase = class extends EventTarget {
|
|
|
3122
3131
|
dispose() {}
|
|
3123
3132
|
};
|
|
3124
3133
|
//#endregion
|
|
3134
|
+
//#region src/utils/lyric-line-break.ts
|
|
3135
|
+
/**
|
|
3136
|
+
* 单个词超过容器宽度时的大惩罚倍数
|
|
3137
|
+
*/
|
|
3138
|
+
const OVERFLOW_PENALTY_MULTIPLIER = 1e3;
|
|
3139
|
+
/**
|
|
3140
|
+
* 截断 CJK 词组边界的惩罚比例
|
|
3141
|
+
*
|
|
3142
|
+
* 相对于容器宽度
|
|
3143
|
+
*/
|
|
3144
|
+
const CJK_BREAK_PENALTY_RATIO = .15;
|
|
3145
|
+
/**
|
|
3146
|
+
* 截断普通文本(非空格、非 CJK 词界)的惩罚比例
|
|
3147
|
+
*/
|
|
3148
|
+
const NORMAL_BREAK_PENALTY_RATIO = .5;
|
|
3149
|
+
/**
|
|
3150
|
+
* 在空格处断开的奖励比例
|
|
3151
|
+
*/
|
|
3152
|
+
const SPACE_BREAK_REWARD_RATIO = .4;
|
|
3153
|
+
/**
|
|
3154
|
+
* 在标点符号处断开的奖励比例
|
|
3155
|
+
*
|
|
3156
|
+
* 比空格更高以便优先一点在标点处换行
|
|
3157
|
+
*/
|
|
3158
|
+
const PUNCTUATION_BREAK_REWARD_RATIO = .6;
|
|
3159
|
+
const PUNCTUATION_REGEX = /[,.;:!?,。;:!?、)】》」』’”)[\]}>~…]$/;
|
|
3160
|
+
/**
|
|
3161
|
+
* 计算平均行长度的断点位置
|
|
3162
|
+
* @param children 子节点信息
|
|
3163
|
+
* @param containerWidth 容器可用内容宽度
|
|
3164
|
+
* @param fullText 完整的行文本
|
|
3165
|
+
* @param segmenter 预创建的 Intl.Segmenter 分词器
|
|
3166
|
+
* @returns 需要在其前面插入 `<br>` 的子节点索引数组,升序
|
|
3167
|
+
*/
|
|
3168
|
+
function calcBalancedBreaks(children, containerWidth, fullText, segmenter) {
|
|
3169
|
+
const n = children.length;
|
|
3170
|
+
if (n === 0 || containerWidth <= 0) return [];
|
|
3171
|
+
const cjkBoundaries = /* @__PURE__ */ new Set();
|
|
3172
|
+
let offset = 0;
|
|
3173
|
+
for (const { segment, isWordLike } of segmenter.segment(fullText)) {
|
|
3174
|
+
if (offset > 0 && isWordLike) {
|
|
3175
|
+
if ([...segment].some((ch) => isCJK(ch))) cjkBoundaries.add(offset);
|
|
3176
|
+
}
|
|
3177
|
+
offset += segment.length;
|
|
3178
|
+
}
|
|
3179
|
+
const charOffsets = new Int32Array(n + 1);
|
|
3180
|
+
const prefixWidth = new Float64Array(n + 1);
|
|
3181
|
+
for (let i = 0; i < n; i++) {
|
|
3182
|
+
charOffsets[i + 1] = charOffsets[i] + children[i].text.length;
|
|
3183
|
+
prefixWidth[i + 1] = prefixWidth[i] + children[i].width;
|
|
3184
|
+
}
|
|
3185
|
+
if (prefixWidth[n] <= containerWidth) return [];
|
|
3186
|
+
/**
|
|
3187
|
+
* dp[i] 表示将 index i 到 n-1 的节点进行排版的最小代价
|
|
3188
|
+
*/
|
|
3189
|
+
const dp = new Float64Array(n + 1).fill(Number.POSITIVE_INFINITY);
|
|
3190
|
+
const nextBreak = new Int32Array(n + 1).fill(-1);
|
|
3191
|
+
dp[n] = 0;
|
|
3192
|
+
const PENALTY_CJK = (containerWidth * CJK_BREAK_PENALTY_RATIO) ** 2;
|
|
3193
|
+
const PENALTY_NORMAL = (containerWidth * NORMAL_BREAK_PENALTY_RATIO) ** 2;
|
|
3194
|
+
for (let i = n - 1; i >= 0; i--) for (let j = i + 1; j <= n; j++) {
|
|
3195
|
+
const w = prefixWidth[j] - prefixWidth[i];
|
|
3196
|
+
let lineCost = 0;
|
|
3197
|
+
if (w > containerWidth) if (j === i + 1) lineCost = (w - containerWidth) ** 2 * OVERFLOW_PENALTY_MULTIPLIER;
|
|
3198
|
+
else continue;
|
|
3199
|
+
else lineCost = (containerWidth - w) ** 2;
|
|
3200
|
+
let breakPenalty = 0;
|
|
3201
|
+
if (j < n) {
|
|
3202
|
+
const prevChild = children[j - 1];
|
|
3203
|
+
if (PUNCTUATION_REGEX.test(prevChild.text)) breakPenalty = -((containerWidth * PUNCTUATION_BREAK_REWARD_RATIO) ** 2);
|
|
3204
|
+
else if (prevChild.isSpace) breakPenalty = -((containerWidth * SPACE_BREAK_REWARD_RATIO) ** 2);
|
|
3205
|
+
else if (cjkBoundaries.has(charOffsets[j])) breakPenalty = PENALTY_CJK;
|
|
3206
|
+
else breakPenalty = PENALTY_NORMAL;
|
|
3207
|
+
}
|
|
3208
|
+
const totalCost = lineCost + breakPenalty + dp[j];
|
|
3209
|
+
if (totalCost < dp[i]) {
|
|
3210
|
+
dp[i] = totalCost;
|
|
3211
|
+
nextBreak[i] = j;
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3214
|
+
const breaks = [];
|
|
3215
|
+
let curr = 0;
|
|
3216
|
+
while (curr < n) {
|
|
3217
|
+
curr = nextBreak[curr];
|
|
3218
|
+
if (curr > 0 && curr < n) breaks.push(curr);
|
|
3219
|
+
}
|
|
3220
|
+
return breaks;
|
|
3221
|
+
}
|
|
3222
|
+
//#endregion
|
|
3223
|
+
//#region src/utils/line-balancer.ts
|
|
3224
|
+
let sharedCanvasCtx = null;
|
|
3225
|
+
function getMeasurementContext() {
|
|
3226
|
+
if (!sharedCanvasCtx) sharedCanvasCtx = document.createElement("canvas").getContext("2d");
|
|
3227
|
+
return sharedCanvasCtx;
|
|
3228
|
+
}
|
|
3229
|
+
/**
|
|
3230
|
+
* 用于平衡歌词行在换行后的各行长度
|
|
3231
|
+
*/
|
|
3232
|
+
var LineBalancer = class {
|
|
3233
|
+
isBalancing = false;
|
|
3234
|
+
lastBalancedContainerWidth = -1;
|
|
3235
|
+
constructor(mainElement) {
|
|
3236
|
+
this.mainElement = mainElement;
|
|
3237
|
+
}
|
|
3238
|
+
balanceLineBreaks(isNonDynamic, hasSplittedWords, wordSegmenter) {
|
|
3239
|
+
if (this.isBalancing || !this.mainElement) return;
|
|
3240
|
+
const computedStyle = getComputedStyle(this.mainElement);
|
|
3241
|
+
const paddingLeft = Number.parseFloat(computedStyle.paddingLeft) || 0;
|
|
3242
|
+
const paddingRight = Number.parseFloat(computedStyle.paddingRight) || 0;
|
|
3243
|
+
const containerWidth = this.mainElement.clientWidth - paddingLeft - paddingRight;
|
|
3244
|
+
if (containerWidth <= 0) return;
|
|
3245
|
+
if (isNonDynamic) {
|
|
3246
|
+
this.balanceNonDynamicLineBreaks(containerWidth, computedStyle, wordSegmenter);
|
|
3247
|
+
return;
|
|
3248
|
+
}
|
|
3249
|
+
if (!hasSplittedWords) return;
|
|
3250
|
+
this.balanceDynamicLineBreaks(containerWidth, wordSegmenter);
|
|
3251
|
+
}
|
|
3252
|
+
reset() {
|
|
3253
|
+
this.lastBalancedContainerWidth = -1;
|
|
3254
|
+
}
|
|
3255
|
+
executeLineBalance(containerWidth, adapter, wordSegmenter) {
|
|
3256
|
+
const existingBrs = this.mainElement.querySelectorAll("br");
|
|
3257
|
+
if (containerWidth === this.lastBalancedContainerWidth && existingBrs.length > 0) return;
|
|
3258
|
+
adapter.resetDOM();
|
|
3259
|
+
const prevWhiteSpace = this.mainElement.style.whiteSpace;
|
|
3260
|
+
this.mainElement.style.whiteSpace = "nowrap";
|
|
3261
|
+
const parentElement = this.mainElement.parentElement;
|
|
3262
|
+
let prevTransform = "";
|
|
3263
|
+
let transformChanged = false;
|
|
3264
|
+
if (parentElement) {
|
|
3265
|
+
prevTransform = parentElement.style.transform;
|
|
3266
|
+
if (prevTransform && prevTransform !== "none") {
|
|
3267
|
+
parentElement.style.transform = "none";
|
|
3268
|
+
transformChanged = true;
|
|
3269
|
+
}
|
|
3270
|
+
}
|
|
3271
|
+
let lockAcquired = false;
|
|
3272
|
+
try {
|
|
3273
|
+
const { childInfos, fullText } = adapter.buildChildInfos();
|
|
3274
|
+
let layoutWidth = childInfos.reduce((sum, c) => sum + c.width, 0);
|
|
3275
|
+
if (adapter.needsCalibration) {
|
|
3276
|
+
const range = document.createRange();
|
|
3277
|
+
range.selectNodeContents(this.mainElement);
|
|
3278
|
+
const visualWidth = range.getBoundingClientRect().width;
|
|
3279
|
+
if (layoutWidth > 0 && visualWidth > 0) {
|
|
3280
|
+
const scale = visualWidth / layoutWidth;
|
|
3281
|
+
for (const info of childInfos) info.width *= scale;
|
|
3282
|
+
}
|
|
3283
|
+
layoutWidth = visualWidth;
|
|
3284
|
+
}
|
|
3285
|
+
const safeContainerWidth = Math.max(1, containerWidth);
|
|
3286
|
+
if (layoutWidth <= safeContainerWidth) {
|
|
3287
|
+
this.lastBalancedContainerWidth = containerWidth;
|
|
3288
|
+
return;
|
|
3289
|
+
}
|
|
3290
|
+
const breaks = calcBalancedBreaks(childInfos, safeContainerWidth, fullText, wordSegmenter);
|
|
3291
|
+
if (breaks.length === 0) {
|
|
3292
|
+
this.lastBalancedContainerWidth = containerWidth;
|
|
3293
|
+
return;
|
|
3294
|
+
}
|
|
3295
|
+
this.isBalancing = true;
|
|
3296
|
+
lockAcquired = true;
|
|
3297
|
+
adapter.applyBreaks(breaks, childInfos);
|
|
3298
|
+
this.lastBalancedContainerWidth = containerWidth;
|
|
3299
|
+
this.isBalancing = false;
|
|
3300
|
+
} finally {
|
|
3301
|
+
this.mainElement.style.whiteSpace = prevWhiteSpace;
|
|
3302
|
+
if (transformChanged && parentElement) parentElement.style.transform = prevTransform;
|
|
3303
|
+
if (lockAcquired) this.isBalancing = false;
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
balanceDynamicLineBreaks(containerWidth, wordSegmenter) {
|
|
3307
|
+
const infoToNode = [];
|
|
3308
|
+
this.executeLineBalance(containerWidth, {
|
|
3309
|
+
resetDOM: () => {
|
|
3310
|
+
this.mainElement.querySelectorAll("br").forEach((br) => {
|
|
3311
|
+
br.remove();
|
|
3312
|
+
});
|
|
3313
|
+
},
|
|
3314
|
+
buildChildInfos: () => {
|
|
3315
|
+
infoToNode.length = 0;
|
|
3316
|
+
const childNodes = Array.from(this.mainElement.childNodes);
|
|
3317
|
+
const childInfos = [];
|
|
3318
|
+
const range = document.createRange();
|
|
3319
|
+
for (const node of childNodes) if (node.nodeType === Node.TEXT_NODE) {
|
|
3320
|
+
const text = node.textContent ?? "";
|
|
3321
|
+
if (text.length === 0) continue;
|
|
3322
|
+
range.selectNodeContents(node);
|
|
3323
|
+
childInfos.push({
|
|
3324
|
+
width: range.getBoundingClientRect().width,
|
|
3325
|
+
text,
|
|
3326
|
+
isSpace: text.trim().length === 0
|
|
3327
|
+
});
|
|
3328
|
+
infoToNode.push(node);
|
|
3329
|
+
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
3330
|
+
const el = node;
|
|
3331
|
+
const rect = el.getBoundingClientRect();
|
|
3332
|
+
const elStyle = getComputedStyle(el);
|
|
3333
|
+
const marginLeft = Number.parseFloat(elStyle.marginLeft) || 0;
|
|
3334
|
+
const marginRight = Number.parseFloat(elStyle.marginRight) || 0;
|
|
3335
|
+
childInfos.push({
|
|
3336
|
+
width: Math.max(0, rect.width + marginLeft + marginRight),
|
|
3337
|
+
text: el.textContent ?? "",
|
|
3338
|
+
isSpace: false
|
|
3339
|
+
});
|
|
3340
|
+
infoToNode.push(node);
|
|
3341
|
+
}
|
|
3342
|
+
return {
|
|
3343
|
+
childInfos,
|
|
3344
|
+
fullText: childInfos.map((c) => c.text).join("")
|
|
3345
|
+
};
|
|
3346
|
+
},
|
|
3347
|
+
applyBreaks: (breaks) => {
|
|
3348
|
+
for (let i = breaks.length - 1; i >= 0; i--) {
|
|
3349
|
+
const breakIndex = breaks[i];
|
|
3350
|
+
if (breakIndex >= 0 && breakIndex < infoToNode.length) this.mainElement.insertBefore(document.createElement("br"), infoToNode[breakIndex]);
|
|
3351
|
+
}
|
|
3352
|
+
},
|
|
3353
|
+
needsCalibration: false
|
|
3354
|
+
}, wordSegmenter);
|
|
3355
|
+
}
|
|
3356
|
+
balanceNonDynamicLineBreaks(containerWidth, computedStyle, wordSegmenter) {
|
|
3357
|
+
const fullText = this.mainElement.textContent ?? "";
|
|
3358
|
+
if (fullText.trim().length === 0) return;
|
|
3359
|
+
this.executeLineBalance(containerWidth, {
|
|
3360
|
+
resetDOM: () => {
|
|
3361
|
+
this.mainElement.innerHTML = "";
|
|
3362
|
+
this.mainElement.textContent = fullText;
|
|
3363
|
+
},
|
|
3364
|
+
buildChildInfos: () => {
|
|
3365
|
+
const ctx = getMeasurementContext();
|
|
3366
|
+
if (!ctx) {
|
|
3367
|
+
console.debug("Canvas 2D context is not supported, skipping line balancing");
|
|
3368
|
+
return {
|
|
3369
|
+
childInfos: [],
|
|
3370
|
+
fullText
|
|
3371
|
+
};
|
|
3372
|
+
}
|
|
3373
|
+
ctx.font = `${computedStyle.fontWeight} ${computedStyle.fontSize} ${computedStyle.fontFamily}`;
|
|
3374
|
+
if ("letterSpacing" in ctx) ctx.letterSpacing = computedStyle.letterSpacing !== "normal" ? computedStyle.letterSpacing : "0px";
|
|
3375
|
+
if ("wordSpacing" in ctx) ctx.wordSpacing = computedStyle.wordSpacing !== "normal" ? computedStyle.wordSpacing : "0px";
|
|
3376
|
+
const childInfos = [];
|
|
3377
|
+
for (const { segment } of wordSegmenter.segment(fullText)) childInfos.push({
|
|
3378
|
+
width: ctx.measureText(segment).width,
|
|
3379
|
+
text: segment,
|
|
3380
|
+
isSpace: segment.trim().length === 0
|
|
3381
|
+
});
|
|
3382
|
+
return {
|
|
3383
|
+
childInfos,
|
|
3384
|
+
fullText
|
|
3385
|
+
};
|
|
3386
|
+
},
|
|
3387
|
+
applyBreaks: (breaks, childInfos) => {
|
|
3388
|
+
this.mainElement.innerHTML = "";
|
|
3389
|
+
const breakSet = new Set(breaks);
|
|
3390
|
+
const fragment = document.createDocumentFragment();
|
|
3391
|
+
for (let i = 0; i < childInfos.length; i++) {
|
|
3392
|
+
if (breakSet.has(i)) fragment.appendChild(document.createElement("br"));
|
|
3393
|
+
fragment.appendChild(document.createTextNode(childInfos[i].text));
|
|
3394
|
+
}
|
|
3395
|
+
this.mainElement.appendChild(fragment);
|
|
3396
|
+
},
|
|
3397
|
+
needsCalibration: true
|
|
3398
|
+
}, wordSegmenter);
|
|
3399
|
+
}
|
|
3400
|
+
};
|
|
3401
|
+
//#endregion
|
|
3125
3402
|
//#region src/utils/lyric-split-words.ts
|
|
3126
|
-
const
|
|
3403
|
+
const SPLIT_WHITESPACE_RE = /(\s+)/;
|
|
3404
|
+
const WHITESPACE_RE = /\s/g;
|
|
3127
3405
|
/**
|
|
3128
3406
|
* 将输入的单词重新分组,之间没有空格的单词将会组合成一个单词数组
|
|
3129
3407
|
*
|
|
@@ -3134,27 +3412,41 @@ const hasSegmenter = typeof Intl !== "undefined" && typeof Intl.Segmenter !== "u
|
|
|
3134
3412
|
* @returns 重新分组后的单词数组
|
|
3135
3413
|
*/
|
|
3136
3414
|
function chunkAndSplitLyricWords(words) {
|
|
3137
|
-
const
|
|
3415
|
+
const result = [];
|
|
3416
|
+
let currentGroup = [];
|
|
3417
|
+
const flushGroup = () => {
|
|
3418
|
+
if (currentGroup.length > 0) {
|
|
3419
|
+
result.push(currentGroup.length === 1 ? currentGroup[0] : [...currentGroup]);
|
|
3420
|
+
currentGroup = [];
|
|
3421
|
+
}
|
|
3422
|
+
};
|
|
3423
|
+
const processAtom = (atom) => {
|
|
3424
|
+
const isSpace = atom.word.trim().length === 0;
|
|
3425
|
+
const hasRuby = (atom.ruby?.length ?? 0) > 0;
|
|
3426
|
+
const isCJKChar = isCJK(atom.word);
|
|
3427
|
+
if (!isSpace && !hasRuby && !isCJKChar) currentGroup.push(atom);
|
|
3428
|
+
else {
|
|
3429
|
+
flushGroup();
|
|
3430
|
+
result.push(atom);
|
|
3431
|
+
}
|
|
3432
|
+
};
|
|
3138
3433
|
for (const w of words) {
|
|
3139
3434
|
const isSpace = w.word.trim().length === 0;
|
|
3140
3435
|
const romanWord = w.romanWord ?? "";
|
|
3141
3436
|
const obscene = w.obscene ?? false;
|
|
3142
3437
|
const hasRuby = (w.ruby?.length ?? 0) > 0;
|
|
3143
|
-
if (isSpace) {
|
|
3144
|
-
|
|
3438
|
+
if (isSpace || hasRuby) {
|
|
3439
|
+
processAtom({ ...w });
|
|
3145
3440
|
continue;
|
|
3146
3441
|
}
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
}
|
|
3151
|
-
const parts = w.word.split(/(\s+)/).filter((p) => p.length > 0);
|
|
3442
|
+
const parts = w.word.split(SPLIT_WHITESPACE_RE).filter((p) => p.length > 0);
|
|
3443
|
+
const totalLength = w.word.replace(WHITESPACE_RE, "").length || 1;
|
|
3444
|
+
const timePerUnit = (w.endTime - w.startTime) / totalLength;
|
|
3152
3445
|
let currentOffset = 0;
|
|
3153
|
-
const totalLength = w.word.replace(/\s/g, "").length || 1;
|
|
3154
3446
|
for (const part of parts) {
|
|
3155
3447
|
if (!part.trim()) {
|
|
3156
|
-
const startTime = w.startTime + currentOffset
|
|
3157
|
-
|
|
3448
|
+
const startTime = w.startTime + currentOffset * timePerUnit;
|
|
3449
|
+
processAtom({
|
|
3158
3450
|
word: part,
|
|
3159
3451
|
romanWord: "",
|
|
3160
3452
|
startTime,
|
|
@@ -3166,63 +3458,31 @@ function chunkAndSplitLyricWords(words) {
|
|
|
3166
3458
|
if (isCJK(part) && part.length > 1 && romanWord.trim().length === 0) {
|
|
3167
3459
|
const chars = part.split("");
|
|
3168
3460
|
for (const char of chars) {
|
|
3169
|
-
const
|
|
3170
|
-
|
|
3171
|
-
atoms.push({
|
|
3461
|
+
const startTime = w.startTime + currentOffset * timePerUnit;
|
|
3462
|
+
processAtom({
|
|
3172
3463
|
word: char,
|
|
3173
3464
|
romanWord: "",
|
|
3174
3465
|
startTime,
|
|
3175
|
-
endTime: startTime +
|
|
3466
|
+
endTime: startTime + timePerUnit,
|
|
3176
3467
|
obscene
|
|
3177
3468
|
});
|
|
3178
3469
|
currentOffset += 1;
|
|
3179
3470
|
}
|
|
3180
3471
|
} else {
|
|
3181
3472
|
const partRealLen = part.length;
|
|
3182
|
-
const
|
|
3183
|
-
|
|
3184
|
-
atoms.push({
|
|
3473
|
+
const startTime = w.startTime + currentOffset * timePerUnit;
|
|
3474
|
+
processAtom({
|
|
3185
3475
|
word: part,
|
|
3186
3476
|
romanWord,
|
|
3187
3477
|
startTime,
|
|
3188
|
-
endTime: startTime +
|
|
3478
|
+
endTime: startTime + partRealLen * timePerUnit,
|
|
3189
3479
|
obscene
|
|
3190
3480
|
});
|
|
3191
3481
|
currentOffset += partRealLen;
|
|
3192
3482
|
}
|
|
3193
3483
|
}
|
|
3194
3484
|
}
|
|
3195
|
-
|
|
3196
|
-
const fullText = atoms.map((a) => a.word).join("");
|
|
3197
|
-
const segmenter = new Intl.Segmenter(void 0, { granularity: "word" });
|
|
3198
|
-
const segments = Array.from(segmenter.segment(fullText));
|
|
3199
|
-
const result = [];
|
|
3200
|
-
let atomIndex = 0;
|
|
3201
|
-
let expectedLength = 0;
|
|
3202
|
-
let actualLength = 0;
|
|
3203
|
-
let currentGroup = [];
|
|
3204
|
-
for (const segment of segments) {
|
|
3205
|
-
const segmentLen = segment.segment.length;
|
|
3206
|
-
expectedLength += segmentLen;
|
|
3207
|
-
while (actualLength < expectedLength && atomIndex < atoms.length) {
|
|
3208
|
-
const currentAtom = atoms[atomIndex];
|
|
3209
|
-
currentGroup.push(currentAtom);
|
|
3210
|
-
actualLength += currentAtom.word.length;
|
|
3211
|
-
atomIndex++;
|
|
3212
|
-
}
|
|
3213
|
-
if (actualLength === expectedLength) {
|
|
3214
|
-
while (currentGroup.length > 1 && !currentGroup[0].word.trim()) {
|
|
3215
|
-
const spaceAtom = currentGroup.shift();
|
|
3216
|
-
if (spaceAtom) result.push(spaceAtom);
|
|
3217
|
-
}
|
|
3218
|
-
if (currentGroup.length === 1) result.push(currentGroup[0]);
|
|
3219
|
-
else if (currentGroup.length > 1) result.push(currentGroup);
|
|
3220
|
-
currentGroup = [];
|
|
3221
|
-
}
|
|
3222
|
-
}
|
|
3223
|
-
while (atomIndex < atoms.length) result.push(atoms[atomIndex++]);
|
|
3224
|
-
if (currentGroup.length > 0) if (currentGroup.length === 1) result.push(currentGroup[0]);
|
|
3225
|
-
else result.push(currentGroup);
|
|
3485
|
+
flushGroup();
|
|
3226
3486
|
return result;
|
|
3227
3487
|
}
|
|
3228
3488
|
//#endregion
|
|
@@ -3304,12 +3564,15 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3304
3564
|
splittedWords = [];
|
|
3305
3565
|
built = false;
|
|
3306
3566
|
lineSize = [0, 0];
|
|
3307
|
-
renderMode =
|
|
3567
|
+
renderMode = 0;
|
|
3308
3568
|
currentBrightAlpha = 1;
|
|
3309
3569
|
currentDarkAlpha = .2;
|
|
3310
3570
|
targetBrightAlpha = 1;
|
|
3311
3571
|
targetDarkAlpha = .2;
|
|
3312
|
-
|
|
3572
|
+
/**
|
|
3573
|
+
* 用于平衡换行、尽量减少各行长度差异的类
|
|
3574
|
+
*/
|
|
3575
|
+
balancer;
|
|
3313
3576
|
constructor(lyricPlayer, lyricLine = {
|
|
3314
3577
|
words: [],
|
|
3315
3578
|
translatedLyric: "",
|
|
@@ -3337,6 +3600,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3337
3600
|
main.setAttribute("class", lyric_player_module_default.lyricMainLine);
|
|
3338
3601
|
trans.setAttribute("class", lyric_player_module_default.lyricSubLine);
|
|
3339
3602
|
roman.setAttribute("class", lyric_player_module_default.lyricSubLine);
|
|
3603
|
+
if (LyricLineBase.wordSegmenter) this.balancer = new LineBalancer(main);
|
|
3340
3604
|
this.rebuildStyle();
|
|
3341
3605
|
}
|
|
3342
3606
|
listenersMap = /* @__PURE__ */ new Map();
|
|
@@ -3410,7 +3674,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3410
3674
|
disable() {
|
|
3411
3675
|
this.isEnabled = false;
|
|
3412
3676
|
this.element.classList.remove(lyric_player_module_default.active);
|
|
3413
|
-
this.renderMode =
|
|
3677
|
+
this.renderMode = 0;
|
|
3414
3678
|
const main = this.element.children[0];
|
|
3415
3679
|
for (const word of this.splittedWords) {
|
|
3416
3680
|
for (const a of word.elementAnimations) if (a.id === "float-word" || a.id.includes("emphasize-word-float-only")) {
|
|
@@ -3546,7 +3810,14 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3546
3810
|
const displayWord = this.lyricPlayer.processObsceneWord(word);
|
|
3547
3811
|
if (shouldEmphasize) {
|
|
3548
3812
|
mainWordEl.classList.add(lyric_player_module_default.emphasize);
|
|
3549
|
-
|
|
3813
|
+
const trimmedWord = displayWord.trim();
|
|
3814
|
+
if (LyricLineBase.graphemeSegmenter) for (const { segment } of LyricLineBase.graphemeSegmenter.segment(trimmedWord)) {
|
|
3815
|
+
const charEl = document.createElement("span");
|
|
3816
|
+
charEl.innerText = segment;
|
|
3817
|
+
subElements.push(charEl);
|
|
3818
|
+
wordContainer.appendChild(charEl);
|
|
3819
|
+
}
|
|
3820
|
+
else for (const segment of Array.from(trimmedWord)) {
|
|
3550
3821
|
const charEl = document.createElement("span");
|
|
3551
3822
|
charEl.innerText = segment;
|
|
3552
3823
|
subElements.push(charEl);
|
|
@@ -3728,6 +3999,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3728
3999
|
word.padding = 0;
|
|
3729
4000
|
}
|
|
3730
4001
|
}
|
|
4002
|
+
if (this.balancer && LyricLineBase.wordSegmenter) this.balancer.balanceLineBreaks(this.lyricPlayer._getIsNonDynamic(), this.splittedWords.length > 0, LyricLineBase.wordSegmenter);
|
|
3731
4003
|
if (this.lyricPlayer.supportMaskImage) this.generateWebAnimationBasedMaskImage();
|
|
3732
4004
|
else this.generateCalcBasedMaskImage();
|
|
3733
4005
|
if (this.isEnabled) {
|
|
@@ -3900,7 +4172,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3900
4172
|
const factor = Math.max(0, Math.min(1, (scale - .97) / .03));
|
|
3901
4173
|
const dynamicDarkAlpha = factor * .2 + .2;
|
|
3902
4174
|
const dynamicBrightAlpha = factor * .8 + .2;
|
|
3903
|
-
if (this.renderMode ===
|
|
4175
|
+
if (this.renderMode === 0) {
|
|
3904
4176
|
this.targetBrightAlpha = dynamicDarkAlpha;
|
|
3905
4177
|
this.targetDarkAlpha = dynamicDarkAlpha;
|
|
3906
4178
|
} else {
|
|
@@ -3922,7 +4194,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3922
4194
|
this.element.style.setProperty("--bright-mask-alpha", this.currentBrightAlpha.toFixed(3));
|
|
3923
4195
|
this.element.style.setProperty("--dark-mask-alpha", this.currentDarkAlpha.toFixed(3));
|
|
3924
4196
|
}
|
|
3925
|
-
setTransform(top = this.top, scale = this.scale, opacity = 1, blur = 0, force = false, delay = 0, mode =
|
|
4197
|
+
setTransform(top = this.top, scale = this.scale, opacity = 1, blur = 0, force = false, delay = 0, mode = 0) {
|
|
3926
4198
|
super.setTransform(top, scale, opacity, blur, force, delay);
|
|
3927
4199
|
this.renderMode = mode;
|
|
3928
4200
|
const beforeInSight = this.isInSight;
|
|
@@ -3979,6 +4251,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3979
4251
|
return !(t > pb + h + ov || b < -h - ov);
|
|
3980
4252
|
}
|
|
3981
4253
|
disposeElements() {
|
|
4254
|
+
this.balancer?.reset();
|
|
3982
4255
|
for (const realWord of this.splittedWords) {
|
|
3983
4256
|
for (const a of realWord.elementAnimations) a.cancel();
|
|
3984
4257
|
for (const a of realWord.maskAnimations) a.cancel();
|