feat(text): integrate Google Fonts API with dynamic loading

Added Google Fonts support to text tool:

Font Loader System:
- Created GoogleFontsLoader class with caching and loading states
- Singleton instance with preloading of popular fonts (Roboto, Open Sans, Lato, Montserrat)
- Handles font loading via Google Fonts API with error handling
- Tracks loaded, loading, and error states per font

UI Improvements:
- Updated font selector with optgroups (Web Safe Fonts vs Google Fonts)
- 13 web-safe fonts + 14 popular Google Fonts
- Font preview in dropdown (fontFamily style applied to options)
- Async loading on font selection with error handling

Features:
- 27 total fonts available (13 web-safe + 14 Google Fonts)
- Automatic preloading of 4 most popular fonts on app start
- Font caching to avoid redundant loads
- Fallback to web-safe fonts if Google Fonts fail to load

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-21 16:44:42 +01:00
parent 517f57126a
commit 041db0aaff
2 changed files with 184 additions and 17 deletions

149
lib/google-fonts-loader.ts Normal file
View File

@@ -0,0 +1,149 @@
/**
* 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<string>();
private loadingFonts = new Map<string, Promise<void>>();
private fontStatuses = new Map<string, FontLoadStatus>();
/**
* Load a Google Font dynamically
*/
async loadFont(fontFamily: string): Promise<void> {
// 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<void> {
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<void> {
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']);
}