@applemusic-like-lyrics/core 0.4.0 → 0.4.1
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 +313 -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 +311 -64
- package/dist/amll-core.mjs.map +1 -1
- package/dist/style.css +0 -3
- 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,251 @@ 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
|
+
* @param children 子节点信息
|
|
3156
|
+
* @param containerWidth 容器可用内容宽度
|
|
3157
|
+
* @param fullText 完整的行文本
|
|
3158
|
+
* @param segmenter 预创建的 Intl.Segmenter 分词器
|
|
3159
|
+
* @returns 需要在其前面插入 `<br>` 的子节点索引数组,升序
|
|
3160
|
+
*/
|
|
3161
|
+
function calcBalancedBreaks(children, containerWidth, fullText, segmenter) {
|
|
3162
|
+
const n = children.length;
|
|
3163
|
+
if (n === 0 || containerWidth <= 0) return [];
|
|
3164
|
+
const cjkBoundaries = /* @__PURE__ */ new Set();
|
|
3165
|
+
let offset = 0;
|
|
3166
|
+
for (const { segment, isWordLike } of segmenter.segment(fullText)) {
|
|
3167
|
+
if (offset > 0 && isWordLike) {
|
|
3168
|
+
if ([...segment].some((ch) => isCJK(ch))) cjkBoundaries.add(offset);
|
|
3169
|
+
}
|
|
3170
|
+
offset += segment.length;
|
|
3171
|
+
}
|
|
3172
|
+
const charOffsets = new Int32Array(n + 1);
|
|
3173
|
+
const prefixWidth = new Float64Array(n + 1);
|
|
3174
|
+
for (let i = 0; i < n; i++) {
|
|
3175
|
+
charOffsets[i + 1] = charOffsets[i] + children[i].text.length;
|
|
3176
|
+
prefixWidth[i + 1] = prefixWidth[i] + children[i].width;
|
|
3177
|
+
}
|
|
3178
|
+
if (prefixWidth[n] <= containerWidth) return [];
|
|
3179
|
+
/**
|
|
3180
|
+
* dp[i] 表示将 index i 到 n-1 的节点进行排版的最小代价
|
|
3181
|
+
*/
|
|
3182
|
+
const dp = new Float64Array(n + 1).fill(Number.POSITIVE_INFINITY);
|
|
3183
|
+
const nextBreak = new Int32Array(n + 1).fill(-1);
|
|
3184
|
+
dp[n] = 0;
|
|
3185
|
+
const PENALTY_CJK = (containerWidth * CJK_BREAK_PENALTY_RATIO) ** 2;
|
|
3186
|
+
const PENALTY_NORMAL = (containerWidth * NORMAL_BREAK_PENALTY_RATIO) ** 2;
|
|
3187
|
+
for (let i = n - 1; i >= 0; i--) for (let j = i + 1; j <= n; j++) {
|
|
3188
|
+
const w = prefixWidth[j] - prefixWidth[i];
|
|
3189
|
+
let lineCost = 0;
|
|
3190
|
+
if (w > containerWidth) if (j === i + 1) lineCost = (w - containerWidth) ** 2 * OVERFLOW_PENALTY_MULTIPLIER;
|
|
3191
|
+
else continue;
|
|
3192
|
+
else lineCost = (containerWidth - w) ** 2;
|
|
3193
|
+
let breakPenalty = 0;
|
|
3194
|
+
if (j < n) if (children[j - 1].isSpace) breakPenalty = -((containerWidth * SPACE_BREAK_REWARD_RATIO) ** 2);
|
|
3195
|
+
else if (cjkBoundaries.has(charOffsets[j])) breakPenalty = PENALTY_CJK;
|
|
3196
|
+
else breakPenalty = PENALTY_NORMAL;
|
|
3197
|
+
const totalCost = lineCost + breakPenalty + dp[j];
|
|
3198
|
+
if (totalCost < dp[i]) {
|
|
3199
|
+
dp[i] = totalCost;
|
|
3200
|
+
nextBreak[i] = j;
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
const breaks = [];
|
|
3204
|
+
let curr = 0;
|
|
3205
|
+
while (curr < n) {
|
|
3206
|
+
curr = nextBreak[curr];
|
|
3207
|
+
if (curr > 0 && curr < n) breaks.push(curr);
|
|
3208
|
+
}
|
|
3209
|
+
return breaks;
|
|
3210
|
+
}
|
|
3211
|
+
//#endregion
|
|
3212
|
+
//#region src/utils/line-balancer.ts
|
|
3213
|
+
let sharedCanvasCtx = null;
|
|
3214
|
+
function getMeasurementContext() {
|
|
3215
|
+
if (!sharedCanvasCtx) sharedCanvasCtx = document.createElement("canvas").getContext("2d");
|
|
3216
|
+
return sharedCanvasCtx;
|
|
3217
|
+
}
|
|
3218
|
+
/**
|
|
3219
|
+
* 用于平衡歌词行在换行后的各行长度
|
|
3220
|
+
*/
|
|
3221
|
+
var LineBalancer = class LineBalancer {
|
|
3222
|
+
isBalancing = false;
|
|
3223
|
+
lastBalancedContainerWidth = -1;
|
|
3224
|
+
/**
|
|
3225
|
+
* 防止误差导致的意外换行
|
|
3226
|
+
*/
|
|
3227
|
+
static SAFE_WIDTH_PADDING = 25;
|
|
3228
|
+
constructor(mainElement) {
|
|
3229
|
+
this.mainElement = mainElement;
|
|
3230
|
+
}
|
|
3231
|
+
balanceLineBreaks(isNonDynamic, hasSplittedWords, wordSegmenter) {
|
|
3232
|
+
if (this.isBalancing || !this.mainElement) return;
|
|
3233
|
+
const computedStyle = getComputedStyle(this.mainElement);
|
|
3234
|
+
const paddingLeft = Number.parseFloat(computedStyle.paddingLeft) || 0;
|
|
3235
|
+
const paddingRight = Number.parseFloat(computedStyle.paddingRight) || 0;
|
|
3236
|
+
const containerWidth = this.mainElement.clientWidth - paddingLeft - paddingRight;
|
|
3237
|
+
if (containerWidth <= 0) return;
|
|
3238
|
+
if (isNonDynamic) {
|
|
3239
|
+
this.balanceNonDynamicLineBreaks(containerWidth, computedStyle, wordSegmenter);
|
|
3240
|
+
return;
|
|
3241
|
+
}
|
|
3242
|
+
if (!hasSplittedWords) return;
|
|
3243
|
+
this.balanceDynamicLineBreaks(containerWidth, wordSegmenter);
|
|
3244
|
+
}
|
|
3245
|
+
reset() {
|
|
3246
|
+
this.lastBalancedContainerWidth = -1;
|
|
3247
|
+
}
|
|
3248
|
+
executeLineBalance(containerWidth, adapter, wordSegmenter) {
|
|
3249
|
+
const existingBrs = this.mainElement.querySelectorAll("br");
|
|
3250
|
+
if (containerWidth === this.lastBalancedContainerWidth && existingBrs.length > 0) return;
|
|
3251
|
+
adapter.resetDOM();
|
|
3252
|
+
const prevWhiteSpace = this.mainElement.style.whiteSpace;
|
|
3253
|
+
this.mainElement.style.whiteSpace = "nowrap";
|
|
3254
|
+
try {
|
|
3255
|
+
const range = document.createRange();
|
|
3256
|
+
range.selectNodeContents(this.mainElement);
|
|
3257
|
+
const lineWidth = range.getBoundingClientRect().width;
|
|
3258
|
+
const safeContainerWidth = Math.max(1, containerWidth - LineBalancer.SAFE_WIDTH_PADDING);
|
|
3259
|
+
if (lineWidth <= safeContainerWidth) {
|
|
3260
|
+
this.lastBalancedContainerWidth = containerWidth;
|
|
3261
|
+
return;
|
|
3262
|
+
}
|
|
3263
|
+
const { childInfos, fullText } = adapter.buildChildInfos();
|
|
3264
|
+
const measuredTotal = childInfos.reduce((sum, c) => sum + c.width, 0);
|
|
3265
|
+
if (measuredTotal > 0 && lineWidth > 0) {
|
|
3266
|
+
const scale = lineWidth / measuredTotal;
|
|
3267
|
+
for (const info of childInfos) info.width *= scale;
|
|
3268
|
+
}
|
|
3269
|
+
const breaks = calcBalancedBreaks(childInfos, safeContainerWidth, fullText, wordSegmenter);
|
|
3270
|
+
if (breaks.length === 0) {
|
|
3271
|
+
this.lastBalancedContainerWidth = containerWidth;
|
|
3272
|
+
return;
|
|
3273
|
+
}
|
|
3274
|
+
this.isBalancing = true;
|
|
3275
|
+
adapter.applyBreaks(breaks, childInfos);
|
|
3276
|
+
this.lastBalancedContainerWidth = containerWidth;
|
|
3277
|
+
this.isBalancing = false;
|
|
3278
|
+
} finally {
|
|
3279
|
+
this.mainElement.style.whiteSpace = prevWhiteSpace;
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
balanceDynamicLineBreaks(containerWidth, wordSegmenter) {
|
|
3283
|
+
const infoToNode = [];
|
|
3284
|
+
this.executeLineBalance(containerWidth, {
|
|
3285
|
+
resetDOM: () => {
|
|
3286
|
+
this.mainElement.querySelectorAll("br").forEach((br) => {
|
|
3287
|
+
br.remove();
|
|
3288
|
+
});
|
|
3289
|
+
},
|
|
3290
|
+
buildChildInfos: () => {
|
|
3291
|
+
infoToNode.length = 0;
|
|
3292
|
+
const childNodes = Array.from(this.mainElement.childNodes);
|
|
3293
|
+
const childInfos = [];
|
|
3294
|
+
const range = document.createRange();
|
|
3295
|
+
for (const node of childNodes) if (node.nodeType === Node.TEXT_NODE) {
|
|
3296
|
+
const text = node.textContent ?? "";
|
|
3297
|
+
if (text.length === 0) continue;
|
|
3298
|
+
range.selectNodeContents(node);
|
|
3299
|
+
childInfos.push({
|
|
3300
|
+
width: range.getBoundingClientRect().width,
|
|
3301
|
+
text,
|
|
3302
|
+
isSpace: text.trim().length === 0
|
|
3303
|
+
});
|
|
3304
|
+
infoToNode.push(node);
|
|
3305
|
+
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
3306
|
+
const el = node;
|
|
3307
|
+
const rect = el.getBoundingClientRect();
|
|
3308
|
+
const elStyle = getComputedStyle(el);
|
|
3309
|
+
const marginLeft = Number.parseFloat(elStyle.marginLeft) || 0;
|
|
3310
|
+
const marginRight = Number.parseFloat(elStyle.marginRight) || 0;
|
|
3311
|
+
childInfos.push({
|
|
3312
|
+
width: Math.max(0, rect.width + marginLeft + marginRight),
|
|
3313
|
+
text: el.textContent ?? "",
|
|
3314
|
+
isSpace: false
|
|
3315
|
+
});
|
|
3316
|
+
infoToNode.push(node);
|
|
3317
|
+
}
|
|
3318
|
+
return {
|
|
3319
|
+
childInfos,
|
|
3320
|
+
fullText: childInfos.map((c) => c.text).join("")
|
|
3321
|
+
};
|
|
3322
|
+
},
|
|
3323
|
+
applyBreaks: (breaks) => {
|
|
3324
|
+
for (let i = breaks.length - 1; i >= 0; i--) {
|
|
3325
|
+
const breakIndex = breaks[i];
|
|
3326
|
+
if (breakIndex >= 0 && breakIndex < infoToNode.length) this.mainElement.insertBefore(document.createElement("br"), infoToNode[breakIndex]);
|
|
3327
|
+
}
|
|
3328
|
+
}
|
|
3329
|
+
}, wordSegmenter);
|
|
3330
|
+
}
|
|
3331
|
+
balanceNonDynamicLineBreaks(containerWidth, computedStyle, wordSegmenter) {
|
|
3332
|
+
const fullText = this.mainElement.textContent ?? "";
|
|
3333
|
+
if (fullText.trim().length === 0) return;
|
|
3334
|
+
this.executeLineBalance(containerWidth, {
|
|
3335
|
+
resetDOM: () => {
|
|
3336
|
+
this.mainElement.innerHTML = "";
|
|
3337
|
+
this.mainElement.textContent = fullText;
|
|
3338
|
+
},
|
|
3339
|
+
buildChildInfos: () => {
|
|
3340
|
+
const ctx = getMeasurementContext();
|
|
3341
|
+
if (!ctx) {
|
|
3342
|
+
console.debug("Canvas 2D context is not supported, skipping line balancing");
|
|
3343
|
+
return {
|
|
3344
|
+
childInfos: [],
|
|
3345
|
+
fullText
|
|
3346
|
+
};
|
|
3347
|
+
}
|
|
3348
|
+
ctx.font = `${computedStyle.fontWeight} ${computedStyle.fontSize} ${computedStyle.fontFamily}`;
|
|
3349
|
+
if ("letterSpacing" in ctx) ctx.letterSpacing = computedStyle.letterSpacing !== "normal" ? computedStyle.letterSpacing : "0px";
|
|
3350
|
+
if ("wordSpacing" in ctx) ctx.wordSpacing = computedStyle.wordSpacing !== "normal" ? computedStyle.wordSpacing : "0px";
|
|
3351
|
+
const childInfos = [];
|
|
3352
|
+
for (const { segment } of wordSegmenter.segment(fullText)) childInfos.push({
|
|
3353
|
+
width: ctx.measureText(segment).width,
|
|
3354
|
+
text: segment,
|
|
3355
|
+
isSpace: segment.trim().length === 0
|
|
3356
|
+
});
|
|
3357
|
+
return {
|
|
3358
|
+
childInfos,
|
|
3359
|
+
fullText
|
|
3360
|
+
};
|
|
3361
|
+
},
|
|
3362
|
+
applyBreaks: (breaks, childInfos) => {
|
|
3363
|
+
this.mainElement.innerHTML = "";
|
|
3364
|
+
const breakSet = new Set(breaks);
|
|
3365
|
+
const fragment = document.createDocumentFragment();
|
|
3366
|
+
for (let i = 0; i < childInfos.length; i++) {
|
|
3367
|
+
if (breakSet.has(i)) fragment.appendChild(document.createElement("br"));
|
|
3368
|
+
fragment.appendChild(document.createTextNode(childInfos[i].text));
|
|
3369
|
+
}
|
|
3370
|
+
this.mainElement.appendChild(fragment);
|
|
3371
|
+
}
|
|
3372
|
+
}, wordSegmenter);
|
|
3373
|
+
}
|
|
3374
|
+
};
|
|
3375
|
+
//#endregion
|
|
3125
3376
|
//#region src/utils/lyric-split-words.ts
|
|
3126
|
-
const
|
|
3377
|
+
const SPLIT_WHITESPACE_RE = /(\s+)/;
|
|
3378
|
+
const WHITESPACE_RE = /\s/g;
|
|
3127
3379
|
/**
|
|
3128
3380
|
* 将输入的单词重新分组,之间没有空格的单词将会组合成一个单词数组
|
|
3129
3381
|
*
|
|
@@ -3134,27 +3386,41 @@ const hasSegmenter = typeof Intl !== "undefined" && typeof Intl.Segmenter !== "u
|
|
|
3134
3386
|
* @returns 重新分组后的单词数组
|
|
3135
3387
|
*/
|
|
3136
3388
|
function chunkAndSplitLyricWords(words) {
|
|
3137
|
-
const
|
|
3389
|
+
const result = [];
|
|
3390
|
+
let currentGroup = [];
|
|
3391
|
+
const flushGroup = () => {
|
|
3392
|
+
if (currentGroup.length > 0) {
|
|
3393
|
+
result.push(currentGroup.length === 1 ? currentGroup[0] : [...currentGroup]);
|
|
3394
|
+
currentGroup = [];
|
|
3395
|
+
}
|
|
3396
|
+
};
|
|
3397
|
+
const processAtom = (atom) => {
|
|
3398
|
+
const isSpace = atom.word.trim().length === 0;
|
|
3399
|
+
const hasRuby = (atom.ruby?.length ?? 0) > 0;
|
|
3400
|
+
const isCJKChar = isCJK(atom.word);
|
|
3401
|
+
if (!isSpace && !hasRuby && !isCJKChar) currentGroup.push(atom);
|
|
3402
|
+
else {
|
|
3403
|
+
flushGroup();
|
|
3404
|
+
result.push(atom);
|
|
3405
|
+
}
|
|
3406
|
+
};
|
|
3138
3407
|
for (const w of words) {
|
|
3139
3408
|
const isSpace = w.word.trim().length === 0;
|
|
3140
3409
|
const romanWord = w.romanWord ?? "";
|
|
3141
3410
|
const obscene = w.obscene ?? false;
|
|
3142
3411
|
const hasRuby = (w.ruby?.length ?? 0) > 0;
|
|
3143
|
-
if (isSpace) {
|
|
3144
|
-
|
|
3145
|
-
continue;
|
|
3146
|
-
}
|
|
3147
|
-
if (hasRuby) {
|
|
3148
|
-
atoms.push({ ...w });
|
|
3412
|
+
if (isSpace || hasRuby) {
|
|
3413
|
+
processAtom({ ...w });
|
|
3149
3414
|
continue;
|
|
3150
3415
|
}
|
|
3151
|
-
const parts = w.word.split(
|
|
3416
|
+
const parts = w.word.split(SPLIT_WHITESPACE_RE).filter((p) => p.length > 0);
|
|
3417
|
+
const totalLength = w.word.replace(WHITESPACE_RE, "").length || 1;
|
|
3418
|
+
const timePerUnit = (w.endTime - w.startTime) / totalLength;
|
|
3152
3419
|
let currentOffset = 0;
|
|
3153
|
-
const totalLength = w.word.replace(/\s/g, "").length || 1;
|
|
3154
3420
|
for (const part of parts) {
|
|
3155
3421
|
if (!part.trim()) {
|
|
3156
|
-
const startTime = w.startTime + currentOffset
|
|
3157
|
-
|
|
3422
|
+
const startTime = w.startTime + currentOffset * timePerUnit;
|
|
3423
|
+
processAtom({
|
|
3158
3424
|
word: part,
|
|
3159
3425
|
romanWord: "",
|
|
3160
3426
|
startTime,
|
|
@@ -3166,63 +3432,31 @@ function chunkAndSplitLyricWords(words) {
|
|
|
3166
3432
|
if (isCJK(part) && part.length > 1 && romanWord.trim().length === 0) {
|
|
3167
3433
|
const chars = part.split("");
|
|
3168
3434
|
for (const char of chars) {
|
|
3169
|
-
const
|
|
3170
|
-
|
|
3171
|
-
atoms.push({
|
|
3435
|
+
const startTime = w.startTime + currentOffset * timePerUnit;
|
|
3436
|
+
processAtom({
|
|
3172
3437
|
word: char,
|
|
3173
3438
|
romanWord: "",
|
|
3174
3439
|
startTime,
|
|
3175
|
-
endTime: startTime +
|
|
3440
|
+
endTime: startTime + timePerUnit,
|
|
3176
3441
|
obscene
|
|
3177
3442
|
});
|
|
3178
3443
|
currentOffset += 1;
|
|
3179
3444
|
}
|
|
3180
3445
|
} else {
|
|
3181
3446
|
const partRealLen = part.length;
|
|
3182
|
-
const
|
|
3183
|
-
|
|
3184
|
-
atoms.push({
|
|
3447
|
+
const startTime = w.startTime + currentOffset * timePerUnit;
|
|
3448
|
+
processAtom({
|
|
3185
3449
|
word: part,
|
|
3186
3450
|
romanWord,
|
|
3187
3451
|
startTime,
|
|
3188
|
-
endTime: startTime +
|
|
3452
|
+
endTime: startTime + partRealLen * timePerUnit,
|
|
3189
3453
|
obscene
|
|
3190
3454
|
});
|
|
3191
3455
|
currentOffset += partRealLen;
|
|
3192
3456
|
}
|
|
3193
3457
|
}
|
|
3194
3458
|
}
|
|
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);
|
|
3459
|
+
flushGroup();
|
|
3226
3460
|
return result;
|
|
3227
3461
|
}
|
|
3228
3462
|
//#endregion
|
|
@@ -3304,12 +3538,15 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3304
3538
|
splittedWords = [];
|
|
3305
3539
|
built = false;
|
|
3306
3540
|
lineSize = [0, 0];
|
|
3307
|
-
renderMode =
|
|
3541
|
+
renderMode = 0;
|
|
3308
3542
|
currentBrightAlpha = 1;
|
|
3309
3543
|
currentDarkAlpha = .2;
|
|
3310
3544
|
targetBrightAlpha = 1;
|
|
3311
3545
|
targetDarkAlpha = .2;
|
|
3312
|
-
|
|
3546
|
+
/**
|
|
3547
|
+
* 用于平衡换行、尽量减少各行长度差异的类
|
|
3548
|
+
*/
|
|
3549
|
+
balancer;
|
|
3313
3550
|
constructor(lyricPlayer, lyricLine = {
|
|
3314
3551
|
words: [],
|
|
3315
3552
|
translatedLyric: "",
|
|
@@ -3337,6 +3574,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3337
3574
|
main.setAttribute("class", lyric_player_module_default.lyricMainLine);
|
|
3338
3575
|
trans.setAttribute("class", lyric_player_module_default.lyricSubLine);
|
|
3339
3576
|
roman.setAttribute("class", lyric_player_module_default.lyricSubLine);
|
|
3577
|
+
if (LyricLineBase.wordSegmenter) this.balancer = new LineBalancer(main);
|
|
3340
3578
|
this.rebuildStyle();
|
|
3341
3579
|
}
|
|
3342
3580
|
listenersMap = /* @__PURE__ */ new Map();
|
|
@@ -3410,7 +3648,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3410
3648
|
disable() {
|
|
3411
3649
|
this.isEnabled = false;
|
|
3412
3650
|
this.element.classList.remove(lyric_player_module_default.active);
|
|
3413
|
-
this.renderMode =
|
|
3651
|
+
this.renderMode = 0;
|
|
3414
3652
|
const main = this.element.children[0];
|
|
3415
3653
|
for (const word of this.splittedWords) {
|
|
3416
3654
|
for (const a of word.elementAnimations) if (a.id === "float-word" || a.id.includes("emphasize-word-float-only")) {
|
|
@@ -3546,7 +3784,14 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3546
3784
|
const displayWord = this.lyricPlayer.processObsceneWord(word);
|
|
3547
3785
|
if (shouldEmphasize) {
|
|
3548
3786
|
mainWordEl.classList.add(lyric_player_module_default.emphasize);
|
|
3549
|
-
|
|
3787
|
+
const trimmedWord = displayWord.trim();
|
|
3788
|
+
if (LyricLineBase.graphemeSegmenter) for (const { segment } of LyricLineBase.graphemeSegmenter.segment(trimmedWord)) {
|
|
3789
|
+
const charEl = document.createElement("span");
|
|
3790
|
+
charEl.innerText = segment;
|
|
3791
|
+
subElements.push(charEl);
|
|
3792
|
+
wordContainer.appendChild(charEl);
|
|
3793
|
+
}
|
|
3794
|
+
else for (const segment of Array.from(trimmedWord)) {
|
|
3550
3795
|
const charEl = document.createElement("span");
|
|
3551
3796
|
charEl.innerText = segment;
|
|
3552
3797
|
subElements.push(charEl);
|
|
@@ -3728,6 +3973,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3728
3973
|
word.padding = 0;
|
|
3729
3974
|
}
|
|
3730
3975
|
}
|
|
3976
|
+
if (this.balancer && LyricLineBase.wordSegmenter) this.balancer.balanceLineBreaks(this.lyricPlayer._getIsNonDynamic(), this.splittedWords.length > 0, LyricLineBase.wordSegmenter);
|
|
3731
3977
|
if (this.lyricPlayer.supportMaskImage) this.generateWebAnimationBasedMaskImage();
|
|
3732
3978
|
else this.generateCalcBasedMaskImage();
|
|
3733
3979
|
if (this.isEnabled) {
|
|
@@ -3900,7 +4146,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3900
4146
|
const factor = Math.max(0, Math.min(1, (scale - .97) / .03));
|
|
3901
4147
|
const dynamicDarkAlpha = factor * .2 + .2;
|
|
3902
4148
|
const dynamicBrightAlpha = factor * .8 + .2;
|
|
3903
|
-
if (this.renderMode ===
|
|
4149
|
+
if (this.renderMode === 0) {
|
|
3904
4150
|
this.targetBrightAlpha = dynamicDarkAlpha;
|
|
3905
4151
|
this.targetDarkAlpha = dynamicDarkAlpha;
|
|
3906
4152
|
} else {
|
|
@@ -3922,7 +4168,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3922
4168
|
this.element.style.setProperty("--bright-mask-alpha", this.currentBrightAlpha.toFixed(3));
|
|
3923
4169
|
this.element.style.setProperty("--dark-mask-alpha", this.currentDarkAlpha.toFixed(3));
|
|
3924
4170
|
}
|
|
3925
|
-
setTransform(top = this.top, scale = this.scale, opacity = 1, blur = 0, force = false, delay = 0, mode =
|
|
4171
|
+
setTransform(top = this.top, scale = this.scale, opacity = 1, blur = 0, force = false, delay = 0, mode = 0) {
|
|
3926
4172
|
super.setTransform(top, scale, opacity, blur, force, delay);
|
|
3927
4173
|
this.renderMode = mode;
|
|
3928
4174
|
const beforeInSight = this.isInSight;
|
|
@@ -3979,6 +4225,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3979
4225
|
return !(t > pb + h + ov || b < -h - ov);
|
|
3980
4226
|
}
|
|
3981
4227
|
disposeElements() {
|
|
4228
|
+
this.balancer?.reset();
|
|
3982
4229
|
for (const realWord of this.splittedWords) {
|
|
3983
4230
|
for (const a of realWord.elementAnimations) a.cancel();
|
|
3984
4231
|
for (const a of realWord.maskAnimations) a.cancel();
|