'use strict'; const fs = require('fs'); const path = require('path'); const sharp = require('sharp'); const logoPath = path.join(__dirname, '..', 'logo.png'); let faviconBuffer = null; // Оборачивает PNG-изображения в ICO-контейнер (PNG-записи внутри ICO // понимаются современными браузерами). entries: [{ png: Buffer, width }] function buildIcoFromPngs(entries) { const header = Buffer.alloc(6); header.writeUInt16LE(0, 0); // reserved header.writeUInt16LE(1, 2); // type: icon header.writeUInt16LE(entries.length, 4); const dirSize = 16 * entries.length; let offset = 6 + dirSize; const parts = []; for (const e of entries) { const dir = Buffer.alloc(16); const w = e.width >= 256 ? 0 : e.width; // 0 означает 256 const h = e.height >= 256 ? 0 : e.height; dir.writeUInt8(w, 0); dir.writeUInt8(h, 1); dir.writeUInt8(0, 2); // color count dir.writeUInt8(0, 3); // reserved dir.writeUInt16LE(1, 4); // planes dir.writeUInt16LE(32, 6); // bit count dir.writeUInt32LE(e.png.length, 8); // bytes in resource dir.writeUInt32LE(offset, 12); // image offset parts.push(dir); offset += e.png.length; } return Buffer.concat([header, ...parts, ...entries.map((e) => e.png)]); } // Читает logo.png, уменьшает до 32x32 и 256x256 (с сохранением пропорций, // без обрезки) и собирает favicon.ico. Возвращает Buffer или null. async function loadFavicon() { faviconBuffer = null; try { const png = fs.readFileSync(logoPath); if (!png || png.length < 8 || png.toString('latin1', 0, 8) !== '\x89PNG\r\n\x1a\n') { return null; } const meta = await sharp(png).metadata(); const baseWidth = meta.width || 256; const baseHeight = meta.height || 256; const smallPng = await sharp(png) .resize({ width: 32, height: 32, fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } }) .png() .toBuffer(); const largePng = await sharp(png) .resize({ width: 256, height: 256, fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } }) .png() .toBuffer(); faviconBuffer = buildIcoFromPngs([ { png: smallPng, width: 32, height: 32 }, { png: largePng, width: baseWidth, height: baseHeight } ]); return faviconBuffer; } catch (err) { console.warn(`⚠ Favicon: не удалось подготовить фавикон из logo.png: ${err.message}`); return null; } } function getFavicon() { return faviconBuffer; } module.exports = { loadFavicon, getFavicon, buildIcoFromPngs, logoPath };