Files
memefast/resources/js/modules/editor/fonts.jsx
2025-07-07 21:17:00 +08:00

298 lines
8.8 KiB
JavaScript

// fonts.jsx - Centralized Font Management System
// =============================================================================
// FONT IMPORTS - All Fontsource imports in one place
// =============================================================================
// Montserrat - Modern sans-serif, great for UI text
import '@fontsource/montserrat/400-italic.css'; // Italic
import '@fontsource/montserrat/400.css'; // Normal
import '@fontsource/montserrat/700-italic.css'; // Bold Italic
import '@fontsource/montserrat/700.css'; // Bold
// Bungee - Display font for headers and watermarks
import '@fontsource/bungee/400.css';
// Optional: Add more fonts here as needed
// import '@fontsource/inter/400.css';
// import '@fontsource/roboto/400.css';
// NOTE: Make sure to install these packages:
// npm install @fontsource/montserrat @fontsource/bungee
// =============================================================================
// FONT CONFIGURATION - Single source of truth for all font data
// =============================================================================
const FONTS = {
montserrat: {
name: 'Montserrat',
family: 'Montserrat',
category: 'sans-serif',
weights: [400, 700],
styles: ['normal', 'italic'],
description: 'Modern geometric sans-serif',
preview: 'The quick brown fox jumps over the lazy dog',
},
bungee: {
name: 'Bungee',
family: 'Bungee',
category: 'display',
weights: [400],
styles: ['normal'],
description: 'Decorative display font',
preview: 'memefa.st',
},
arial: {
name: 'Arial',
family: 'Arial',
category: 'system',
weights: [400, 700],
styles: ['normal', 'italic'],
description: 'System fallback font',
preview: 'System default font',
},
};
// =============================================================================
// FONT UTILITIES - Helper functions for font operations
// =============================================================================
/**
* Get available fonts as array for UI components
* @returns {Array} Array of font objects for dropdowns/selectors
*/
const getAvailableFonts = () => {
return Object.values(FONTS).map((font) => ({
name: font.name,
value: font.family,
category: font.category,
description: font.description,
preview: font.preview,
}));
};
/**
* Get font family name by key
* @param {string} fontKey - Key from FONTS object
* @returns {string} Font family name
*/
const getFontFamily = (fontKey) => {
return FONTS[fontKey]?.family || FONTS.montserrat.family;
};
/**
* Get CSS font-style value based on element properties
* @param {Object} element - Text element with font properties
* @returns {string} CSS font-style value
*/
const getFontStyle = (element) => {
const isBold = element.fontWeight === 'bold' || element.fontWeight === 700;
const isItalic = element.fontStyle === 'italic';
if (isBold && isItalic) {
return 'bold italic';
} else if (isBold) {
return 'bold';
} else if (isItalic) {
return 'italic';
} else {
return 'normal';
}
};
/**
* Get numeric font weight
* @param {string|number} fontWeight - Font weight value
* @returns {number} Numeric font weight
*/
const getFontWeight = (fontWeight) => {
if (fontWeight === 'bold') return 700;
if (typeof fontWeight === 'number') return fontWeight;
return 400; // default normal
};
/**
* Validate if font supports specific weight/style combination
* @param {string} fontFamily - Font family name
* @param {string|number} fontWeight - Font weight
* @param {string} fontStyle - Font style
* @returns {boolean} Whether combination is supported
*/
const isFontCombinationSupported = (fontFamily, fontWeight, fontStyle) => {
const font = Object.values(FONTS).find((f) => f.family === fontFamily);
if (!font) return false;
const numericWeight = getFontWeight(fontWeight);
const hasWeight = font.weights.includes(numericWeight);
const hasStyle = font.styles.includes(fontStyle || 'normal');
return hasWeight && hasStyle;
};
/**
* Get fallback font if current combination isn't supported
* @param {string} fontFamily - Desired font family
* @param {string|number} fontWeight - Desired font weight
* @param {string} fontStyle - Desired font style
* @returns {Object} Safe font configuration
*/
const getSafeFontConfig = (fontFamily, fontWeight, fontStyle) => {
if (isFontCombinationSupported(fontFamily, fontWeight, fontStyle)) {
return {
fontFamily,
fontWeight: getFontWeight(fontWeight),
fontStyle: fontStyle || 'normal',
};
}
// Fallback to Montserrat normal
return {
fontFamily: FONTS.montserrat.family,
fontWeight: 400,
fontStyle: 'normal',
};
};
// =============================================================================
// FONT LOADING UTILITIES
// =============================================================================
/**
* Ensure specific fonts are loaded before use
* @param {Array} fontSpecs - Array of font specifications
* @returns {Promise} Promise that resolves when fonts are loaded
*/
const loadFonts = async (fontSpecs = []) => {
if (!('fonts' in document)) {
console.warn('Font Loading API not supported');
return;
}
const fontPromises = fontSpecs.map(({ fontFamily, fontWeight, fontStyle, fontSize = 16 }) => {
const weight = getFontWeight(fontWeight);
const style = fontStyle || 'normal';
const fontSpec = `${weight} ${style} ${fontSize}px "${fontFamily}"`;
return document.fonts.load(fontSpec).catch((err) => {
console.warn(`Failed to load font: ${fontSpec}`, err);
return null;
});
});
await Promise.all(fontPromises);
// Allow fonts to settle
await new Promise((resolve) => setTimeout(resolve, 100));
};
/**
* Load fonts used in timeline elements
* @param {Array} timelineElements - Array of timeline elements
* @returns {Promise} Promise that resolves when all fonts are loaded
*/
const loadTimelineFonts = async (timelineElements = []) => {
const fontSpecs = new Set();
// Collect text element fonts
timelineElements
.filter((el) => el.type === 'text')
.forEach((text) => {
const fontFamily = text.fontFamily || FONTS.montserrat.family;
const fontWeight = text.fontWeight;
const fontStyle = text.fontStyle;
fontSpecs.add(
JSON.stringify({
fontFamily,
fontWeight: getFontWeight(fontWeight),
fontStyle: fontStyle || 'normal',
fontSize: Math.max(text.fontSize || 16, 16), // Ensure minimum size for loading
}),
);
});
// Add watermark font (Bungee)
fontSpecs.add(
JSON.stringify({
fontFamily: FONTS.bungee.family,
fontWeight: 400,
fontStyle: 'normal',
fontSize: 20,
}),
);
// Convert back to objects and load
const uniqueFontSpecs = Array.from(fontSpecs).map((spec) => JSON.parse(spec));
await loadFonts(uniqueFontSpecs);
};
// =============================================================================
// PRESET CONFIGURATIONS
// =============================================================================
/**
* Default text element configuration
*/
const DEFAULT_TEXT_CONFIG = {
fontFamily: FONTS.montserrat.family,
fontSize: 24,
fontWeight: 700, // Bold by default
fontStyle: 'normal',
fill: '#ffffff',
stroke: '#000000',
strokeWidth: 2,
};
/**
* Watermark configuration
*/
const WATERMARK_CONFIG = {
fontFamily: FONTS.bungee.family,
fontSize: 20,
fontWeight: 400,
fontStyle: 'normal',
fill: 'white',
stroke: 'black',
strokeWidth: 2,
opacity: 0.5,
};
// =============================================================================
// EXPORTS
// =============================================================================
// =============================================================================
// EXPORTS
// =============================================================================
// Named exports for individual functions
export {
DEFAULT_TEXT_CONFIG,
FONTS,
WATERMARK_CONFIG,
getAvailableFonts,
getFontFamily,
getFontStyle,
getFontWeight,
getSafeFontConfig,
isFontCombinationSupported,
loadFonts,
loadTimelineFonts,
};
// Default export with all functions grouped
export default {
FONTS,
getAvailableFonts,
getFontFamily,
getFontStyle,
getFontWeight,
isFontCombinationSupported,
getSafeFontConfig,
loadFonts,
loadTimelineFonts,
DEFAULT_TEXT_CONFIG,
WATERMARK_CONFIG,
};