import type { ContextProfile, FontSpec, MeasureResult, MeasureWidthImpl, TextWrapContext } from './types'; import { LruCache } from './cache'; import { assertFontSpecComplete, buildFontSpecKey } from './fontSpecKey'; // 模块级常驻缓存(两级) const textWidthCache = new LruCache>(2000); const loggedMeasureFailures = new Set(); function buildTextKey(args: { contextProfile: ContextProfile; fontSpecKey: string; text: string }): string { // key 拼接规则必须确定性:|| 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 | null; measureWidthImpl?: MeasureWidthImpl; /** WIDGET 场景是否启用测量;默认 false(未启用即 WIDTH_UNKNOWN) */ widgetEnableMeasure?: boolean; }): Promise { 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' } }; } }