// Generate simple 1-color PNG icons using raw buffer construction // PNG signature + IHDR + IDAT + IEND for a solid-color square import fs from "fs"; import path from "path"; import { fileURLToPath } from "url"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); function createSolidPng(size: number, r: number, g: number, b: number): Buffer { // Simple zlib compression for raw RGBA data // This is a minimal approach; for solid colors we can construct a valid PNG // Using a pre-built minimal solid color PNG template would be easier // Let's use a small helper that creates a valid PNG via canvas-like approach // Actually, let's just write a simple PNG with the minimal required chunks const width = size; const height = size; const bitDepth = 8; const colorType = 2; // RGB // PNG signature const signature = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); // IHDR chunk const ihdrData = Buffer.alloc(13); ihdrData.writeUInt32BE(width, 0); ihdrData.writeUInt32BE(height, 4); ihdrData.writeUInt8(bitDepth, 8); ihdrData.writeUInt8(colorType, 9); ihdrData.writeUInt8(0, 10); // compression ihdrData.writeUInt8(0, 11); // filter ihdrData.writeUInt8(0, 12); // interlace const ihdrChunk = makeChunk("IHDR", ihdrData); // IDAT chunk - raw image data with filter byte // Each row: 1 filter byte (0 = none) + width * 3 bytes (RGB) const rowSize = 1 + width * 3; const imageData = Buffer.alloc(rowSize * height); for (let y = 0; y < height; y++) { imageData[y * rowSize] = 0; // filter: none for (let x = 0; x < width; x++) { const offset = y * rowSize + 1 + x * 3; imageData[offset] = r; imageData[offset + 1] = g; imageData[offset + 2] = b; } } const compressed = zlibDeflate(imageData); const idatChunk = makeChunk("IDAT", compressed); // IEND chunk const iendChunk = makeChunk("IEND", Buffer.alloc(0)); return Buffer.concat([signature, ihdrChunk, idatChunk, iendChunk]); } function makeChunk(type: string, data: Buffer): Buffer { const typeBuf = Buffer.from(type, "ascii"); const lenBuf = Buffer.alloc(4); lenBuf.writeUInt32BE(data.length, 0); const crcBuf = Buffer.alloc(4); const crcData = Buffer.concat([typeBuf, data]); crcBuf.writeInt32BE(crc32(crcData), 0); return Buffer.concat([lenBuf, typeBuf, data, crcBuf]); } function crc32(buf: Buffer): number { const table = new Int32Array(256); for (let i = 0; i < 256; i++) { let c = i; for (let k = 0; k < 8; k++) { c = (c & 1) ? (0xedb88320 ^ (c >>> 1)) : (c >>> 1); } table[i] = c; } let crc = -1; for (let i = 0; i < buf.length; i++) { crc = table[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8); } return (crc ^ -1) >>> 0; } function zlibDeflate(data: Buffer): Buffer { // Minimal deflate: no compression blocks (type 0) for small data // For larger images, we need proper zlib. Let's use Node's built-in zlib. import("zlib").then((zlib) => { // Not needed - we'll do this synchronously via a different approach }); // Actually, we can use the zlib module directly since we're in Node const zlib = require("zlib"); return zlib.deflateRawSync(data); } const iconsDir = path.join(__dirname, "..", "src", "icons"); fs.mkdirSync(iconsDir, { recursive: true }); // Green color (#22c55e) for InvoiceChaser const sizes = [16, 48, 128]; for (const size of sizes) { const png = createSolidPng(size, 34, 197, 94); fs.writeFileSync(path.join(iconsDir, `icon${size}.png`), png); } console.log("Icons generated successfully");