diff --git a/web/app/src/untertitelstil.ts b/web/app/src/untertitelstil.ts index 557e481..7cfb2ea 100644 --- a/web/app/src/untertitelstil.ts +++ b/web/app/src/untertitelstil.ts @@ -53,14 +53,41 @@ export const GROESSE_SCHRITT = 5; /** Das Vielfache der Schriftgrösse, das eine Zeile hoch ist. */ const ZEILENABSTAND = 1.25; -/** Wo die Unterkante des Untertitels sitzt, in Prozent der Videohöhe. +/** Wie weit die Einblendungen vom unteren Bildrand abrücken, in Prozent der + * Videohöhe. * * **Nicht am Bildrand.** Der Browser setzt Einblendungen von sich aus ganz * nach unten. Dort liegt bei uns die Bedienleiste des Spielers — und bei - * einem zweizeiligen Untertitel verschwand die untere Zeile darunter. Zehn - * Prozent Abstand halten sie frei, unabhängig von der Schriftgrösse und - * davon, ob eine oder zwei Zeilen dastehen. */ -const UNTERKANTE = 90; + * einem zweizeiligen Untertitel verschwand die untere Zeile darunter. */ +const ABSTAND_UNTEN = 10; + +/** Ob der Browser den Kasten kennt, in den er die Einblendungen setzt. + * + * **Die Unterscheidung, um die es hier geht.** Der Weg über die Norm — + * `VTTCue.lineAlign = "end"` und `line` in Prozent — ist der richtige, und + * Chrome und Firefox befolgen ihn. WebKit nicht: dort gibt `line` die + * **Ober**kante an, und ein zweizeiliger Untertitel wächst darunter aus dem + * Bild heraus. Gemessen an einem Bildschirmfoto: die zweite Zeile war mitten + * im Buchstaben abgeschnitten. Ebenso bleibt dort `line-height` aus `::cue` + * wirkungslos — zwischen den Zeilen klaffte das Vierfache einer Zeilenhöhe. + * + * Beide Löcher stopft derselbe Griff: der Kasten, den WebKit selbst erfunden + * hat. Ihn gibt es dort, wo die Norm hier versagt. + * + * Erkannt wird er, indem eine Regel damit geschrieben und nachgesehen wird, + * ob sie stehen blieb — ein Browser, der den Selektor nicht versteht, wirft + * sie weg. Keine Abfrage der Browserkennung: die lügt. */ +let kastenBekannt: boolean | undefined; + +function kenntKasten(): boolean { + if (kastenBekannt !== undefined) return kastenBekannt; + const probe = document.createElement('style'); + probe.textContent = 'video::-webkit-media-text-track-container{top:0}'; + document.head.appendChild(probe); + kastenBekannt = (probe.sheet?.cssRules.length ?? 0) > 0; + probe.remove(); + return kastenBekannt; +} export type Untertitelstil = { /** In Prozent der Größe, die der Browser von sich aus wählt. */ @@ -137,6 +164,20 @@ export function stilAnwenden(stil: Untertitelstil): void { color: ${stil.farbe}; background-color: ${stil.hintergrund}; ${rand} +} +/* Derselbe Zeilenabstand noch einmal, fuer WebKit: in ::cue greift er + dort nicht, am Kasten mit den Zeilen schon. Wo beides wirkt, steht + zweimal dasselbe und nichts streitet sich. */ +video::-webkit-media-text-track-display { + line-height: ${ZEILENABSTAND} !important; +} +/* Und so ruecken die Einblendungen vom Rand ab: verschoben wird der + ganze Kasten, nicht die einzelne Einblendung. Damit kann keine davon + unten herausragen, egal wie viele Zeilen sie hat und egal, wie der + Browser die Zeilenangabe auslegt. */ +video::-webkit-media-text-track-container { + transform: translateY(-${ABSTAND_UNTEN}%); + line-height: ${ZEILENABSTAND}; }`; let block = document.getElementById(SCHLUESSEL) as HTMLStyleElement | null; @@ -155,22 +196,19 @@ function zahlImRahmen(wert: unknown, vorgabe: number): number { return Math.max(GROESSE_KLEINST, Math.min(GROESSE_GROESST, gerundet)); } -/** Hebt die Einblendungen einer Spur vom Bildrand ab. +/** Hebt die Einblendungen einer Spur vom Bildrand ab — wo CSS nicht reicht. * - * **Warum über die Cues und nicht über CSS.** `::cue` gestaltet den Text, - * aber nicht seinen Platz: die Position steht in der Datei, und der Browser - * setzt sie ohne Angabe ganz nach unten. Erreichbar ist sie nur hier, am - * fertig geladenen `VTTCue`. + * **Nur noch für Browser ohne den WebKit-Kasten.** Kennt einer ihn, ist das + * Abrücken schon erledigt (siehe `stilAnwenden`), und beides zugleich + * schöbe doppelt. Übrig bleibt im Wesentlichen Firefox — dort gibt es den + * Kasten nicht, dafür hält er sich an die Norm. * - * **`lineAlign: "end"` ist der eigentliche Trick.** Damit gibt `line` die - * Lage der *Unterkante* an. Ohne ihn wäre es die Oberkante — und dann - * stünde ein zweizeiliger Untertitel wieder weiter unten als ein - * einzeiliger, also genau der Fall, der das Problem war. - * - * Kennt ein Browser `lineAlign` nicht, bleibt der Wert einfach liegen; die - * Einblendung sitzt dann etwas höher als nötig, aber nie zu tief. */ + * **`lineAlign: "end"` ist dabei der Trick.** Damit gibt `line` die Lage der + * *Unterkante* an. Ohne ihn wäre es die Oberkante — und dann wüchse ein + * zweizeiliger Untertitel nach unten aus dem Bild. Genau das tut WebKit, + * und genau deshalb wird dieser Weg dort nicht mehr gegangen. */ export function einblendungenAusrichten(spur: TextTrack | null | undefined): void { - if (!spur?.cues) return; + if (!spur?.cues || kenntKasten()) return; for (const roh of Array.from(spur.cues)) { const cue = roh as VTTCue; @@ -180,6 +218,6 @@ export function einblendungenAusrichten(spur: TextTrack | null | undefined): voi cue.snapToLines = false; cue.lineAlign = 'end'; - cue.line = UNTERKANTE; + cue.line = 100 - ABSTAND_UNTEN; } }