import type { TextSettings } from '@/types/text'; /** * Render text on canvas with specified settings */ export function renderText( ctx: CanvasRenderingContext2D, x: number, y: number, settings: TextSettings ): void { if (!settings.text) return; ctx.save(); // Build font string const font = `${settings.fontStyle} ${settings.fontWeight} ${settings.fontSize}px ${settings.fontFamily}`; ctx.font = font; ctx.fillStyle = settings.color; ctx.textAlign = settings.align; ctx.textBaseline = settings.baseline; // Handle multi-line text const lines = settings.text.split('\n'); const lineHeightPx = settings.fontSize * settings.lineHeight; lines.forEach((line, index) => { const lineY = y + index * lineHeightPx; // Apply letter spacing if needed if (settings.letterSpacing !== 0) { renderTextWithLetterSpacing(ctx, line, x, lineY, settings.letterSpacing); } else { ctx.fillText(line, x, lineY); } }); ctx.restore(); } /** * Render text with custom letter spacing */ function renderTextWithLetterSpacing( ctx: CanvasRenderingContext2D, text: string, x: number, y: number, letterSpacing: number ): void { const chars = text.split(''); let currentX = x; // Adjust starting position based on text align if (ctx.textAlign === 'center') { const totalWidth = measureTextWidth(ctx, text, letterSpacing); currentX = x - totalWidth / 2; } else if (ctx.textAlign === 'right') { const totalWidth = measureTextWidth(ctx, text, letterSpacing); currentX = x - totalWidth; } // Draw each character with spacing chars.forEach((char) => { ctx.fillText(char, currentX, y); currentX += ctx.measureText(char).width + letterSpacing; }); } /** * Measure text width including letter spacing */ function measureTextWidth( ctx: CanvasRenderingContext2D, text: string, letterSpacing: number ): number { const chars = text.split(''); let width = 0; chars.forEach((char) => { width += ctx.measureText(char).width + letterSpacing; }); return width - letterSpacing; // Remove last letter spacing } /** * Common web-safe fonts */ export const WEB_SAFE_FONTS = [ 'Arial', 'Arial Black', 'Comic Sans MS', 'Courier New', 'Georgia', 'Impact', 'Lucida Console', 'Lucida Sans Unicode', 'Palatino Linotype', 'Tahoma', 'Times New Roman', 'Trebuchet MS', 'Verdana', ] as const; /** * Popular Google Fonts (will be loaded dynamically) */ export const GOOGLE_FONTS = [ 'Roboto', 'Open Sans', 'Lato', 'Montserrat', 'Oswald', 'Source Sans Pro', 'Raleway', 'PT Sans', 'Merriweather', 'Playfair Display', 'Ubuntu', 'Noto Sans', 'Poppins', 'Inter', ] as const; /** * Load a Google Font dynamically */ export function loadGoogleFont(fontFamily: string): Promise { return new Promise((resolve, reject) => { // Check if font is already loaded 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); }); }