Skip to content

Releases

svglib v2.4.0

Choose a tag to compare

@deeplook deeplook released this 04 Nov 20:25
f99f799

Added

  • <textPath> is now supported: text follows the <path> named by its href
    or xlink:href, with startOffset (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, and side, method, spacing
    and per-glyph dx/dy/rotate are not supported (#298, #532).
  • The plain opacity property is now supported, as a presentation attribute,
    in style attributes 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 font shorthand is now expanded into font-style, font-weight,
    font-size and font-family, in style attributes 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-python is 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 install installs the dev dependency group (it used the dev extra,
    which doesn't exist), make uninstall no longer passes an option that
    uv pip doesn't know, and make test-dist works: scripts/test-dist.sh
    builds the wheel and tests it in a clean virtual environment. New:
    make check-versions.
  • tests/test_basic.py is 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 marker network, so
    pytest -m "not network" (or make test-fast) skips them.
  • tests/test_public_api.py records the names, signatures and class members
    of svglib, svglib.svglib, svglib.utils and svglib.fonts in
    tests/public_api.json, so that restructuring the modules can't silently
    change what user code can import.
  • The READMEs and notes in demos/ and tests/ are Markdown now, and the
    testing instructions describe the current uv and Makefile based workflow.

Fixed

  • A style attribute or <style> rule whose value contains a colon or a
    semicolon, such as cursor: url(http://example.com/c.cur), auto or a data
    URL, no longer makes svg2rlg() fail with ValueError or truncates the
    value. Declarations are now parsed with tinycss2, malformed ones are
    skipped, and !important and comments are no longer part of the value
    (#527, thanks to @Pharex38).
  • baseline-shift on text is now also read from style attributes 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-dasharray values that SVG calls invalid (any negative length, or a
    list that sums to zero) no longer produce an invalid PDF dash pattern and
    raise ValueError while rendering; they give a solid line. none no 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

Choose a tag to compare

@deeplook deeplook released this 04 Nov 09:49

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

Choose a tag to compare

@deeplook deeplook released this 23 Oct 09:30

Security

  • A long run of digits in the arguments of a path arc command (A or a)
    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 .svgz file to a sibling .svg file, 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 with RecursionError. 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

Choose a tag to compare

@deeplook deeplook released this 22 Oct 12:32

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 after MAX_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

Choose a tag to compare

@deeplook deeplook released this 05 Oct 11:08

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 checked os.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-in external_reference_root on
    svg2rlg()/SvgRenderer confines resolution to a trusted directory,
    propagated into nested external-SVG renderers. Relative references, including
    .., keep working by default.

  • The svg2pdf command line tool now sets external_reference_root to 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-root with a
    parent directory to allow shared assets, or / to allow any relative
    reference. Library callers are unaffected: the default stays None.

svglib 2.2.0

Choose a tag to compare

@deeplook deeplook released this 14 Aug 12:01
c5c1ed1

Tooling and housekeeping

  • Modernized type annotations across src/svglib/ and tests/ to use PEP
    585 builtin generics (list[...], dict[...], tuple[...], set[...])
    instead of typing.List/Dict/Tuple/Set, and simplified a few
    emptiness/membership checks (not points instead of len(points) == 0,
    a set literal instead of a list for in checks). No behavior change.
  • Silenced a UserWarning from test_convert_pdf_png that came from
    ReportLab's own renderPM.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-size no longer has PX_TO_PT applied twice. The viewport group
    already carries the conversion, but String.fontSize and 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-tspan runs were laid out in the wrong unit (issue #502). Both now
    stay in user units, like every other coordinate below the transform.
    String.fontSize in the shape tree is in user units accordingly; multiply by
    the enclosing group's transform to get the rendered size.

svglib 2.1.0

Choose a tag to compare

@deeplook deeplook released this 01 Aug 11:27
eee9a45

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 _LinearGradientShape and
    _RadialGradientShape to LinearGradientShape and RadialGradientShape.
    They appear in the rendered Drawing tree and were never meant to be
    private, so the leading underscore was misleading.

Removed

  • Removed the deprecated _LinearGradientShape and _RadialGradientShape
    aliases. Use LinearGradientShape and RadialGradientShape instead.

Fixed

  • The visibility property is now respected on shapes. Elements with
    visibility="hidden" (or collapse), set directly or inherited from an
    ancestor, were still rendered (issue #359).

  • display: none set through a style attribute is now respected. Only the
    display presentation 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, and height of 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-family values are now split per CSS instead of on whitespace. An
    unquoted multi-word name such as font-family: Cascadia Code was split into
    Cascadia and Code, 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 handed shlex an
    unbalanced quote and raised ValueError: 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-rule attribute instead of incorrectly
    using fill-rule (issue #485).

  • fill="url(...)" and gradient xlink:href values 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 implement beginPath(),
    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 --strict on its own source, with a single documented per-module
    override for the untyped reportlab/cssselect2 base classes it subclasses.
  • Pinned a [tool.mypy] configuration in pyproject.toml and now enforce mypy
    in CI (new type-check.yml workflow) and via a local pre-commit hook, so type
    regressions fail before merge.
  • Tightened internal gradient handling types: parsed gradient definitions now
    use a TypedDict instead of an opaque Dict[str, Any], and the internal
    group/parent parameters are typed as Optional[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 uniconv sample 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

Choose a tag to compare

@deeplook deeplook released this 18 Jun 07:12

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

Choose a tag to compare

@deeplook deeplook released this 17 Jun 15:31
3afc7c9

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.0 as a direct dependency; it was previously an undeclared transitive dependency pulled in by reportlab (#463).

Fixes #463, #464.

svglib 2.0.0

Choose a tag to compare

@deeplook deeplook released this 16 Jun 13:36

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, and q (quarter-millimetre) in convertLength (#449).
  • rem now resolves against the root <svg> element's font-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 rlpycairo to the bitmaps extra, so Cairo is no longer part of
    the default installation path.
  • Fix inconsistent unit handling: bare numbers and px units 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
    convertLengthToPt helper is used where absolute point values are needed
    (font sizes, canvas dimensions) (#439).
  • Fix SVGs with a viewBox but no explicit width/height: the
    viewport scale now defaults to PX_TO_PT (0.75) instead of 1, preventing
    content from being cropped.
  • Add support for SVG linearGradient and radialGradient paint servers
    (#442).
  • Add support for SVG <switch> elements with conditional rendering (#441).
  • Fix fill-rule handling for paths by setting ReportLab fillMode
    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-match calls by resolving the executable path and passing
    arguments after --.
  • Fixed a SVG path interpretation bug with two successive M or m commands
    (#414).