export interface WebFontFamilyDefinition { readonly family: string; readonly weights: readonly number[]; readonly italic: boolean | readonly number[]; } export interface LoadWebFontFamiliesOptions { readonly document: Document; readonly linkId: string; readonly families: readonly WebFontFamilyDefinition[]; } function sortedUniqueWeights(weights: readonly number[]): number[] { return [...new Set(weights)].sort((left, right) => left - right); } function familyQuery(definition: WebFontFamilyDefinition): string { const weights = sortedUniqueWeights(definition.weights); if (weights.length === 0) { throw new Error(`Web font "${definition.family}" must declare at least one weight.`); } const italicWeights = definition.italic === true ? weights : definition.italic === false ? [] : sortedUniqueWeights(definition.italic).filter((weight) => weights.includes(weight)); const family = encodeURIComponent(definition.family); if (italicWeights.length === 0) { return `family=${family}:wght@${weights.map(String).join(';')}`; } const axes = [ ...weights.map((weight) => `0,${String(weight)}`), ...italicWeights.map((weight) => `1,${String(weight)}`), ]; return `family=${family}:ital,wght@${axes.join(';')}`; } export function buildWebFontStylesheetUrl(families: readonly WebFontFamilyDefinition[]): string { if (families.length === 0) { throw new Error('At least one web font family is required.'); } return `https://fonts.googleapis.com/css2?${families.map(familyQuery).join('&')}&display=swap`; } export function loadWebFontFamilies({ document, linkId, families, }: LoadWebFontFamiliesOptions): () => void { const href = buildWebFontStylesheetUrl(families); const existing = document.getElementById(linkId); if (existing instanceof HTMLLinkElement && existing.getAttribute('href') === href) { return () => existing.remove(); } existing?.remove(); const link = document.createElement('link'); link.id = linkId; link.rel = 'stylesheet'; link.href = href; document.head.appendChild(link); return () => link.remove(); }