The hand-packed icns was pixel-clean (verified by unpacking and scanning every size), but macOS 26 composites legacy .icns icons into the Liquid Glass system at render time; stacked on Icon Composer's baked PNG-export lighting, that clips the rim highlights into the near-corner specks and abrupt highlight ends (owner report 2026-10-13). The fix is the real layered icon: mac.icon now points at the Icon Composer document (build/icon.icon, copied from ~/Documents/Affinity/lh-logo) and electron-builder compiles it via actool into Assets.car — macOS 26+ renders the true Liquid Glass design (dark/tinted variants included). actool's backwards-compat icns caps at 256px, so scripts/after-pack.cjs swaps in the existing full-res build/icon.icns before signing: pre-Tahoe macOS keeps today's quality, Tahoe gets the layered icon. Windows/Linux get the owner's flat design (rounded squircle with baked transparency): scripts/make-icons.mjs renders build/icon-flat.svg via resvg into build/icon.ico (16→256 PNG entries) + build/icon.png (512), and electron-builder.yml pins win.icon/linux.icon. desktop:dist injects DEVELOPER_DIR (actool needs Xcode; the machine selects CommandLineTools by default, CI's setup-xcode takes precedence via the :- default). The release workflow's mac job selects Xcode 26+ explicitly. Verified locally: mac --dir build (Assets.car + checksummed full-res fallback + CFBundleIconName/IconFile) and the win-unpacked exe carries our ico bytes; NSIS/AppImage assembly is CI's (cross-build tooling can't run on arm64 macOS). Runbook updated. 1610/1610.
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`);
|