Repository navigation
Releases
svglib v2.4.0
Added
<textPath>is now supported: text follows the<path>named by itshref
orxlink:href, withstartOffset(a length or a percentage) and
text-anchor, and styled<tspan>children. ReportLab can't draw text along
a path, so each glyph is placed and rotated individually, at the point of the
path under its midpoint; glyphs beyond the end of the path are dropped. Only
paths of the same document can be followed, andside,method,spacing
and per-glyphdx/dy/rotateare not supported (#298, #532).- The plain
opacityproperty is now supported, as a presentation attribute,
instyleattributes and in<style>rules, including percentages. Opacity
composes down the tree, so a shape at 0.5 inside a group at 0.5 is drawn at
0.25. It is applied per shape, so overlapping shapes inside a translucent
group show through each other, and images are not affected (#109, #525,
thanks to @Pharex38). - The CSS
fontshorthand is now expanded intofont-style,font-weight,
font-sizeandfont-family, instyleattributes and in<style>rules.
Invalid shorthands are ignored (#343, #526, thanks to @Pharex38).
Removed
- Dropped support for Python 3.9 (and PyPy 3.9). pip 26.2.2, the new
minimum safe version, requires Python 3.10 or newer.requires-pythonis now
>=3.10, the 3.9 classifier is gone, and CI no longer tests 3.9 or PyPy 3.9.
Tooling and housekeeping
- CI now fails if a locked or installed dependency, or the uv binary, is below
its minimum safe version (scripts/check_min_versions.py). - CI runs the lowest supported Python, 3.10, next to 3.13, 3.15 and PyPy 3.11.
Python 3.15 is declared supported (classifier and tox). make installinstalls the dev dependency group (it used thedevextra,
which doesn't exist),make uninstallno longer passes an option that
uv pipdoesn't know, andmake test-distworks:scripts/test-dist.sh
builds the wheel and tests it in a clean virtual environment. New:
make check-versions.tests/test_basic.pyis split into modules by topic (test_paths.py,
test_text.py,test_text_path.py,test_use.py,test_gradients.py, ...).
The tests that download files carry the pytest markernetwork, so
pytest -m "not network"(ormake test-fast) skips them.tests/test_public_api.pyrecords the names, signatures and class members
ofsvglib,svglib.svglib,svglib.utilsandsvglib.fontsin
tests/public_api.json, so that restructuring the modules can't silently
change what user code can import.- The READMEs and notes in
demos/andtests/are Markdown now, and the
testing instructions describe the current uv and Makefile based workflow.
Fixed
- A
styleattribute or<style>rule whose value contains a colon or a
semicolon, such ascursor: url(http://example.com/c.cur), autoor a data
URL, no longer makessvg2rlg()fail withValueErroror truncates the
value. Declarations are now parsed withtinycss2, malformed ones are
skipped, and!importantand comments are no longer part of the value
(#527, thanks to @Pharex38). baseline-shifton text is now also read fromstyleattributes and
<style>rules, and a percentage is taken relative to the font size instead
of being used as a length in user units. A value on an enclosing<g>does
not affect the text, as the property is not inherited (#528, thanks to
@Pharex38).stroke-dasharrayvalues that SVG calls invalid (any negative length, or a
list that sums to zero) no longer produce an invalid PDF dash pattern and
raiseValueErrorwhile rendering; they give a solid line.noneno longer
relies on a swallowed exception, and an odd number of values is repeated once
to make it even, as the specification requires (#529, thanks to @Pharex38).
svglib v2.3.3
Security
- The limit on
<use>expansions added in 2.3.1 counted expansions, not their
size. Each expansion copies and renders the whole element it refers to, so
a few kilobytes of nested<use>groups with a large group at the bottom
could still take minutes and gigabytes to render (CWE-400,
GHSA-xpfm-fw4h-qxwv, an incomplete fix of GHSA-555j-qj3j-c8jv). The elements
copied by all expansions of a document, external SVGs included, are now
limited too (MAX_USE_CLONED_ELEMENTS, 20,000); once it is spent the
remaining<use>elements are skipped and an error is logged. Thanks to
Adrian Korwel (@adriank31) for the report.
svglib v2.3.2
Security
- A long run of digits in the arguments of a path arc command (
Aora)
could make parsing take exponential time, so a tiny SVG could block a
process for minutes or longer (CWE-1333, CWE-400, GHSA-249c-7rgc-f52h).
Arc arguments are now read one token at a time, without regex backtracking.
Parsing of a malformed arc stops at the first argument that does not fit.
Fixed
- Accept explicit plus signs on arc rotation and endpoints, and tab, carriage
return and line feed separators in transform arguments, as allowed by SVG
(#524, thanks to @jdotc1). svg2rlg()no longer unpacks a.svgzfile to a sibling.svgfile, which
overwrote an existing file of that name and then deleted it. The compressed
data is now read as a stream (#518, thanks to @jdotc1).- A
<use>that refers to itself, or to a cycle of elements that each<use>
the next one, no longer crashes withRecursionError. The offending<use>
is skipped and an error is logged, as already done for cyclic references
between external files (#519, thanks to @gistrec for the report).
svglib v2.3.1
Security
- Rendering an SVG could be made to take exponential time and memory through
nested<use>elements, where each level references a group containing many
<use>elements of the previous level (CWE-400, GHSA-555j-qj3j-c8jv).
renderUse()now stops afterMAX_USE_EXPANSIONS(10,000) expansions per
document, logs one error and skips the remaining<use>elements. The budget
is shared with the renderers of external SVG files, so referencing many
external files does not reset it.
svglib 2.3.0
Security
-
External
<image>/<use>references can no longer escape the document's own
directory.xlink_href_target()joined the reference onto the source
directory and only checkedos.access(), so an absolute reference discarded
that directory and..climbed above it, letting an untrusted SVG name any
file the process could read (CWE-22, GHSA-2p5c-8vcc-4rcw). Absolute
references are now refused, and the new opt-inexternal_reference_rooton
svg2rlg()/SvgRendererconfines resolution to a trusted directory,
propagated into nested external-SVG renderers. Relative references, including
.., keep working by default. -
The
svg2pdfcommand line tool now setsexternal_reference_rootto the
input file's own directory, since a file converted from the command line is
routinely one the user did not author. Pass-R/--external-rootwith a
parent directory to allow shared assets, or/to allow any relative
reference. Library callers are unaffected: the default staysNone.
svglib 2.2.0
Tooling and housekeeping
- Modernized type annotations across
src/svglib/andtests/to use PEP
585 builtin generics (list[...],dict[...],tuple[...],set[...])
instead oftyping.List/Dict/Tuple/Set, and simplified a few
emptiness/membership checks (not pointsinstead oflen(points) == 0,
a set literal instead of a list forinchecks). No behavior change. - Silenced a
UserWarningfromtest_convert_pdf_pngthat came from
ReportLab's ownrenderPM.drawImage, not from svglib: it reopens a
<image>file referenced by path and converts it straight to RGB without
normalizing palette-with-transparency PNGs first, unlike svglib's own
handling of base64-embedded images. The referenced PNG (a dedicated
tRNS-chunk test fixture) still renders correctly; there was nothing to fix
in svglib's output.
Fixed
font-sizeno longer hasPX_TO_PTapplied twice. The viewport group
already carries the conversion, butString.fontSizeand the advance widths
used to position text fragments were converted to points as well. Text
therefore rendered at 0.75x the size of the geometry in the same document,
and multi-tspanruns were laid out in the wrong unit (issue #502). Both now
stay in user units, like every other coordinate below the transform.
String.fontSizein the shape tree is in user units accordingly; multiply by
the enclosing group's transform to get the rendered size.
svglib 2.1.0
Added
- Declared support for Python 3.14 (added the classifier and CI matrix
entry across Ubuntu, macOS, and Windows, alongside the existing 3.9–3.13). - Clipping paths now support
<circle>,<ellipse>, and<polygon>clip
shapes, in addition to paths and rectangles, and clipping masks referenced
from within a<use>element. - Transforms are now applied to clipping-path shapes, fixing clip paths that
relied on previously unsupported transforms (issue #366). - The gradient shape classes now implement
getBounds(), so drawings that
contain gradient fills can be measured — for example when used as a flowable
by rst2pdf (issue #466).
Changed
- Renamed the gradient shape classes
_LinearGradientShapeand
_RadialGradientShapetoLinearGradientShapeandRadialGradientShape.
They appear in the renderedDrawingtree and were never meant to be
private, so the leading underscore was misleading.
Removed
- Removed the deprecated
_LinearGradientShapeand_RadialGradientShape
aliases. UseLinearGradientShapeandRadialGradientShapeinstead.
Fixed
-
The
visibilityproperty is now respected on shapes. Elements with
visibility="hidden"(orcollapse), set directly or inherited from an
ancestor, were still rendered (issue #359). -
display: noneset through astyleattribute is now respected. Only the
displaypresentation attribute was read, so<g style="display:none">and
<rect style="display:none"/>were still rendered (issue #401). -
Embedded
<image>geometry is no longer truncated to whole points. The
x,y,width, andheightof an SVG<image>were each passed through
int(), so a raster placed at fractional coordinates landed up to ~1pt away
from a vector element with identical coordinates. Because width/height were
truncated too, the error grew across the image rather than being a constant
shift (issue #490). -
font-familyvalues are now split per CSS instead of on whitespace. An
unquoted multi-word name such asfont-family: Cascadia Codewas split into
CascadiaandCode, so a font registered under its full name was never
found (issue #374). Commas inside a quoted name (font-family: "Foo, Bar", Arial) no longer split the list either; that previously handedshlexan
unbalanced quote and raisedValueError: No closing quotation, aborting the
conversion of a valid value. -
Replaced
locale.getdefaultlocale()(used for<switch>systemLanguage
matching) with a small environment-variable lookup.getdefaultlocale()is
deprecated and is removed in Python 3.15; the replacement keeps the same
behaviour, emits no deprecation warning on 3.12–3.14, and adds unit tests for
the language-matching path. -
Clipping paths now support the
clip-ruleattribute instead of incorrectly
usingfill-rule(issue #485). -
fill="url(...)"and gradientxlink:hrefvalues are now matched correctly
when the referenced id is quoted (url('#name'),url("#name")) or
contains a right parenthesis; the previous regex rejected or truncated
these forms. -
Rendering to
reportlab.graphics.renderSVG.SVGCanvas(as used by
easy-thumbnails[svg]) no longer raises
AttributeError: 'SVGCanvas' object has no attribute 'beginPath'when
drawing a clipped gradient; that canvas doesn't implementbeginPath(),
so the gradient's clip path is now skipped on it instead of erroring.
Type safety and internal quality (no behavior change)
- Completed static type annotations for
svglib.py; the package now passes
mypy --stricton its own source, with a single documented per-module
override for the untyped reportlab/cssselect2 base classes it subclasses. - Pinned a
[tool.mypy]configuration inpyproject.tomland now enforce mypy
in CI (newtype-check.ymlworkflow) and via a local pre-commit hook, so type
regressions fail before merge. - Tightened internal gradient handling types: parsed gradient definitions now
use aTypedDictinstead of an opaqueDict[str, Any], and the internal
group/parent parameters are typed asOptional[Group]. - Added docstrings to the remaining undocumented functions in
svglib.py,
bringing documentation coverage to 100%.
Tooling and housekeeping
- Removed the DeepSource configuration. Its checks were redundant with ruff,
mypy --strict, CodeQL, and the pytest matrix, and only produced recurring
noise on pull requests. - Removed the long-defunct
uniconvsample tests. They contained no assertions
and shelled out to UniConvertor, an abandoned Python 2 tool, so they had been
permanently skipped. - Fixed and revived the Windows CI workflow. It was a disabled placeholder that
compiled GTK/Cairo from source (a ~20–40 minute step); it now runs a real
CPython 3.9/3.13 matrix in well under a minute by dropping the source build
and skipping the network-heavy sample tests on Windows (they already run on
Ubuntu and macOS). The redundant manual Windows workflow was removed.
svglib 2.0.2
Supply-chain hygiene release — no code changes.
The 2.0.1 PyPI attestation was generated by a manual workflow_dispatch run from refs/heads/main that raced ahead of the release event. As a result the Sigstore certificate embedded in the PEP 740 attestation identified refs/heads/main as the source rather than refs/tags/v2.0.1, making it impossible to verify the package against the tagged commit. This release is published exclusively via the release: [published] trigger so the attestation identity is refs/tags/v2.0.2.
svglib 2.0.1
Supply-chain hygiene release — no code changes.
This release replaces 2.0.0, which was published directly with uv publish and therefore lacked the PEP 740 provenance attestation that was present in 1.6.0. Releases must be triggered via the GitHub Actions release workflow (publish-to-pypi.yml), which uses PyPI Trusted Publishing (OIDC) to produce a SLSA Level 3 attestation. Publishing locally — even with uv publish --trusted-publishing — relies on a local OAuth identity and does not meet that bar.
- Declare
pillow>=9.0.0as a direct dependency; it was previously an undeclared transitive dependency pulled in by reportlab (#463).
svglib 2.0.0
2.0.0 (2026-06-16)
Identical to 2.0b1 in terms of code; this release promotes the beta to a
stable release and converts the project documentation files from RST to
Markdown format.
2.0b1 (2026-05-26)
Breaking change — output sizes will differ from 1.x.
svglib now correctly maps SVG user units to ReportLab points using the
standard SVG/CSS conversion factor: 1 px = 0.75 pt (96 dpi). Previous
releases treated 1 user unit as 1 pt, which is 33 % too large. Any SVG
whose width/height or viewBox uses user units or px will
produce a PDF that is 75 % of its previous linear dimensions (same
proportions, correct physical size).
Migration — if you need to preserve the old apparent size, scale the
returned Drawing object before use:
```python
from svglib.svglib import svg2rlg
drawing = svg2rlg("file.svg")
Restore 1.x dimensions (1 user unit → 1 pt, non-spec):
factor = 4 / 3 # 1 / 0.75
drawing.width *= factor
drawing.height *= factor
drawing.scale(factor, factor)
```
- Add support for SVG 2 length units:
rem,vw,vh,vmin,
vmax, andq(quarter-millimetre) inconvertLength(#449). remnow resolves against the root<svg>element'sfont-size
(falling back to the CSS default of 16 px when not set).- Warn when loading SVGs created with Inkscape < 0.92, which used a
non-standard 90 dpi reference resolution (#452). - Move
rlpycairoto thebitmapsextra, so Cairo is no longer part of
the default installation path. - Fix inconsistent unit handling: bare numbers and
pxunits now produce
identical output, as required by the SVG spec (§5.9.2).convertLength
now returns user units (px) instead of ReportLab points; a new
convertLengthToPthelper is used where absolute point values are needed
(font sizes, canvas dimensions) (#439). - Fix SVGs with a
viewBoxbut no explicitwidth/height: the
viewport scale now defaults toPX_TO_PT(0.75) instead of 1, preventing
content from being cropped. - Add support for SVG
linearGradientandradialGradientpaint servers
(#442). - Add support for SVG
<switch>elements with conditional rendering (#441). - Fix
fill-rulehandling for paths by setting ReportLabfillMode
directly (#440). - Fix case-insensitive font family lookup in
FontMap(#433, #435). - Fix fontconfig handling when resolved fonts use non-normal weight or style.
- Improve missing font warnings by pointing users to
register_font(). - Harden
fc-matchcalls by resolving the executable path and passing
arguments after--. - Fixed a SVG path interpretation bug with two successive M or m commands
(#414).