svg-bbox
v1.3.1
Published
A set of tools to compute and to use a SVG bounding box you can trust (as opposed to the unreliable .getBBox() scourge)
Maintainers
Readme
🎬 New in v1.2: native support for FBF.SVG frame-by-frame animations (produced by
svg2fbf) — pass--fbf-frame Nto 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()
- Installation
- Platform Compatibility
- Browser Configuration
- What This Package Provides
- Quickstart
- More Usage Examples
- Library API Reference
- Tools CLI Commands Usage
- Renaming Workflow with the HTML Viewer
- Companion Projects
- Troubleshooting
- Contributing
- License
The Problem with .getBBox()
The native SVG .getBBox() method is fundamentally broken:
| Feature | .getBBox() | SvgVisualBBox |
| --------------------------------------- | ------------ | ----------------------------- |
| Filters (blur, shadows, glows) | :x: Ignored | :white_check_mark: Measured |
| Stroke width | :x: Ignored | :white_check_mark: Included |
| Complex text (ligatures, RTL, textPath) | :x: Wrong | :white_check_mark: Accurate |
| <use>, masks, clipping paths | :x: Fails | :white_check_mark: Works |
| Transformed elements | :x: Garbage | :white_check_mark: Correct |
| Cross-browser consistency | :x: Varies | :white_check_mark: 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 BBox | Chrome .getBBox() | SvgVisualBBox |
| | | |
| ❌ WRONG | ❌ WRONG | ✅ CORRECT |
| (svg file here) | (svg file here) | (svg file here) |
| Width: 554pxHeight: 379pxUndersized by ~48% | Width: 999pxHeight: 301pxMissing ~78px of stroke | Width: 1077pxHeight: 379pxIncludes 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.svgGlobal 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
# …etcLocal 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.svgMixing 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-bboxFor 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-bboxBun'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 [email protected] --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 normallynpm / 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-browsersThe 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-playwrightVia 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.svgNote: Two equivalent command styles appear throughout the docs:
bunx sbb-getbbox/npx sbb-getbbox— when installed via a package managernode sbb-getbbox.cjs— when running from a cloned checkoutBoth 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 boxessbb-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 objectssbb-fix-viewbox- Fix missing viewBox/dimensionssbb-svg2png- Render SVG to PNGsbb-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 Inkscapesbb-inkscape-extract- Extract objects by ID using Inkscapesbb-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.1Every 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 installRequirements
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
pathmodule (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:
- 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"- Detects Chrome in
/Applications/ - Uses native
opencommand 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- 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.svgBrowser 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:
- User-specified path (
SVG_BBOX_BROWSER_PATHorPUPPETEER_EXECUTABLE_PATH) - Puppeteer's bundled Chrome (most reliable, auto-downloaded on first use)
- System Chrome (installed via package manager or download)
- 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
# 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"# 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"# 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"# 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"# 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
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-detectedChecking 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 elementgetSvgElementsUnionVisualBBox(targets[], options)- Union bbox for multiple elementsgetSvgElementVisibleAndFullBBoxes(target, options)- Get both clipped (viewBox-respecting) and unclipped boundsshowTrueBBoxBorder(target, options)- Visual debugging overlaywaitForDocumentFonts(document, timeoutMs)- Wait for fonts before measuringsetViewBoxOnObjects(svgRootOrId, ids, options)- Reframe a viewBox around named objectsgetSvgRootViewBoxExpansionForFullDrawing(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 SVGdescribeFbf(svgString)- Inspect the frame inventoryisFbfSvg(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
svg2fbfanimation before computing/rendering
2. CLI Tools
| Tool | Source | Description | Example Usage |
| ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | -------------------------------------------------------- |
| Core Tools (Our Visual BBox Algorithm) | | | |
| sbb-getbbox | source | Get bbox info using our pixel-accurate visual algorithm | sbb-getbbox drawing.svg |
| sbb-extract | source | List/rename/extract/export SVG objects with visual catalog | sbb-extract sprites.svg --list |
| sbb-svg2png | source | Render SVG to PNG with accurate bbox; supports FBF.SVG frames via --fbf-frame | sbb-svg2png input.svg output.png |
| sbb-fix-viewbox | source | Repair missing/broken viewBox using visual bbox | sbb-fix-viewbox broken.svg fixed.svg |
| sbb-compare | source | 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 | source | Test bbox accuracy across methods | sbb-test sample.svg |
| Chrome Comparison Tools (Chrome's .getBBox()) | | | |
| sbb-chrome-getbbox | source | Get bbox info using Chrome's .getBBox() | sbb-chrome-getbbox drawing.svg |
| sbb-chrome-extract | source | Extract using Chrome's .getBBox() | sbb-chrome-extract file.svg --id obj1 --output out.svg |
| Inkscape Comparison Tools (Inkscape CLI) | | | |
| sbb-inkscape-getbbox | source | Get bbox info using Inkscape's query commands | sbb-inkscape-getbbox drawing.svg |
| sbb-inkscape-extract | source | Extract by ID using Inkscape | sbb-inkscape-extract file.svg --id obj1 |
| sbb-inkscape-text2path | source | Convert text to paths using Inkscape | sbb-inkscape-text2path input.svg output.svg |
| sbb-inkscape-svg2png | source | SVG to PNG export using Inkscape | sbb-inkscape-svg2png input.svg output.png |
Naming Convention:
sbb-[function]= Our reliable visual bbox algorithmsbb-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.cjsfile 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-bboxThis 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-22All 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.svgThis produces:
sprites.objects.html— a visual catalog.sprites.ids.svg— a version where all objects have IDs likeauto_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 5This 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 2Each 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';orimport 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.pngRule 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-bboxWhat 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 unitsgetSvgElementVisibleAndFullBBoxes(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 elementoptions.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 elementsoptions- 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
