76 lines
2.4 KiB
TypeScript
76 lines
2.4 KiB
TypeScript
export type ArchivedFontStatus = "idle" | "loading" | "ready" | "unavailable";
|
|
|
|
interface LoadableFontFace {
|
|
load: () => Promise<unknown>;
|
|
}
|
|
|
|
interface FontSetPort {
|
|
add: (face: unknown) => unknown;
|
|
check: (font: string) => boolean;
|
|
ready: Promise<unknown>;
|
|
}
|
|
|
|
export interface ArchivedFontReference {
|
|
fontId: string;
|
|
url: string;
|
|
}
|
|
|
|
export function fontFamilyName(fontId: string) {
|
|
if (!/^[A-Za-z0-9][A-Za-z0-9_.:-]{0,119}$/.test(fontId)) throw new Error("font_id_invalid");
|
|
return `Dada_${fontId.replaceAll(/[^A-Za-z0-9_]/g, "_")}`;
|
|
}
|
|
|
|
export class ArchivedFontLoader {
|
|
private readonly createFace: (family: string, source: string) => LoadableFontFace;
|
|
private readonly fontSet: FontSetPort;
|
|
private readonly pending = new Map<string, Promise<ArchivedFontStatus>>();
|
|
private readonly statuses = new Map<string, ArchivedFontStatus>();
|
|
|
|
constructor(input: { createFace: (family: string, source: string) => LoadableFontFace; fontSet: FontSetPort }) {
|
|
this.createFace = input.createFace;
|
|
this.fontSet = input.fontSet;
|
|
}
|
|
|
|
status(fontId: string): ArchivedFontStatus {
|
|
return this.statuses.get(fontId) ?? "idle";
|
|
}
|
|
|
|
ensure(reference: ArchivedFontReference): Promise<ArchivedFontStatus> {
|
|
const existing = this.pending.get(reference.fontId);
|
|
if (existing) return existing;
|
|
const operation = this.load(reference);
|
|
this.pending.set(reference.fontId, operation);
|
|
return operation;
|
|
}
|
|
|
|
retry(reference: ArchivedFontReference) {
|
|
this.pending.delete(reference.fontId);
|
|
this.statuses.delete(reference.fontId);
|
|
return this.ensure(reference);
|
|
}
|
|
|
|
private async load(reference: ArchivedFontReference): Promise<ArchivedFontStatus> {
|
|
const family = fontFamilyName(reference.fontId);
|
|
this.statuses.set(reference.fontId, "loading");
|
|
try {
|
|
const face = this.createFace(family, `url("${reference.url}")`);
|
|
const loaded = await face.load();
|
|
this.fontSet.add(loaded);
|
|
await this.fontSet.ready;
|
|
if (!this.fontSet.check(`16px "${family}"`)) throw new Error("font_not_ready");
|
|
this.statuses.set(reference.fontId, "ready");
|
|
return "ready";
|
|
} catch {
|
|
this.statuses.set(reference.fontId, "unavailable");
|
|
return "unavailable";
|
|
}
|
|
}
|
|
}
|
|
|
|
export function createBrowserArchivedFontLoader() {
|
|
return new ArchivedFontLoader({
|
|
createFace: (family, source) => new FontFace(family, source),
|
|
fontSet: document.fonts as unknown as FontSetPort,
|
|
});
|
|
}
|