/** * Normalize fonts from Google Fonts and Fontshare to unified model * * Transforms provider-specific font data into a common interface * for consistent handling across the application. */ import type { FontCategory, FontStyleUrls, FontSubset, FontshareFont, GoogleFontItem, UnifiedFont, UnifiedFontVariant, } from '../../model/types'; /** * Map Google Fonts category to unified FontCategory */ function mapGoogleCategory(category: string): FontCategory { const normalized = category.toLowerCase(); if (normalized.includes('sans-serif')) { return 'sans-serif'; } if (normalized.includes('serif')) { return 'serif'; } if (normalized.includes('display')) { return 'display'; } if (normalized.includes('handwriting') || normalized.includes('cursive')) { return 'handwriting'; } if (normalized.includes('monospace')) { return 'monospace'; } // Default fallback return 'sans-serif'; } /** * Map Fontshare category to unified FontCategory */ function mapFontshareCategory(category: string): FontCategory { const normalized = category.toLowerCase(); if (normalized === 'sans' || normalized === 'sans-serif') { return 'sans-serif'; } if (normalized === 'serif') { return 'serif'; } if (normalized === 'display') { return 'display'; } if (normalized === 'script') { return 'handwriting'; } if (normalized === 'mono' || normalized === 'monospace') { return 'monospace'; } // Default fallback return 'sans-serif'; } /** * Map Google subset to unified FontSubset */ function mapGoogleSubset(subset: string): FontSubset | null { const validSubsets: FontSubset[] = [ 'latin', 'latin-ext', 'cyrillic', 'greek', 'arabic', 'devanagari', ]; return validSubsets.includes(subset as FontSubset) ? (subset as FontSubset) : null; } /** * Map Fontshare script to unified FontSubset */ function mapFontshareScript(script: string): FontSubset | null { const normalized = script.toLowerCase(); const mapping: Record = { latin: 'latin', 'latin-ext': 'latin-ext', cyrillic: 'cyrillic', greek: 'greek', arabic: 'arabic', devanagari: 'devanagari', }; return mapping[normalized] ?? null; } /** * Normalize Google Font to unified model * * @param apiFont - Font item from Google Fonts API * @returns Unified font model * * @example * ```ts * const roboto = normalizeGoogleFont({ * family: 'Roboto', * category: 'sans-serif', * variants: ['regular', '700'], * subsets: ['latin', 'latin-ext'], * files: { regular: '...', '700': '...' } * }); * * console.log(roboto.id); // 'Roboto' * console.log(roboto.provider); // 'google' * ``` */ export function normalizeGoogleFont(apiFont: GoogleFontItem): UnifiedFont { const category = mapGoogleCategory(apiFont.category); const subsets = apiFont.subsets .map(mapGoogleSubset) .filter((subset): subset is FontSubset => subset !== null); // Map variant files to style URLs const styles: FontStyleUrls = {}; for (const [variant, url] of Object.entries(apiFont.files)) { const urlString = url as string; // Type assertion for Record if (variant === 'regular' || variant === '400') { styles.regular = urlString; } else if (variant === 'italic' || variant === '400italic') { styles.italic = urlString; } else if (variant === 'bold' || variant === '700') { styles.bold = urlString; } else if (variant === 'bolditalic' || variant === '700italic') { styles.boldItalic = urlString; } } return { id: apiFont.family, name: apiFont.family, provider: 'google', category, subsets, variants: apiFont.variants, styles, metadata: { cachedAt: Date.now(), version: apiFont.version, lastModified: apiFont.lastModified, }, features: { isVariable: false, // Google Fonts doesn't expose variable font info tags: [], }, }; } /** * Normalize Fontshare font to unified model * * @param apiFont - Font item from Fontshare API * @returns Unified font model * * @example * ```ts * const satoshi = normalizeFontshareFont({ * id: 'uuid', * name: 'Satoshi', * slug: 'satoshi', * category: 'Sans', * script: 'latin', * styles: [ ... ] * }); * * console.log(satoshi.id); // 'satoshi' * console.log(satoshi.provider); // 'fontshare' * ``` */ export function normalizeFontshareFont(apiFont: FontshareFont): UnifiedFont { const category = mapFontshareCategory(apiFont.category); const subset = mapFontshareScript(apiFont.script); const subsets = subset ? [subset] : []; // Extract variant names from styles const variants = apiFont.styles.map(style => { const weightLabel = style.weight.label; const isItalic = style.is_italic; return (isItalic ? `${weightLabel}italic` : weightLabel) as UnifiedFontVariant; }); // Map styles to URLs const styles: FontStyleUrls = {}; for (const style of apiFont.styles) { if (style.is_variable) { // Variable font - store as primary variant styles.regular = style.file; break; } const weight = style.weight.number; const isItalic = style.is_italic; if (weight === 400 && !isItalic) { styles.regular = style.file; } else if (weight === 400 && isItalic) { styles.italic = style.file; } else if (weight >= 700 && !isItalic) { styles.bold = style.file; } else if (weight >= 700 && isItalic) { styles.boldItalic = style.file; } } // Extract variable font axes const axes = apiFont.axes.map(axis => ({ name: axis.name, property: axis.property, default: axis.range_default, min: axis.range_left, max: axis.range_right, })); // Extract tags const tags = apiFont.font_tags.map(tag => tag.name); return { id: apiFont.slug, name: apiFont.name, provider: 'fontshare', category, subsets, variants, styles, metadata: { cachedAt: Date.now(), version: apiFont.version, lastModified: apiFont.inserted_at, popularity: apiFont.views, }, features: { isVariable: apiFont.axes.length > 0, axes: axes.length > 0 ? axes : undefined, tags: tags.length > 0 ? tags : undefined, }, }; } /** * Normalize multiple Google Fonts to unified model * * @param apiFonts - Array of Google Font items * @returns Array of unified fonts */ export function normalizeGoogleFonts( apiFonts: GoogleFontItem[], ): UnifiedFont[] { return apiFonts.map(normalizeGoogleFont); } /** * Normalize multiple Fontshare fonts to unified model * * @param apiFonts - Array of Fontshare font items * @returns Array of unified fonts */ export function normalizeFontshareFonts( apiFonts: FontshareFont[], ): UnifiedFont[] { return apiFonts.map(normalizeFontshareFont); } // Re-export UnifiedFont for backward compatibility export type { UnifiedFont } from '../../model/types/normalize';