import type { CSSProperties } from "vue"; /** * Resolve image paths for use in layouts. * * This function handles multiple types of image paths: * - Already resolved paths (from Vite plugin transformation) - returned as-is * - Full URLs (http/https) - returned as-is * - Data URLs (data:) - returned as-is * - Blob URLs (blob:) - returned as-is * - Root-relative paths (/path) - returned as-is * - Colors (#hex, rgb) - returned as-is * - Relative paths (./path, path) - converted to root-relative * * When the vite-plugins.ts is active, relative paths in frontmatter are * automatically transformed into resolved import URLs before reaching * this function, so they'll be handled by the "already resolved" case. */ export function getImageUrl(path?: string): string { if (!path) return ""; // If it's a color, don't process it if (path.startsWith("#") || path.startsWith("rgb")) return path; // If it's already a resolved URL (from Vite import), use as-is // This includes: // - Data URLs: data:image/... // - Blob URLs: blob:... // - Full URLs: http:// or https:// // - Vite asset URLs: /assets/image.hash.png or /@fs/... if ( path.startsWith("data:") || path.startsWith("blob:") || path.match(/^https?:\/\//) || path.startsWith("/@fs/") || path.match(/^\/assets\//) ) { return path; } // If it's a root-relative path (starts with /), use it as is if (path.startsWith("/")) return path; // For relative paths, try to use the base URL if available // This handles the case where images are in the public folder const baseUrl = typeof import.meta !== 'undefined' ? (import.meta.env?.BASE_URL || '/') : '/'; // Remove ./ prefix if present const cleanPath = path.startsWith("./") ? path.slice(2) : path; // Return with base URL return `${baseUrl}${cleanPath}`; } /** * Generate background style object for image or color * @param imagePath - Path to the image or color string * @param scale - Optional scale factor for the image (e.g., '50%', '0.5') * @param align - Optional alignment for the image (e.g., 'top', 'bottom', 'left', 'right') */ export function getImageStyle( imagePath?: string, scale?: string, align?: string ): CSSProperties { if (!imagePath) return {}; // If it's a color, set as background color if (imagePath.startsWith("#") || imagePath.startsWith("rgb")) { return { backgroundColor: imagePath, }; } // Parse alignment let backgroundPosition = "center"; if (align) { // Handle single-direction alignments if (["top", "bottom", "left", "right"].includes(align)) { backgroundPosition = align; } // Handle combined alignments (e.g., 'top left', 'bottom right') else if (align.includes(" ")) { backgroundPosition = align; } } // Parse scale and convert to background-size let backgroundSize = "contain"; // Default to 'contain' to prevent cropping if (scale) { // If scale is a percentage string if (scale.endsWith("%")) { // For 100%, use 'contain' to show the full image without cropping if (scale === "100%") { backgroundSize = "contain"; } else { backgroundSize = scale; } } // If scale is a decimal number else if (!isNaN(Number(scale))) { const scaleNum = Number(scale); // For scale = 1.0 (100%), use 'contain' to show the full image if (scaleNum === 1.0) { backgroundSize = "contain"; } else { backgroundSize = `${scaleNum * 100}%`; } } // If scale is 'contain' or 'cover' else if (["contain", "cover", "auto"].includes(scale)) { backgroundSize = scale; } } // Return combined style return { backgroundImage: `url("${getImageUrl(imagePath)}")`, backgroundPosition, backgroundSize, backgroundRepeat: "no-repeat", }; } /** * Generate style for a foreground image element (not background) * @param scale - Optional scale factor for the image (e.g., '50%', '0.5') * @param align - Optional alignment for the image container (e.g., 'center', 'flex-start', 'flex-end') */ export function getForegroundImageStyle( scale?: string, align?: string ): CSSProperties { const style: CSSProperties = { maxWidth: "100%", maxHeight: "100%", objectFit: "contain", }; // Apply scaling if specified if (scale) { // If scale is a percentage string if (scale.endsWith("%")) { style.width = scale; } // If scale is a decimal number else if (!isNaN(Number(scale))) { const scaleNum = Number(scale); style.width = `${scaleNum * 100}%`; } } return style; } /** * Generate container style for image alignment * @param align - Alignment direction ('center', 'top', 'bottom', 'left', 'right', etc) */ export function getImageContainerStyle(align?: string): CSSProperties { const style: CSSProperties = { display: "flex", justifyContent: "center", alignItems: "center", height: "100%", width: "100%", overflow: "hidden", }; if (align) { switch (align) { case "top": style.alignItems = "flex-start"; break; case "bottom": style.alignItems = "flex-end"; break; case "left": style.justifyContent = "flex-start"; break; case "right": style.justifyContent = "flex-end"; break; case "top-left": style.alignItems = "flex-start"; style.justifyContent = "flex-start"; break; case "top-right": style.alignItems = "flex-start"; style.justifyContent = "flex-end"; break; case "bottom-left": style.alignItems = "flex-end"; style.justifyContent = "flex-start"; break; case "bottom-right": style.alignItems = "flex-end"; style.justifyContent = "flex-end"; break; } } return style; }