UNPKG

pixel-forge

Version:

A comprehensive generator for social media previews, favicons, and visual assets across all platforms

419 lines (418 loc) 17.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.FaviconGenerator = void 0; const path_1 = __importDefault(require("path")); const fs_1 = require("fs"); const image_processor_1 = require("../../core/image-processor"); class FaviconGenerator { constructor(sourceImage, config) { this.config = config; this.sourceImage = sourceImage; } /** * Generate all favicon formats and sizes */ async generate(options = {}) { const { includeICO = true, includePNG = true, includeSVG = true, includeApple = true, includeAndroid = true, includeWindows = true, includeSafari = true } = options; // Generate standard PNG favicons if (includePNG) { await this.generatePNGFavicons(); } // Generate ICO format if (includeICO) { await this.generateICOFavicon(); } // Generate SVG favicon if (includeSVG) { await this.generateSVGFavicon(); } // Generate Apple touch icons if (includeApple) { await this.generateAppleIcons(); } // Generate Android/PWA icons if (includeAndroid) { await this.generateAndroidIcons(); } // Generate Windows tiles if (includeWindows) { await this.generateWindowsTiles(); } // Generate Safari pinned tab if (includeSafari) { await this.generateSafariIcon(); } } /** * Generate standard PNG favicons in multiple sizes */ async generatePNGFavicons() { const promises = []; for (const size of image_processor_1.ImageSizes.favicon) { const promise = (async () => { const processor = new image_processor_1.ImageProcessor(this.sourceImage); const resizedFile = await processor.resize(size, size, { fit: 'contain', background: 'transparent', zoom: 1.1 // Add 10% zoom for better visibility }); const finalProcessor = new image_processor_1.ImageProcessor(resizedFile); const outputPath = path_1.default.join(this.config.output.path, `favicon-${size}x${size}.png`); await finalProcessor.save(outputPath); await processor.cleanup(); await finalProcessor.cleanup(); })(); promises.push(promise); } // Also generate favicon.png (32x32 default) const defaultPromise = (async () => { const processor = new image_processor_1.ImageProcessor(this.sourceImage); const resizedFile = await processor.resize(32, 32, { fit: 'contain', background: 'transparent', zoom: 1.1 }); const finalProcessor = new image_processor_1.ImageProcessor(resizedFile); const outputPath = path_1.default.join(this.config.output.path, 'favicon.png'); await finalProcessor.save(outputPath); await processor.cleanup(); await finalProcessor.cleanup(); })(); promises.push(defaultPromise); await Promise.all(promises); } /** * Generate multi-size ICO file using ImageMagick's native ICO support */ async generateICOFavicon() { const processor = new image_processor_1.ImageProcessor(this.sourceImage); const outputPath = path_1.default.join(this.config.output.path, 'favicon.ico'); // ImageMagick can create proper ICO files with multiple sizes try { const resizedFile = await processor.resize(32, 32, { fit: 'contain', background: 'transparent', zoom: 1.1 }); const finalProcessor = new image_processor_1.ImageProcessor(resizedFile); await finalProcessor.save(outputPath, { format: 'ico' }); await processor.cleanup(); await finalProcessor.cleanup(); } catch (error) { // Fallback to PNG with ICO extension for compatibility console.warn('ICO generation failed, falling back to PNG format'); const resizedFile = await processor.resize(32, 32, { fit: 'contain', background: 'transparent', zoom: 1.1 }); const finalProcessor = new image_processor_1.ImageProcessor(resizedFile); await finalProcessor.save(outputPath.replace('.ico', '.png')); await fs_1.promises.rename(outputPath.replace('.ico', '.png'), outputPath); await processor.cleanup(); await finalProcessor.cleanup(); } } /** * Generate SVG favicon for modern browsers */ async generateSVGFavicon() { const outputPath = path_1.default.join(this.config.output.path, 'favicon.svg'); if (this.sourceImage.endsWith('.svg')) { // Copy existing SVG await fs_1.promises.copyFile(this.sourceImage, outputPath); } else { // Convert to SVG using ImageMagick (better than Sharp's approach) const processor = new image_processor_1.ImageProcessor(this.sourceImage); try { const resizedFile = await processor.resize(64, 64, { fit: 'contain', background: 'transparent', zoom: 1.1 }); const finalProcessor = new image_processor_1.ImageProcessor(resizedFile); await finalProcessor.save(outputPath, { format: 'svg' }); await processor.cleanup(); await finalProcessor.cleanup(); } catch (error) { // Fallback to PNG with SVG extension if SVG conversion fails console.warn('SVG generation failed, creating PNG with SVG extension for compatibility'); const resizedFile = await processor.resize(64, 64, { fit: 'contain', background: 'transparent', zoom: 1.1 }); const finalProcessor = new image_processor_1.ImageProcessor(resizedFile); await finalProcessor.save(outputPath.replace('.svg', '.png')); await fs_1.promises.rename(outputPath.replace('.svg', '.png'), outputPath); await processor.cleanup(); await finalProcessor.cleanup(); } } } /** * Generate Apple touch icons */ async generateAppleIcons() { const promises = []; for (const size of image_processor_1.ImageSizes.apple) { const promise = (async () => { const processor = new image_processor_1.ImageProcessor(this.sourceImage); const resizedFile = await processor.resize(size, size, { fit: 'contain', background: this.config.backgroundColor, zoom: 1.1 }); const finalProcessor = new image_processor_1.ImageProcessor(resizedFile); const outputPath = path_1.default.join(this.config.output.path, `apple-touch-icon-${size}x${size}.png`); await finalProcessor.save(outputPath); await processor.cleanup(); await finalProcessor.cleanup(); })(); promises.push(promise); } // Standard apple-touch-icon.png (180x180) const defaultPromise = (async () => { const processor = new image_processor_1.ImageProcessor(this.sourceImage); const resizedFile = await processor.resize(180, 180, { fit: 'contain', background: this.config.backgroundColor, zoom: 1.1 }); const finalProcessor = new image_processor_1.ImageProcessor(resizedFile); const outputPath = path_1.default.join(this.config.output.path, 'apple-touch-icon.png'); await finalProcessor.save(outputPath); await processor.cleanup(); await finalProcessor.cleanup(); })(); promises.push(defaultPromise); await Promise.all(promises); } /** * Generate Android/PWA icons */ async generateAndroidIcons() { const promises = []; for (const size of image_processor_1.ImageSizes.android) { const promise = (async () => { const processor = new image_processor_1.ImageProcessor(this.sourceImage); const resizedFile = await processor.resize(size, size, { fit: 'contain', background: this.config.backgroundColor, zoom: 1.1 }); const finalProcessor = new image_processor_1.ImageProcessor(resizedFile); const outputPath = path_1.default.join(this.config.output.path, `android-chrome-${size}x${size}.png`); await finalProcessor.save(outputPath); await processor.cleanup(); await finalProcessor.cleanup(); })(); promises.push(promise); } await Promise.all(promises); // Generate web app manifest await this.generateManifest(); } /** * Generate Windows tiles (Microsoft) */ async generateWindowsTiles() { const promises = []; for (const size of image_processor_1.ImageSizes.mstile) { const promise = (async () => { const processor = new image_processor_1.ImageProcessor(this.sourceImage); const resizedFile = await processor.resize(size.width, size.height, { fit: 'contain', background: this.config.themeColor, zoom: 1.1 }); const finalProcessor = new image_processor_1.ImageProcessor(resizedFile); const outputPath = path_1.default.join(this.config.output.path, `mstile-${size.width}x${size.height}.png`); await finalProcessor.save(outputPath); await processor.cleanup(); await finalProcessor.cleanup(); })(); promises.push(promise); } await Promise.all(promises); // Generate browserconfig.xml await this.generateBrowserConfig(); } /** * Generate browserconfig.xml for Microsoft Edge and IE */ async generateBrowserConfig() { const { prefix = '/' } = this.config.output; const browserconfig = `<?xml version="1.0" encoding="utf-8"?> <browserconfig> <msapplication> <tile> <square70x70logo src="${prefix}mstile-70x70.png"/> <square150x150logo src="${prefix}mstile-150x150.png"/> <wide310x150logo src="${prefix}mstile-310x150.png"/> <square310x310logo src="${prefix}mstile-310x310.png"/> <TileColor>${this.config.themeColor}</TileColor> </tile> </msapplication> </browserconfig>`; const outputPath = path_1.default.join(this.config.output.path, 'browserconfig.xml'); await fs_1.promises.writeFile(outputPath, browserconfig); } /** * Generate Safari pinned tab icon */ async generateSafariIcon() { // Generate a simplified SVG for Safari pinned tabs // This should be a monochrome, high-contrast version const outputPath = path_1.default.join(this.config.output.path, 'safari-pinned-tab.svg'); // Create a simplified SVG representation // In a real implementation, you might want to use a library like potrace // For now, we'll create a basic SVG template const svgContent = `<?xml version="1.0" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> <svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000" preserveAspectRatio="xMidYMid meet"> <metadata> Created by Social Forge </metadata> <g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)" fill="#000000" stroke="none"> <path d="M0 2560 l0 -2560 2560 0 2560 0 0 2560 0 2560 -2560 0 -2560 0 0 -2560z"/> </g> </svg>`; await fs_1.promises.writeFile(outputPath, svgContent); } /** * Generate manifest.json for PWA support */ async generateManifest() { const { prefix = '/' } = this.config.output; const manifest = { name: this.config.appName, short_name: this.config.appName, description: this.config.description || '', theme_color: this.config.themeColor, background_color: this.config.backgroundColor, display: 'standalone', orientation: 'portrait-primary', start_url: '/', scope: '/', icons: [ { src: `${prefix}android-chrome-192x192.png`, sizes: '192x192', type: 'image/png', purpose: 'any maskable' }, { src: `${prefix}android-chrome-512x512.png`, sizes: '512x512', type: 'image/png', purpose: 'any maskable' } ] }; const outputPath = path_1.default.join(this.config.output.path, 'manifest.json'); await fs_1.promises.writeFile(outputPath, JSON.stringify(manifest, null, 2)); } /** * Get HTML meta tags for favicons */ getMetaTags() { const prefix = this.config.output.prefix || '/'; return [ // Standard favicons `<link rel="icon" type="image/x-icon" href="${prefix}favicon.ico">`, `<link rel="icon" type="image/png" sizes="32x32" href="${prefix}favicon-32x32.png">`, `<link rel="icon" type="image/png" sizes="16x16" href="${prefix}favicon-16x16.png">`, `<link rel="icon" type="image/svg+xml" href="${prefix}favicon.svg">`, // Apple touch icons `<link rel="apple-touch-icon" sizes="180x180" href="${prefix}apple-touch-icon.png">`, // Android/PWA `<link rel="manifest" href="${prefix}manifest.json">`, `<link rel="icon" type="image/png" sizes="192x192" href="${prefix}android-chrome-192x192.png">`, `<link rel="icon" type="image/png" sizes="512x512" href="${prefix}android-chrome-512x512.png">`, // Windows `<meta name="msapplication-config" content="${prefix}browserconfig.xml">`, `<meta name="msapplication-TileColor" content="${this.config.themeColor}">`, // Safari `<link rel="mask-icon" href="${prefix}safari-pinned-tab.svg" color="${this.config.themeColor}">`, // Theme colors `<meta name="theme-color" content="${this.config.themeColor}">`, `<meta name="apple-mobile-web-app-status-bar-style" content="default">`, `<meta name="apple-mobile-web-app-capable" content="yes">`, `<meta name="apple-mobile-web-app-title" content="${this.config.appName}">`, ]; } /** * Get Next.js metadata configuration for favicons */ getNextMetadata() { const prefix = this.config.output.prefix || '/'; return { icons: { icon: [ { url: `${prefix}favicon.ico`, sizes: '32x32', type: 'image/x-icon' }, { url: `${prefix}favicon-16x16.png`, sizes: '16x16', type: 'image/png' }, { url: `${prefix}favicon-32x32.png`, sizes: '32x32', type: 'image/png' }, { url: `${prefix}favicon.svg`, type: 'image/svg+xml' }, ], apple: [ { url: `${prefix}apple-touch-icon.png`, sizes: '180x180', type: 'image/png' }, ], other: [ { url: `${prefix}android-chrome-192x192.png`, sizes: '192x192', type: 'image/png' }, { url: `${prefix}android-chrome-512x512.png`, sizes: '512x512', type: 'image/png' }, ], }, manifest: `${prefix}manifest.json`, themeColor: this.config.themeColor, other: { 'msapplication-config': `${prefix}browserconfig.xml`, 'msapplication-TileColor': this.config.themeColor, }, }; } /** * Get list of generated files */ getGeneratedFiles() { return [ // Standard favicons 'favicon.ico', 'favicon.png', 'favicon.svg', 'favicon-16x16.png', 'favicon-32x32.png', 'favicon-48x48.png', 'favicon-64x64.png', 'favicon-128x128.png', 'favicon-256x256.png', // Apple icons 'apple-touch-icon.png', 'apple-touch-icon-180x180.png', // Android/PWA 'android-chrome-192x192.png', 'android-chrome-512x512.png', 'manifest.json', // Windows 'mstile-70x70.png', 'mstile-150x150.png', 'mstile-310x150.png', 'mstile-310x310.png', 'browserconfig.xml', // Safari 'safari-pinned-tab.svg', ]; } } exports.FaviconGenerator = FaviconGenerator;