mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-27 18:22:41 -08:00
Fix renderer overlay loading and modularize renderer
This commit is contained in:
206
src/renderer/subtitle-render.ts
Normal file
206
src/renderer/subtitle-render.ts
Normal file
@@ -0,0 +1,206 @@
|
||||
import type {
|
||||
MergedToken,
|
||||
SecondarySubMode,
|
||||
SubtitleData,
|
||||
SubtitleStyleConfig,
|
||||
} from "../types";
|
||||
import type { RendererContext } from "./context";
|
||||
|
||||
function normalizeSubtitle(text: string, trim = true): string {
|
||||
if (!text) return "";
|
||||
|
||||
let normalized = text.replace(/\\N/g, "\n").replace(/\\n/g, "\n");
|
||||
normalized = normalized.replace(/\{[^}]*\}/g, "");
|
||||
|
||||
return trim ? normalized.trim() : normalized;
|
||||
}
|
||||
|
||||
function renderWithTokens(root: HTMLElement, tokens: MergedToken[]): void {
|
||||
const fragment = document.createDocumentFragment();
|
||||
|
||||
for (const token of tokens) {
|
||||
const surface = token.surface;
|
||||
|
||||
if (surface.includes("\n")) {
|
||||
const parts = surface.split("\n");
|
||||
for (let i = 0; i < parts.length; i += 1) {
|
||||
if (parts[i]) {
|
||||
const span = document.createElement("span");
|
||||
span.className = "word";
|
||||
span.textContent = parts[i];
|
||||
if (token.reading) span.dataset.reading = token.reading;
|
||||
if (token.headword) span.dataset.headword = token.headword;
|
||||
fragment.appendChild(span);
|
||||
}
|
||||
if (i < parts.length - 1) {
|
||||
fragment.appendChild(document.createElement("br"));
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const span = document.createElement("span");
|
||||
span.className = "word";
|
||||
span.textContent = surface;
|
||||
if (token.reading) span.dataset.reading = token.reading;
|
||||
if (token.headword) span.dataset.headword = token.headword;
|
||||
fragment.appendChild(span);
|
||||
}
|
||||
|
||||
root.appendChild(fragment);
|
||||
}
|
||||
|
||||
function renderCharacterLevel(root: HTMLElement, text: string): void {
|
||||
const fragment = document.createDocumentFragment();
|
||||
|
||||
for (const char of text) {
|
||||
if (char === "\n") {
|
||||
fragment.appendChild(document.createElement("br"));
|
||||
continue;
|
||||
}
|
||||
const span = document.createElement("span");
|
||||
span.className = "c";
|
||||
span.textContent = char;
|
||||
fragment.appendChild(span);
|
||||
}
|
||||
|
||||
root.appendChild(fragment);
|
||||
}
|
||||
|
||||
function renderPlainTextPreserveLineBreaks(root: HTMLElement, text: string): void {
|
||||
const lines = text.split("\n");
|
||||
const fragment = document.createDocumentFragment();
|
||||
|
||||
for (let i = 0; i < lines.length; i += 1) {
|
||||
fragment.appendChild(document.createTextNode(lines[i]));
|
||||
if (i < lines.length - 1) {
|
||||
fragment.appendChild(document.createElement("br"));
|
||||
}
|
||||
}
|
||||
|
||||
root.appendChild(fragment);
|
||||
}
|
||||
|
||||
export function createSubtitleRenderer(ctx: RendererContext) {
|
||||
function renderSubtitle(data: SubtitleData | string): void {
|
||||
ctx.dom.subtitleRoot.innerHTML = "";
|
||||
ctx.state.lastHoverSelectionKey = "";
|
||||
ctx.state.lastHoverSelectionNode = null;
|
||||
|
||||
let text: string;
|
||||
let tokens: MergedToken[] | null;
|
||||
|
||||
if (typeof data === "string") {
|
||||
text = data;
|
||||
tokens = null;
|
||||
} else if (data && typeof data === "object") {
|
||||
text = data.text;
|
||||
tokens = data.tokens;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!text) return;
|
||||
|
||||
if (ctx.platform.isInvisibleLayer) {
|
||||
const normalizedInvisible = normalizeSubtitle(text, false);
|
||||
ctx.state.currentInvisibleSubtitleLineCount = Math.max(
|
||||
1,
|
||||
normalizedInvisible.split("\n").length,
|
||||
);
|
||||
renderPlainTextPreserveLineBreaks(ctx.dom.subtitleRoot, normalizedInvisible);
|
||||
return;
|
||||
}
|
||||
|
||||
const normalized = normalizeSubtitle(text);
|
||||
if (tokens && tokens.length > 0) {
|
||||
renderWithTokens(ctx.dom.subtitleRoot, tokens);
|
||||
return;
|
||||
}
|
||||
renderCharacterLevel(ctx.dom.subtitleRoot, normalized);
|
||||
}
|
||||
|
||||
function renderSecondarySub(text: string): void {
|
||||
ctx.dom.secondarySubRoot.innerHTML = "";
|
||||
if (!text) return;
|
||||
|
||||
const normalized = text
|
||||
.replace(/\\N/g, "\n")
|
||||
.replace(/\\n/g, "\n")
|
||||
.replace(/\{[^}]*\}/g, "")
|
||||
.trim();
|
||||
|
||||
if (!normalized) return;
|
||||
|
||||
const lines = normalized.split("\n");
|
||||
for (let i = 0; i < lines.length; i += 1) {
|
||||
if (lines[i]) {
|
||||
ctx.dom.secondarySubRoot.appendChild(document.createTextNode(lines[i]));
|
||||
}
|
||||
if (i < lines.length - 1) {
|
||||
ctx.dom.secondarySubRoot.appendChild(document.createElement("br"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateSecondarySubMode(mode: SecondarySubMode): void {
|
||||
ctx.dom.secondarySubContainer.classList.remove(
|
||||
"secondary-sub-hidden",
|
||||
"secondary-sub-visible",
|
||||
"secondary-sub-hover",
|
||||
);
|
||||
ctx.dom.secondarySubContainer.classList.add(`secondary-sub-${mode}`);
|
||||
}
|
||||
|
||||
function applySubtitleFontSize(fontSize: number): void {
|
||||
const clampedSize = Math.max(10, fontSize);
|
||||
ctx.dom.subtitleRoot.style.fontSize = `${clampedSize}px`;
|
||||
document.documentElement.style.setProperty(
|
||||
"--subtitle-font-size",
|
||||
`${clampedSize}px`,
|
||||
);
|
||||
}
|
||||
|
||||
function applySubtitleStyle(style: SubtitleStyleConfig | null): void {
|
||||
if (!style) return;
|
||||
|
||||
if (style.fontFamily) ctx.dom.subtitleRoot.style.fontFamily = style.fontFamily;
|
||||
if (style.fontSize) ctx.dom.subtitleRoot.style.fontSize = `${style.fontSize}px`;
|
||||
if (style.fontColor) ctx.dom.subtitleRoot.style.color = style.fontColor;
|
||||
if (style.fontWeight) ctx.dom.subtitleRoot.style.fontWeight = style.fontWeight;
|
||||
if (style.fontStyle) ctx.dom.subtitleRoot.style.fontStyle = style.fontStyle;
|
||||
if (style.backgroundColor) {
|
||||
ctx.dom.subtitleContainer.style.background = style.backgroundColor;
|
||||
}
|
||||
|
||||
const secondaryStyle = style.secondary;
|
||||
if (!secondaryStyle) return;
|
||||
|
||||
if (secondaryStyle.fontFamily) {
|
||||
ctx.dom.secondarySubRoot.style.fontFamily = secondaryStyle.fontFamily;
|
||||
}
|
||||
if (secondaryStyle.fontSize) {
|
||||
ctx.dom.secondarySubRoot.style.fontSize = `${secondaryStyle.fontSize}px`;
|
||||
}
|
||||
if (secondaryStyle.fontColor) {
|
||||
ctx.dom.secondarySubRoot.style.color = secondaryStyle.fontColor;
|
||||
}
|
||||
if (secondaryStyle.fontWeight) {
|
||||
ctx.dom.secondarySubRoot.style.fontWeight = secondaryStyle.fontWeight;
|
||||
}
|
||||
if (secondaryStyle.fontStyle) {
|
||||
ctx.dom.secondarySubRoot.style.fontStyle = secondaryStyle.fontStyle;
|
||||
}
|
||||
if (secondaryStyle.backgroundColor) {
|
||||
ctx.dom.secondarySubContainer.style.background = secondaryStyle.backgroundColor;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
applySubtitleFontSize,
|
||||
applySubtitleStyle,
|
||||
renderSecondarySub,
|
||||
renderSubtitle,
|
||||
updateSecondarySubMode,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user