A fast, lossless SVG optimizer that removes unnecessary data and produces clean, lightweight vector graphics.
Project description
SVG Polish
A fast, lossless, type-safe SVG optimizer for Python.
svg_polish shrinks SVG files — strips editor metadata, collapses redundant attributes, dedups gradients, optimises path data and transforms — while guaranteeing that the output renders identically to the input. The library is fully typed (py.typed), thread-safe, secure-by-default against XML attacks, and ships with a single short API: optimize().
Why SVG Polish
| Before | After |
|---|---|
style="fill:#ff0000;stroke:#000000" |
fill="#f00" |
| Inkscape / Sketch / Adobe namespaces | Removed |
| Verbose path commands | Collapsed and rounded |
| Duplicate gradients | Deduplicated |
| Hostile XXE / billion-laughs payload | Rejected before parsing |
Install
Requires Python 3.10+.
pip install svg-polish
The install is pure Python and depends only on defusedxml. A
svg-polish[fast] extra is reserved for the v1.x lxml-backed XML
engine (~3–5× faster on large files); v1.0 ships only the
defusedxml.minidom backend.
Quick start
Python
from svg_polish import optimize
optimized = optimize('<svg xmlns="http://www.w3.org/2000/svg">…</svg>')
That's the whole pitch. Read from disk, write to disk:
from pathlib import Path
from svg_polish import optimize, optimize_path
with open("input.svg", encoding="utf-8") as f:
svg = f.read()
optimized = optimize(svg)
with open("output.svg", "w", encoding="utf-8") as f:
f.write(optimized)
# or, in one line via the path-aware helper:
Path("output.svg").write_text(optimize_path("input.svg"), encoding="utf-8")
Pass OptimizeOptions for tuning:
from svg_polish import optimize, OptimizeOptions
opts = OptimizeOptions(
digits=3,
shorten_ids=True,
enable_viewboxing=True,
strip_comments=True,
)
optimized = optimize(svg, opts)
For metrics, use optimize_with_stats:
from svg_polish import optimize_with_stats
result = optimize_with_stats(svg)
print(f"saved {result.saved_bytes} B ({result.saved_ratio:.1%}) in {result.duration_ms:.1f} ms")
For async web frameworks:
from svg_polish import optimize_async
async def handler(svg: str) -> str:
return await optimize_async(svg)
Command line
svg-polish -i input.svg -o output.svg
cat input.svg | svg-polish > output.svg
svg-polish -i input.svg -o output.svgz # gzip-compressed output
Aggressive settings:
svg-polish -i input.svg -o output.svg \
--enable-viewboxing \
--enable-id-stripping \
--enable-comment-stripping \
--shorten-ids \
--indent=none
Run svg-polish --help for the full flag list.
Public API
| Symbol | Purpose |
|---|---|
optimize(svg, options=None) |
Canonical entry point. Alias of optimize_string. |
optimize_string(svg, options=None) |
str/bytes in, str out. |
optimize_bytes(svg, options=None) |
bytes in, UTF-8 bytes out. |
optimize_path(path, options=None) |
Read from a filesystem path. |
optimize_async(svg, options=None) |
await-able wrapper via asyncio.to_thread. |
optimize_with_stats(svg, options=None) |
Returns an OptimizeResult with metrics. |
OptimizeOptions |
Frozen dataclass — the only configuration shape. |
OptimizeResult |
Optimised SVG + stats + duration. |
ScourStats |
Per-pass counters. |
Exceptions: SvgPolishError (base) → SvgParseError, SvgPathSyntaxError, SvgTransformSyntaxError, SvgOptimizeError, SvgSecurityError, InvalidOptionError. See docs/api.md for the full reference.
What it does
- Removes editor metadata (Inkscape, Sodipodi, Illustrator, Sketch).
- Strips default attribute values and empty attributes.
- Converts colors to the shortest equivalent form.
- Deduplicates
<linearGradient>/<radialGradient>definitions. - Collapses
<g>wrappers and merges sibling groups. - Optimises
<path>ddata (relative coords, h/v/s shortcuts, segment merging). - Optimises
transform,patternTransform,gradientTransform. - Reduces numeric precision to a configurable digit count.
- Optionally shortens IDs, strips comments, converts to viewBox, embeds rasters.
- Custom serialiser produces tight, deterministic output.
All passes are lossless by default — see docs/performance.md for the opt-in decimal_engine="float" mode that trades reproducibility for ~3-5× faster numeric arithmetic.
Security
Inputs are parsed through defusedxml, so XML external entity attacks, billion-laughs, and external DTD fetches are rejected before they touch the optimiser. Inputs over 100 MB are refused by default. See SECURITY.md for the threat model and docs/security.md for usage patterns.
Documentation
docs/api.md— Python API reference.docs/architecture.md— module layout, layering, pipeline.docs/performance.md— tuning guide and benchmarks.docs/security.md— usage patterns for untrusted input.docs/cli.md— command-line reference.docs/configuration.md—OptimizeOptionsfield-by-field.
Development
git clone https://github.com/g-battaglia/svg_polish.git
cd svg_polish
uv sync
uv run poe test # full test suite (~660 tests)
uv run poe test-cov # with coverage HTML report
uv run poe lint # ruff lint
uv run poe format # ruff format
uv run poe typecheck # mypy strict
uv run poe check # all of the above
uv run poe bench # save a performance baseline
uv run poe bench-compare # compare current run against the baseline
Origin
svg_polish is a fork of Scour, originally created by Jeff Schiller and Louis Simard in 2010, later maintained by Tobias Oberstein and Patrick Storz. Upstream Scour has been dormant since August 2021. This v1.0 release is a ground-up modernisation:
- Python 3.10+ only; no
six, no Python 2 compatibility shims. - Single typed public surface (
OptimizeOptions,OptimizeResult,optimize_*). - Modular passes (one file per transformation) instead of a 4 700-line monolith.
- Thread-safe
Decimalprecision contexts. - Secure-by-default XML parsing with
defusedxml. - 100% line coverage,
mypy --strictclean,ruffclean.
License
Apache License 2.0 — see LICENSE.
svg_regex.py is derived from code by Enthought, Inc. (BSD 3-Clause). Full attribution in NOTICE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file svg_polish-1.0.0.tar.gz.
File metadata
- Download URL: svg_polish-1.0.0.tar.gz
- Upload date:
- Size: 210.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
56df761f7f0beb7c41060616e7eb5ddc990ce78e4a3323f04d62790ab431273d
|
|
| MD5 |
14b5ab2e35442d3b3c339f04cb13b6df
|
|
| BLAKE2b-256 |
7dc5e7987aa81660268e7cc64db5360ca28ef6e729cd225a28443d9da246a2ec
|
File details
Details for the file svg_polish-1.0.0-py3-none-any.whl.
File metadata
- Download URL: svg_polish-1.0.0-py3-none-any.whl
- Upload date:
- Size: 122.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a513b3f584b64b293c2535d0231eb8bb27e977f193f8f1e5f59cdf7fc59ae61d
|
|
| MD5 |
01718a40aefaec5872f4e96226a4f90e
|
|
| BLAKE2b-256 |
593e3de48cd6c5c0c94924fb28127bd392ba99175f8a3f03bc43d9eb694bd61c
|