| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071 |
- "use strict";
- /**
- * Assemble a Windows multi-resolution .ico from the icon.iconset PNGs.
- *
- * ICO format (PNG-embedded, supported by Windows Vista+):
- * ICONDIR (6 bytes): reserved=0, type=1, count=N
- * ICONDIRENTRY (16×N): w,h,colors,reserved,planes,bitcount,size,offset
- * <PNG bytes concatenated>
- * No native tooling (iconutil/sips) — pure Node, cross-buildable on macOS.
- */
- const fs = require("node:fs");
- const path = require("node:path");
- const ICONSET = path.join(__dirname, "..", "assets", "icon.iconset");
- const OUT = path.join(__dirname, "..", "assets", "icon.ico");
- const SIZES = [16, 32, 48, 64, 128, 256];
- const SOURCES = {
- 16: "icon_16x16.png",
- 32: "icon_32x32.png",
- 64: "icon_32x32@2x.png",
- 128: "icon_128x128.png",
- 256: "icon_256x256.png",
- // 48 has no direct source; reuse 64 — Windows scales acceptably.
- 48: "icon_32x32@2x.png",
- };
- function pngDims(buf) {
- // PNG IHDR: width (4 bytes BE) at offset 16, height at 20.
- return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20) };
- }
- function build() {
- const entries = [];
- for (const size of SIZES) {
- const file = path.join(ICONSET, SOURCES[size]);
- if (!fs.existsSync(file)) { console.warn(`skip ${size}: missing ${SOURCES[size]}`); continue; }
- const buf = fs.readFileSync(file);
- if (buf.slice(0, 8).toString("hex") !== "89504e470d0a1a0a") throw new Error(`${file}: not a PNG`);
- const { width, height } = pngDims(buf);
- entries.push({ size, width, height, buf });
- }
- if (entries.length === 0) throw new Error("no PNG sources found");
- const count = entries.length;
- const headerSize = 6 + 16 * count;
- let offset = headerSize;
- const dir = Buffer.alloc(headerSize);
- dir.writeUInt16LE(0, 0); // reserved
- dir.writeUInt16LE(1, 2); // type = icon
- dir.writeUInt16LE(count, 4);
- entries.forEach((e, i) => {
- const base = 6 + 16 * i;
- dir.writeUInt8(e.size >= 256 ? 0 : e.size, base); // width (0 = 256)
- dir.writeUInt8(e.size >= 256 ? 0 : e.size, base + 1); // height
- dir.writeUInt8(0, base + 2); // colorCount (0 = ≥8bit)
- dir.writeUInt8(0, base + 3); // reserved
- dir.writeUInt16LE(1, base + 4); // planes
- dir.writeUInt16LE(32, base + 6); // bitCount
- dir.writeUInt32LE(e.buf.length, base + 8); // bytesInRes
- dir.writeUInt32LE(offset, base + 12); // imageOffset
- offset += e.buf.length;
- });
- const out = Buffer.concat([dir, ...entries.map((e) => e.buf)]);
- fs.writeFileSync(OUT, out);
- console.log(`built ${OUT}: ${count} sizes [${entries.map((e) => e.size).join(",")}] (${out.length} bytes)`);
- }
- build();
|