更新换行算法和APP-PUSH
This commit is contained in:
110
client/src/features/textWrap/measure/measureWidthCached.ts
Normal file
110
client/src/features/textWrap/measure/measureWidthCached.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import type { ContextProfile, FontSpec, MeasureResult, MeasureWidthImpl, TextWrapContext } from './types';
|
||||
import { LruCache } from './cache';
|
||||
import { assertFontSpecComplete, buildFontSpecKey } from './fontSpecKey';
|
||||
|
||||
// 模块级常驻缓存(两级)
|
||||
const textWidthCache = new LruCache<Promise<number>>(2000);
|
||||
const loggedMeasureFailures = new Set<string>();
|
||||
|
||||
function buildTextKey(args: { contextProfile: ContextProfile; fontSpecKey: string; text: string }): string {
|
||||
// key 拼接规则必须确定性:<contextProfile>|<fontSpecKey>|<text>
|
||||
return `${args.contextProfile}|${args.fontSpecKey}|${args.text}`;
|
||||
}
|
||||
|
||||
function isValidWidth(v: unknown): v is number {
|
||||
return typeof v === 'number' && Number.isFinite(v) && v >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 测量文本宽度(带缓存)。
|
||||
*
|
||||
* 约束:
|
||||
* - 若启用测量(提供 measureWidthImpl),fontSpec 必须完整;缺字段直接报错(简体中文)
|
||||
* - 若不启用测量(measureWidthImpl 缺失或 context=WIDGET 且明确不启用),进入 approx mode:width=null
|
||||
*/
|
||||
export async function measureWidthCached(args: {
|
||||
text: string;
|
||||
context: TextWrapContext;
|
||||
contextProfile: ContextProfile;
|
||||
fontSpec?: Partial<FontSpec> | null;
|
||||
measureWidthImpl?: MeasureWidthImpl;
|
||||
/** WIDGET 场景是否启用测量;默认 false(未启用即 WIDTH_UNKNOWN) */
|
||||
widgetEnableMeasure?: boolean;
|
||||
}): Promise<MeasureResult> {
|
||||
const text = String(args.text ?? '');
|
||||
|
||||
const enabled =
|
||||
typeof args.measureWidthImpl === 'function' && (args.context === 'APP' || args.widgetEnableMeasure === true);
|
||||
|
||||
if (!enabled) {
|
||||
return { width: null, meta: { isApprox: true, reason: 'WIDTH_UNKNOWN' } };
|
||||
}
|
||||
|
||||
// 启用测量时:fontSpec 缺字段必须报错
|
||||
const fontSpec = args.fontSpec;
|
||||
assertFontSpecComplete(fontSpec);
|
||||
const fontSpecKey = buildFontSpecKey(fontSpec);
|
||||
const key = buildTextKey({ contextProfile: args.contextProfile, fontSpecKey, text });
|
||||
|
||||
const cached = textWidthCache.get(key);
|
||||
if (cached) {
|
||||
try {
|
||||
const w = await cached;
|
||||
return isValidWidth(w) ? { width: w, meta: { isApprox: false } } : { width: null, meta: { isApprox: true, reason: 'MEASURE_FAILED' } };
|
||||
} catch {
|
||||
if (__DEV__ && !loggedMeasureFailures.has(key)) {
|
||||
loggedMeasureFailures.add(key);
|
||||
console.log('[TextWrap][Measure] measureWidthCached: 命中缓存但 Promise 失败(MEASURE_FAILED)', {
|
||||
context: args.context,
|
||||
contextProfile: args.contextProfile,
|
||||
fontSpec: args.fontSpec,
|
||||
textPreview: text.slice(0, 80),
|
||||
textLength: text.length,
|
||||
});
|
||||
}
|
||||
return { width: null, meta: { isApprox: true, reason: 'MEASURE_FAILED' } };
|
||||
}
|
||||
}
|
||||
|
||||
const promise = (async () => {
|
||||
try {
|
||||
const w = await args.measureWidthImpl!({
|
||||
text,
|
||||
context: args.context,
|
||||
contextProfile: args.contextProfile,
|
||||
fontSpec,
|
||||
});
|
||||
if (!isValidWidth(w)) {
|
||||
throw new Error('测量结果非法(必须为有限且非负的 number)。');
|
||||
}
|
||||
return w;
|
||||
} catch (error) {
|
||||
if (__DEV__ && !loggedMeasureFailures.has(key)) {
|
||||
loggedMeasureFailures.add(key);
|
||||
console.log('[TextWrap][Measure] measureWidthImpl 抛错(MEASURE_FAILED)', {
|
||||
context: args.context,
|
||||
contextProfile: args.contextProfile,
|
||||
fontSpec,
|
||||
textPreview: text.slice(0, 80),
|
||||
textLength: text.length,
|
||||
errorName: (error as any)?.name,
|
||||
errorMessage: String((error as any)?.message ?? error),
|
||||
errorStack: (error as any)?.stack,
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
})();
|
||||
|
||||
textWidthCache.set(key, promise);
|
||||
|
||||
try {
|
||||
const w = await promise;
|
||||
return { width: w, meta: { isApprox: false } };
|
||||
} catch {
|
||||
// 若失败,删除缓存,避免缓存住失败结果
|
||||
textWidthCache.delete(key);
|
||||
return { width: null, meta: { isApprox: true, reason: 'MEASURE_FAILED' } };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user