mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-27 18:22:41 -08:00
fix(renderer): calibrate macOS invisible overlay spacing
This commit is contained in:
@@ -163,7 +163,7 @@ test('applyTypography applies full mpv letter spacing scale on macOS', () => {
|
|||||||
assert.equal(ctx.dom.subtitleRoot.style.getPropertyValue('letter-spacing'), '3px');
|
assert.equal(ctx.dom.subtitleRoot.style.getPropertyValue('letter-spacing'), '3px');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('applyTypography uses tighter macOS line-height for invisible multiline alignment', () => {
|
test('applyTypography uses macOS multiline-tuned line-height for invisible overlay', () => {
|
||||||
const ctx = createContext({
|
const ctx = createContext({
|
||||||
isMacOSPlatform: true,
|
isMacOSPlatform: true,
|
||||||
lineCount: 3,
|
lineCount: 3,
|
||||||
@@ -178,7 +178,7 @@ test('applyTypography uses tighter macOS line-height for invisible multiline ali
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.equal(ctx.dom.subtitleRoot.style.getPropertyValue('line-height'), '0.96');
|
assert.equal(ctx.dom.subtitleRoot.style.getPropertyValue('line-height'), '1.62');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('applyVerticalPosition uses subtitle position margin and baseline compensation', () => {
|
test('applyVerticalPosition uses subtitle position margin and baseline compensation', () => {
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import type { MpvSubtitleRenderMetrics } from '../../types';
|
import type { MpvSubtitleRenderMetrics } from '../../types';
|
||||||
import type { RendererContext } from '../context';
|
import type { RendererContext } from '../context';
|
||||||
|
|
||||||
|
const INVISIBLE_MACOS_VERTICAL_NUDGE_PX = 5;
|
||||||
|
const INVISIBLE_MACOS_LINE_HEIGHT_SINGLE = '1.08';
|
||||||
|
const INVISIBLE_MACOS_LINE_HEIGHT_MULTI = '1.35';
|
||||||
|
const INVISIBLE_MACOS_LINE_HEIGHT_MULTI_DENSE = '1.48';
|
||||||
|
|
||||||
let fontMetricsCanvas: HTMLCanvasElement | null = null;
|
let fontMetricsCanvas: HTMLCanvasElement | null = null;
|
||||||
|
|
||||||
export function applyContainerBaseLayout(
|
export function applyContainerBaseLayout(
|
||||||
@@ -112,6 +117,13 @@ function resolveFontFamily(rawFont: string): string {
|
|||||||
: `"${rawFont}", sans-serif`;
|
: `"${rawFont}", sans-serif`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveInvisibleLineHeight(lineCount: number, isMacOSPlatform: boolean): string {
|
||||||
|
if (!isMacOSPlatform) return 'normal';
|
||||||
|
if (lineCount >= 3) return INVISIBLE_MACOS_LINE_HEIGHT_MULTI_DENSE;
|
||||||
|
if (lineCount >= 2) return INVISIBLE_MACOS_LINE_HEIGHT_MULTI;
|
||||||
|
return INVISIBLE_MACOS_LINE_HEIGHT_SINGLE;
|
||||||
|
}
|
||||||
|
|
||||||
function resolveLetterSpacing(
|
function resolveLetterSpacing(
|
||||||
spacing: number,
|
spacing: number,
|
||||||
pxPerScaledPixel: number,
|
pxPerScaledPixel: number,
|
||||||
@@ -123,10 +135,6 @@ function resolveLetterSpacing(
|
|||||||
return '0px';
|
return '0px';
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveInvisibleLineHeight(isMacOSPlatform: boolean): string {
|
|
||||||
return isMacOSPlatform ? '0.96' : '1';
|
|
||||||
}
|
|
||||||
|
|
||||||
function measureFontDescentPx(ctx: RendererContext): number | null {
|
function measureFontDescentPx(ctx: RendererContext): number | null {
|
||||||
if (typeof document === 'undefined') return null;
|
if (typeof document === 'undefined') return null;
|
||||||
const computedStyle = getComputedStyle(ctx.dom.subtitleRoot);
|
const computedStyle = getComputedStyle(ctx.dom.subtitleRoot);
|
||||||
@@ -148,6 +156,42 @@ function measureFontDescentPx(ctx: RendererContext): number | null {
|
|||||||
return metrics.actualBoundingBoxDescent;
|
return metrics.actualBoundingBoxDescent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyComputedLineHeightCompensation(
|
||||||
|
ctx: RendererContext,
|
||||||
|
effectiveFontSize: number,
|
||||||
|
): void {
|
||||||
|
const computedLineHeight = parseFloat(getComputedStyle(ctx.dom.subtitleRoot).lineHeight);
|
||||||
|
if (!Number.isFinite(computedLineHeight) || computedLineHeight <= effectiveFontSize) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const halfLeading = (computedLineHeight - effectiveFontSize) / 2;
|
||||||
|
if (halfLeading <= 0.5) return;
|
||||||
|
|
||||||
|
const currentBottom = parseFloat(ctx.dom.subtitleContainer.style.bottom);
|
||||||
|
if (Number.isFinite(currentBottom)) {
|
||||||
|
ctx.dom.subtitleContainer.style.bottom = `${Math.max(0, currentBottom - halfLeading)}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentTop = parseFloat(ctx.dom.subtitleContainer.style.top);
|
||||||
|
if (Number.isFinite(currentTop)) {
|
||||||
|
ctx.dom.subtitleContainer.style.top = `${Math.max(0, currentTop - halfLeading)}px`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyMacOSAdjustments(ctx: RendererContext): void {
|
||||||
|
const isMacOSPlatform = ctx.platform.isMacOSPlatform;
|
||||||
|
if (!isMacOSPlatform) return;
|
||||||
|
|
||||||
|
const currentBottom = parseFloat(ctx.dom.subtitleContainer.style.bottom);
|
||||||
|
if (!Number.isFinite(currentBottom)) return;
|
||||||
|
|
||||||
|
ctx.dom.subtitleContainer.style.bottom = `${Math.max(
|
||||||
|
0,
|
||||||
|
currentBottom + INVISIBLE_MACOS_VERTICAL_NUDGE_PX,
|
||||||
|
)}px`;
|
||||||
|
}
|
||||||
|
|
||||||
export function applyTypography(
|
export function applyTypography(
|
||||||
ctx: RendererContext,
|
ctx: RendererContext,
|
||||||
params: {
|
params: {
|
||||||
@@ -157,11 +201,14 @@ export function applyTypography(
|
|||||||
},
|
},
|
||||||
): void {
|
): void {
|
||||||
const isMacOSPlatform = ctx.platform.isMacOSPlatform;
|
const isMacOSPlatform = ctx.platform.isMacOSPlatform;
|
||||||
|
const lineCount = Math.max(1, ctx.state.currentInvisibleSubtitleLineCount);
|
||||||
|
const invisibleLineHeight = resolveInvisibleLineHeight(lineCount, isMacOSPlatform);
|
||||||
|
|
||||||
|
ctx.dom.subtitleRoot.style.setProperty('--invisible-sub-line-height', invisibleLineHeight);
|
||||||
ctx.dom.subtitleRoot.style.setProperty(
|
ctx.dom.subtitleRoot.style.setProperty(
|
||||||
'line-height',
|
'line-height',
|
||||||
resolveInvisibleLineHeight(isMacOSPlatform),
|
invisibleLineHeight,
|
||||||
'important',
|
isMacOSPlatform ? 'important' : '',
|
||||||
);
|
);
|
||||||
ctx.dom.subtitleRoot.style.fontFamily = resolveFontFamily(params.metrics.subFont);
|
ctx.dom.subtitleRoot.style.fontFamily = resolveFontFamily(params.metrics.subFont);
|
||||||
ctx.dom.subtitleRoot.style.setProperty(
|
ctx.dom.subtitleRoot.style.setProperty(
|
||||||
@@ -175,4 +222,7 @@ export function applyTypography(
|
|||||||
ctx.dom.subtitleRoot.style.transform = '';
|
ctx.dom.subtitleRoot.style.transform = '';
|
||||||
ctx.dom.subtitleRoot.style.transformOrigin = '';
|
ctx.dom.subtitleRoot.style.transformOrigin = '';
|
||||||
ctx.state.invisibleMeasuredDescentPx = measureFontDescentPx(ctx);
|
ctx.state.invisibleMeasuredDescentPx = measureFontDescentPx(ctx);
|
||||||
|
|
||||||
|
applyComputedLineHeightCompensation(ctx, params.effectiveFontSize);
|
||||||
|
applyMacOSAdjustments(ctx);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,7 +76,9 @@ export function createMpvSubtitleLayoutController(
|
|||||||
options.applyInvisibleSubtitleOffsetPosition();
|
options.applyInvisibleSubtitleOffsetPosition();
|
||||||
options.updateInvisiblePositionEditHud();
|
options.updateInvisiblePositionEditHud();
|
||||||
|
|
||||||
console.log('[invisible-overlay] Applied mpv subtitle render metrics from', source);
|
if (source !== 'subtitle-change') {
|
||||||
|
console.log('[invisible-overlay] Applied mpv subtitle render metrics from', source);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -240,6 +240,9 @@ function runGuardedAsync(action: string, fn: () => Promise<void> | void): void {
|
|||||||
|
|
||||||
async function init(): Promise<void> {
|
async function init(): Promise<void> {
|
||||||
document.body.classList.add(`layer-${ctx.platform.overlayLayer}`);
|
document.body.classList.add(`layer-${ctx.platform.overlayLayer}`);
|
||||||
|
if (ctx.platform.isMacOSPlatform) {
|
||||||
|
document.body.classList.add('platform-macos');
|
||||||
|
}
|
||||||
|
|
||||||
window.electronAPI.onSubtitle((data: SubtitleData) => {
|
window.electronAPI.onSubtitle((data: SubtitleData) => {
|
||||||
runGuarded('subtitle:update', () => {
|
runGuarded('subtitle:update', () => {
|
||||||
@@ -252,7 +255,7 @@ async function init(): Promise<void> {
|
|||||||
if (ctx.platform.isInvisibleLayer && ctx.state.mpvSubtitleRenderMetrics) {
|
if (ctx.platform.isInvisibleLayer && ctx.state.mpvSubtitleRenderMetrics) {
|
||||||
positioning.applyInvisibleSubtitleLayoutFromMpvMetrics(
|
positioning.applyInvisibleSubtitleLayoutFromMpvMetrics(
|
||||||
ctx.state.mpvSubtitleRenderMetrics,
|
ctx.state.mpvSubtitleRenderMetrics,
|
||||||
'subtitle',
|
'subtitle-change',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
measurementReporter.schedule();
|
measurementReporter.schedule();
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ body {
|
|||||||
#subtitleRoot {
|
#subtitleRoot {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 35px;
|
font-size: 35px;
|
||||||
line-height: 1.5;
|
line-height: var(--visible-sub-line-height, 1.32);
|
||||||
color: #cad3f5;
|
color: #cad3f5;
|
||||||
--subtitle-known-word-color: #a6da95;
|
--subtitle-known-word-color: #a6da95;
|
||||||
--subtitle-n-plus-one-color: #c6a0f6;
|
--subtitle-n-plus-one-color: #c6a0f6;
|
||||||
@@ -426,7 +426,12 @@ body.settings-modal-open #subtitleContainer {
|
|||||||
#subtitleRoot br {
|
#subtitleRoot br {
|
||||||
display: block;
|
display: block;
|
||||||
content: '';
|
content: '';
|
||||||
margin-bottom: 0.3em;
|
margin-bottom: var(--visible-sub-line-gap, 0.08em);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.platform-macos.layer-visible #subtitleRoot {
|
||||||
|
--visible-sub-line-height: 1.64;
|
||||||
|
--visible-sub-line-gap: 0.54em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#subtitleRoot.has-selection .word:hover,
|
#subtitleRoot.has-selection .word:hover,
|
||||||
@@ -452,7 +457,7 @@ body.layer-invisible #subtitleRoot .c {
|
|||||||
-webkit-text-fill-color: transparent !important;
|
-webkit-text-fill-color: transparent !important;
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
caret-color: transparent !important;
|
caret-color: transparent !important;
|
||||||
line-height: normal !important;
|
line-height: var(--invisible-sub-line-height, normal) !important;
|
||||||
font-kerning: auto;
|
font-kerning: auto;
|
||||||
letter-spacing: normal;
|
letter-spacing: normal;
|
||||||
font-variant-ligatures: normal;
|
font-variant-ligatures: normal;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
normalizeSubtitle,
|
normalizeSubtitle,
|
||||||
shouldRenderTokenizedSubtitle,
|
shouldRenderTokenizedSubtitle,
|
||||||
} from './subtitle-render.js';
|
} from './subtitle-render.js';
|
||||||
|
import { resolveInvisibleLineHeight } from './positioning/invisible-layout-helpers.js';
|
||||||
|
|
||||||
function createToken(overrides: Partial<MergedToken>): MergedToken {
|
function createToken(overrides: Partial<MergedToken>): MergedToken {
|
||||||
return {
|
return {
|
||||||
@@ -328,4 +329,32 @@ test('JLPT CSS rules use underline-only styling in renderer stylesheet', () => {
|
|||||||
);
|
);
|
||||||
assert.match(block, /color:\s*var\(/);
|
assert.match(block, /color:\s*var\(/);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const invisibleBlock = extractClassBlock(
|
||||||
|
cssText,
|
||||||
|
'body.layer-invisible #subtitleRoot',
|
||||||
|
);
|
||||||
|
assert.match(
|
||||||
|
invisibleBlock,
|
||||||
|
/line-height:\s*var\(--invisible-sub-line-height,\s*normal\)\s*!important;/,
|
||||||
|
);
|
||||||
|
|
||||||
|
const visibleMacBlock = extractClassBlock(
|
||||||
|
cssText,
|
||||||
|
'body.platform-macos.layer-visible #subtitleRoot',
|
||||||
|
);
|
||||||
|
assert.match(visibleMacBlock, /--visible-sub-line-height:\s*1\.64;/);
|
||||||
|
assert.match(visibleMacBlock, /--visible-sub-line-gap:\s*0\.54em;/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('invisible overlay uses looser line height on macOS for multi-line subtitles', () => {
|
||||||
|
assert.equal(resolveInvisibleLineHeight(1, true), '1.08');
|
||||||
|
assert.equal(resolveInvisibleLineHeight(2, true), '1.5');
|
||||||
|
assert.equal(resolveInvisibleLineHeight(3, true), '1.62');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('invisible overlay keeps default line height on non-macOS platforms', () => {
|
||||||
|
assert.equal(resolveInvisibleLineHeight(1, false), 'normal');
|
||||||
|
assert.equal(resolveInvisibleLineHeight(2, false), 'normal');
|
||||||
|
assert.equal(resolveInvisibleLineHeight(4, false), 'normal');
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user