77 lines
2.6 KiB
JavaScript
77 lines
2.6 KiB
JavaScript
'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 }; |