DEV Community

IronSoftware
IronSoftware

Posted on

WeasyPrint in Python: The Install Is the Deployment Story

$ docker run --rm python:3.12-slim python -c "import weasyprint"
Traceback (most recent call last):
  ...
OSError: cannot load library 'libgobject-2.0-0': libgobject-2.0-0: cannot open
shared object file: No such file or directory
Enter fullscreen mode Exit fullscreen mode

The script that worked on the laptop stops at the import line in the container, and nothing in that message says Pango. WeasyPrint renders through a purpose-built layout engine that reaches for Pango at the system level, so a bare python:slim base image cannot get as far as write_pdf() until that library is layered in by hand, and the same step repeats on every target the pipeline touches. That is a deployment commitment rather than a package install, and it is worth pricing next to rendering the same markup from one package.

Full disclosure. IronPDF is built by our team at Iron Software. This read traces what WeasyPrint's per-target Pango install and absent script engine cost a deployment, and what IronPDF renders with its engine already inside the package.

Pango Installs Per Target, Not Per Project

Pango has to be added by hand wherever the code runs, arriving as libpango-1.0-0 on Debian and Ubuntu, pango through Homebrew on macOS, and pango via MSYS2 on Windows. A serverless function needs it baked into a custom layer before deployment rather than resolved by pip install, which puts the renderer's requirements in the build pipeline instead of the requirements file. Fonts follow the same system-level model, since WeasyPrint always uses Fontconfig to reach fonts even on Windows and macOS, so anything installed system-wide appears automatically and is checkable with fc-list and fc-match, while a custom @font-face rule needs a weasyprint.text.fonts.FontConfiguration object passed alongside the CSS and reused across every CSS source, where IronPDF resolves the same font from the stylesheet the browser engine already reads.

The surface has shrunk rather than grown, which is worth knowing before copying an old runbook. The current Python dependency list covers pydyf, cffi, tinyhtml5, tinycss2, cssselect2, Pyphen, Pillow, and fonttools, with no Cairo and no GDK-PixBuf anywhere in it. Version 53.0, released on 31 July 2021, is where that changed, adding pydyf as a dependency and deprecating every output format except PDF. Installation guides and forum answers written before that change still tell readers to install Cairo and GDK-PixBuf beside Pango, which now wastes a build step rather than fixing anything. IronPDF keeps the equivalent decision inside one packaged runtime, so the deployment target does not change what has to be installed first.

Nothing on the Page Ever Runs

Content that appears only after client-side script has run never reaches the page. WeasyPrint does not embed a browser engine, so anything that exists only after client-side script runs is absent from the output, whether that is a single-page application rendered without a server-side pass, a chart drawn by a JavaScript charting library, or a widget populated by an API call after load. There is no delay flag to wait on and no fallback browser to hand the page to, because there is no script engine in the design at all.

from weasyprint import HTML

HTML("invoice.html").write_pdf("invoice.pdf")
Enter fullscreen mode Exit fullscreen mode

That reads the file, applies its stylesheet, and writes the PDF in one call, with no browser process to launch and no async loop to manage, which is the whole job for an invoice or a catalogue built from static markup. The same template with a client-side chart in it produces a document with an empty space where the chart belongs, and closing that gap means either a server-side rendering pass or a renderer that runs the script first before capturing what the charting library drew.

How Far Print-First CSS Reaches

Paged media is the scope the engine was built for. The documentation lists support across close to twenty W3C CSS modules, including CSS Paged Media 3, Generated Content for Paged Media, Fragmentation 3 and 4, and Multi-column Layout, which are the specifications governing page boxes, running headers and footers, page counters, and content that has to break cleanly across pages. The project is equally precise about its own edges, noting that Flexbox works for simple use cases but is not deeply tested and that Grid works for simple cases with limitations, so a team reading the CSS support page knows which patterns to test first. Maintenance is current rather than nostalgic, with 9,546 stars, 865 forks, and 137 open issues on a repository that took commits the day this was written, 119 releases since 2011 and six to nine of them in each of 2023, 2024, and 2025, and a LICENSE file naming BSD 3-Clause terms precisely where the PyPI classifier only says BSD. Where a document is authored for print from the start and every element of it is static, that engine is the ceiling of what a paged-CSS renderer needs to reach, and IronPDF answers the browser-authored half of the same problem with PDF/A output and file attachments on the same document object.

