# PD4ML for Python Compared with WeasyPrint, xhtml2pdf and Playwright

Python developers who need HTML to PDF usually pick one of a few libraries: WeasyPrint, xhtml2pdf, a headless browser driven by Playwright, or pdfkit with wkhtmltopdf. Most are free and install with pip, and each works well for some kinds of documents. This page compares them with PD4ML for Python, the pip package that brings the PD4ML layout engine to Python, so you can see where each one fits: CSS layout, PDF/A and PDF/UA, e-invoices, Word output, signatures, deployment and license.

The comparison covers PD4ML v4.1.1 and, marked {4.2}, features of the upcoming PD4ML 4.2 with its new v42 layout engine. Information about the other libraries comes from their documentation and PyPI pages as of November 2026; a cell marked "not verified" means we could not confirm it from public sources. ReportLab is not included: it builds PDFs from Python code rather than from HTML.

## At a glance

* **PD4ML for Python** (`pd4ml` on PyPI) runs the PD4ML engine in a Java virtual machine inside the Python process, with no server, and exposes its full API to Python. It needs Python 3.9 and Java 8 or later. Commercial license, perpetual, from €100; free evaluation with a watermark.
* **[WeasyPrint](https://doc.courtbouillon.org/weasyprint/stable/)** (70.0) is a print-oriented HTML and CSS renderer written in Python on top of Pango. It is the most complete of the open-source options: paged media, PDF/A, PDF/UA and PDF/X. BSD-3-Clause.
* **[xhtml2pdf](https://xhtml2pdf.readthedocs.io/)** (0.2.28) converts HTML and CSS 2.1, with parts of CSS3, to PDF through ReportLab, in pure Python. It is actively developed again and has added Flexbox, signatures and PDF/X-4. Apache-2.0.
* **[Playwright](https://playwright.dev/python/docs/api/class-page#page-pdf)** (1.64) drives a headless Chromium whose print to PDF renders HTML as Chrome does, JavaScript included, but with few PDF options. Apache-2.0.
* **[pdfkit](https://github.com/JazzCore/python-pdfkit)** wraps the [wkhtmltopdf](https://wkhtmltopdf.org/) command-line tool, whose Qt WebKit engine dates from 2012. wkhtmltopdf has been archived since January 2023 and pdfkit's last release was in 2021. MIT (pdfkit) and LGPL-3.0 (wkhtmltopdf).

## Requirements, installation and license

| Item | PD4ML | [WeasyPrint](https://doc.courtbouillon.org/weasyprint/stable/) | [xhtml2pdf](https://xhtml2pdf.readthedocs.io/) | [Playwright](https://playwright.dev/python/docs/api/class-page#page-pdf) (Chromium) | [pdfkit](https://github.com/JazzCore/python-pdfkit) ([wkhtmltopdf](https://wkhtmltopdf.org/)) |
|---|---|---|---|---|---|
| Install | `pip install pd4ml` | `pip install weasyprint` | `pip install xhtml2pdf` | `pip install playwright`, then `playwright install chromium` | `pip install pdfkit` plus the wkhtmltopdf binary |
| Python version | 3.9 or later | 3.10 or later | 3.10 or later | 3.10 or later | Any Python 3 |
| Runtime | Python plus Java 8 or later | Python plus the Pango library (1.44 or later) | Pure Python (ReportLab) | Python plus a downloaded Chromium | Python plus wkhtmltopdf |
| Engine | Own print-focused engines: v40 and v42 (Chrome-verified layout) {4.2} | Own CSS layout engine | ReportLab Platypus | Chromium | Qt WebKit (2012) |
| Maintained | ✓ | ✓ | ✓ | ✓ | — wkhtmltopdf archived in 2023 |
| License | Commercial, perpetual | BSD-3-Clause | Apache-2.0 | Apache-2.0 | MIT; wkhtmltopdf LGPL-3.0 |
| Price | €100 single, €1,600–3,200 site, €8,000–16,000 enterprise (one-time) | Free | Free | Free | Free |
| Commercial support | ✓ | ~ From its authors' company | — Community | — Community | — |

PD4ML's Java runs inside the Python process: there is no separate server to deploy, and a long-running process (a web server, a worker) starts Java once. The first conversion of a fresh installation downloads the PD4ML library unless it was installed ahead of time with `pd4ml install`. Details: [PD4ML for Python](https://pd4ml.com/docs/integrations/pd4ml-python.html).

## HTML and CSS layout support

| Feature | PD4ML | [WeasyPrint](https://doc.courtbouillon.org/weasyprint/stable/) | [xhtml2pdf](https://xhtml2pdf.readthedocs.io/) | [Playwright](https://playwright.dev/python/docs/api/class-page#page-pdf) (Chromium) | [pdfkit](https://github.com/JazzCore/python-pdfkit) ([wkhtmltopdf](https://wkhtmltopdf.org/)) |
|---|---|---|---|---|---|
| CSS level | Modern CSS; v42 engine checked against Chrome {4.2} | CSS 2.1 and much of CSS3, paged media | CSS 2.1 and some CSS3 | Current Chrome | CSS 2.1 and early CSS3 of 2012 WebKit |
| Flexbox | ✓ | ~ Supported, "not deeply tested" | ~ Level 1; `inline-flex` laid out as `flex` | ✓ | — Old `-webkit-box` syntax only |
| CSS Grid | ✓ Template areas, line names, dense placement {4.2} | ~ No subgrid, `inline-grid`, `auto-fill`/`auto-fit`, or rows split across pages | — | ✓ | — |
| Absolute and fixed positioning | ✓ | ✓ | ✓ | ✓ | ✓ |
| CSS @page | ✓ Size, `:first`/`:left`/`:right`, named pages, margin boxes {4.2} | ✓ Named pages, margin boxes, `:nth()` | ~ Size, margins, named page templates and frames | ~ Size; margins through options | — |
| Headers, footers, page numbers | ✓ HTML headers and footers with page and total counts | ✓ Margin boxes and counters | ✓ Static frames, `<pdf:pagenumber>` | ~ Header and footer templates | ✓ HTML header and footer |
| Multi-column layout | ✓ | ~ No column breaks or spanning | ? | ✓ | ~ |
| Media queries | ✓ Level 4 range syntax {4.2} | ✓ | ~ Media types only | ✓ | ✓ |
| Vector SVG | ✓ | ✓ | ✓ Through svglib | ✓ | ✓ |
| MathML | ✓ | ? | — | ✓ | — |
| Barcodes and QR codes | ✓ QR codes (`<qrcode>`) | — | ✓ `<pdf:barcode>` | — Through JavaScript libraries | — Through JavaScript libraries |
| JavaScript | — | — | — | ✓ | ✓ Old engine |

PD4ML's `@page` support is described in [CSS @page rules](https://pd4ml.com/docs/features/css-page-rules-page-size-margins-named-pages-and-margin-boxes.html), and its DOM API (Java, DMS and UA editions) in [modifying the HTML document before conversion](https://pd4ml.com/docs/features/modifying-the-html-document-before-conversion.html).

A headless browser renders whatever Chrome renders, including pages built by JavaScript, but its print to PDF knows little about paged documents: no margin boxes, no named pages, no PDF standards. WeasyPrint is the strongest of the open-source libraries for paged media.

## Output formats

| Format | PD4ML | [WeasyPrint](https://doc.courtbouillon.org/weasyprint/stable/) | [xhtml2pdf](https://xhtml2pdf.readthedocs.io/) | [Playwright](https://playwright.dev/python/docs/api/class-page#page-pdf) (Chromium) | [pdfkit](https://github.com/JazzCore/python-pdfkit) ([wkhtmltopdf](https://wkhtmltopdf.org/)) |
|---|---|---|---|---|---|
| PDF | ✓ | ✓ | ✓ | ✓ | ✓ |
| DOCX (Word) | ✓ | — | — | — | — |
| RTF | ✓ | — | — | — | — |
| PNG and TIFF images | ✓ Paged | — | — | ~ Screenshots, not paged | ~ wkhtmltoimage, not through pdfkit |
| Several formats from one parse | ✓ | — | — | — | — |

## PDF/A, PDF/UA and e-invoices

| Standard | PD4ML | [WeasyPrint](https://doc.courtbouillon.org/weasyprint/stable/) | [xhtml2pdf](https://xhtml2pdf.readthedocs.io/) | [Playwright](https://playwright.dev/python/docs/api/class-page#page-pdf) (Chromium) | [pdfkit](https://github.com/JazzCore/python-pdfkit) ([wkhtmltopdf](https://wkhtmltopdf.org/)) |
|---|---|---|---|---|---|
| PDF/A | ✓ PDF/A-1a/1b, 2a/2b/2u, 3a/3b/3u, 4, 4e | ✓ PDF/A-1 to 4 (a, b, u, e, f) | — | — | — |
| PDF/UA accessible PDF | ✓ PDF/UA-1 and PDF/UA-2 | ✓ PDF/UA-1 and PDF/UA-2 | — | ~ Tagged PDF option, no PDF/UA conformance | — |
| ZUGFeRD and Factur-X | ✓ Presets, including accessible PDF/A-3a invoices | ✓ PDF/A-3b with the invoice attached | — | — | — |
| PDF/X for print | — | ✓ PDF/X-1a, 3, 4, 5g | ✓ PDF/X-4 | — | — |
| PDF 2.0 | ✓ | ? | — | — | — |

WeasyPrint states that it generates these variants but does not guarantee their validity; check its output with a validator. See [PDF/A, PDF/UA and e-invoicing formats](https://pd4ml.com/docs/pd4ml-manual.html#11.2-pdfa-pdfua-and-e-invoicing-formats) and [ZUGFeRD and Factur-X PDF invoices](https://pd4ml.com/docs/features/zugferd-and-factur-x-pdf-invoices.html).

## Fonts and international text

| Feature | PD4ML | [WeasyPrint](https://doc.courtbouillon.org/weasyprint/stable/) | [xhtml2pdf](https://xhtml2pdf.readthedocs.io/) | [Playwright](https://playwright.dev/python/docs/api/class-page#page-pdf) (Chromium) | [pdfkit](https://github.com/JazzCore/python-pdfkit) ([wkhtmltopdf](https://wkhtmltopdf.org/)) |
|---|---|---|---|---|---|
| TrueType and OpenType embedding with subsetting | ✓ | ✓ | ✓ | ✓ | ✓ |
| Arabic and Hebrew, right to left | ✓ | ✓ | ✓ Shaped with arabic-reshaper | ✓ | ✓ |
| Indic and other complex scripts | ✓ | ✓ HarfBuzz through Pango | ? | ✓ | ~ |
| Kerning and ligatures | ✓ | ✓ | ? | ✓ | ✓ |
| Color emoji | ✓ COLR and CBDT fonts {4.2} | ? | ? | ✓ | ~ |
| WOFF2 web fonts | ✓ | ✓ | ✓ With the `fonts` extra | ✓ | — |

## Security, signatures and forms

| Feature | PD4ML | [WeasyPrint](https://doc.courtbouillon.org/weasyprint/stable/) | [xhtml2pdf](https://xhtml2pdf.readthedocs.io/) | [Playwright](https://playwright.dev/python/docs/api/class-page#page-pdf) (Chromium) | [pdfkit](https://github.com/JazzCore/python-pdfkit) ([wkhtmltopdf](https://wkhtmltopdf.org/)) |
|---|---|---|---|---|---|
| Passwords and permissions | ✓ RC4, AES-128/256 | — | ✓ | — | — |
| Digital signatures | ✓ PKCS#12, PKCS#11, HSM, cloud KMS, RFC 3161, PAdES-LT/LTV | — | ✓ PKCS#12 and PKCS#11 through pyHanko | — | — |
| Fillable forms from HTML | ✓ Inputs, selects, text areas, buttons | ✓ Text inputs, check boxes, text areas, selects | ✓ No radio button fields | — | ~ `--enable-forms` |
| Merge, optimize, XFDF | ✓ | — | — | — | — |
| Fetching untrusted resources | ✓ Allow and deny lists | ✓ URL fetcher | ✓ Restrictive defaults | ~ Browser sandbox | — Known SSRF issue, unpatched |

Any of these PDFs can also be signed afterwards with a separate Python library such as pyHanko.

## When to choose which

* **WeasyPrint** suits print-designed documents written for paged media (margin boxes, named pages), PDF/A, PDF/UA and PDF/X, when a permissive license matters and Pango can be installed.
* **xhtml2pdf** suits simple reports and invoices in pure Python, with no system libraries, including encrypted and signed output.
* **Playwright** suits pages that need JavaScript or must look exactly like the browser, when no PDF standard is required and a browser can run on the server.
* **pdfkit with wkhtmltopdf** suits existing projects only: the engine is unmaintained and has unpatched security issues, so new projects should use one of the others.
* **PD4ML for Python** suits templates written with modern CSS (Flexbox, Grid, `@page`), documents that also need DOCX or RTF, PDF/UA and e-invoices from HTML, digital signatures with long-term validation, and teams that want commercial support. It needs Java on the server.

## Frequently asked questions

### What is the best Python library for converting HTML to PDF?

For print-designed HTML without JavaScript, WeasyPrint is the strongest free option. For pages that depend on JavaScript, a headless Chromium through Playwright. If you need CSS Grid without limits, Word output, PDF/A together with digital signatures, or commercial support, PD4ML for Python covers that through pip, with Java on the server.

### Is PD4ML a good alternative to WeasyPrint?

Yes, when you need DOCX or RTF output, digital signatures, encryption, CSS Grid features WeasyPrint does not support, or one library for PDF, Word and images. WeasyPrint needs no Java and is free; it is the better fit for teams that only need PDF.

### Is PD4ML a good alternative to wkhtmltopdf and pdfkit?

Yes. wkhtmltopdf was archived in January 2023 and its WebKit engine dates from 2012, so it supports neither Flexbox nor Grid. PD4ML supports both, has no browser engine to keep patched, and adds PDF/A, PDF/UA and signatures.

### Should I use a headless browser instead?

A headless Chromium (Playwright, pyppeteer) renders exactly what Chrome shows, JavaScript included. It cannot produce PDF/A, PDF/UA, encrypted or signed PDFs, and each server needs a browser to install and keep updated. PD4ML does not run JavaScript; it lays out the HTML it is given.

### Do I need Java to use PD4ML in Python?

Yes. PD4ML for Python starts a Java 8 or later virtual machine inside the Python process. There is no separate server; a long-running process starts Java once, and each conversion then takes milliseconds for an ordinary document.

### Can Python create PDF/A and ZUGFeRD invoices from HTML?

Yes, with PD4ML or WeasyPrint. PD4ML has presets for ZUGFeRD and Factur-X on PDF/A-3, including accessible PDF/A-3a invoices; WeasyPrint documents Factur-X on PDF/A-3b with the invoice attached.

### Can Python convert HTML to Word (DOCX)?

Among these libraries, only PD4ML converts HTML and CSS to DOCX and RTF, from the same layout as the PDF.

## Try PD4ML for Python

Install the module with `pip install pd4ml` and convert one of your own templates. Unlicensed output carries a watermark, with all features enabled. See [PD4ML for Python](https://pd4ml.com/docs/integrations/pd4ml-python.html), the [comparison with PHP libraries](https://pd4ml.com/docs/integrations/pd4ml-php/php-html-to-pdf-library-comparison.html), the [comparison with Java HTML-to-PDF libraries](https://pd4ml.com/docs/html-to-pdf-library-comparison.html), and [PD4ML editions and prices](https://pd4ml.com/products/).

*Product names are trademarks of their respective owners. Information about other libraries is taken from their public documentation as of November 2026 and may have changed; corrections are welcome through [PD4ML support](https://pd4ml.com/support-topics/). This page is not legal advice on open-source licenses.*
