SVG Path Editor Documentation#
A small, high-precision library for editing, transforming, and optimizing SVG paths in Python.
It is a port of svg-path-editor-lib 1.0.3 to Python with additional improvements:
Decimal-based geometry: all coordinates are stored as
decimal.Decimal, with high-precision SymPy-based computations where appropriate. This preserves the decimal values stored in an SVG path and avoids the binary round-off errors that occur withfloat.In-place and out-of-place operations: most geometric operations are available in a mutating (
scale,rotate, …) and a non-mutating (scaled,rotated, …) variant.list-like path modification API: path-level manipulations (insert, remove, change type, …) are exposed as methods on
SvgPath.Typed and documented: extensive type hints and docstrings for good IDE support and static analysis.
Quick Start#
Basic Usage#
A good place to start is to parse an SVG path string into an
SvgPath and print it:
from svg_path_editor import SvgPath, change_path_origin, optimize_path, reverse_path
path = SvgPath("M-15 14s5 7.5 15 7.5 15-7.5 15-7.5 z")
# SvgPath implements __str__ with fairly readable (non-minified) output
# M -15 14 s 5 7.5 15 7.5 s 15 -7.5 15 -7.5 z
print(path)
# Custom decimals and minified output (decimals=None, minify=False by default)
# M-15 14s5 7.5 15 7.5 15-7.5 15-7.5z
print(path.as_string(decimals=1, minify=True))
# SvgPath also implements __format__, with m denoting minify=True
print(f"{path:.2m} or {path:m.2}")
Geometric Operations#
Geometric operations are available in both out-of-place and in-place variants.
Out-of-place#
path = SvgPath("M-15 14s5 7.5 15 7.5 15-7.5 15-7.5 z")
# Out-of-place scale
# M -30 28 s 10 15 30 15 s 30 -15 30 -15 z
print(path.scaled(kx=2, ky=2))
# Out-of-place translate
# M -14 14.5 s 5 7.5 15 7.5 s 15 -7.5 15 -7.5 z
print(path.translated(dx=1, dy=0.5))
# Out-of-place rotate around (0, 0)
# M -14 -15 s -7.5 5 -7.5 15 s 7.5 15 7.5 15 z
print(path.rotated(ox=0, oy=0, degrees=90))
In-place#
path = SvgPath("M-15 14s5 7.5 15 7.5 15-7.5 15-7.5 z")
# In-place scale
# M -30 28 s 10 15 30 15 s 30 -15 30 -15 z
path.scale(kx=2, ky=2)
print(path)
# In-place translate
# M -29 28.5 s 10 15 30 15 s 30 -15 30 -15 z
path.translate(dx=1, dy=0.5)
print(path)
# In-place rotate
# M -28.5 -29 s -15 10 -15 30 s 15 30 15 30 z
path.rotate(ox=0, oy=0, degrees=90)
print(path)
Absolute vs. Relative Commands#
Commands can be stored as either absolute (M, L, C, …) or
relative (m, l, c, …). You can convert between equivalent
representations, either in-place or out-of-place:
path = SvgPath("M-15 14s5 7.5 15 7.5 15-7.5 15-7.5 z")
# In-place: `SvgPath.relative` mutates the instance
absolute = path.clone()
absolute.relative = False
# M -15 14 S -10 21.5 0 21.5 S 15 14 15 14 Z
print(absolute)
# Out-of-place: `SvgPath.with_relative()` returns a new path
relative = path.with_relative(True)
# m -15 14 s 5 7.5 15 7.5 s 15 -7.5 15 -7.5 z
print(relative)
Path Modification#
SvgPath exposes methods that modify the structure of a path in
place, including parts of the list API:
from svg_path_editor import Point, SvgPath
from svg_path_editor.svg import QuadraticBezierCurveTo
path = SvgPath("M0 0L10 0V10Z")
# Deep copy
clone = path.clone()
# M 0 0 L 10 0 V 10 Z
print(clone)
# In-place removal of the `L` command
path.remove(path.path[1])
# M 0 0 V 10 Z
print(path)
# In-place insertion of a quadratic Bézier curve where the `L` command was
path.insert(1, QuadraticBezierCurveTo([5, -5, 10, 0], relative=False))
# M 0 0 Q 5 -5 10 0 V 10 Z
print(path)
# In-place command type change from `V` to `L` (equivalent, but longer)
path.change_type(2, "L")
# M 0 0 Q 5 -5 10 0 L 10 10 Z
print(path)
# In-place move of a particular point
path.set_location(path.target_locations[-2], to=Point(5, 10))
# M 0 0 Q 5 -5 10 0 L 5 10 Z
print(path)
# The clone is unaffected by these changes
print(clone)
Higher-Level Path Operations#
Higher-level operations work out-of-place as well:
from svg_path_editor import SvgPath, change_path_origin, reverse_path
path = SvgPath("M-15 14s5 7.5 15 7.5 15-7.5 15-7.5 z")
# Reverse path direction
# M 15 14 S 10 21.5 0 21.5 S -15 14 -15 14 Z
print(reverse_path(path))
# Change the origin (starting command) within a subpath
# M 0 21.5 c 10 0 15 -7.5 15 -7.5 L -15 14 s 5 7.5 15 7.5
print(change_path_origin(path, new_origin_index=2))
Decimal-Based Geometry#
Internally, all coordinates and numeric parameters are stored as
decimal.Decimal:
Constructors and geometric methods accept
int,float,str, orDecimal; values are converted toDecimalimmediately.Arithmetic (translation, scaling, rotation, etc.) is performed in terms of
Decimalto retain the decimal representation in an SVG path and avoid introducing binary round-off errors.The decimal precision can be controlled via Python’s
decimalcontext.
from decimal import localcontext
from svg_path_editor import SvgPath
path = SvgPath("M0 0h10v10z")
# Default precision: 28 places
# Rotation computed with SymPy for high-precision trigonometric functions
rotated = path.rotated(0, 0, -45)
# M 0 0 l 7.071067811865475244008443621 -7.071067811865475244008443621 l 7.071067811865475244008443621 7.071067811865475244008443621 z
print(rotated)
# Precision can be reduced when printing
# M 0 0 l 7.07107 -7.07107 l 7.07107 7.07107 z
print(f"{rotated:.5}")
# The precision can be controlled using `getcontext`/`localcontext`
# Since `Decimal` is a floating-point format, the precision specifies the overall
# number of digits, not just the number of decimal places.
with localcontext() as ctx:
ctx.prec = 6
rotated = path.rotated(0, 0, -45)
# Same shape, but with reduced numeric precision
# M 0 0 l 7.07107 -7.07107 l 7.07107 7.07107 z
print(rotated)
Path Optimization#
optimize_path() rewrites a path into an equivalent but more
compact form and is also out-of-place:
from svg_path_editor import SvgPath, optimize_path
path = SvgPath("M-15 14s5 7.5 15 7.5 15-7.5 15-7.5 z")
optimized = optimize_path(
path,
# Remove redundant M/Z or degenerate L/H/V.
remove_useless_commands=True,
# Remove empty closed subpaths (M immediately followed by Z).
remove_orphan_dots=True,
# Convert eligible C/Q to S/T.
use_shorthands=True,
# Replace L with H/V where possible.
use_horizontal_and_vertical_lines=True,
# Choose relative/absolute per command to minimize size.
use_relative_absolute=True,
# Try reversing path direction if it reduces output length.
# This may change the appearance of stroked paths!
use_reverse=True,
# Convert final line segments that return to start into Z.
# This may change the appearance of stroked paths!
use_close_path=True,
)
# More readable form
# M -15 14 s 5 7.5 15 7.5 S 15 14 15 14 z
print(optimized)
# Minified form
# M-15 14s5 7.5 15 7.5S15 14 15 14z
print(f"{optimized:m}")