TypePHP 编译器
https://swoole.com/aot/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
147 lines
4.5 KiB
147 lines
4.5 KiB
/**
|
|
* SVG → ICO 转换脚本 (Node.js)
|
|
*
|
|
* 使用 sharp 库将 SVG 渲染为多尺寸 PNG,再合并为 ICO
|
|
*
|
|
* 用法: node svg-to-ico.js <input.svg> [output.ico]
|
|
*/
|
|
|
|
const sharp = require('sharp');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const sizes = [16, 32, 48, 64, 128, 256];
|
|
|
|
async function main() {
|
|
const args = process.argv.slice(2);
|
|
if (args.length < 1) {
|
|
console.log('用法: node svg-to-ico.js <input.svg> [output.ico]');
|
|
process.exit(1);
|
|
}
|
|
|
|
const inputSvg = path.resolve(args[0]);
|
|
const outputIco = args.length >= 2
|
|
? path.resolve(args[1])
|
|
: path.join(path.dirname(inputSvg), path.basename(inputSvg, '.svg') + '.ico');
|
|
|
|
if (!fs.existsSync(inputSvg)) {
|
|
console.log(`错误: SVG 文件不存在: ${inputSvg}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log('=== SVG → ICO 转换工具 ===\n');
|
|
console.log(`输入: ${inputSvg}`);
|
|
console.log(`输出: ${outputIco}\n`);
|
|
|
|
const svgBuffer = fs.readFileSync(inputSvg);
|
|
|
|
// 步骤1: 渲染 SVG 为多个尺寸的 PNG
|
|
const pngBuffers = {};
|
|
for (const size of sizes) {
|
|
console.log(`[1/2] 渲染 SVG → PNG (${size}x${size})...`);
|
|
try {
|
|
const pngBuffer = await sharp(svgBuffer, { density: 300 })
|
|
.resize(size, size, { fit: 'contain', background: { r: 255, g: 255, b: 255, alpha: 1 } })
|
|
.png()
|
|
.toBuffer();
|
|
pngBuffers[size] = pngBuffer;
|
|
console.log(` 完成: ${size}x${size} (${pngBuffer.length} bytes)`);
|
|
} catch (err) {
|
|
console.log(` 警告: ${size}x${size} PNG 生成失败: ${err.message},跳过`);
|
|
}
|
|
}
|
|
|
|
const validSizes = Object.keys(pngBuffers).map(Number).sort((a, b) => a - b);
|
|
if (validSizes.length === 0) {
|
|
console.log('\n错误: 所有尺寸的 PNG 都生成失败');
|
|
process.exit(1);
|
|
}
|
|
|
|
// 步骤2: 合并为 ICO
|
|
console.log('\n[2/2] 合并 PNG → ICO...');
|
|
const icoBuffer = createIcoFromPngs(pngBuffers, validSizes);
|
|
fs.writeFileSync(outputIco, icoBuffer);
|
|
|
|
console.log(`\n成功! ICO 文件已生成: ${outputIco}`);
|
|
console.log(`包含尺寸: ${validSizes.join(', ')}`);
|
|
console.log(`文件大小: ${icoBuffer.length} bytes`);
|
|
}
|
|
|
|
/**
|
|
* 将多个 PNG buffer 合并为 ICO 格式的 Buffer
|
|
*/
|
|
function createIcoFromPngs(pngBuffers, sizes) {
|
|
const imageCount = sizes.length;
|
|
|
|
// 计算各部分大小
|
|
const headerSize = 6; // ICO 头部
|
|
const dirEntrySize = 16; // 每个目录条目
|
|
const dirSize = dirEntrySize * imageCount;
|
|
|
|
let dataOffset = headerSize + dirSize;
|
|
|
|
// 构建目录条目
|
|
const dirEntries = [];
|
|
for (const size of sizes) {
|
|
const pngData = pngBuffers[size];
|
|
const dataSize = pngData.length;
|
|
|
|
// Width/Height: 0 表示 256
|
|
const w = size >= 256 ? 0 : size;
|
|
const h = size >= 256 ? 0 : size;
|
|
|
|
dirEntries.push({
|
|
width: w,
|
|
height: h,
|
|
colorCount: 0,
|
|
reserved: 0,
|
|
planes: 1,
|
|
bitCount: 32,
|
|
dataSize: dataSize,
|
|
offset: dataOffset
|
|
});
|
|
|
|
dataOffset += dataSize;
|
|
}
|
|
|
|
// 计算总大小
|
|
let totalSize = headerSize + dirSize;
|
|
for (const size of sizes) {
|
|
totalSize += pngBuffers[size].length;
|
|
}
|
|
|
|
// 构建 ICO 二进制数据
|
|
const buffer = Buffer.alloc(totalSize);
|
|
let pos = 0;
|
|
|
|
// ICO 头部 (6 bytes)
|
|
buffer.writeUInt16LE(0, pos); pos += 2; // Reserved
|
|
buffer.writeUInt16LE(1, pos); pos += 2; // Type: 1 = ICO
|
|
buffer.writeUInt16LE(imageCount, pos); pos += 2; // Image count
|
|
|
|
// 目录条目
|
|
for (const entry of dirEntries) {
|
|
buffer.writeUInt8(entry.width, pos); pos += 1;
|
|
buffer.writeUInt8(entry.height, pos); pos += 1;
|
|
buffer.writeUInt8(entry.colorCount, pos); pos += 1;
|
|
buffer.writeUInt8(entry.reserved, pos); pos += 1;
|
|
buffer.writeUInt16LE(entry.planes, pos); pos += 2;
|
|
buffer.writeUInt16LE(entry.bitCount, pos); pos += 2;
|
|
buffer.writeUInt32LE(entry.dataSize, pos); pos += 4;
|
|
buffer.writeUInt32LE(entry.offset, pos); pos += 4;
|
|
}
|
|
|
|
// 图像数据
|
|
for (const size of sizes) {
|
|
const pngData = pngBuffers[size];
|
|
pngData.copy(buffer, pos);
|
|
pos += pngData.length;
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
main().catch(err => {
|
|
console.error('错误:', err);
|
|
process.exit(1);
|
|
});
|
|
|