Files
xh-medical-sign-web/packages/document-fonts/index.ts
T

55 lines
2.0 KiB
TypeScript

/** 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<string, Promise<string>>();
export function loadDocumentFont(
font: DocumentFont | null,
apiBase: string,
): Promise<string> {
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;
}