feat: complete TASK-WP4-03 text templates
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
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,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user