Files
diff-highlighter/scripts/make-icons.mjs
Tilo Klarenbeek 314c34fe52 Initial commit: Diff Highlighter Chrome extension (MV3, TypeScript)
Highlights .diff/.patch files inline — additions green, deletions red,
hunk/file headers styled, with toggleable highlight.js syntax highlighting.

- MV3, content script gated on text/plain + URL ending in .diff/.patch
- Two-phase render: instant diff colors, then chunked idle-time syntax
- Popup switches + Alt+D shortcut, persisted in chrome.storage.sync, live re-render
- Light/dark via prefers-color-scheme; only the `storage` permission
- TypeScript + esbuild build; Forgejo Actions CI (typecheck + build)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 13:01:06 +02:00

92 lines
3.2 KiB
JavaScript

// Generates the extension icons (green "added" band over a red "removed" band)
// as PNGs, with zero dependencies — just Node's zlib. Run: node scripts/make-icons.mjs
import zlib from 'node:zlib';
import { writeFileSync, mkdirSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const outDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../icons');
const CRC_TABLE = (() => {
const t = new Uint32Array(256);
for (let n = 0; n < 256; n++) {
let c = n;
for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
t[n] = c >>> 0;
}
return t;
})();
function crc32(buf) {
let c = 0xffffffff;
for (let i = 0; i < buf.length; i++) c = CRC_TABLE[(c ^ buf[i]) & 0xff] ^ (c >>> 8);
return (c ^ 0xffffffff) >>> 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]);
}
function png(size, rgba) {
const sig = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
const ihdr = Buffer.alloc(13);
ihdr.writeUInt32BE(size, 0);
ihdr.writeUInt32BE(size, 4);
ihdr[8] = 8; // bit depth
ihdr[9] = 6; // color type RGBA
const stride = size * 4;
const raw = Buffer.alloc((stride + 1) * size);
for (let y = 0; y < size; y++) {
raw[y * (stride + 1)] = 0; // filter: none
rgba.copy(raw, y * (stride + 1) + 1, y * stride, y * stride + stride);
}
const idat = zlib.deflateSync(raw, { level: 9 });
return Buffer.concat([sig, chunk('IHDR', ihdr), chunk('IDAT', idat), chunk('IEND', Buffer.alloc(0))]);
}
function insideRounded(x, y, x0, y0, x1, y1, r) {
const cxL = x0 + r, cxR = x1 - r, cyT = y0 + r, cyB = y1 - r;
let cx = null, cy = null;
if (x < cxL && y < cyT) { cx = cxL; cy = cyT; }
else if (x > cxR && y < cyT) { cx = cxR; cy = cyT; }
else if (x < cxL && y > cyB) { cx = cxL; cy = cyB; }
else if (x > cxR && y > cyB) { cx = cxR; cy = cyB; }
if (cx === null) return true;
const dx = x - cx, dy = y - cy;
return dx * dx + dy * dy <= r * r;
}
function makeIcon(size) {
const px = Buffer.alloc(size * size * 4); // transparent
const green = [46, 160, 67, 255];
const red = [215, 58, 73, 255];
const inset = Math.max(1, Math.round(size * 0.06));
const r = Math.round(size * 0.2);
const x0 = inset, y0 = inset, x1 = size - 1 - inset, y1 = size - 1 - inset;
const mid = Math.floor(size / 2);
const gap = Math.max(1, Math.round(size * 0.04));
const set = (x, y, c) => {
const i = (y * size + x) * 4;
px[i] = c[0]; px[i + 1] = c[1]; px[i + 2] = c[2]; px[i + 3] = c[3];
};
for (let y = y0; y <= y1; y++) {
for (let x = x0; x <= x1; x++) {
if (!insideRounded(x, y, x0, y0, x1, y1, r)) continue;
if (Math.abs(y - mid) < gap) continue; // divider between bands
set(x, y, y < mid ? green : red);
}
}
return png(size, px);
}
mkdirSync(outDir, { recursive: true });
for (const size of [16, 32, 48, 128]) {
writeFileSync(path.join(outDir, `icon${size}.png`), makeIcon(size));
}
console.log('icons written ->', path.relative(process.cwd(), outDir));