Capability WeasyPrint 69.0 IronPDF for Python
Rendering engine Purpose-built engine for paged CSS Chromium, driven by ChromePdfRenderer
JavaScript on the page Not executed, by design Executed before the page is captured
CSS coverage About 20 W3C modules, partial on Flexbox and Grid Whatever the bundled Chromium build supports
System library to install Pango 1.44 or later, per target 0, the package carries its own engine
PDF forms Supported through --pdf-forms Create, fill, and flatten on the same document
Archival output PDF/A, PDF/UA, PDF/X, and e-invoicing profiles PDF/A from RenderHtmlAsPdf, then SaveAs
Watermarks, stamps, and signatures Not a documented feature Stamping, watermarking, and signing
Python versions accepted 3.10 to 3.14 3.7 and later, one wheel

Table 1. Rendering and document output only, WeasyPrint against IronPDF for Python, as each project documents itself.

A deployment is decided by the script row and the system-library row. How the rest reads depends entirely on how the source markup was authored.

Where IronPDF Fits Instead

The case that moves a team is usually that missing script engine made concrete, meaning a dashboard, a report drawn by a client-side charting library, or any page whose real content appears only after script execution finishes.

from ironpdf import *

renderer = ChromePdfRenderer()
pdf = renderer.RenderUrlAsPdf("https://dashboard.internal.example.com/report")
pdf.SaveAs("dashboard-report.pdf")
Enter fullscreen mode Exit fullscreen mode

That writes the dashboard as the browser draws it, with the charts already rendered, because the script has run before the page is captured. Static files and raw strings go through the same object via RenderHtmlFileAsPdf and RenderHtmlAsPdf, and once the document exists, watermarking or stamping it is part of the same API rather than a second package.

Three CVEs and a Documented Throughput Limit

Three CVEs sit against WeasyPrint's own code, and all three have fixes. CVE-2024-28184, tracked as GHSA-35jj-wx47-4w8r at CVSS 7.4, let a crafted document attach an arbitrary local file or URL past the intended fetcher restrictions, patched in 61.2. CVE-2025-68616, GHSA-983w-rhvv-gwmv at CVSS 7.5, let a server-side request forgery protection be bypassed through an HTTP redirect in the default URL fetcher, patched in 68.0. CVE-2026-49452, GHSA-jhhc-3hcp-qhm5 at CVSS 6.5, was a CSS injection through unescaped attribute values with presentational hints enabled, patched in 69.0, and the project's own release notes for 69.0 name it as a security update. A scanner showing that number with no patched version listed is reading a stale advisory field rather than an open issue. Running 69.0 closes all three, and the fetcher settings belong in the same review as the metadata and permissions on the output.

Throughput is the other recurring cost, and the documentation states it rather than leaving it to a benchmark. WeasyPrint is often slower than other web engines, per its own docs, which recommend avoiding large CSS frameworks, replacing tables with block layouts, and caching images to keep run times reasonable. For a batch generating thousands of documents a day, that guidance is an infrastructure line item and a set of constraints on how the templates may be written, where a renderer with documented parallel and asynchronous paths spends the same budget on concurrency instead.

IronPDF renders the same page with its engine already inside the package, so no per-target system library joins the build, the script that assembles the layout runs before capture, and throughput is a concurrency setting rather than a documented limit. The narrow case that stays with WeasyPrint is a static document authored for print where archival profiles are contractual. IronPDF has a free trial if you want to put one of your existing templates through it before that Pango layer gets written into another image.

Which one are you running today, and did the install behave the way you expected? Tell us in the comments, especially if you hit the Pango step on a container image or a serverless target and had to work out where it belonged in the build.

WeasyPrint is the property of its maintainers, and we have no affiliation with CourtBouillon. The dependency, licence, and advisory details above are drawn from the project's own documentation, repository, and the published CVE records at the time of writing. If a detail has moved since, say so below.

Top comments (0)