/** Immutable font descriptor shared by clinical and patient document renderers. */ export interface DocumentFont { id: string; name: string; regularSha256: string; boldSha256: string | null; } const loaded = new Map>(); export function loadDocumentFont( font: DocumentFont | null, apiBase: string, ): Promise { if (!font) return Promise.resolve(""); const key = `${apiBase}:${font.id}:${font.regularSha256}:${font.boldSha256 ?? ""}`; const existing = loaded.get(key); if (existing) return existing; const family = `Medisign-${font.regularSha256}-${font.boldSha256 ?? "regular"}`; const load = async () => { const descriptors = [ { sha: font.regularSha256, weight: "400" }, { sha: font.boldSha256 ?? font.regularSha256, weight: "700" }, ]; const faces = await Promise.all( descriptors.map(async ({ sha, weight }) => { const url = `${apiBase.replace(/\/$/, "")}/v1/document-fonts/${encodeURIComponent(font.id)}/${sha}.ttf`; const response = await fetch(url, { signal: AbortSignal.timeout(30000), }); if (!response.ok) throw new Error("文书字体加载失败,请重试或联系管理员。"); const bytes = await response.arrayBuffer(); // The URL names an immutable digest; verify before registering the face. if (globalThis.crypto?.subtle) { const digest = await crypto.subtle.digest("SHA-256", bytes); const actual = Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0"), ).join(""); if (actual !== sha) throw new Error("文书字体校验失败。"); } return new FontFace(family, bytes, { weight }).load(); }), ); for (const face of faces) document.fonts.add(face); return `"${family}"`; }; const promise = load().catch((error: unknown) => { loaded.delete(key); throw error; }); loaded.set(key, promise); return promise; }