55 lines
2.0 KiB
TypeScript
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;
|
|
}
|