diff --git a/components/editor/tool-options.tsx b/components/editor/tool-options.tsx index 32118c4..c244b0b 100644 --- a/components/editor/tool-options.tsx +++ b/components/editor/tool-options.tsx @@ -4,6 +4,9 @@ import { useToolStore } from '@/store'; import { useShapeStore } from '@/store/shape-store'; import { useSelectionStore } from '@/store/selection-store'; import { useTextStore } from '@/store/text-store'; +import { WEB_SAFE_FONTS, GOOGLE_FONTS } from '@/lib/text-utils'; +import { googleFontsLoader } from '@/lib/google-fonts-loader'; +import { useCallback } from 'react'; export function ToolOptions() { const { activeTool, settings, setSize, setOpacity, setHardness, setColor, setFlow } = useToolStore(); @@ -21,6 +24,22 @@ export function ToolOptions() { setColor: setTextColor, } = useTextStore(); + // Handle font change with Google Fonts loading + const handleFontChange = useCallback( + async (fontFamily: string) => { + // Check if it's a Google Font + if (GOOGLE_FONTS.includes(fontFamily as any)) { + try { + await googleFontsLoader.loadFont(fontFamily); + } catch (error) { + console.error('Failed to load Google Font:', error); + } + } + setFontFamily(fontFamily); + }, + [setFontFamily] + ); + // Drawing tools: brush, pencil, eraser const isDrawingTool = ['brush', 'eraser', 'pencil'].includes(activeTool); const showHardness = ['brush'].includes(activeTool); @@ -233,25 +252,24 @@ export function ToolOptions() { diff --git a/lib/google-fonts-loader.ts b/lib/google-fonts-loader.ts new file mode 100644 index 0000000..d26aa89 --- /dev/null +++ b/lib/google-fonts-loader.ts @@ -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(); + 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']); +}