diff --git a/README.md b/README.md index 67f49f0..8d83fef 100644 --- a/README.md +++ b/README.md @@ -171,3 +171,4 @@ Layout: - `infra/` — OpenTofu for the frontpage and metadata hosting - `brand/` — canonical logo SVGs (`mark.svg`, `sticker.svg`); the icon PNGs in `public/icons/` and `web/public/icons/` are rendered from `sticker.svg` + with `npm run icons` (needs a local Chrome; set `CHROME` to pick one) diff --git a/package.json b/package.json index 676fa99..6a1dc01 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "build": "vite build", "dev": "node scripts/dev.mjs", "verify:dist": "node scripts/verify-dist.mjs", + "icons": "node scripts/render-icons.mjs", "package": "npm run build && npm run verify:dist", "test": "vitest run", "check": "tsc --noEmit" diff --git a/public/icons/icon128.png b/public/icons/icon128.png index e52d0ebd22bd1b5e4d511e960eaeba9ae3f22197..a3bc1c790c588aef0876f82d379589a7e1fe06fa 100644 GIT binary patch literal 8498 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 diff --git a/public/icons/icon16.png b/public/icons/icon16.png index 3074d1bafe51f7955b2996842b7ce00e11a72843..152ac69c9a20c122222b7feff363dc5223363d0d 100644 GIT binary patch delta 809 zcmX@ldVp<$O8r+)7srqa#wnAsJ%SS@j{kq|<3GDsEBPFZOmxf$%8Gl&yLD#By|Y=bOfK8axRKVebM~&wmg--; zTYePHdv5#uzs>pgHt$En z2wm;8Fe^-}|4M82mR$!992WGE{9eGYvgG&f&zkA_wwIp%61Ua=u>Ruo{Ja&{e}{!n z^E&OtRlZL{hoj<~N&K|+r^D`@+BH!^SomvHt;wU!^XJd+;OXw@30v)~>g8&1BuUUc zwm`(yQA6a=<;%ixad98MeXDOP>6Q1{M|vmKCLK^jVj}@I|TN_+vvxCKKPoPm3Pj{vhDy)|M#s;_&i&awS`~*DqSR zao$PBXV0HU7Ejchwd~H_yOtM^9~aLo{OECh2hYR^p4*y`k73G{jL7qTwjhG~^m-#vYEA4pDb5uLR5&hJ?-SUC9G8uagS-b!oTyMFrR^z`(bdy6JyA6+Er z_p@L5&K)u3>_?Lvf95>fcazWIfZM-$^U8dE{yaIdREAsmXZ`!0t5(eX`x2&zaJ00& zo7eAs^ob!hLOq%hsWy=ya-@NfT zj-{u+-#I?Gz0J+JzSK2RGSg`0nP00uNC})%dXG!^e{Ry*YAgbtn~f0 zojpGn@m=O?TV)!b?0@XL%F?uwSD)skl$L&zPg+^ez`*eTztX`o0)`9>3~Zomz|64e Xq++hZZ;x#Z3=9mOu6{1-oD!MqzSnp`#i}JjYliJZc1j zlsh?fykaDp)^9L)#a^&}{(&E?JxACRAG!sp=yY(}3CTq{@^A+&$h}lH!}{{Miu4kx zI}$(D)AOF)tFHTgr}&)cfhj&}Ta%Cb+qERePjFTg;jTQoX;r-phasoMWC15jCNGY3 zk4--o&(QZfcP>pWHvZt>E!*eM*j=5jbF*?4^Q>iurf1G7-K=w3`t;OTAOCc>sUC+P zz1luCuI|C92NxXvFP(kZ`MLmK`-X6v^Ufl!CpPi81`2e#ELd4sQaV@hW5kxs>g5WB zO6TL>y?%SJu5{OC<|_ujSn3ZOL^&%;{ti`|=nycC)tueY+$a3zn{c0f^BN8;yR2-! zVaFHNoo9VJCb7Kim(8(k*|u%*y?=A(*34j>V|8G!vBLukzMR{~Ze@u@@4OSQuFXEp z#$jz(@maB)mQWQvJ&!vgOm_9FHpsWgDGL3uVfgXq&1hmaQXs zWzE53w+&ibr>aX!cmIrMU|{(F-)qhN>kJGGY@kHR%+P#+PsAZ^Uj+jL1B0ilpUXO@ GgeCx-j6ic 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 MUHx3vIVCg!0GLs#2><{9 delta 2375 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 GgeCwKtc0fk diff --git a/scripts/render-icons.mjs b/scripts/render-icons.mjs new file mode 100644 index 0000000..13943ab --- /dev/null +++ b/scripts/render-icons.mjs @@ -0,0 +1,137 @@ +// Renders the icon PNGs from brand/sticker.svg so the logo has a single +// source of truth. Plain node plus a headless Chrome; no npm dependencies. +// +// Chrome's one-shot --screenshot mode proved unreliable at small window +// sizes on this pipeline — depending on size and timing it emits an +// all-transparent or partially painted frame. So instead of screenshotting, +// each size loads the SVG (inlined as a data URI, given explicit +// width/height so it rasterizes crisply) into a canvas, and the page +// reports the PNG data URL plus its own opaque-pixel count via --dump-dom. +// The script then verifies the IHDR dimensions and that enough of the tile +// is actually painted, so a misrender fails loudly instead of shipping. +// +// Usage: node scripts/render-icons.mjs (also: npm run icons) +// Set CHROME to point at a specific Chrome/Chromium binary. + +import { execFileSync } from 'node:child_process' +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join, resolve } from 'node:path' +import { pathToFileURL } from 'node:url' + +const SVG = resolve('brand/sticker.svg') + +// size -> destinations, relative to the repo root +const OUTPUTS = { + 16: ['public/icons/icon16.png'], + 32: ['public/icons/icon32.png', 'web/public/icons/icon32.png'], + 48: ['public/icons/icon48.png'], + 128: ['public/icons/icon128.png', 'web/public/icons/icon128.png'], +} + +// The sticker fills most of its tile; a healthy render is well above this. +// A blank or partial paint (the failure modes we've actually seen) is far +// below it. +const MIN_OPAQUE_FRACTION = 0.3 + +/** Width and height from a PNG's IHDR chunk. Takes any Uint8Array. */ +export function pngSize(bytes) { + const magic = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a] + if (bytes.length < 24 || !magic.every((b, i) => bytes[i] === b)) { + throw new Error('not a PNG') + } + const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength) + return { width: dv.getUint32(16), height: dv.getUint32(20) } +} + +function findChrome() { + const candidates = process.env.CHROME + ? [process.env.CHROME] + : ['google-chrome', 'google-chrome-stable', 'chromium', 'chromium-browser'] + for (const bin of candidates) { + try { + execFileSync(bin, ['--version'], { stdio: 'ignore' }) + return bin + } catch { + // keep looking + } + } + throw new Error('no Chrome found; set CHROME=/path/to/chrome') +} + +function renderOne(chrome, work, size) { + // Explicit root dimensions so the SVG has an intrinsic size and Chrome + // rasterizes it at exactly the canvas resolution. A data URI keeps the + // image same-origin so the canvas stays readable. + const svgText = readFileSync(SVG, 'utf8').replace( + '`, + ) + const dom = execFileSync( + chrome, + [ + '--headless=new', + '--disable-gpu', + '--no-sandbox', + `--user-data-dir=${join(work, `profile${size}`)}`, + '--virtual-time-budget=5000', + '--dump-dom', + pathToFileURL(wrapper).href, + ], + // stderr ignored: headless Chrome logs harmless GPU-init noise there. + { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024, stdio: ['ignore', 'pipe', 'ignore'] }, + ) + const m = dom.match(/RESULT (\d+) (data:image\/png;base64,[A-Za-z0-9+/=]+)/) + if (!m) throw new Error(`icon${size}: no render result in page output`) + return { opaque: Number(m[1]), png: Buffer.from(m[2].split(',')[1], 'base64') } +} + +function main() { + const chrome = findChrome() + const work = mkdtempSync(join(tmpdir(), 'substandard-icons-')) + try { + for (const [size, dests] of Object.entries(OUTPUTS)) { + const { opaque, png } = renderOne(chrome, work, Number(size)) + const dim = pngSize(png) + if (dim.width !== Number(size) || dim.height !== Number(size)) { + throw new Error(`icon${size}: rendered ${dim.width}x${dim.height}`) + } + const fraction = opaque / (size * size) + if (fraction < MIN_OPAQUE_FRACTION) { + throw new Error( + `icon${size}: only ${(fraction * 100).toFixed(1)}% opaque — misrender`, + ) + } + for (const dest of dests) writeFileSync(resolve(dest), png) + console.log( + `icon${size}: ${png.length} bytes, ${(fraction * 100).toFixed(0)}% opaque -> ${dests.join(', ')}`, + ) + } + } finally { + rmSync(work, { recursive: true, force: true }) + } +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + main() +} diff --git a/src/lib/render-icons.test.ts b/src/lib/render-icons.test.ts new file mode 100644 index 0000000..06c3444 --- /dev/null +++ b/src/lib/render-icons.test.ts @@ -0,0 +1,27 @@ +// The render script's only pure logic: the IHDR sanity check that catches +// Chrome's occasional blank- or partial-render misfires. +import { describe, expect, it } from 'vitest' + +// @ts-expect-error plain-node script, no type declarations +import { pngSize } from '../../scripts/render-icons.mjs' + +function fakePng(width: number, height: number): Uint8Array { + const bytes = new Uint8Array(24) + bytes.set([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]) + const dv = new DataView(bytes.buffer) + dv.setUint32(16, width) + dv.setUint32(20, height) + return bytes +} + +describe('pngSize', () => { + it('reads IHDR dimensions', () => { + expect(pngSize(fakePng(128, 128))).toEqual({ width: 128, height: 128 }) + expect(pngSize(fakePng(16, 32))).toEqual({ width: 16, height: 32 }) + }) + + it('rejects non-PNG bytes', () => { + expect(() => pngSize(new TextEncoder().encode(''))).toThrow('not a PNG') + expect(() => pngSize(new Uint8Array(0))).toThrow('not a PNG') + }) +}) diff --git a/web/public/icons/icon128.png b/web/public/icons/icon128.png index e52d0ebd22bd1b5e4d511e960eaeba9ae3f22197..a3bc1c790c588aef0876f82d379589a7e1fe06fa 100644 GIT binary patch literal 8498 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 diff --git a/web/public/icons/icon32.png b/web/public/icons/icon32.png index 2cbbf5c825c9879c043974d097fb3c29f716cfd3..6685054f24e640a10425f84c542f66d0eaa3ee81 100644 GIT binary patch delta 1894 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