Files
mindfulness/client/src/features/textWrap/overflow/fallback.ts
2026-02-10 11:39:33 +08:00

87 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { Token } from '../core/types';
import { joinTokens } from '../core/joinTokens';
import { applyEllipsisToLastLine } from './ellipsis';
import type { ApplyOverflowFallbackInput, ApplyOverflowFallbackResult, PartialLayoutInput, PartialLayoutLine } from './types';
function nTokens(tokens: Token[]): number {
return Array.isArray(tokens) ? tokens.length : 0;
}
function coversToEnd(partial: PartialLayoutInput | null | undefined, n: number): boolean {
if (!partial || !Array.isArray(partial.lines) || partial.lines.length === 0) return false;
const last = partial.lines[partial.lines.length - 1];
return (last?.end ?? -1) === n;
}
function buildLinesFromPartial(args: {
tokens: Token[];
partial?: PartialLayoutInput | null;
maxLines: number;
lang: 'TC' | 'EN';
}): Array<{ start: number; end: number; text: string }> {
const N = nTokens(args.tokens);
const maxLines = Math.max(1, args.maxLines | 0);
const rawSeparators = args.lang === 'TC' ? Array.from({ length: Math.max(0, N) }, () => '') : undefined;
if (args.partial && Array.isArray(args.partial.lines) && args.partial.lines.length > 0) {
const out: Array<{ start: number; end: number; text: string }> = [];
for (const line of args.partial.lines.slice(0, maxLines)) {
const start = Math.max(0, line.start | 0);
const end = Math.min(N, Math.max(start, line.end | 0));
const text = line.text ?? joinTokens(args.tokens, start, end, rawSeparators);
out.push({ start, end, text });
}
return out;
}
// 若没有 partial把全文当单行 best-effort
return [{ start: 0, end: N, text: joinTokens(args.tokens, 0, N, rawSeparators) }];
}
function toWrapped(lines: string[]): { lines: string[]; wrappedText: string } {
const outLines = lines.filter((s) => s !== undefined) as string[];
return { lines: outLines, wrappedText: outLines.join('\n') };
}
function toSingleLine(tokens: Token[]): { lines: string[]; wrappedText: string } {
const N = nTokens(tokens);
// 注意:这里不知道语言口径,因此 SYSTEM_DEFAULT 的单行文本在上层保证传入“原文”更稳妥。
// 为保持最小可用,这里仍使用默认 joinTokensEN=空格 joinTC=grapheme 之间可能会有空格)。
const text = joinTokens(tokens, 0, N);
return { lines: [text], wrappedText: text };
}
export async function applyOverflowFallback(input: ApplyOverflowFallbackInput): Promise<ApplyOverflowFallbackResult> {
const tokens = input.tokens ?? [];
const N = nTokens(tokens);
const maxLines = Math.max(1, input.maxLines | 0);
const isOverflow = !coversToEnd(input.partialLayout, N);
// 若其实没有 overflow直接返回NONE
if (!isOverflow) {
const base = buildLinesFromPartial({ tokens, partial: input.partialLayout, maxLines, lang: input.lang });
const { lines, wrappedText } = toWrapped(base.map((l) => l.text));
return { lines, wrappedText, meta: { fallback_type: 'NONE', overflow_type: 'NONE', reason: input.reason } };
}
if (input.overflowMode === 'SYSTEM_DEFAULT') {
const { lines, wrappedText } = toSingleLine(tokens);
return { lines, wrappedText, meta: { fallback_type: 'SYSTEM_DEFAULT', overflow_type: 'NONE', reason: input.reason } };
}
if (input.overflowMode === 'CLIP') {
const base = buildLinesFromPartial({ tokens, partial: input.partialLayout, maxLines, lang: input.lang });
const { lines, wrappedText } = toWrapped(base.slice(0, maxLines).map((l) => l.text));
return { lines, wrappedText, meta: { fallback_type: 'NONE', overflow_type: 'CLIP', reason: input.reason } };
}
// ELLIPSIS
const base = buildLinesFromPartial({ tokens, partial: input.partialLayout, maxLines, lang: input.lang });
const { lines } = await applyEllipsisToLastLine({ input, baseLines: base.slice(0, maxLines) });
const wrapped = toWrapped(lines);
return { ...wrapped, meta: { fallback_type: 'NONE', overflow_type: 'ELLIPSIS', reason: input.reason } };
}