@braccato/core 1.16.1 → 1.16.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/engine.js +43 -6
- package/dist/stage.d.ts +15 -0
- package/dist/stage.js +34 -0
- package/package.json +1 -1
package/dist/engine.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
// that never meet. `themeSettings.ts` holds the third thing under that rule.
|
|
19
19
|
import { ANIMATING_CLASS, CREDITS_CLASS, CURRENT_LYRICS_CLASS, FOOTER_CLASS, LINE_CLASS, PAUSED_CLASS, ROMANIZED_LYRICS_CLASS, RTL_CLASS, TRANSLATED_LYRICS_CLASS, USER_SCROLLING_CLASS, WORD_STATE_ACTIVE, WORD_STATE_ATTR, WORD_STATE_PAST, WORD_STATE_UPCOMING, } from "./constants.js";
|
|
20
20
|
import { INSTRUMENTAL_WAVE_PATH_HIGH, INSTRUMENTAL_WAVE_PATH_LOW } from "./instrumental.js";
|
|
21
|
-
import { layoutStage, planStage, queuedStageY, } from "./stage.js";
|
|
21
|
+
import { layoutStage, planStage, queuedStageY, stageTextSpan, } from "./stage.js";
|
|
22
22
|
import { registerThemeSetting } from "./themeSettings.js";
|
|
23
23
|
import { clamp, getRelativeLayoutBounds, positiveModulo, roundedMs, toMs } from "./util.js";
|
|
24
24
|
const NO_LYRICS_ELEMENT_LOG = "No lyrics element found on the page, skipping lyrics injection";
|
|
@@ -1976,26 +1976,63 @@ function stageElements(engine) {
|
|
|
1976
1976
|
}
|
|
1977
1977
|
return { elements, items };
|
|
1978
1978
|
}
|
|
1979
|
-
function originXOf(
|
|
1980
|
-
const align = engine.window.getComputedStyle(element).textAlign;
|
|
1979
|
+
function originXOf(align) {
|
|
1981
1980
|
if (align === "center")
|
|
1982
1981
|
return 0.5;
|
|
1983
1982
|
if (align === "right" || align === "end")
|
|
1984
1983
|
return 1;
|
|
1985
1984
|
return 0;
|
|
1986
1985
|
}
|
|
1986
|
+
const ELEMENT_NODE = 1;
|
|
1987
|
+
const TEXT_NODE = 3;
|
|
1988
|
+
function contentWidthOf(element, style) {
|
|
1989
|
+
return (element.clientWidth - (Number.parseFloat(style.paddingLeft) || 0) - (Number.parseFloat(style.paddingRight) || 0));
|
|
1990
|
+
}
|
|
1991
|
+
// A block that fills its parent is looked inside; inline boxes and shrunk blocks (the romanization pill) count whole.
|
|
1992
|
+
function collectContentRects(engine, range, node, contentWidth, rects) {
|
|
1993
|
+
for (const child of node.childNodes) {
|
|
1994
|
+
if (child.nodeType === TEXT_NODE) {
|
|
1995
|
+
range.selectNodeContents(child);
|
|
1996
|
+
rects.push(...range.getClientRects());
|
|
1997
|
+
continue;
|
|
1998
|
+
}
|
|
1999
|
+
if (child.nodeType !== ELEMENT_NODE)
|
|
2000
|
+
continue;
|
|
2001
|
+
const element = child;
|
|
2002
|
+
const style = engine.window.getComputedStyle(element);
|
|
2003
|
+
if (style.display === "none")
|
|
2004
|
+
continue;
|
|
2005
|
+
if (style.display === "contents") {
|
|
2006
|
+
collectContentRects(engine, range, element, contentWidth, rects);
|
|
2007
|
+
}
|
|
2008
|
+
else if (style.display.startsWith("inline")) {
|
|
2009
|
+
rects.push(...element.getClientRects());
|
|
2010
|
+
}
|
|
2011
|
+
else if (element.offsetWidth < contentWidth - 1) {
|
|
2012
|
+
rects.push(element.getBoundingClientRect());
|
|
2013
|
+
}
|
|
2014
|
+
else {
|
|
2015
|
+
collectContentRects(engine, range, element, contentWidthOf(element, style), rects);
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
return rects;
|
|
2019
|
+
}
|
|
1987
2020
|
export function measureStage(engine) {
|
|
1988
2021
|
engine.stageMetrics.clear();
|
|
1989
2022
|
const container = engine.lyricsContainer;
|
|
1990
2023
|
if (!container)
|
|
1991
2024
|
return;
|
|
1992
2025
|
engine.stageFontSize = Number.parseFloat(engine.window.getComputedStyle(container).fontSize) || 16;
|
|
2026
|
+
const range = engine.document.createRange();
|
|
1993
2027
|
for (const element of stageElements(engine).elements) {
|
|
2028
|
+
const style = engine.window.getComputedStyle(element);
|
|
2029
|
+
const originX = originXOf(style.textAlign);
|
|
2030
|
+
const span = stageTextSpan(element.offsetLeft, element.offsetWidth, element.getBoundingClientRect(), collectContentRects(engine, range, element, contentWidthOf(element, style), []), { left: Number.parseFloat(style.paddingLeft) || 0, right: Number.parseFloat(style.paddingRight) || 0 }, originX);
|
|
1994
2031
|
engine.stageMetrics.set(element, {
|
|
1995
2032
|
height: element.offsetHeight,
|
|
1996
|
-
left:
|
|
1997
|
-
width:
|
|
1998
|
-
originX
|
|
2033
|
+
left: span.left,
|
|
2034
|
+
width: span.width,
|
|
2035
|
+
originX,
|
|
1999
2036
|
});
|
|
2000
2037
|
}
|
|
2001
2038
|
engine.stageKey = "";
|
package/dist/stage.d.ts
CHANGED
|
@@ -35,6 +35,21 @@ export interface StageBox {
|
|
|
35
35
|
width: number;
|
|
36
36
|
height: number;
|
|
37
37
|
}
|
|
38
|
+
export interface StageSpanRect {
|
|
39
|
+
x: number;
|
|
40
|
+
width: number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* A wrapped `fit-content` box fills the width offered, so the plate follows the text instead. The rects
|
|
44
|
+
* miss printed `::before` labels, so the span is widened to what the alignment guarantees.
|
|
45
|
+
*/
|
|
46
|
+
export declare function stageTextSpan(offsetLeft: number, offsetWidth: number, box: StageSpanRect, textRects: Iterable<StageSpanRect>, padding: {
|
|
47
|
+
left: number;
|
|
48
|
+
right: number;
|
|
49
|
+
}, originX: number): {
|
|
50
|
+
left: number;
|
|
51
|
+
width: number;
|
|
52
|
+
};
|
|
38
53
|
/** Where a line waits below the floor, and where it rises from as it enters. */
|
|
39
54
|
export declare function queuedStageY(stageHeight: number, height: number, gap: number): number;
|
|
40
55
|
export declare function layoutStage(roles: readonly StageRole[], items: readonly StageItem[], metrics: readonly StageMetrics[], previousY: readonly (number | null)[], geometry: StageGeometry): {
|
package/dist/stage.js
CHANGED
|
@@ -36,6 +36,40 @@ export function planStage(items, timeS) {
|
|
|
36
36
|
}
|
|
37
37
|
return roles;
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* A wrapped `fit-content` box fills the width offered, so the plate follows the text instead. The rects
|
|
41
|
+
* miss printed `::before` labels, so the span is widened to what the alignment guarantees.
|
|
42
|
+
*/
|
|
43
|
+
export function stageTextSpan(offsetLeft, offsetWidth, box, textRects, padding, originX) {
|
|
44
|
+
const whole = { left: offsetLeft, width: offsetWidth };
|
|
45
|
+
if (box.width <= 0 || offsetWidth <= 0 || originX === 1)
|
|
46
|
+
return whole;
|
|
47
|
+
const scale = box.width / offsetWidth;
|
|
48
|
+
let start = Number.POSITIVE_INFINITY;
|
|
49
|
+
let end = Number.NEGATIVE_INFINITY;
|
|
50
|
+
for (const rect of textRects) {
|
|
51
|
+
if (rect.width <= 0)
|
|
52
|
+
continue;
|
|
53
|
+
start = Math.min(start, (rect.x - box.x) / scale);
|
|
54
|
+
end = Math.max(end, (rect.x + rect.width - box.x) / scale);
|
|
55
|
+
}
|
|
56
|
+
if (start > end)
|
|
57
|
+
return whole;
|
|
58
|
+
const contentLeft = padding.left;
|
|
59
|
+
const contentRight = offsetWidth - padding.right;
|
|
60
|
+
if (originX === 0) {
|
|
61
|
+
start = contentLeft;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
const centre = (contentLeft + contentRight) / 2;
|
|
65
|
+
const half = Math.max(end - centre, centre - start);
|
|
66
|
+
start = centre - half;
|
|
67
|
+
end = centre + half;
|
|
68
|
+
}
|
|
69
|
+
const left = Math.max(0, start - padding.left);
|
|
70
|
+
const right = Math.min(offsetWidth, end + padding.right);
|
|
71
|
+
return { left: offsetLeft + left, width: right - left };
|
|
72
|
+
}
|
|
39
73
|
function unionBox(box, next) {
|
|
40
74
|
if (box === null)
|
|
41
75
|
return next;
|