/** * Google Fonts Loader * Handles dynamic loading and caching of Google Fonts */ export interface FontLoadStatus { loaded: boolean; loading: boolean; error: string | null; } class GoogleFontsLoader { private loadedFonts = new Set(); private loadingFonts = new Map>(); private fontStatuses = new Map(); /** * Load a Google Font dynamically */ async loadFont(fontFamily: string): Promise { // Already loaded if (this.loadedFonts.has(fontFamily)) { return Promise.resolve(); } // Currently loading if (this.loadingFonts.has(fontFamily)) { return this.loadingFonts.get(fontFamily)!; } // Start loading this.fontStatuses.set(fontFamily, { loaded: false, loading: true, error: null, }); const loadPromise = this.loadFontImpl(fontFamily); this.loadingFonts.set(fontFamily, loadPromise); try { await loadPromise; this.loadedFonts.add(fontFamily); this.fontStatuses.set(fontFamily, { loaded: true, loading: false, error: null, }); } catch (error) { this.fontStatuses.set(fontFamily, { loaded: false, loading: false, error: error instanceof Error ? error.message : 'Unknown error', }); throw error; } finally { this.loadingFonts.delete(fontFamily); } } /** * Implementation of font loading */ private async loadFontImpl(fontFamily: string): Promise { return new Promise((resolve, reject) => { // Check if font is already loaded in browser if (document.fonts.check(`16px "${fontFamily}"`)) { resolve(); return; } // Create link element to load font const link = document.createElement('link'); link.rel = 'stylesheet'; link.href = `https://fonts.googleapis.com/css2?family=${fontFamily.replace( / /g, '+' )}:wght@100;300;400;500;700;900&display=swap`; link.onload = () => { // Wait for font to be ready document.fonts .load(`16px "${fontFamily}"`) .then(() => { resolve(); }) .catch(reject); }; link.onerror = () => { reject(new Error(`Failed to load font: ${fontFamily}`)); }; document.head.appendChild(link); }); } /** * Preload multiple fonts */ async preloadFonts(fontFamilies: string[]): Promise { await Promise.allSettled(fontFamilies.map((font) => this.loadFont(font))); } /** * Get font loading status */ getStatus(fontFamily: string): FontLoadStatus { return ( this.fontStatuses.get(fontFamily) || { loaded: false, loading: false, error: null, } ); } /** * Check if font is loaded */ isLoaded(fontFamily: string): boolean { return this.loadedFonts.has(fontFamily); } /** * Check if font is currently loading */ isLoading(fontFamily: string): boolean { return this.loadingFonts.has(fontFamily); } /** * Clear cache (for testing) */ clearCache(): void { this.loadedFonts.clear(); this.loadingFonts.clear(); this.fontStatuses.clear(); } } // Singleton instance export const googleFontsLoader = new GoogleFontsLoader(); // Preload popular fonts on app start if (typeof window !== 'undefined') { // Preload most popular fonts googleFontsLoader.preloadFonts(['Roboto', 'Open Sans', 'Lato', 'Montserrat']); }