56 lines
2.6 KiB
JavaScript
56 lines
2.6 KiB
JavaScript
// Regenerate the Windows/Linux icon set from the flat design source
|
||||
|
|
// (build/icon-flat.svg — the owner's Affinity export, rounded squircle
|
|||
|
|
// with baked transparency, owner ruling 2026-10-13).
|
|||
|
|
//
|
|||
|
|
// build/icon.ico — Windows (.ico with 16/24/32/48/64/128/256 PNG entries)
|
|||
|
|
// build/icon.png — Linux (512×512; AppImage/deb/desktop entry)
|
|||
|
|
//
|
|||
|
|
// macOS does NOT come from here: build/icon.icon (the Icon Composer
|
|||
|
|
// document) is the mac source of truth, compiled by actool at package
|
|||
|
|
// time into Assets.car (macOS 26 Liquid Glass) + a fallback icns; the
|
|||
|
|
// hand-packed build/icon.icns replaces that fallback's 256px cap via
|
|||
|
|
// scripts/after-pack.cjs (pre-Tahoe keeps full-res).
|
|||
|
|
//
|
|||
|
|
// Renders each size DIRECTLY from the SVG (resvg) — sharper squircle
|
|||
|
|
// edges than downsampling a raster. Committed outputs; rerun only when
|
|||
|
|
// the design changes: `node scripts/make-icons.mjs`.
|
|||
|
|
import { Resvg } from "@resvg/resvg-js";
|
|||
|
|
import { readFileSync, writeFileSync } from "node:fs";
|
|||
|
|
import path from "node:path";
|
|||
|
|
import { fileURLToPath } from "node:url";
|
|||
|
|
|
|||
|
|
const root = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|||
|
|
const svg = readFileSync(path.join(root, "build", "icon-flat.svg"), "utf8");
|
|||
|
|
|
|||
|
|
const render = (size) => new Resvg(svg, { fitTo: { mode: "width", value: size } }).render(size, size).asPng();
|
|||
|
|
|
|||
|
|
// ---- build/icon.ico: ICONDIR + PNG entries ------------------------------
|
|||
|
|
const ICO_SIZES = [16, 24, 32, 48, 64, 128, 256];
|
|||
|
|
const pngs = ICO_SIZES.map(render);
|
|||
|
|
// Header (6) + one 16-byte directory entry per image, then the PNGs.
|
|||
|
|
const header = Buffer.alloc(6 + ICO_SIZES.length * 16);
|
|||
|
|
header.writeUInt16LE(0, 0); // reserved
|
|||
|
|
header.writeUInt16LE(1, 2); // type: icon
|
|||
|
|
header.writeUInt16LE(ICO_SIZES.length, 4);
|
|||
|
|
let offset = header.length;
|
|||
|
|
ICO_SIZES.forEach((size, i) => {
|
|||
|
|
const e = 6 + i * 16;
|
|||
|
|
header.writeUInt8(size === 256 ? 0 : size, e); // 0 encodes 256
|
|||
|
|
header.writeUInt8(size === 256 ? 0 : size, e + 1);
|
|||
|
|
header.writeUInt8(0, e + 2); // colors
|
|||
|
|
header.writeUInt8(0, e + 3); // reserved
|
|||
|
|
header.writeUInt16LE(1, e + 4); // planes
|
|||
|
|
header.writeUInt16LE(32, e + 6); // bpp
|
|||
|
|
header.writeUInt32LE(pngs[i].length, e + 8);
|
|||
|
|
header.writeUInt32LE(offset, e + 12);
|
|||
|
|
offset += pngs[i].length;
|
|||
|
|
});
|
|||
|
|
const ico = Buffer.concat([header, ...pngs]);
|
|||
|
|
writeFileSync(path.join(root, "build", "icon.ico"), ico);
|
|||
|
|
console.log(`build/icon.ico ${ICO_SIZES.join("/")} — ${(ico.length / 1024).toFixed(1)} KB`);
|
|||
|
|
|
|||
|
|
// ---- build/icon.png: the Linux icon -------------------------------------
|
|||
|
|
const png512 = render(512);
|
|||
|
|
writeFileSync(path.join(root, "build", "icon.png"), png512);
|
|||
|
|
console.log(`build/icon.png 512×512 — ${(png512.length / 1024).toFixed(1)} KB`);
|