From 6ba833769c1e2800cc59c3616defb147221ef119 Mon Sep 17 00:00:00 2001 From: wagesj45 Date: Sun, 24 Aug 2025 01:10:55 -0500 Subject: [PATCH] fix(icons): generate non-transparent PNGs\n\n- prefer rsvg-convert/inkscape with fallback to ImageMagick\n- inject explicit stroke/fill on to avoid inheritance issues\n- document rasterizer behavior in icons/README --- icons/README.md | 3 +- scripts/build-icons.js | 67 ++++++++++++++++++++++++++++++------------ 2 files changed, 51 insertions(+), 19 deletions(-) diff --git a/icons/README.md b/icons/README.md index 7d20d39..5f71db6 100644 --- a/icons/README.md +++ b/icons/README.md @@ -15,7 +15,7 @@ Run `npm run build` (or `npm run build:icons`) to regenerate. The manifest is wi Notes ----- - Source: `icons/file-search.svg` uses `stroke="currentColor"`. -- The build script replaces `currentColor` with configured colors and rasterizes with a transparent background. +- The build script replaces `currentColor` with configured colors and rasterizes with a transparent background. It also injects explicit `stroke`/`fill` on each path for better compatibility across renderers. - Default stroke colors use the project emphasis palette: - Add‑on icons (48/96/128): Primary 2 (deep) `#223544`. - Toolbar icons (16/32): Primary 1 (light) `#5AC3D6`. @@ -24,3 +24,4 @@ Notes - `ICON_COLOR_TOOLBAR` (16/32); default `#5AC3D6`. - Firefox does not require .ico; PNG is recommended. - Theme variants can be added later via `browser_action.theme_icons`. + - Rasterizer: the script will use `rsvg-convert` if available, then `inkscape`, and falls back to ImageMagick (`magick`/`convert`). Install one of these locally. diff --git a/scripts/build-icons.js b/scripts/build-icons.js index dc86cef..e5ae2b2 100644 --- a/scripts/build-icons.js +++ b/scripts/build-icons.js @@ -28,12 +28,22 @@ const TOOLBAR_SIZES = [16, 32]; const COLOR_ADDON = process.env.ICON_COLOR_ADDON || '#223544'; const COLOR_TOOLBAR = process.env.ICON_COLOR_TOOLBAR || '#5AC3D6'; -function ensureMagick() { - // Prefer calling binaries directly to avoid shell init output polluting stdout. - let ok = spawnSync('magick', ['-version']); - if (ok && ok.status === 0) return 'magick'; - ok = spawnSync('convert', ['-version']); - if (ok && ok.status === 0) return 'convert'; +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; } @@ -46,24 +56,45 @@ function readSvg() { } function colorize(svg, color) { - // Replace currentColor with explicit hex color. Keep case-insensitive safety. - return svg.replace(/currentColor/gi, color); + // Replace currentColor with explicit hex color and inject explicit stroke attributes on each path + let s = svg.replace(/currentColor/gi, color); + // Ensure each has explicit stroke and fill to avoid inheritance issues in some renderers + s = s.replace(/]*?)\/>/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 ``; + }); + return s; } -function rasterize(magickBin, svgString, size, outPath) { - // Use ImageMagick, transparent background, high density for crisp vector rasterization. - const args = ['-background', 'none', '-density', '384', 'svg:-', '-resize', `${size}x${size}`, outPath]; - const proc = spawnSync(magickBin, args, { input: svgString, stdio: ['pipe', 'inherit', 'inherit'] }); +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}`); + console.error(`Failed to generate ${outPath} using ${raster.kind} (${cmd}).`); process.exit(proc.status || 1); } } function main() { - const magick = ensureMagick(); - if (!magick) { - console.error('ImageMagick not found. Please install it or adjust this script to use inkscape/rsvg-convert.'); + 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 }); @@ -74,7 +105,7 @@ function main() { const addonSvg = colorize(base, COLOR_ADDON); for (const s of ADDON_SIZES) { const out = path.join(ICONS_DIR, `icon-${s}.png`); - rasterize(magick, addonSvg, s, out); + rasterize(raster, addonSvg, s, out); console.log(`Generated ${path.relative(REPO_ROOT, out)}`); } @@ -82,7 +113,7 @@ function main() { const toolbarSvg = colorize(base, COLOR_TOOLBAR); for (const s of TOOLBAR_SIZES) { const out = path.join(ICONS_DIR, `icon-${s}.png`); - rasterize(magick, toolbarSvg, s, out); + rasterize(raster, toolbarSvg, s, out); console.log(`Generated ${path.relative(REPO_ROOT, out)}`); } }