Files
cv-maker/scripts/generate-icons.mjs
2026-09-16 21:34:09 +02:00

88 lines
2.9 KiB
JavaScript

import { deflateSync } from "node:zlib";
import { writeFileSync, mkdirSync } from "node:fs";
// Civetest: minimal solid-color PNG encoder (non-interlaced, 8-bit RGB)
function crc32(buf) {
let c = ~0;
for (let n = 0; n < buf.length; n++) {
c ^= buf[n];
for (let k = 0; k < 8; k++) c = (c >>> 1) ^ (0xedb88320 & -(c & 1));
}
return ~c >>> 0;
}
function chunk(type, data) {
const len = Buffer.alloc(4);
len.writeUInt32BE(data.length, 0);
const typeBuf = Buffer.from(type, "ascii");
const crc = Buffer.alloc(4);
crc.writeUInt32BE(crc32(Buffer.concat([typeBuf, data])), 0);
return Buffer.concat([len, typeBuf, data, crc]);
}
// Simple canvas: draw background + centered white square "page" motif
function draw(size) {
const bg = [37, 99, 235]; // accent #2563eb
const white = [255, 255, 255];
const rows = [];
for (let y = 0; y < size; y++) {
const row = Buffer.alloc(1 + size * 3);
let p = 1;
for (let x = 0; x < size; x++) {
let color = bg;
// white rounded "page / document" block centered
const margin = Math.round(size * 0.22);
const inner = size - margin * 2;
const xm = x - margin;
const ym = y - margin;
const radius = Math.round(size * 0.08);
if (
xm >= 0 &&
ym >= 0 &&
xm < inner &&
ym < inner &&
(xm <= radius || xm >= inner - radius || ym <= radius || ym >= inner - radius || Math.hypot(xm - radius, ym - radius) <= radius || Math.hypot(xm - (inner - radius), ym - radius) <= radius || Math.hypot(xm - radius, ym - (inner - radius)) <= radius || Math.hypot(xm - (inner - radius), ym - (inner - radius)) <= radius)
) {
color = white;
}
// blue horizontal bars to suggest "CV" lines
const barTop = Math.round(size * 0.45);
const barHeight = Math.round(size * 0.06);
if (ym >= barTop && ym < barTop + barHeight && xm > Math.round(size * 0.32) && xm < inner - Math.round(size * 0.25)) {
color = bg;
}
if (ym >= barTop + barHeight * 2 && ym < barTop + barHeight * 3 && xm > Math.round(size * 0.38) && xm < inner - Math.round(size * 0.1)) {
color = bg;
}
row[p++] = color[0];
row[p++] = color[1];
row[p++] = color[2];
}
rows.push(row);
}
return Buffer.concat(rows);
}
function png(size, file) {
const raw = draw(size);
const ihdr = Buffer.alloc(13);
ihdr.writeUInt32BE(size, 0);
ihdr.writeUInt32BE(size, 4);
ihdr[8] = 8; // bit depth
ihdr[9] = 2; // color type RGB
ihdr[10] = 0;
ihdr[11] = 0;
ihdr[12] = 0;
const png = Buffer.concat([
Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]),
chunk("IHDR", ihdr),
chunk("IDAT", deflateSync(raw, { level: 9 })),
chunk("IEND", Buffer.alloc(0)),
]);
writeFileSync(file, png);
console.log(`generated ${file} (${size}x${size})`);
}
mkdirSync("./public/icons", { recursive: true });
png(192, "./public/icons/icon-192.png");
png(512, "./public/icons/icon-512.png");