更新换行算法和APP-PUSH
This commit is contained in:
108
client/src/features/textWrap/overflow/ellipsis.ts
Normal file
108
client/src/features/textWrap/overflow/ellipsis.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import type { Token } from '../core/types';
|
||||
import { joinTokens } from '../core/joinTokens';
|
||||
import { measureWidthCached } from '../measure/measureWidthCached';
|
||||
|
||||
import type { ApplyOverflowFallbackInput } from './types';
|
||||
|
||||
const DEFAULT_TC_PUNCTS = [',', '。', '!', '?', ';', ':', '、'];
|
||||
|
||||
export function cleanLineBeforeEllipsis(line: string, lang: 'TC' | 'EN', tcPunctuations?: string[]): string {
|
||||
const s = String(line ?? '');
|
||||
const trimmed = s.replace(/\s+$/g, '');
|
||||
if (trimmed === '') return '';
|
||||
|
||||
if (lang === 'TC') {
|
||||
const puncts = new Set(tcPunctuations && tcPunctuations.length > 0 ? tcPunctuations : DEFAULT_TC_PUNCTS);
|
||||
const last = trimmed.slice(-1);
|
||||
if (puncts.has(last)) return trimmed.slice(0, -1);
|
||||
}
|
||||
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
async function measureIfPossible(args: {
|
||||
text: string;
|
||||
input: ApplyOverflowFallbackInput;
|
||||
}): Promise<number | null> {
|
||||
const { input, text } = args;
|
||||
if (!input.measure) return null;
|
||||
|
||||
const enabled =
|
||||
typeof input.measure.measureWidthImpl === 'function' &&
|
||||
(input.measure.context === 'APP' || input.measure.widgetEnableMeasure === true);
|
||||
|
||||
if (!enabled) return null;
|
||||
|
||||
const res = await measureWidthCached({
|
||||
text,
|
||||
context: input.measure.context,
|
||||
contextProfile: input.measure.contextProfile,
|
||||
fontSpec: input.measure.fontSpec,
|
||||
measureWidthImpl: input.measure.measureWidthImpl,
|
||||
widgetEnableMeasure: input.measure.widgetEnableMeasure,
|
||||
});
|
||||
|
||||
return res.width;
|
||||
}
|
||||
|
||||
/**
|
||||
* 给最后一行加省略号,并在可测量时保证不超宽:
|
||||
* - EN 回退单位:整词(token)
|
||||
* - TC 回退单位:grapheme(token)
|
||||
*/
|
||||
export async function applyEllipsisToLastLine(args: {
|
||||
input: ApplyOverflowFallbackInput;
|
||||
baseLines: Array<{ start: number; end: number; text: string }>;
|
||||
}): Promise<{ lines: string[] }> {
|
||||
const { input } = args;
|
||||
const maxLines = Math.max(1, input.maxLines | 0);
|
||||
const lines = args.baseLines.slice(0, maxLines);
|
||||
if (lines.length === 0) return { lines: [] };
|
||||
|
||||
const lastIdx = lines.length - 1;
|
||||
const last = lines[lastIdx]!;
|
||||
|
||||
// 先按规则清理,再拼接 ellipsisToken
|
||||
const cleaned = cleanLineBeforeEllipsis(last.text, input.lang, input.tcPunctuations);
|
||||
let candidateText = `${cleaned}${input.ellipsisToken}`;
|
||||
|
||||
// 若无测量能力:直接输出(确定性)
|
||||
const measured = await measureIfPossible({ text: candidateText, input });
|
||||
if (measured === null) {
|
||||
const out = lines.map((l, idx) => (idx === lastIdx ? candidateText : l.text));
|
||||
return { lines: out };
|
||||
}
|
||||
|
||||
// 有测量能力:若超宽则按 token 回退
|
||||
if (measured <= input.availableWidth) {
|
||||
const out = lines.map((l, idx) => (idx === lastIdx ? candidateText : l.text));
|
||||
return { lines: out };
|
||||
}
|
||||
|
||||
// 回退:逐步减少最后一行 token 数再加省略号
|
||||
let end = last.end;
|
||||
const start = last.start;
|
||||
let foundFit = false;
|
||||
while (end > start) {
|
||||
end -= 1;
|
||||
const base = joinTokens(input.tokens, start, end);
|
||||
const cleaned2 = cleanLineBeforeEllipsis(base, input.lang, input.tcPunctuations);
|
||||
const nextCandidate = `${cleaned2}${input.ellipsisToken}`;
|
||||
const w = await measureIfPossible({ text: nextCandidate, input });
|
||||
if (w !== null && w <= input.availableWidth) {
|
||||
candidateText = nextCandidate;
|
||||
foundFit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 若连“仅省略号”都无法满足宽度(极窄场景),也必须给出确定性输出:直接输出 ellipsisToken
|
||||
// 注意:这可能仍然超宽,但已是最小可表达形式;上层可通过 meta 做治理。
|
||||
if (!foundFit) {
|
||||
candidateText = input.ellipsisToken;
|
||||
}
|
||||
|
||||
const out = lines.map((l, idx) => (idx === lastIdx ? candidateText : l.text));
|
||||
return { lines: out };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user