Files
learnharness/scripts/make-icons.mjs
T
amosblomqvist cf042b25e7 icons: per-platform icon set — real Liquid Glass on macOS, flat design on Windows/Linux
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.
2026-10-13 10:51:22 +03:00

56 lines
2.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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`);