Skip to content
EmasoftPublic

Latest commit

 

History

500 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SvgVisualBBox

A JavaScript library for accurate SVG bounding box computation

Finally, bounding boxes you can trust.

npm version npm downloads bundle size

CI Status License: MIT Node.js TypeScript Ready

unpkg CDN jsDelivr CDN GitHub svg2fbf companion

🎬 New in v1.2: native support for FBF.SVG frame-by-frame animations (produced by svg2fbf) — pass --fbf-frame N to any CLI tool (sbb-getbbox, sbb-extract, sbb-svg2png, sbb-compare, the chrome / inkscape variants, …) to pin a specific frame before the operation runs. Library consumers get the same behaviour from one helper: require('svg-bbox/fbf').extractFbfFrame(svg, n). Jump to the Companion Projects section ↓


📚 Table of Contents


The Problem with .getBBox()

The native SVG .getBBox() method is fundamentally broken:

Feature .getBBox() SvgVisualBBox
Filters (blur, shadows, glows) ❌ Ignored ✅ Measured
Stroke width ❌ Ignored ✅ Included
Complex text (ligatures, RTL, textPath) ❌ Wrong ✅ Accurate
<use>, masks, clipping paths ❌ Fails ✅ Works
Transformed elements ❌ Garbage ✅ Correct
Cross-browser consistency ❌ Varies ✅ Consistent

Our approach: Measure what the browser actually paints, pixel by pixel. No geometry guesswork, no lies.

Visual Comparison: Oval Badge with Dashed Stroke

Here's what happens when extracting an SVG element using three different bbox methods:

Inkscape Chrome SvgVisualBBox
Inkscape BBox Chrome .getBBox() SvgVisualBBox
Inkscape ResultInkscape Result getBBox ResultgetBBox Result SvgVisualBBox ResultSvgVisualBBox Result
❌ WRONG ❌ WRONG ✅ CORRECT
(svg file here) (svg file here) (svg file here)
Width: 554px
Height: 379px
Undersized by ~48%
Width: 999px
Height: 301px
Missing ~78px of stroke
Width: 1077px
Height: 379px
Includes full visual bounds

Source: test_oval_badge.svg

Generate this comparison yourself: examples/bbox-comparison.js - Run node examples/bbox-comparison.js assets/test_oval_badge.svg oval_badge to create your own comparison with timestamped output directory.

Why the differences?

  • Inkscape: Truncates half the image!
  • .getBBox(): Ignores stroke width - bbox is wrong!
  • SvgVisualBBox: Perfect!

📦 Installation

Quick Install (no installation required)

Run any tool on demand with npx (npm) or bunx (bun):

npx svg-bbox                         # interactive launcher
npx sbb-getbbox myfile.svg
npx sbb-svg2png myfile.svg out.png
npx sbb-extract myfile.svg --list

# bun equivalent
bunx svg-bbox
bunx sbb-getbbox myfile.svg

Global install (recommended for frequent use)

Package manager Install Update Uninstall
npm npm install -g svg-bbox npm update -g svg-bbox npm uninstall -g svg-bbox
bun bun add -g svg-bbox bun update -g svg-bbox bun remove -g svg-bbox
pnpm pnpm add -g svg-bbox pnpm update -g svg-bbox pnpm remove -g svg-bbox
yarn yarn global add svg-bbox yarn global upgrade svg-bbox yarn global remove svg-bbox

After a global install the 13 binaries are on $PATH:

svg-bbox              # interactive launcher (or shows the suite menu with --help)
sbb-getbbox file.svg
sbb-svg2png file.svg out.png
# …etc

Local install (per-project dependency)

npm install svg-bbox        # or: bun add svg-bbox / pnpm add svg-bbox / yarn add svg-bbox

# Run via your package manager:
npx sbb-getbbox file.svg
bunx sbb-getbbox file.svg
pnpm exec sbb-getbbox file.svg
yarn sbb-getbbox file.svg

Mixing package managers (avoid this)

Installing svg-bbox with two package managers at the same time leads to two parallel copies, two node_modules trees, and two $PATH entries that race for which sbb-* binary wins. Always uninstall before switching:

# Switching from npm-global → bun-global
npm uninstall -g svg-bbox    # remove the npm copy first
bun add -g svg-bbox          # then install with bun

# Switching from bun-global → npm-global
bun remove -g svg-bbox
npm install -g svg-bbox

For local installs the same rule applies: delete node_modules/ and the existing lockfile (or use npm uninstall svg-bbox / bun remove svg-bbox) before re-installing with the other manager. Do not commit two lockfiles (package-lock.json + bun.lock + pnpm-lock.yaml) into one repo — pick one package manager per project.

If you have already created a mixed install, this is the safe cleanup:

# 1. Remove the package from every manager you used
npm uninstall -g svg-bbox 2>/dev/null
bun remove -g svg-bbox 2>/dev/null
pnpm remove -g svg-bbox 2>/dev/null
yarn global remove svg-bbox 2>/dev/null

# 2. Verify nothing remains on PATH
which svg-bbox sbb-getbbox sbb-compare    # should print nothing

# 3. Install fresh with your chosen manager
bun add -g svg-bbox

Bun's "minimum release age" — fresh-publish gotcha

By default Bun's resolver blocks any package version published less than 3 days ago (minimum-release-age: 259200). This is a defence against account-takeover attacks. If you try to install a brand-new svg-bbox release within that window, Bun fails with:

error: No version matching "svg-bbox" found for specifier "1.3.0"
       (blocked by minimum-release-age: 259200 seconds)

Workarounds — pick whichever fits your context:

# One-off install (recommended): override per-command
bun add svg-bbox@1.3.0 --minimum-release-age 0

# Persistent (all installs in this shell)
BUN_CONFIG_MINIMUM_RELEASE_AGE=0 bun add svg-bbox

# Or wait 3 days and bun installs the version normally

npm / pnpm / yarn do not implement this policy and are unaffected.

Postinstall scripts (what runs, and how to opt out)

svg-bbox itself ships no postinstall hook. However, two transitive dependencies do — and npm install runs them by default:

Dependency Postinstall does Disk impact
puppeteer Downloads a matching Chromium build ~150 MB
esbuild Downloads the platform-specific binary ~10 MB

If you would rather use your system Chrome (or you are on a sandboxed CI / air-gapped network and want zero downloads), skip them at install time:

# npm / pnpm / yarn: --ignore-scripts disables ALL postinstall hooks
npm install svg-bbox --ignore-scripts
pnpm install svg-bbox --ignore-scripts
yarn add svg-bbox --ignore-scripts

# bun: postinstall scripts are OFF by default unless the package is in
# `trustedDependencies`. svg-bbox lists puppeteer/sharp/esbuild there for
# convenience — you can drop them from your own package.json if you don't
# want the auto-download.

Then either point the toolkit at your system Chrome:

export SVG_BBOX_BROWSER_PATH="/path/to/chrome"   # or use the macOS/Linux paths svg-bbox auto-detects
export SVG_BBOX_SKIP_BROWSER_DOWNLOAD=1          # suppress any future auto-downloads

…or run the opt-in browser installer when you actually need it:

# Manually run the browser-install step that the postinstall would have run
npm exec -- svg-bbox install-browsers
# or
bunx svg-bbox install-browsers

The svg-bbox install-browsers script runs playwright install --with-deps chromium && npx puppeteer browsers install chrome. Nothing is downloaded unless you invoke it.

Clean uninstall

# npm                          bun
npm uninstall svg-bbox      ;  bun remove svg-bbox
npm uninstall -g svg-bbox   ;  bun remove -g svg-bbox

# Remove the auto-downloaded Chromium too (if you used it)
rm -rf ~/.cache/puppeteer
rm -rf ~/.cache/ms-playwright

Via CDN (browser, no build tools)

For direct browser usage, use the minified UMD build from a CDN:

<!-- Via unpkg (recommended) -->
<script src="https://unpkg.com/svg-bbox@latest/SvgVisualBBox.min.js"></script>

<!-- Via jsdelivr -->
<script src="https://cdn.jsdelivr.net/npm/svg-bbox@latest/SvgVisualBBox.min.js"></script>

<!-- Then use the global SvgVisualBBox object -->
<script>
  (async () => {
    await SvgVisualBBox.waitForDocumentFonts(document, 5000);
    const bbox =
      await SvgVisualBBox.getSvgElementVisualBBoxTwoPassAggressive('my-svg-id');
    console.log('BBox:', bbox);
  })();
</script>

CDN bundle sizes: original ~90 KB, minified ~25 KB (≈ 72 % reduction).

Clone from GitHub

git clone https://github.com/Emasoft/SVG-BBOX.git
cd SVG-BBOX
bun install     # or: npm install / pnpm install / yarn install

# Run tools directly from source
node sbb-getbbox.cjs myfile.svg

Note: Two equivalent command styles appear throughout the docs:

  • bunx sbb-getbbox / npx sbb-getbbox — when installed via a package manager
  • node sbb-getbbox.cjs — when running from a cloned checkout

Both forms are interchangeable. Help is identical: <tool> --help.

After installation, the following CLI commands are available:

Main Entry Point:

  • svg-bbox - Start here! Shows help and lists all available commands

Core Tools (Recommended):

  • sbb-getbbox - Compute visual bounding boxes
  • sbb-chrome-getbbox - Get bbox using Chrome's native .getBBox() (for comparison)
  • sbb-chrome-extract - Extract using Chrome's native .getBBox() (for comparison)
  • sbb-extract - List, extract, and export SVG objects
  • sbb-fix-viewbox - Fix missing viewBox/dimensions
  • sbb-svg2png - Render SVG to PNG
  • sbb-compare - Compare images visually (SVG/PNG, pixel-by-pixel)
  • sbb-test - Test library functions

Inkscape Integration Tools ⚠️ (For comparison only - see warnings below):

  • sbb-inkscape-text2path - Convert text to paths using Inkscape
  • sbb-inkscape-extract - Extract objects by ID using Inkscape
  • sbb-inkscape-svg2png - SVG to PNG export using Inkscape

⚠️ Accuracy Warning: Inkscape tools have known issues with font bounding boxes. Use core tools for production. Inkscape tools are for comparison purposes only.

Discovering tools — --help and --version

Every binary ships a rich, sectioned help screen. Each one starts with a branded header box (tool name, version, parent package, repository URL), followed by a multi-paragraph DESCRIPTION, USAGE syntax, real-world EXAMPLES, OPTIONS split into Common / Tool / Advanced groups, optional BATCH MODE and FBF.SVG SUPPORT sections (where applicable), an ENVIRONMENT variables table, EXIT CODES, free-form NOTES, and a LEARN MORE footer with links back to this repo.

