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 的单行文本在上层保证传入“原文”更稳妥。 // 为保持最小可用,这里仍使用默认 joinTokens(EN=空格 join,TC=grapheme 之间可能会有空格)。 const text = joinTokens(tokens, 0, N); return { lines: [text], wrappedText: text }; } export async function applyOverflowFallback(input: ApplyOverflowFallbackInput): Promise { 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 } }; }