archive-org-link-grabber/scripts/build-icons.js

121 lines
4.5 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.

#!/usr/bin/env node
/*
Generates extension and toolbar PNG icons from the base SVG with transparent
background and configurable stroke colors. Requires ImageMagick (`magick`).
Theme defaults (aligned with project palette):
Primary 2 (deep): #223544 → add-on icon strokes (default)
Primary 1 (light): #5AC3D6 → toolbar icon strokes (default)
Override via environment variables (optional):
ICON_COLOR_ADDON hex color for add-on icons (48/96/128). Default: #223544
ICON_COLOR_TOOLBAR hex color for toolbar icons (16/32). Default: #5AC3D6
Usage:
node scripts/build-icons.js
*/
const fs = require('fs');
const path = require('path');
const { spawnSync } = require('child_process');
const REPO_ROOT = path.join(__dirname, '..');
const ICONS_DIR = path.join(REPO_ROOT, 'icons');
const SRC_SVG = path.join(ICONS_DIR, 'file-search.svg');
const ADDON_SIZES = [48, 96, 128];
const TOOLBAR_SIZES = [16, 32];
const COLOR_ADDON = process.env.ICON_COLOR_ADDON || '#223544';
const COLOR_TOOLBAR = process.env.ICON_COLOR_TOOLBAR || '#5AC3D6';
function which(cmd, args = ['--version']) {
try {
const res = spawnSync(cmd, args, { stdio: 'ignore' });
return res && res.status === 0;
} catch (_) {
return false;
}
}
function ensureRasterizer() {
// Prefer rsvg-convert (librsvg), then Inkscape, then ImageMagick.
if (which('rsvg-convert', ['-v'])) return { kind: 'rsvg', bin: 'rsvg-convert' };
// Inkscape 1.x CLI: --pipe reads from stdin
if (which('inkscape', ['--version'])) return { kind: 'inkscape', bin: 'inkscape' };
if (which('magick', ['-version'])) return { kind: 'magick', bin: 'magick' };
if (which('convert', ['-version'])) return { kind: 'magick', bin: 'convert' };
return null;
}
function readSvg() {
if (!fs.existsSync(SRC_SVG)) {
console.error(`Base SVG not found: ${path.relative(REPO_ROOT, SRC_SVG)}`);
process.exit(1);
}
return fs.readFileSync(SRC_SVG, 'utf8');
}
function colorize(svg, color) {
// Replace currentColor with explicit hex color and inject explicit stroke attributes on each path
let s = svg.replace(/currentColor/gi, color);
// Ensure each <path> has explicit stroke and fill to avoid inheritance issues in some renderers
s = s.replace(/<path\b([^>]*?)\/>/g, (m, attrs) => {
let a = attrs;
if (!/\bstroke\s*=/.test(a)) a = ` stroke=\"${color}\"` + a;
if (!/\bstroke-width\s*=/.test(a)) a = ` stroke-width=\"1.8\"` + a;
if (!/\bstroke-linecap\s*=/.test(a)) a = ` stroke-linecap=\"round\"` + a;
if (!/\bstroke-linejoin\s*=/.test(a)) a = ` stroke-linejoin=\"round\"` + a;
if (!/\bfill\s*=/.test(a)) a = ` fill=\"none\"` + a;
return `<path${a}/>`;
});
return s;
}
function rasterize(raster, svgString, size, outPath) {
let args;
let cmd = raster.bin;
if (raster.kind === 'rsvg') {
// rsvg-convert reads SVG and outputs PNG; specify size and transparent background
args = ['-f', 'png', '-w', String(size), '-h', String(size), '--background-color=transparent', '-o', outPath, '-'];
} else if (raster.kind === 'inkscape') {
// Inkscape 1.x: --export-type=png --export-filename=out --export-width/height, read from stdin via --pipe
args = ['--export-type=png', `--export-filename=${outPath}`, `--export-width=${size}`, `--export-height=${size}`, '--pipe'];
} else {
// ImageMagick
args = ['-background', 'none', '-density', '384', 'svg:-', '-resize', `${size}x${size}`, outPath];
}
const proc = spawnSync(cmd, args, { input: svgString, stdio: ['pipe', 'inherit', 'inherit'] });
if (proc.status !== 0) {
console.error(`Failed to generate ${outPath} using ${raster.kind} (${cmd}).`);
process.exit(proc.status || 1);
}
}
function main() {
const raster = ensureRasterizer();
if (!raster) {
console.error('No SVG rasterizer found. Install one of: rsvg-convert, inkscape, or ImageMagick (magick/convert).');
process.exit(1);
}
if (!fs.existsSync(ICONS_DIR)) fs.mkdirSync(ICONS_DIR, { recursive: true });
const base = readSvg();
// Add-on icons
const addonSvg = colorize(base, COLOR_ADDON);
for (const s of ADDON_SIZES) {
const out = path.join(ICONS_DIR, `icon-${s}.png`);
rasterize(raster, addonSvg, s, out);
console.log(`Generated ${path.relative(REPO_ROOT, out)}`);
}
// Toolbar icons
const toolbarSvg = colorize(base, COLOR_TOOLBAR);
for (const s of TOOLBAR_SIZES) {
const out = path.join(ICONS_DIR, `icon-${s}.png`);
rasterize(raster, toolbarSvg, s, out);
console.log(`Generated ${path.relative(REPO_ROOT, out)}`);
}
}
main();