sbb-compare --help
╔════════════════════════════════════════════════════════════════════════════╗
║                                                                            ║
║  sbb-compare  v1.3.1                                                       ║
║  Pixel-diff any pair of images: SVG-vs-SVG, SVG-vs-PNG, PNG-vs-PNG,        ║
║  FBF.SVG-vs-anything.                                                      ║
║                                                                            ║
║  Part of svg-bbox · https://github.com/Emasoft/SVG-BBOX                    ║
║                                                                            ║
╚════════════════════════════════════════════════════════════════════════════╝

DESCRIPTION
───────────
  Renders both inputs to bitmaps with headless Chromium and computes the
  per-pixel difference. Either side can be an SVG, a PNG, or an FBF.SVG ...
…
sbb-getbbox --version    # -> sbb-getbbox version 1.3.1

Every tool also exposes:

  • -h, --help — full help screen
  • -v, --version — version string
  • A consistent EXIT CODES table (see each tool's --help)

From Source

git clone https://github.com/Emasoft/SVG-BBOX.git
cd SVG-BBOX
bun install

Requirements

IMPORTANT: You need Node.js ≥ 24 and Chrome or Chromium installed.

⚠️ ONLY Chrome/Chromium are supported — other browsers have poor SVG support. This library uses headless Chrome via Puppeteer for measurements, and visual verification must use the same browser engine to match results.

After installing, Puppeteer will automatically download a compatible Chromium browser. Alternatively, you can use your system Chrome by setting the PUPPETEER_EXECUTABLE_PATH environment variable.


Platform Compatibility

✅ Fully cross-platform compatible:

  • Windows 10/11 - All CLI tools work natively (PowerShell, CMD, Git Bash)
  • macOS - All versions supported (Intel and Apple Silicon)
  • Linux - All major distributions (Ubuntu, Debian, Fedora, etc.)

Key features:

  • All file paths use Node.js path module (no hardcoded / or \ separators)
  • Platform-specific commands handled automatically (Chrome detection, file opening)
  • Works with file paths containing spaces on all platforms
  • Pure Node.js CLI tools (no bash scripts required)

Platform-specific notes:

Windows
  • Chrome/Chromium auto-detection works with default install locations
  • File paths with spaces are properly handled
  • Use PowerShell or CMD (no WSL required)
  • Git Bash also supported
# PowerShell example
sbb-getbbox "C:\My Files\drawing.svg"
macOS
  • Detects Chrome in /Applications/
  • Uses native open command for file viewing
  • Works on both Intel and Apple Silicon Macs
# macOS example
chmod +x node_modules/.bin/sbb-*  # Make executable (first time only)
sbb-getbbox ~/Documents/drawing.svg
Linux
  • Auto-detects google-chrome, chromium, chromium-browser
  • All standard Linux file paths supported
# Linux example
chmod +x node_modules/.bin/sbb-*  # Make executable (first time only)
sbb-getbbox /home/user/drawings/test.svg

Browser Configuration

svg-bbox uses Chrome/Chromium for accurate SVG measurements via Puppeteer. By default, it automatically detects and uses an available browser.

Environment Variables

Variable Purpose Example
SVG_BBOX_SKIP_BROWSER_DOWNLOAD Disable automatic Chrome download 1 or true
SVG_BBOX_BROWSER_PATH Use a specific browser executable /path/to/chrome
PUPPETEER_EXECUTABLE_PATH Puppeteer's native browser path override /path/to/chromium

Browser Detection Order

svg-bbox searches for browsers in this priority order:

  1. User-specified path (SVG_BBOX_BROWSER_PATH or PUPPETEER_EXECUTABLE_PATH)
  2. Puppeteer's bundled Chrome (most reliable, auto-downloaded on first use)
  3. System Chrome (installed via package manager or download)
  4. System Chromium (common on Linux distros like Debian/Ubuntu)

If no browser is found and auto-download is enabled, Puppeteer's Chrome is downloaded automatically.

Disabling Automatic Downloads

To prevent automatic browser downloads (useful for CI/CD, air-gapped systems, or when you prefer system browsers):

# Disable auto-download
export SVG_BBOX_SKIP_BROWSER_DOWNLOAD=1

# Then install a browser manually (see below)

Manual Browser Installation

macOS
# Via Homebrew
brew install --cask google-chrome
# or
brew install --cask chromium

# Specify custom path (if needed)
export SVG_BBOX_BROWSER_PATH="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
Linux (Debian/Ubuntu)
# Chrome
wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list'
sudo apt update && sudo apt install google-chrome-stable

# Or Chromium (often pre-installed)
sudo apt install chromium-browser

# Specify custom path (if needed)
export SVG_BBOX_BROWSER_PATH="/usr/bin/google-chrome"
Linux (Fedora/RHEL)
# Chrome
sudo dnf install google-chrome-stable

# Or Chromium
sudo dnf install chromium

# Specify custom path (if needed)
export SVG_BBOX_BROWSER_PATH="/usr/bin/google-chrome"
Linux (Arch)
# Chrome (AUR)
yay -S google-chrome

# Or Chromium
sudo pacman -S chromium

# Specify custom path (if needed)
export SVG_BBOX_BROWSER_PATH="/usr/bin/chromium"
Windows
# Via Chocolatey
choco install googlechrome
# or
choco install chromium

# Via winget
winget install Google.Chrome

# Specify custom path (if needed)
$env:SVG_BBOX_BROWSER_PATH = "C:\Program Files\Google\Chrome\Application\chrome.exe"

Or download directly from https://www.google.com/chrome/

FreeBSD/OpenBSD
# FreeBSD
pkg install chromium

# OpenBSD
pkg_add chromium

# Specify custom path
export SVG_BBOX_BROWSER_PATH="/usr/local/bin/chromium"

Using Puppeteer's Browser Installer

If you prefer Puppeteer's bundled browser (recommended for consistency):

# Download Chrome for Puppeteer
bunx puppeteer browsers install chrome
# or with npx:
npx puppeteer browsers install chrome

# This installs to ~/.cache/puppeteer/ and is auto-detected

Checking Browser Status

# Check which browser svg-bbox will use
node -e "console.log(require('svg-bbox/lib/ensure-browser.cjs').getBrowserStatus())"

Output example:

{
  "installed": true,
  "path": "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
  "source": "system-chrome",
  "downloadDisabled": false
}

What This Package Provides

1. Core Library

JavaScript library for accurate visual bounding box computation and for extracting individual frames from FBF.SVG animations. Three flavours ship in the npm package; the right one is picked automatically based on how you load it (see Choose your runtime for the full table):

File Used when… Contents
SvgVisualBBox.min.js <script src="…"> in a browser, served via unpkg / jsDelivr CDN Full UMD library, minified by Terser
SvgVisualBBox.js Browser bundlers, import 'svg-bbox' in Bun Same UMD, unminified for debugging
SvgVisualBBox.cjs require('svg-bbox') / import 'svg-bbox' in Node FBF helpers + DOM-bound stubs that throw helpful errors when called outside a browser
lib/fbf.cjs require('svg-bbox/fbf') (slimmer FBF-only entry) FBF.SVG helpers only

Available Functions (full library — browser, bundler, Bun):

  • getSvgElementVisualBBoxTwoPassAggressive(target, options) - Compute accurate visual bbox for any element
  • getSvgElementsUnionVisualBBox(targets[], options) - Union bbox for multiple elements
  • getSvgElementVisibleAndFullBBoxes(target, options) - Get both clipped (viewBox-respecting) and unclipped bounds
  • showTrueBBoxBorder(target, options) - Visual debugging overlay
  • waitForDocumentFonts(document, timeoutMs) - Wait for fonts before measuring
  • setViewBoxOnObjects(svgRootOrId, ids, options) - Reframe a viewBox around named objects
  • getSvgRootViewBoxExpansionForFullDrawing(svgRootOrId, options) - Compute viewBox padding to cover the full drawing
  • FBF.SVG support (also available in Node — pure string manipulation):
    • extractFbfFrame(svgString, frameNumber) - Pin one frame and return a renderable SVG
    • describeFbf(svgString) - Inspect the frame inventory
    • isFbfSvg(svgString) - Quick FBF detection

Capabilities:

  • Font-aware: Arabic, CJK, ligatures, RTL, textPath, custom fonts
  • Filter-safe: Blur, shadows, masks, clipping
  • Stroke-aware: Width, caps, joins, markers, patterns
  • FBF.SVG-aware: pin any frame of a svg2fbf animation before computing/rendering

2. CLI Tools

Tool
Source
Description Example Usage
Core Tools (Our Visual BBox Algorithm)
sbb-getbbox Get bbox info using our pixel-accurate visual algorithm sbb-getbbox drawing.svg
sbb-extract List/rename/extract/export SVG objects with visual catalog sbb-extract sprites.svg --list
sbb-svg2png Render SVG to PNG with accurate bbox; supports FBF.SVG frames via --fbf-frame sbb-svg2png input.svg output.png
sbb-fix-viewbox Repair missing/broken viewBox using visual bbox sbb-fix-viewbox broken.svg fixed.svg
sbb-compare Visual diff between images (SVG/PNG pixel comparison); supports FBF.SVG frames via --fbf-frame sbb-compare a.svg b.png --out-diff diff.png
sbb-test Test bbox accuracy across methods sbb-test sample.svg
Chrome Comparison Tools (Chrome's .getBBox())
sbb-chrome-getbbox Get bbox info using Chrome's .getBBox() sbb-chrome-getbbox drawing.svg
sbb-chrome-extract Extract using Chrome's .getBBox() sbb-chrome-extract file.svg --id obj1 --output out.svg
Inkscape Comparison Tools (Inkscape CLI)
sbb-inkscape-getbbox Get bbox info using Inkscape's query commands sbb-inkscape-getbbox drawing.svg
sbb-inkscape-extract Extract by ID using Inkscape sbb-inkscape-extract file.svg --id obj1
sbb-inkscape-text2path Convert text to paths using Inkscape sbb-inkscape-text2path input.svg output.svg
sbb-inkscape-svg2png SVG to PNG export using Inkscape sbb-inkscape-svg2png input.svg output.png

Naming Convention:

  • sbb-[function] = Our reliable visual bbox algorithm
  • sbb-chrome-[function] = Chrome's .getBBox() method (for comparison)
  • sbb-inkscape-[function] = Inkscape tools (for comparison)

Run npx svg-bbox or any tool with --help for detailed usage.

Note: The lib/cli-utils.cjs file is a library module, not a CLI launcher. Do not run it directly. Use the tools listed above instead.


🚀 Quickstart

1. See All Available Commands

npx svg-bbox

This displays help with all available tools and usage examples.


2. Render an SVG to PNG at the correct size

npx sbb-svg2png input.svg output.png --mode full --scale 4
  • Detects the full drawing extents.
  • Sets an appropriate viewBox.
  • Renders to PNG at 4 px per SVG unit.

Render specific frames from a Frame-By-Frame SVG (FBF.SVG)

sbb-svg2png knows about the FBF.SVG format produced by svg2fbf. Use --fbf-frame to snapshot any frame (or set of frames) without manually editing the SVG. The tool pins the PROSKENION <use xlink:href> to #FRAME0000N and drops the swap <animate> child before rendering, so you get exactly the frame you asked for.

# Single frame
npx sbb-svg2png anim.fbf.svg frame-007.png --fbf-frame 7

# Explicit list — auto-derived per-frame outputs
# (frames-FRAME00007.png, frames-FRAME00023.png, ...)
npx sbb-svg2png anim.fbf.svg frames.png --fbf-frame 7,23,87,345

# Inclusive range with a {n} placeholder for the bare frame number
# (thumb-1.png, thumb-2.png, ..., thumb-30.png)
npx sbb-svg2png anim.fbf.svg "thumb-{n}.png" --fbf-frame 1-30 --scale 2

# Mix lists and ranges
npx sbb-svg2png anim.fbf.svg out.png --fbf-frame 1-3,10,20-22

All other rendering options (--mode, --scale, --background, --margin, --width/--height, --jpg) apply to every pinned frame. Errors are clear when the input is not an FBF.SVG or any frame number is out of range.


3. Fix an SVG that has no viewBox / width / height

npx sbb-fix-viewbox broken.svg fixed/broken.fixed.svg
  • Computes the full visual drawing box.
  • Writes a new SVG with:
    • viewBox="x y width height"
    • Consistent width / height.

4. List all objects visually & generate a rename JSON

npx sbb-extract sprites.svg --list --assign-ids --out-fixed sprites.ids.svg

This produces:

  • sprites.objects.html — a visual catalog.
  • sprites.ids.svg — a version where all objects have IDs like auto_id_path_3.

Open sprites.objects.html in a browser to see previews and define new ID names.


5. Extract one object as its own SVG

npx sbb-extract sprites.renamed.svg \
  --extract icon_save icon_save.svg \
  --margin 5

This creates icon_save.svg sized exactly to the visual bounds of #icon_save (with 5 units of padding).


6. Export all objects as individual SVGs

npx sbb-extract sprites.renamed.svg \
  --export-all exported \
  --export-groups \
  --margin 2

Each object / group becomes its own SVG, with:

  • Correct viewBox
  • Includes <defs> for filters, patterns, markers
  • Ancestor transforms preserved

7. Library: SvgVisualBBox.js

Choose your runtime

Single-page cheatsheet — svg-bbox ships four physical files, three entry points (the package's exports map routes each runtime to the right one automatically), and 12 CLI bins. The table below is the canonical map: it tells you, for every realistic scenario, which file is loaded, what you actually get, and how to use it with a copy-pasteable one-liner.

# Scenario / Runtime Code you write File served / loaded What you get Status Notes
1 Browser via CDN (production, minified) <script src="https://unpkg.com/svg-bbox@latest/SvgVisualBBox.min.js"></script>
or <script src="https://cdn.jsdelivr.net/npm/svg-bbox@latest/SvgVisualBBox.min.js"></script>
then SvgVisualBBox.extractFbfFrame(svg, 7)
SvgVisualBBox.min.js window.SvgVisualBBox.* — full library (all bbox fns + FBF helpers) ✅ verified in Puppeteer Terser-minified UMD, ~29 KB. Both unpkg and jsDelivr serve this file from npm; pick whichever CDN you prefer (or pin a specific version with @1.2.1 in place of @latest).
2 Browser via CDN (debug, unminified) <script src="https://unpkg.com/svg-bbox@latest/SvgVisualBBox.js"></script>
or <script src="https://cdn.jsdelivr.net/npm/svg-bbox@latest/SvgVisualBBox.js"></script>
SvgVisualBBox.js Same as #1 but unminified, ~111 KB ✅ verified Useful when stepping through in DevTools
3 Browser via npm + bundler (Webpack, Vite, esbuild, Rollup, Parcel) import { extractFbfFrame, getSvgElementVisualBBoxTwoPassAggressive } from 'svg-bbox'; SvgVisualBBox.js (via exports."."."browser") Full library ✅ verified Bundler resolves the "browser" export condition
4 Bun (CJS or ESM) import { extractFbfFrame } from 'svg-bbox';
or const { extractFbfFrame } = require('svg-bbox');
SvgVisualBBox.js (via exports."."."bun") Full library — including DOM-bound bbox functions ✅ verified end-to-end (real node_modules install) Bun's UMD interop loads the global side-effect cleanly
5 Node.js (CJS) — FBF only const { extractFbfFrame } = require('svg-bbox');
extractFbfFrame(svgString, 7)
SvgVisualBBox.cjs (via exports."."."require") FBF helpers (work) + DOM-bound fns stubbed ✅ verified end-to-end (real node_modules install) Calling a DOM stub throws an actionable error pointing at scenario #7
6 Node.js (ESM) — FBF only import { extractFbfFrame } from 'svg-bbox';
or
import x from 'svg-bbox';
then x.extractFbfFrame(svgString, 7)
SvgVisualBBox.cjs (via exports."."."import") Same as #5 — both named and default imports work ✅ verified end-to-end (real node_modules install) Resolved through Node's CJS-from-ESM interop
7 Node.js (CJS or ESM) — bbox computation Spin up Puppeteer, inject the UMD into the page, call from page.evaluate(). See Pattern C below. SvgVisualBBox.js injected via addScriptTag({ path: require.resolve('svg-bbox') }) Full library running in headless Chrome ✅ verified (every shipped CLI tool uses this exact pattern) Required because getBBox()/<canvas>/font-loading only exist in a real browser
8 Slimmer FBF-only entry (any runtime, CJS or ESM) import { extractFbfFrame } from 'svg-bbox/fbf';
or require('svg-bbox/fbf')
lib/fbf.cjs FBF helpers only ✅ verified Smallest dependency surface; safe in pure-Node services that never need bbox
9 CLI tool — bbox npx sbb-getbbox drawing.svg --json sbb-getbbox.cjs (CJS bin) Bbox JSON for one or many SVGs ✅ verified Internally uses scenario #7
10 CLI tool — render npx sbb-svg2png anim.fbf.svg out.png --fbf-frame 7 sbb-svg2png.cjs (CJS bin) PNG/JPEG file at correct visual bounds ✅ verified Supports list/range syntax for FBF batches
11 CLI tool — extract / repair / compare / etc. npx sbb-extract sprites.svg --list, npx sbb-fix-viewbox broken.svg, npx sbb-compare a.svg b.png, … sbb-*.cjs (12 CLI bins total) See the Tools CLI Commands Usage section ✅ verified Every tool that takes one SVG accepts --fbf-frame N

Quick decision tree:

  • 🌐 Running in a browser (or behind a bundler that targets a browser)? → Scenarios 1, 2, 3 — use the full library directly.
  • ⚡ Running in Bun? → Scenario 4 — full library works via plain require/import.
  • 🟢 Running in plain Node and you need to pin an FBF.SVG frame? → Scenarios 5, 6, or 8 — pure-string helper, no Puppeteer needed.
  • 🟢 Running in plain Node and you need bbox computation? → Scenario 7 — Puppeteer injection (or just shell out to one of the CLI bins in scenarios 9–11).
  • 🛠️ Just want a command-line tool? → Scenarios 9–11 — npx sbb-… and you're done.

CDN URLs at a glance:

File unpkg jsDelivr Use case
SvgVisualBBox.min.js (minified UMD, ~29 KB) unpkg.com/svg-bbox/SvgVisualBBox.min.js cdn.jsdelivr.net/npm/svg-bbox/SvgVisualBBox.min.js Production browser <script> (scenarios 1, also injectable in scenario 7)
SvgVisualBBox.js (unminified UMD, ~111 KB) unpkg.com/svg-bbox/SvgVisualBBox.js cdn.jsdelivr.net/npm/svg-bbox/SvgVisualBBox.js Debug in DevTools (scenario 2)

Both CDNs serve every file in the published npm package — you can substitute @latest with any released version (e.g. @1.2.1) to pin. The Node-only files (SvgVisualBBox.cjs, lib/fbf.cjs, sbb-*.cjs) are also addressable on both CDNs but exist only for npm install workflows; loading them in a browser via <script> won't work because they're CommonJS.

Quick examples for each scenario

<!-- 1: Browser CDN (production) -->
<script src="https://unpkg.com/svg-bbox@latest/SvgVisualBBox.min.js"></script>
<script>
  const bbox = await SvgVisualBBox.getSvgElementVisualBBoxTwoPassAggressive('#my-element');
  const { svg } = SvgVisualBBox.extractFbfFrame(fbfString, 7);  // FBF too
</script>
// 3: Browser bundler (Vite/Webpack/etc.) — same call as the CDN version
import {
  getSvgElementVisualBBoxTwoPassAggressive,
  extractFbfFrame
} from 'svg-bbox';
// 4: Bun — full library directly
import {
  extractFbfFrame,
  getSvgElementVisualBBoxTwoPassAggressive
} from 'svg-bbox';
const { svg, frameId } = extractFbfFrame(fbfString, 7);
// 5/6: Node CJS or ESM — FBF works, bbox throws actionable error
const { extractFbfFrame } = require('svg-bbox'); // CJS
import { extractFbfFrame } from 'svg-bbox'; // ESM
const { svg, frameId, totalFrames } = extractFbfFrame(fbfString, 7);
// 7: Node bbox via Puppeteer injection
const puppeteer = require('puppeteer');
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setContent(`<html><body>${svgContent}</body></html>`);
await page.addScriptTag({ path: require.resolve('svg-bbox') });
const bbox = await page.evaluate(() =>
  SvgVisualBBox.getSvgElementVisualBBoxTwoPassAggressive('svg')
);
// 8: Slim FBF-only entry — works in every runtime, smallest surface
const { extractFbfFrame } = require('svg-bbox/fbf');
//   or:  import { extractFbfFrame } from 'svg-bbox/fbf';
# 9–11: CLI tools (every tool takes --fbf-frame N when given an FBF.SVG)
npx sbb-getbbox drawing.svg --json
npx sbb-svg2png anim.fbf.svg out.png --fbf-frame 7 --scale 4
npx sbb-extract sprites.svg --list
npx sbb-fix-viewbox broken.svg fixed.svg
npx sbb-compare reference.svg anim.fbf.svg --fbf-frame-b 7 --out-diff diff.png

Rule of thumb: if your code runs in a browser (or Bun, or a bundler that targets a browser), import 'svg-bbox' gives you everything. If your code runs in plain Node, you can still call extractFbfFrame() directly, but bbox computations need Puppeteer to spin up Chrome — that's how every shipped CLI tool does it.

Installation

<!-- CDN (minified UMD, recommended for production browser use) -->
<script src="https://unpkg.com/svg-bbox@latest/SvgVisualBBox.min.js"></script>

<!-- Or via npm — UMD source for debugging in a browser -->
<script src="./node_modules/svg-bbox/SvgVisualBBox.js"></script>
# Or install via npm / Bun for use under any of the runtimes above
bun add svg-bbox    # recommended — fastest, full UMD via require/import
npm install svg-bbox

🌐 Browser (embed via CDN mirrors)

You can use SvgVisualBBox.js directly in webpages for accurate bounding box computation and visual debugging.

<script src="https://unpkg.com/svg-bbox@latest/SvgVisualBBox.min.js"></script>
<script>
  (async () => {
    // Get accurate bounding box for any SVG element
    const bbox =
      await SvgVisualBBox.getSvgElementVisualBBoxTwoPassAggressive(
        '#myElement'
      );
    console.log(bbox); // {x: 10, y: 20, width: 100, height: 50}

    // Visual debugging - show border around true bounds
    const result = await SvgVisualBBox.showTrueBBoxBorder('#myElement');
    setTimeout(() => result.remove(), 3000);
  })();
</script>

Advanced Example with the showTrueBBoxBorder() function

<!DOCTYPE html>
<html>
  <head>
    <script src="https://unpkg.com/svg-bbox@latest/SvgVisualBBox.min.js"></script>
  </head>
  <body>
    <svg viewBox="0 0 200 100" width="400">
      <text id="greeting" x="100" y="50" text-anchor="middle" font-size="24">
        Hello SVG!
      </text>
    </svg>

    <script>
      (async () => {
        // Wait for fonts
        await SvgVisualBBox.waitForDocumentFonts();

        // Get accurate bounding box
        const bbox =
          await SvgVisualBBox.getSvgElementVisualBBoxTwoPassAggressive(
            '#greeting'
          );
        console.log('BBox:', bbox); // {x, y, width, height}

        // Show visual border for debugging
        const result = await SvgVisualBBox.showTrueBBoxBorder('#greeting');

        // Reframe viewBox to focus on element
        await SvgVisualBBox.setViewBoxOnObjects('svg', 'greeting', {
          aspect: 'stretch',
          margin: '10px'
        });

        // Remove border after 3 seconds
        setTimeout(() => result.remove(), 3000);
      })();
    </script>
  </body>
</html>

Node.js / Bun

bun add svg-bbox    # recommended - fastest
npm install svg-bbox

What you get from a plain require('svg-bbox') / import 'svg-bbox' depends on your runtime — see the Choose your runtime table above. The three patterns below cover every realistic scenario.

Pattern A — FBF.SVG frame extraction in any Node/Bun runtime

extractFbfFrame() is pure string manipulation, so it works without a browser, without Puppeteer, and without any DOM polyfill:

// CommonJS (Node or Bun)
const { extractFbfFrame } = require('svg-bbox');

const fs = require('fs');
const fbf = fs.readFileSync('animation.fbf.svg', 'utf8');
const { svg, frameId, frameNumber, totalFrames } = extractFbfFrame(
  fbf,
  /* 1-based */ 7
);
fs.writeFileSync('frame-7.svg', svg);
console.log(`Pinned ${frameId} (${frameNumber}/${totalFrames})`);
// ESM (Node or Bun)
import { extractFbfFrame } from 'svg-bbox';
import { readFileSync, writeFileSync } from 'node:fs';

const fbf = readFileSync('animation.fbf.svg', 'utf8');
const { svg } = extractFbfFrame(fbf, 7);
writeFileSync('frame-7.svg', svg);

The same call also works from 'svg-bbox/fbf' if you'd rather depend on the slimmer FBF-only entry point.

Pattern B — Bbox computation in Bun (works directly)

Bun's UMD interop loads the full library, so all bbox functions are usable the moment you require('svg-bbox') — but they still need a DOM at call time. You can run them in a happy-dom / jsdom context, or use Pattern C (Puppeteer) for production-grade results.

Pattern C — Bbox computation in Node (Puppeteer-injection pattern)

The bbox functions need real getBBox() / <canvas> / fonts — that only exists in a browser, so the supported pattern in Node is to inject the library into headless Chrome via Puppeteer. Every shipped CLI tool uses exactly this pattern; copy any of them as a worked example (e.g. sbb-getbbox.cjs).

const puppeteer = require('puppeteer');

const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setContent(`<html><body>${svgContent}</body></html>`);
await page.addScriptTag({ path: require.resolve('svg-bbox') });

const bbox = await page.evaluate(async () => {
  return await SvgVisualBBox.getSvgElementVisualBBoxTwoPassAggressive('svg');
});

If you require('svg-bbox') from Node and try to call a bbox function directly (without Puppeteer), it throws an actionable error pointing at this pattern — so a misuse fails loudly instead of returning undefined.

Functions of the SvgVisualBBox library

The library exposes all functions through the SvgVisualBBox namespace.

waitForDocumentFonts(document, timeoutMs)

Waits for fonts to be ready (or a timeout) before measuring text.

await SvgVisualBBox.waitForDocumentFonts(document, 8000);

getSvgElementVisualBBoxTwoPassAggressive(element, options)

Compute a visual bounding box for an element (including stroke, filters, etc.):

const bbox = await SvgVisualBBox.getSvgElementVisualBBoxTwoPassAggressive(
  element,
  {
    mode: 'unclipped', // ignore viewBox clipping when measuring
    coarseFactor: 3, // coarse sampling
    fineFactor: 24, // fine sampling
    useLayoutScale: true // scale based on layout size
  }
);

// bbox: { x, y, width, height } in SVG user units

getSvgElementVisibleAndFullBBoxes(svgElement, options)

Compute both:

  • visible – what’s inside the current viewBox.
  • full – the entire drawing, ignoring viewBox clipping.

Used by the fixer and renderer to choose between "full drawing" and "visible area inside the viewBox".

showTrueBBoxBorder(target, options) ⭐ NEW

Visual debug helper - Displays a dotted border overlay around any SVG element's true visual bounding box.

// Show border with auto-detected theme
const result = await SvgVisualBBox.showTrueBBoxBorder('#myText');

// Force dark theme for light backgrounds
const result = await SvgVisualBBox.showTrueBBoxBorder('#myPath', {
  theme: 'dark'
});

// Custom styling
const result = await SvgVisualBBox.showTrueBBoxBorder('#myElement', {
  borderColor: 'red',
  borderWidth: '3px',
  padding: 10
});

// Remove border
result.remove();

Features of showTrueBBoxBorder():

  • ✅ Auto-detects system dark/light theme
  • ✅ Force theme with theme: 'light' or 'dark' option
  • ✅ Works with all SVG types (inline, <object>, <iframe>, sprites, dynamic)
  • ✅ Non-intrusive overlay (doesn't modify SVG)
  • ✅ Follows SVG on scroll/resize
  • ✅ Easy cleanup with remove()

🔧 More Usage Examples

In HTML Page

<!DOCTYPE html>
<html>
  <head>
    <script src="https://unpkg.com/svg-bbox@latest/SvgVisualBBox.min.js"></script>
  </head>
  <body>
    <svg id="mySvg" viewBox="0 0 200 100">
      <rect id="myRect" x="10" y="10" width="50" height="30" fill="blue" />
    </svg>

    <script>
      (async () => {
        // Get bounding box
        const bbox =
          await SvgVisualBBox.getSvgElementVisualBBoxTwoPassAggressive(
            '#myRect'
          );
        console.log('BBox:', bbox);

        // Show debug border
        const border = await SvgVisualBBox.showTrueBBoxBorder('#myRect');
        setTimeout(() => border.remove(), 3000); // Remove after 3s
      })();
    </script>
  </body>
</html>

In JavaScript/Node.js

// Install: bun add svg-bbox puppeteer (or: npm install svg-bbox puppeteer)

const puppeteer = require('puppeteer');
const fs = require('fs');
const path = require('path');

async function getBBoxFromSVGFile(svgPath) {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  // Load SVG file
  const svgContent = fs.readFileSync(svgPath, 'utf-8');
  await page.setContent(`
    <!DOCTYPE html>
    <html><body>${svgContent}</body></html>
  `);

  // Inject SvgVisualBBox library
  const libPath = path.join(
    __dirname,
    'node_modules/svg-bbox/SvgVisualBBox.js'
  );
  await page.addScriptTag({ path: libPath });

  // Get bounding box
  const bbox = await page.evaluate(async () => {
    const svg = document.querySelector('svg');
    return await SvgVisualBBox.getSvgElementVisualBBoxTwoPassAggressive(svg);
  });

  await browser.close();
  return bbox;
}

// Usage
getBBoxFromSVGFile('input.svg').then((bbox) => {
  console.log('BBox:', bbox);
});

In TypeScript

// Install: bun add svg-bbox puppeteer @types/puppeteer (or: npm install ...)

import puppeteer from 'puppeteer';
import { readFileSync } from 'fs';
import { join } from 'path';

interface BBox {
  x: number;
  y: number;
  width: number;
  height: number;
}

async function getBBoxFromSVGFile(svgPath: string): Promise<BBox | null> {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  const svgContent = readFileSync(svgPath, 'utf-8');
  await page.setContent(`
    <!DOCTYPE html>
    <html><body>${svgContent}</body></html>
  `);

  const libPath = join(__dirname, 'node_modules/svg-bbox/SvgVisualBBox.js');
  await page.addScriptTag({ path: libPath });

  const bbox = await page.evaluate(async (): Promise<BBox | null> => {
    const svg = document.querySelector('svg');
    if (!svg) return null;
    return await (
      window as any
    ).SvgVisualBBox.getSvgElementVisualBBoxTwoPassAggressive(svg);
  });

  await browser.close();
  return bbox;
}

// Usage
getBBoxFromSVGFile('input.svg').then((bbox) => {
  console.log('BBox:', bbox);
});

In Backend (Node.js File Processing)

// Install: bun add svg-bbox (or: npm install svg-bbox)

const { execFileSync } = require('child_process');
const path = require('path');

// Get path to CLI tool
const sbbGetBBox = path.join(__dirname, 'node_modules/.bin/sbb-getbbox');
const sbbRender = path.join(__dirname, 'node_modules/.bin/sbb-svg2png');
const sbbFixer = path.join(__dirname, 'node_modules/.bin/sbb-fix-viewbox');

// Compute bounding box
function getBBox(svgFile) {
  const output = execFileSync(sbbGetBBox, [svgFile, '--json', 'bbox.json']);
  const result = JSON.parse(require('fs').readFileSync('bbox.json', 'utf-8'));
  return result[svgFile]['WHOLE CONTENT'];
}

// Fix viewBox
function fixViewBox(inputSvg, outputSvg) {
  execFileSync(sbbFixer, [inputSvg, outputSvg]);
}

// Render to PNG
function renderToPNG(svgFile, pngFile, width = 800) {
  execFileSync(sbbRender, [
    svgFile,
    pngFile,
    '--width',
    width.toString(),
    '--background',
    'transparent'
  ]);
}

// Usage
const bbox = getBBox('input.svg');
console.log('BBox:', bbox);

fixViewBox('broken.svg', 'fixed.svg');
renderToPNG('input.svg', 'output.png', 1200);

Using CLI Tools Programmatically

// All CLI tools can be used programmatically via child_process

const { execFile } = require('child_process');
const { promisify } = require('util');
const execFilePromise = promisify(execFile);

// Example: Extract object
async function extractObject(inputSvg, objectId, outputSvg) {
  const { stdout, stderr } = await execFilePromise('sbb-extract', [
    inputSvg,
    '--extract',
    objectId,
    outputSvg,
    '--margin',
    '10'
  ]);
  return { stdout, stderr };
}

// Example: Compare images (SVG or PNG)
async function compareImages(file1, file2) {
  const { stdout } = await execFilePromise('sbb-compare', [
    file1,
    file2,
    '--json'
  ]);
  return JSON.parse(stdout);
}

// Usage
extractObject('sprites.svg', 'icon_home', 'home.svg').then(() =>
  console.log('Extracted!')
);

compareImages('v1.svg', 'v2.svg').then((result) =>
  console.log('Difference:', result.diffPercentage + '%')
);

// Compare SVG against reference PNG
compareImages('output.svg', 'reference.png').then((result) =>
  console.log('Matches reference:', result.diffPercentage === 0)
);

📖 Library API Reference

getSvgElementVisualBBoxTwoPassAggressive(target, options)

Compute accurate visual bounding box for any SVG element.

Parameters:

  • target - CSS selector, ID string, or DOM element
  • options.mode - 'clipped' (respect viewBox) or 'unclipped' (full drawing)
  • options.coarseFactor - Coarse sampling resolution (default: 3)
  • options.fineFactor - Fine sampling resolution (default: 24)

Returns: {x, y, width, height} in SVG user units

getSvgElementsUnionVisualBBox(targets[], options)

Compute union bounding box of multiple elements.

Parameters:

  • targets[] - Array of CSS selectors, ID strings, or DOM elements
  • options - Same as above

Returns: {x, y, width, height}

getSvgElementVisibleAndFullBBoxes(target, options)

Get both clipped (viewBox-respecting) and unclipped bounds.

Returns: {visible: {x,y,width,height}, full: {x,y,width,height}}

showTrueBBoxBorder(target, options)

Visual debugging overlay showing true bounds.

Options: theme, borderColor, borderWidth, padding

Returns: Object with remove() method

waitForDocumentFonts(document, timeoutMs)

Wait for fonts to load before measuring text.

Default timeout: 8000ms

See API.md for comprehensive browser API documentation with examples for:

  • Computing accurate bounding boxes
  • Working with complex text and transforms
  • Handling multiple elements
  • Visual debugging with borders
  • Reframing viewBox to focus on objects
  • Theme customization
  • Error handling
  • Performance tips

Tools CLI commands usage

Renderer: sbb-svg2png.cjs

Render SVG → PNG using Chrome + SvgVisualBBox.

Syntax

node sbb-svg2png.cjs input.svg output.png \
  [--mode full|visible|element] \
  [--element-id someId] \
  [--scale N] \
  [--width W --height H] \
  [--background white|transparent|#rrggbb] \
  [--margin N] \
  [--fbf-frame N | LIST | RANGE] \
  [--jpg [--delete-png-after]] \
  [--batch <file>] \
  [--allow-paths <dirs> | --trusted-mode] \
  [--quiet | --verbose] \
  [--auto-open]

Security Options

By default, sbb-svg2png only allows access to files within the current working directory. These options control file access:

  • --allow-paths <dirs> - Allow access to additional directories (comma-separated)
    • Example: --allow-paths /tmp,/var/artifacts
  • --trusted-mode - Trust all file paths without CWD restriction
    • Use with caution: disables all path security checks
  • --auto-open - Open PNG in Chrome/Chromium after rendering

JPEG Conversion Options

  • --jpg - Also produce a JPEG version at 100% quality
    • PNG is always created first, then converted to JPEG
    • Both files are saved by default
  • --delete-png-after - Delete PNG after creating JPEG (requires --jpg)
    • Useful for batch processing to save disk space

Batch Processing

  • --batch <file> - Process multiple SVGs from a file list
    • Each line: input.svg or input.svg<TAB>output.png
    • Lines starting with # are comments
    • All rendering options apply to each file

Modes

  • --mode full

    • Ignore the SVG’s viewBox when measuring.
    • Render the entire drawing (full visual extent).
  • --mode visible (default)

    • Consider the viewBox as a clipping region.
    • Crop to the visible content inside the viewBox.
  • --mode element --element-id ID

    • Hide everything except the element with that ID.
    • Measure it visually and render a canvas just big enough for that element (+ margin).

FBF.SVG frame extraction (--fbf-frame)

sbb-svg2png recognises the FBF.SVG format — a "Frame-By-Frame SVG" where every animation frame is a self-contained <g id="FRAME0000N"> in <defs> and a single <use id="PROSKENION"> cycles through them via a discrete-mode <animate>. Use --fbf-frame to snapshot a specific frame (or group of frames) without manually mutating the source.

How it works: the tool pins PROSKENION's <use xlink:href> to #FRAME0000N and drops the swap <animate> child before rendering, so the screenshot is exactly the requested frame — independent of any SMIL timeline state.

Accepted forms (1-based, all values must be positive integers):

--fbf-frame 7              # one frame
--fbf-frame 7,23,87,345    # explicit list
--fbf-frame 1-5            # inclusive range
--fbf-frame 1-3,10,20-22   # any mix of lists and ranges

Output naming for multi-frame requests (and single frames with a placeholder):

Output path Frames Files written
out.png 7,23 out-FRAME00007.png, out-FRAME00023.png
out-{frame}.png 7 out-FRAME00007.png (literal id)
out-{n}.png 7 out-7.png (bare number)
thumb-{n}.png 1-30 thumb-1.png … thumb-30.png
out.png (1 frame) 7 out.png verbatim — same as v1.1.x

All other rendering options (--mode, --scale, --background, --margin, --width/--height, --jpg) apply to every pinned frame. Errors are clear and actionable when the input is not an FBF.SVG, when any frame is out of range, or when the syntax is malformed (inverted ranges, empty fragments, non-numeric values are caught at parse time before any browser launch).

For programmatic use, the same logic is exposed via the svg-bbox/fbf subpath export:

const { describeFbf, pinFrame } = require('svg-bbox/fbf');
//   or:  import { describeFbf, pinFrame } from 'svg-bbox/fbf';

const desc = describeFbf(svgString); // { isFbf, frames, padWidth, ... }
const { svg, frameId } = pinFrame(svgString, 7);
// `svg` is now a normal static SVG — feed to any renderer.

Available helpers from this entry point:

Function Purpose
describeFbf(svg) Returns { isFbf, frames, padWidth, minFrame, maxFrame, … }
pinFrame(svg, n) Returns { svg, frameId, frameNumber, totalFrames } (static SVG)
formatFrameId(n, padWidth) Format a 1-based number as FRAME0000N with zero-padding
resolveFrameId(desc, n) Look up the literal frame id for a 1-based number, or null
isFbfSvg(svg) Boolean shortcut — describeFbf(svg).isFbf

Examples

# Transparent PNG of what's actually visible in the viewBox
node sbb-svg2png.cjs map.svg map.png \
  --mode visible \
  --margin 10 \
  --background transparent

# Single FBF.SVG frame at high resolution
node sbb-svg2png.cjs anim.fbf.svg frame-007.png --fbf-frame 7 --scale 8

# Multi-frame thumbnail strip (auto-named outputs)
node sbb-svg2png.cjs anim.fbf.svg frames.png --fbf-frame 1,7,15,30

# Range render with a placeholder for the bare frame number
node sbb-svg2png.cjs anim.fbf.svg "thumb-{n}.png" --fbf-frame 1-30 --scale 2

Comparer: sbb-compare.cjs

Compare images visually using pixel-by-pixel comparison. Supports:

  • SVG vs SVG: Renders both to PNG and compares
  • PNG vs PNG: Direct pixel comparison (dimensions must match)
  • SVG vs PNG: Renders SVG at PNG's exact resolution, then compares
  • PNG vs SVG: Same as above (order doesn't matter)

Syntax

node sbb-compare.cjs file1 file2 [options]

Where file1 and file2 can be .svg or .png files.

Options

  • --out-diff <file> - Output diff PNG file (white=different, black=same)
  • --threshold <1-255> - Pixel difference threshold (default: 1)
    • Pixels differ if any RGBA channel differs by more than threshold/256
  • --alignment <mode> - How to align the two SVGs
    • origin - Align using respective SVG origins (0,0) [default]
    • viewbox-topleft - Align using top-left corners of viewBox
    • viewbox-center - Align using centers of viewBox
    • object:<id> - Align using coordinates of specified object ID
    • custom:<x>,<y> - Align using custom coordinates
  • --resolution <mode> - How to determine render resolution
    • viewbox - Use respective viewBox dimensions [default]
    • nominal - Use respective nominal resolutions
    • full - Use full drawing content (ignore viewBox)
    • scale - Scale to match larger SVG (uniform)
    • stretch - Stretch to match larger SVG (non-uniform)
    • clip - Clip to match smaller SVG
  • --meet-rule <rule> - Aspect ratio rule for 'scale' mode (default: xMidYMid)
  • --slice-rule <rule> - Aspect ratio rule for 'clip' mode (default: xMidYMid)
  • --scale <number> - Resolution multiplier for rendering (default: 4x)
  • --add-missing-viewbox - Force regenerate viewBox for SVGs without one
  • --aspect-ratio-threshold <number> - Maximum allowed aspect ratio difference (default: 0.001)
  • --timeout <ms> - Browser operation timeout in milliseconds (default: 30000)
  • --no-html - Do not open HTML report in browser (report still generated)
  • --headless - Alias for --no-html
  • --quiet - Minimal output - only prints diff percentage
  • --json - Output results as JSON
  • --verbose - Show detailed progress
  • --fbf-frame <N> - Pin any FBF.SVG input to frame N before comparison
  • --fbf-frame-a <N> - Pin frame N on the FIRST input only (overrides --fbf-frame for side A)
  • --fbf-frame-b <N> - Pin frame N on the SECOND input only (overrides --fbf-frame for side B)
  • --help - Display help text with usage examples
  • --version - Display version number

Security Options

By default, sbb-compare only allows access to files within the current working directory. These options control file access:

  • --allow-paths <dirs> - Allow access to additional directories (comma-separated)
    • Example: --allow-paths /tmp,/var/artifacts
  • --trusted-mode - Trust all file paths without CWD restriction
    • Use with caution: disables all path security checks

Understanding preserveAspectRatio Values

The --meet-rule and --slice-rule options accept SVG preserveAspectRatio alignment values. This diagram illustrates how different values affect alignment:

preserveAspectRatio Alignment Diagram
  • meet mode: Scales content to fit entirely within viewport (may have empty space)
    • xMinYMin - Align top-left
    • xMidYMid - Align center (default)
    • xMaxYMax - Align bottom-right
  • slice mode: Scales content to fill viewport (may crop content)
    • Same alignment options as meet mode

Examples

# SVG vs SVG comparison
node sbb-compare.cjs original.svg modified.svg

# PNG vs PNG comparison (dimensions must match exactly)
node sbb-compare.cjs screenshot1.png screenshot2.png --out-diff diff.png

# SVG vs PNG comparison (SVG rendered at PNG's resolution)
node sbb-compare.cjs design.svg reference.png --out-diff diff.png

# Compare with custom threshold (more tolerant)
node sbb-compare.cjs v1.svg v2.svg --threshold 5 --out-diff changes.png

# Align by viewBox centers, scale to match (SVG only)
node sbb-compare.cjs icon1.svg icon2.svg \
  --alignment viewbox-center \
  --resolution scale

# JSON output for automation
node sbb-compare.cjs test1.svg test2.svg --json

Note: For PNG vs PNG comparison, both images must have identical dimensions. For SVG vs PNG comparison, the SVG is automatically rendered at the PNG's exact resolution to ensure accurate pixel-by-pixel comparison.

FBF.SVG frame extraction (--fbf-frame)

sbb-compare recognises the FBF.SVG format and can compare any single frame of an FBF animation against a static SVG, a PNG, or another FBF frame. The targeted side is pinned to the requested frame (PROSKENION's <use xlink:href> is set to #FRAME0000N and the swap <animate> child is dropped) before rendering, so the comparison sees exactly that frame.

Flag Scope Behaviour
--fbf-frame N global — applies to any FBF input Errors loudly when neither input is FBF
--fbf-frame-a N side A only (overrides global) Errors when side A is not an FBF.SVG
--fbf-frame-b N side B only (overrides global) Errors when side B is not an FBF.SVG

Aspect-ratio enforcement is unchanged: the pinned SVG inherits the FBF's viewBox / width / height verbatim, so --aspect-ratio-threshold continues to apply to the pinned side. If the static reference doesn't match the FBF's aspect ratio, the comparison is rejected with a 100% difference, exactly as for any other SVG-vs-SVG mismatch.

# Compare a static reference SVG against frame 7 of an animation
node sbb-compare.cjs reference.svg anim.fbf.svg --fbf-frame-b 7

# Compare a rendered PNG against the same frame (PNG can be on either side)
node sbb-compare.cjs frame-7.png anim.fbf.svg --fbf-frame-b 7

# Regression-check an FBF against itself, both pinned to the same frame
node sbb-compare.cjs anim.fbf.svg anim.fbf.svg \
  --fbf-frame-a 5 --fbf-frame-b 5

# Diff between two different frames of the same animation
node sbb-compare.cjs anim.fbf.svg anim.fbf.svg \
  --fbf-frame-a 1 --fbf-frame-b 30 --out-diff frame-drift.png

Output

Returns:

  • Diff score percentage: (different pixels / total pixels) × 100

    This is an exact calculation of how many pixels differ between the two rendered PNGs. A pixel is counted as "different" if any of its RGBA channels (Red, Green, Blue, or Alpha) differs by more than the threshold. The score ranges from 0% (identical) to 100% (completely different).

  • Total pixels: Total number of pixels in the rendered image (width × height)

  • Different pixels: Count of pixels where any RGBA channel exceeds threshold

  • Diff PNG image: Visual representation (white = different, black = identical)

  • HTML comparison report (automatically generated and opened in browser)

    • Side-by-side SVG comparison with embedded images
    • ViewBox and resolution details for each SVG
    • Comparison settings summary
    • Visual diff PNG with percentage
    • Self-contained (can be shared without dependencies)

Fixer: sbb-fix-viewbox.cjs

Fix missing/inconsistent viewBox and sizes.

Features

ViewBox repair Fixes SVGs with missing or inconsistent viewBox, width, and height attributes. Uses pixel-accurate visual bounds to compute correct values.

Syntax

node sbb-fix-viewbox.cjs input.svg [output.svg]
  • If output.svg is omitted, writes input.fixed.svg.
  • Uses getSvgElementVisibleAndFullBBoxes to find the full drawing bbox.
  • Writes a new SVG that has:
    • viewBox="x y width height"
    • Reasonable width/height matching that aspect ratio.

Example

node sbb-fix-viewbox.cjs broken.svg fixed/broken.fixed.svg

BBox Calculator: sbb-getbbox.cjs

CLI utility for computing visual bounding boxes using canvas-based measurement.

Syntax

Single file:

node sbb-getbbox.cjs <svg-file> [object-ids...] [--ignore-vbox] [--sprite] [--json <file>]

Directory batch:

node sbb-getbbox.cjs --dir <directory> [--filter <regex>] [--sprite] [--json <file>]

List file:

node sbb-getbbox.cjs --list <txt-file> [--sprite] [--json <file>]

Features

  • Whole SVG bbox: Compute bbox for entire SVG content (respecting viewBox)
  • Multiple objects: Get bboxes for specific elements by ID
  • Full drawing mode: Use --ignore-vbox to measure complete drawing (ignoring viewBox clipping)
  • Sprite sheet detection: Use --sprite to automatically detect and process icon sprites/stacks
  • Batch processing: Process entire directories with optional regex filter
  • List files: Process multiple SVGs with per-file object IDs from a text file
  • JSON export: Save results as JSON for programmatic use
  • Auto-repair: Missing SVG attributes (viewBox, width, height, preserveAspectRatio) are computed

Examples

# Compute whole SVG bbox
node sbb-getbbox.cjs drawing.svg

# Compute specific elements
node sbb-getbbox.cjs sprites.svg icon_save icon_load icon_close

# Get full drawing (ignore viewBox)
node sbb-getbbox.cjs drawing.svg --ignore-vbox

# Auto-detect and process sprite sheet
node sbb-getbbox.cjs sprite-sheet.svg --sprite

# Batch process directory with filter
node sbb-getbbox.cjs --dir ./icons --filter "^btn_" --json buttons.json

# Process from list file
node sbb-getbbox.cjs --list process-list.txt --json output.json

Objects List File Format

Each line: <svg-path> [object-ids...] [--ignore-vbox]

# Process whole SVG content
path/to/icons.svg

# Process specific objects
path/to/sprites.svg icon1 icon2 icon3

# Get full drawing bbox (ignore viewBox)
path/to/drawing.svg --ignore-vbox

Sprite Sheet Detection

When using the --sprite flag with no object IDs specified, the tool automatically detects sprite sheets (SVGs used as icon stacks) and processes each sprite/icon separately.

Detection criteria:

  • Size uniformity - Coefficient of variation < 0.3 for widths, heights, or areas
  • Grid arrangement - Icons arranged in rows/columns with consistent spacing
  • Common naming patterns - IDs matching icon_, sprite_, symbol_, glyph_, or numeric patterns
  • Minimum count - At least 3 child elements

Example output:

🎨 Sprite sheet detected!
   Sprites: 6
   Grid: 2 rows × 3 cols
   Avg size: 40.0 × 40.0
   Uniformity: width CV=0.000, height CV=0.000
   Computing bbox for 6 sprites...

SVG: sprite-sheet.svg
├─ icon_1: {x: 5.00, y: 5.00, width: 40.00, height: 40.00}
├─ icon_2: {x: 80.00, y: 5.00, width: 40.00, height: 40.00}
├─ icon_3: {x: 150.00, y: 5.00, width: 40.00, height: 40.00}
└─ ... (remaining sprites)

Output Format

Console:

SVG: path/to/file.svg
├─ WHOLE CONTENT: {x: 0, y: 0, width: 100, height: 100}
├─ icon1: {x: 10, y: 10, width: 20, height: 20}
└─ icon2: {x: 50, y: 50, width: 30, height: 30}

JSON (with --json):

{
  "path/to/file.svg": {
    "WHOLE CONTENT": { "x": 0, "y": 0, "width": 100, "height": 100 },
    "icon1": { "x": 10, "y": 10, "width": 20, "height": 20 },
    "icon2": { "x": 50, "y": 50, "width": 30, "height": 30 }
  }
}

SVG Objects Extractor sbb-extract.cjs

A versatile tool for listing, renaming, extracting, and exporting SVG objects.

1️⃣ List mode — --list

node sbb-extract.cjs input.svg --list \
  [--assign-ids --out-fixed fixed.svg] \
  [--out-html list.html] \
  [--json]

What it does:

  • Scans the SVG for "objects":

    • g, path, rect, circle, ellipse,
    • polygon, polyline, text, image, use, symbol.
  • Automatically detects sprite sheets - identifies SVGs used as icon/sprite stacks and provides helpful tips.

  • Computes a visual bbox for each object.

  • Generates an HTML page:

    • Column #: row number (used in warnings).
    • Column OBJECT ID: current id (empty if none).
    • Column Tag: element name.
    • Column Preview: small <svg> using the object’s bbox and <use href="#id">.
    • Column New ID name: text input + checkbox for renaming.
  • With --assign-ids:

    • Objects without id receive auto IDs (auto_id_path_1, …).
    • If --out-fixed is given, a fixed SVG is saved with those IDs.

HTML extras:

  • Filters:

    • Regex filter (ID, tag, or group IDs).
    • Tag filter (only paths, only groups, etc.).
    • Group filter (only descendants of someGroupId).
    • Area filter (objects whose bbox intersects a given rectangle).
  • Live rename validation:

    • Valid SVG ID syntax: ^[A-Za-z_][A-Za-z0-9_.:-]*$
    • No collision with existing IDs in the SVG.
    • No collision with earlier rows’ new IDs.
    • Invalid rows:
      • Get a subtle red background.
      • Show a red warning message under the input.
    • “Save JSON with renaming” is disabled while any row is invalid.
  • JSON export:

    • Clicking “Save JSON with renaming” downloads a mapping file like:

      {
        "sourceSvgFile": "input.svg",
        "createdAt": "2025-01-01T00:00:00.000Z",
        "mappings": [
          { "from": "auto_id_path_3", "to": "icon_save" },
          { "from": "auto_id_g_5", "to": "button_primary" }
        ]
      }

2️⃣ Rename mode — --rename

Apply renaming rules from a JSON mapping.

node sbb-extract.cjs input.svg --rename mapping.json output.svg [--json]

Accepted JSON forms:

  • Full payload with mappings (as exported by HTML).
  • Bare array: [ { "from": "oldId", "to": "newId" } ].
  • Plain object: { "oldId": "newId", "oldId2": "newId2" }.

What happens:

  • For each mapping (in order):
    • Validates syntax & collisions.
    • If valid:
      • id="from" → id="to".
      • Updates href="#from" / xlink:href="#from" → #to.
      • Updates url(#from) → url(#to) in all attributes (fills, filters, masks, etc.).
    • Invalid mappings are skipped and reported (reason included).

3️⃣ Extract mode — --extract

Extract a single object into its own SVG.

node sbb-extract.cjs input.svg --extract someId output.svg \
  [--margin N] \
  [--include-context] \
  [--json]

Two modes:

  • Default (no --include-context) → pure cut-out:

    • Keeps only:
      • Target element.
      • Its ancestor groups.
      • <defs> (for filters, markers, etc.).
    • No siblings or overlays.
  • With --include-context → cut-out with context:

    • Copies all children of the root <svg> (so overlays & backgrounds stay).
    • Crops the root viewBox to the target object’s bbox (+ margin).
    • Good when you want to see the object under the same filters/overlays but cropped to its own rectangle.

4️⃣ Export-all mode — --export-all

Export every object (and optionally groups) as separate SVGs.

node sbb-extract.cjs input.svg --export-all out-dir \
  [--margin N] \
  [--export-groups] \
  [--batch <file>] \
  [--json]
  • --batch <file> - Process multiple SVGs (one path per line)

  • Objects considered:

    • path, rect, circle, ellipse,
    • polygon, polyline, text, image, use, symbol.
  • With --export-groups:

    • <g> groups are also exported.
    • Recursively exports children within groups.
    • Even nested groups get their own SVG.

Each exported SVG:

  • Has viewBox = bbox (+ margin).
  • Has matching width / height.
  • Contains <defs> from the original.
  • Includes the ancestor chain from the root to the object, with the object’s full subtree.

Inkscape Integration Tools: sbb-inkscape-* ⚠️

⚠️ IMPORTANT ACCURACY WARNING

The Inkscape-based tools (sbb-inkscape-*) are provided for completeness and comparison purposes only. They use Inkscape's rendering engine which has known issues with font bounding box calculations and may produce inaccurate results, especially for text elements.

For production use and accurate bounding box computation, always use the native svg-bbox tools:

  • sbb-svg2png.cjs - Native SVG rendering with accurate bbox
  • sbb-getbbox.cjs - Precise bounding box calculation
  • sbb-extract.cjs - Multi-tool with accurate visual bbox

These native tools use our custom algorithms that correctly handle font metrics and provide reliable, cross-platform results.


Specifying Custom Inkscape Path

All Inkscape tools support the --inkscape-path option to specify a custom Inkscape executable path. This is useful when:

  • Inkscape is not in your system PATH
  • You have multiple Inkscape versions installed
  • Using Inkscape from a non-standard location
# macOS App Bundle
node sbb-inkscape-getbbox.cjs drawing.svg \
  --inkscape-path /Applications/Inkscape.app/Contents/MacOS/inkscape

# Homebrew installation
node sbb-inkscape-getbbox.cjs drawing.svg \
  --inkscape-path /opt/homebrew/bin/inkscape

# Linux custom path
node sbb-inkscape-getbbox.cjs drawing.svg \
  --inkscape-path /usr/local/bin/inkscape

Available Inkscape Tools

0. sbb-inkscape-getbbox.cjs - Get BBox via Inkscape

Get bounding box information using Inkscape's query commands.

node sbb-inkscape-getbbox.cjs input.svg [options]

Options:

  • --all - Query all objects in the SVG
  • --inkscape-path <path> - Custom Inkscape executable path
  • --json - Output results as JSON

Examples:

# Get bbox for specific elements
node sbb-inkscape-getbbox.cjs drawing.svg element1 element2

# Get bbox for all elements
node sbb-inkscape-getbbox.cjs drawing.svg --all

# Use custom Inkscape path
node sbb-inkscape-getbbox.cjs drawing.svg --inkscape-path /opt/homebrew/bin/inkscape

1. sbb-inkscape-text2path.cjs - Convert Text to Paths

Convert text elements to path outlines using Inkscape.

node sbb-inkscape-text2path.cjs input.svg [options]

Options:

  • --output <file> - Output SVG file (default: <input>-paths.svg)
  • --batch <file> - Batch mode (one SVG path per line)
  • --overwrite - Overwrite output file if it exists
  • --skip-comparison - Skip automatic similarity check (faster)
  • --json - Output results as JSON
  • --help - Show help
  • --version - Show version

Example:

# Convert text to paths
node sbb-inkscape-text2path.cjs document.svg

# Convert with custom output
node sbb-inkscape-text2path.cjs document.svg document-paths.svg

# Batch convert multiple files
node sbb-inkscape-text2path.cjs --batch files.txt

# Skip automatic comparison (faster)
node sbb-inkscape-text2path.cjs document.svg --skip-comparison

# Overwrite existing files
node sbb-inkscape-text2path.cjs document.svg --overwrite
2. sbb-inkscape-extract.cjs - Extract Object by ID

Extract a single object from an SVG file by its ID.

node sbb-inkscape-extract.cjs input.svg --id <object-id> [options]

Options:

  • --id <id> - Object ID to extract (required)
  • --output <file> - Output SVG file (default: <input>_<id>.svg)
  • --margin <pixels> - Margin around extracted object in pixels
  • --help - Show help
  • --version - Show version

Example:

# Extract specific object
node sbb-inkscape-extract.cjs sprite.svg --id icon_home

# Extract with custom output
node sbb-inkscape-extract.cjs sprite.svg --id icon_home --output home.svg

# Extract with margin
node sbb-inkscape-extract.cjs sprite.svg --id icon_home --margin 10

⚠️ Note: For more reliable object extraction with accurate bounding boxes, use sbb-extract.cjs --extract instead.

3. sbb-inkscape-svg2png.cjs - SVG to PNG Export

Comprehensive PNG export with full control over all Inkscape parameters.

node sbb-inkscape-svg2png.cjs input.svg [options]

Dimension & Resolution Options:

  • --width <pixels>, --height <pixels> - Export dimensions
  • --dpi <dpi> - Export DPI (default: 96)
  • --margin <pixels> - Margin around export area

Export Area Options:

  • --area-drawing - Bounding box of all objects (default)
  • --area-page - Full SVG page/viewBox area
  • --area-snap - Snap to nearest integer px (pixel-perfect)
  • --id <object-id> - Export specific object by ID

Color & Quality Options:

  • --color-mode <mode> - Gray_1-16, RGB_8-16, GrayAlpha_8-16, RGBA_8-16
  • --compression <0-9> - PNG compression level (default: 6)
  • --antialias <0-3> - Antialiasing level (default: 2)

Background Options:

  • --background <color> - Background color (SVG color string)
  • --background-opacity <n> - Opacity: 0.0-1.0 or 1-255

Legacy File Handling:

  • --convert-dpi <method> - none/scale-viewbox/scale-document

Batch Processing:

  • --batch <file> - Process multiple files (one SVG path per line)

Other Options:

  • --help - Show help
  • --version - Show version

Examples:

# High-quality export with maximum compression
node sbb-inkscape-svg2png.cjs logo.svg \
  --width 1024 --height 1024 \
  --antialias 3 --compression 9

# Export with white background
node sbb-inkscape-svg2png.cjs document.svg \
  --area-page \
  --background white --background-opacity 1.0

# Grayscale export
node sbb-inkscape-svg2png.cjs drawing.svg --color-mode Gray_8

# Pixel-perfect export
node sbb-inkscape-svg2png.cjs pixel-art.svg --area-snap --dpi 96

# Batch export with shared settings
node sbb-inkscape-svg2png.cjs --batch icons.txt \
  --width 256 --height 256 --compression 9

⚠️ Note: For production use, prefer sbb-svg2png.cjs for accurate font rendering and bounding boxes.


Why Use Native Tools Instead?

Feature Inkscape Tools (sbb-inkscape-*) Native Tools (sbb-*)
Font bbox accuracy ❌ Known issues, often incorrect ✅ Accurate calculations
Text rendering ⚠️ Can vary by system ✅ Consistent results
Cross-platform ⚠️ Requires Inkscape install ✅ Works everywhere
Performance ⚠️ Slower (external process) ✅ Fast (native)
Best for Testing/comparison Production use

Recommendation: Use sbb-inkscape-* tools only when you need to:

  • Compare results with Inkscape's output
  • Test compatibility with Inkscape workflows
  • Convert text to paths (no native alternative yet)

For all other use cases, especially involving text or requiring accurate bounding boxes, always use the native sbb-* tools.


🧭 Renaming workflow with the HTML viewer

When the Extractor Tool shows the list of svg elements inside the svg file you can not only select the objects you want to extract, but also choose new object id names to rename them. Your choices will be saved in a json file from a button at the bottom of the html page, and you can exit the page and pass the json to the extractor at any time to extract the elements and also to rename the objects id of all elements inside the svg file.

A typical end‑to‑end workflow:

  1. Analyze the SVG & give everything an ID

    node sbb-extract.cjs sprites.svg \
      --list \
      --assign-ids \
      --out-fixed sprites.ids.svg
  2. Open the HTML catalog in Chrome/Chromium

    • Open sprites.objects.html in Chrome or Chromium ONLY.
    • ⚠️ DO NOT use Safari, Firefox, Edge, or any other browser!
    • Use filters:
      • Regex ^auto_id_ to show only auto-generated IDs.
      • Tag filter to see only <g> groups or only <path> elements.
      • Group filter to focus on one part of the drawing.
      • Area filter to focus on a specific region.
  3. Enter new IDs

    • In “New ID name”, type meaningful names (icon_save, logo_main, button_primary, …).
    • Tick the checkbox for rows you want to rename.
    • Fix any red rows:
      • Syntax issues.
      • ID already exists.
      • Duplicate new ID (lower row loses).
  4. Save JSON mapping

    • Click “Save JSON with renaming”.
    • This downloads sprites.rename.json.
  5. Apply renaming to an SVG

    node sbb-extract.cjs sprites.ids.svg \
      --rename sprites.rename.json \
      sprites.renamed.svg
  6. Extract or export with stable IDs

    # One object
    node sbb-extract.cjs sprites.renamed.svg \
      --extract icon_save icon_save.svg --margin 5
    
    # All objects
    node sbb-extract.cjs sprites.renamed.svg \
      --export-all exported --export-groups --margin 2

🎬 Companion Projects

svg2fbf — Frame-By-Frame SVG animations

svg2fbf is a sister tool that converts a sequence of static SVG frames (or a Lottie / CSS / SMIL animation) into a single self-contained FBF.SVG file: every frame is embedded as a <symbol> and the active frame is swapped via a discrete-mode <animate> driving a PROSKENION <use xlink:href>. The result is a normal SVG that plays in any browser — no external assets, no JavaScript, no canvas.

svg-bbox knows the FBF.SVG format natively. Every CLI tool that takes a single SVG input accepts --fbf-frame N to pin any 1-based frame before the operation runs:

Tool What --fbf-frame N does
sbb-getbbox Compute the bbox of frame N specifically
sbb-extract List / extract / export / rename objects as they appear at frame N
sbb-fix-viewbox Repair the viewBox using frame N as the visual reference
sbb-svg2png Render frame N (also accepts list / range — see below)
sbb-compare Pin a frame on either side of the diff (--fbf-frame-a/-b)
sbb-test Test bbox accuracy on frame N
sbb-chrome-getbbox Same as sbb-getbbox via Chrome's .getBBox()
sbb-chrome-extract Same as sbb-extract via Chrome's .getBBox()
sbb-inkscape-getbbox Same as sbb-getbbox via Inkscape (writes pinned frame to a temp file)
sbb-inkscape-extract Same as sbb-extract via Inkscape
sbb-inkscape-text2path Convert text→path on frame N
sbb-inkscape-svg2png Inkscape PNG render of frame N

sbb-svg2png and sbb-compare extend the syntax to accept lists and ranges (--fbf-frame 7,23,87, --fbf-frame 1-30); every other tool takes one frame number.

# Snapshot frame 7 of an FBF animation as a PNG
npx sbb-svg2png anim.fbf.svg frame-007.png --fbf-frame 7 --scale 4

# Render frames 1-30 to thumb-1.png ... thumb-30.png
npx sbb-svg2png anim.fbf.svg "thumb-{n}.png" --fbf-frame 1-30 --scale 2

# Bbox of frame 7 only (vs. the bbox of every frame combined)
npx sbb-getbbox anim.fbf.svg --fbf-frame 7 --json

# Pixel-diff frame 7 against a hand-authored reference
npx sbb-compare reference.svg anim.fbf.svg \
  --fbf-frame-b 7 --out-diff diff.png

No size limit applies to FBF.SVG inputs. A multi-hour 60 fps vector animation packs millions of frames into one file, and svg-bbox reads it without rejection (file-size checks in lib/security-utils.cjs are Infinity by design).

For programmatic use, the same helper is available everywhere — pick whichever matches your runtime (the package's exports map routes you to the right implementation automatically — see Choose your runtime for the full table):

Node CJS / Node ESM / Bun — main entry:

// Works in every JS runtime — same call, same result
const { extractFbfFrame } = require('svg-bbox');
//   or:  import { extractFbfFrame } from 'svg-bbox';

const { svg, frameId, frameNumber, totalFrames } = extractFbfFrame(
  svgContent,
  /* 1-based */ 7
);
// `svg` is the pinned-frame SVG, ready for any normal renderer.

Slimmer FBF-only entry point (same API, smaller dependency surface):

const { extractFbfFrame } = require('svg-bbox/fbf');
//   or:  import { extractFbfFrame } from 'svg-bbox/fbf';

Browser (CDN <script> tag):

<script src="https://unpkg.com/svg-bbox/SvgVisualBBox.min.js"></script>
<script>
  const { svg, frameId, totalFrames } = SvgVisualBBox.extractFbfFrame(
    svgString,
    /* 1-based */ 7
  );
  // Render `svg` into a canvas, <img>, or <object> — it's a normal SVG now.
</script>

Three implementations sit behind the same API, and which one you load is determined by the exports conditions in package.json:

Implementation Used by Why three?
lib/fbf.cjs (CJS) Every CLI tool, Node require('svg-bbox'), Node import 'svg-bbox', anyone using the svg-bbox/fbf subpath The single source of truth for Node-side FBF logic
SvgVisualBBox.js (UMD) Browser <script> tags, browser bundlers, Bun (bun export condition) Pure browser bundle; ships its own copy of the FBF logic so the browser bundle is self-contained
SvgVisualBBox.cjs (CJS shim) Node require('svg-bbox') and import 'svg-bbox' only Re-exports lib/fbf.cjs; stubs DOM-bound functions with actionable errors so misuse fails loudly

A regression test pins all three to the same fixture to keep them in lockstep.

Both expose the same shape:

extractFbfFrame(svgContent: string, frameNumber: number)
  → { svg: string, frameId: string, frameNumber: number, totalFrames: number }

Throws actionable errors when the input isn't FBF.SVG or the frame number is out of range — error messages name svg2fbf's URL and list the available frame range. Lower-level helpers (describeFbf, isFbfSvg, pinFrame, formatFrameId, resolveFrameId) are also exposed from both surfaces for tests and advanced consumers.

The FBF.SVG format spec lives in the svg2fbf repository.


🛟 Troubleshooting

💥 Puppeteer / browser fails to launch

  • Make sure Chrome or Chromium is installed.
  • If Puppeteer can't find a browser:
    • Try installing the default Chromium: bunx puppeteer browsers install chrome or npx puppeteer browsers install chrome.
    • Or set PUPPETEER_EXECUTABLE_PATH to your Chrome/Chromium binary.

Installing Chrome/Chromium:

  • macOS:

    brew install --cask google-chrome
    # or
    brew install --cask chromium
  • Windows:

  • Linux (Debian/Ubuntu):

    # Chromium (recommended - in official repos)
    sudo apt install chromium-browser
    # Chrome requires adding Google's apt repository first
    # See: https://www.google.com/chrome/ for instructions
  • Linux (Fedora/RHEL):

    sudo dnf install google-chrome-stable
    # or
    sudo dnf install chromium
  • Linux (Arch):

    # Chrome (from AUR - requires yay, paru, or similar AUR helper)
    yay -S google-chrome
    # or Chromium (in official repos)
    sudo pacman -S chromium
  • FreeBSD:

    pkg install chromium
  • OpenBSD:

    pkg_add chromium

⚠️ Wrong browser opened

Tools will ONLY open Chrome/Chromium via the --auto-open flag.

If Chrome/Chromium is not found, you'll see an error message with installation instructions.

CRITICAL: Other browsers have poor SVG support. This library uses headless Chrome for measurements, so visual verification must use the same browser engine.

🖋 Fonts look wrong / text bbox is off

  • The headless browser must be able to load the fonts:
    • If you use web fonts (@font-face), check that the URLs are reachable.
    • If you rely on system fonts, install them on the machine running the scripts.
  • For maximum accuracy, the tools call SvgVisualBBox.waitForDocumentFonts before sampling; still, flaky font hosting can cause issues.

🖼 External images not showing

  • <image> href/xlink:href URLs must be reachable from the headless browser.
  • Local file URLs might need adjustments (file:// vs relative paths).
  • Some environments may block remote HTTP requests (e.g., firewalls, CI restrictions).

🐢 Very large or complex SVGs are slow

  • The sampling is intentionally aggressive for accuracy.
  • If you fork the toolkit and customize SvgVisualBBox.js, you can reduce:
    • coarseFactor / fineFactor
    • Or skip some extra safety margins
  • For bulk processing, consider:
    • Running on a powerful machine.
    • Splitting SVGs into smaller logical parts.

📐 BBox doesn't match your expectations

  • Double-check whether you want:
    • Full drawing (ignore viewBox) → use “full” mode.
    • Only visible area (respect viewBox clipping).
    • Only one object (via extract or element mode).
  • Remember that filters and strokes can extend far beyond the underlying path.

🤝 Contributing

PRs, issues, and ideas are welcome!

  • Found an SVG that breaks the visual bbox heuristics?
  • Have a nasty filter / font combo that behaves oddly?
  • Want a new CLI mode or integration?

Open an issue with a minimal test SVG and a short description of what you expected vs what you saw. Read the CONTRIBUTING file in the repo to know more.


📝 Changelog

See CHANGELOG.md for a detailed history of changes, new features, bug fixes, and breaking changes in each release.

The changelog follows Keep a Changelog format and is automatically generated using git-cliff.


📄 License

This project is licensed under the MIT License.

You’re free to:

  • Use it in commercial and non-commercial projects.
  • Modify and distribute it.
  • Fork it and build your own specialized tooling.

See the `LICENSE` file for full details.

Releases

Packages

Used by

Contributors

Languages