/** * 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, FontProvider, FontSubset, } from '$entities/Font'; import type { FontshareFont } from '$entities/Font'; import type { GoogleFontItem } from './googleFonts'; /** * Font variant types (standardized) */ export type UnifiedFontVariant = string; /** * Font style URLs */ export interface FontStyleUrls { /** Regular weight URL */ regular?: string; /** Italic URL */ italic?: string; /** Bold weight URL */ bold?: string; /** Bold italic URL */ boldItalic?: string; } /** * Font metadata */ export interface FontMetadata { /** Timestamp when font was cached */ cachedAt: number; /** Font version from provider */ version?: string; /** Last modified date from provider */ lastModified?: string; /** Popularity rank (if available from provider) */ popularity?: number; } /** * Font features (variable fonts, axes, tags) */ export interface FontFeatures { /** Whether this is a variable font */ isVariable?: boolean; /** Variable font axes (for Fontshare) */ axes?: Array<{ name: string; property: string; default: number; min: number; max: number; }>; /** Usage tags (for Fontshare) */ tags?: string[]; } /** * Unified font model * * Combines Google Fonts and Fontshare data into a common interface * for consistent font handling across the application. */ export interface UnifiedFont { /** Unique identifier (Google: family name, Fontshare: slug) */ id: string; /** Font display name */ name: string; /** Font provider (google | fontshare) */ provider: FontProvider; /** Font category classification */ category: FontCategory; /** Supported character subsets */ subsets: FontSubset[]; /** Available font variants (weights, styles) */ variants: UnifiedFontVariant[]; /** URL mapping for font file downloads */ styles: FontStyleUrls; /** Additional metadata */ metadata: FontMetadata; /** Advanced font features */ features: FontFeatures; } /** * 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)) { if (variant === 'regular' || variant === '400') { styles.regular = url; } else if (variant === 'italic' || variant === '400italic') { styles.italic = url; } else if (variant === 'bold' || variant === '700') { styles.bold = url; } else if (variant === 'bolditalic' || variant === '700italic') { styles.boldItalic = url; } } 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; }); // 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); }