xhtml2pdf
Pure-Python library that converts HTML5 and CSS into PDF documents using ReportLab, html5lib, and pypdf.
Repository Health
Technical Analysis
xhtml2pdf is a pure-Python library that converts HTML5 and CSS 2.1 (with partial CSS3 support) documents into PDF files, built on top of the ReportLab Toolkit, html5lib, and pypdf. Because it renders PDFs entirely from markup, teams can generate PDF templates such as invoices, reports, and certificates using ordinary web skills instead of learning a dedicated PDF drawing API. The library exposes a simple pisaDocument/CreatePDF entry point for programmatic use in Python applications, plus pisa and xhtml2pdf command-line scripts for one-off conversions.
Under the hood it parses markup with html5lib into a DOM, walks that tree with a CSS-aware tag parser to build a ReportLab “story” of flowables, then lays those out on custom page templates that support headers, footers, frames, and watermarks. Digital signing is supported via pyHanko, and the project has been maintained continuously since 2010, moving from a GPL-licensed “pisa” project to its current Apache-2.0 license under community maintainers.
What You Get
- A
pisaDocument/CreatePDFPython API for converting HTML strings, files, or URLs directly to a PDF file object or stream pisaandxhtml2pdfcommand-line scripts for converting HTML files to PDF from the terminal, including glob patterns for batch runs- CSS 2.1 (plus partial CSS3) support for page sizing, multi-frame layouts, headers/footers, and print-specific
@pagerules - Built-in digital PDF signing and watermarking via pyHanko and the bundled
buildersmodule - Optional Cairo or RenderPM rendering backends (through ReportLab) for advanced bitmap and vector graphics
Common Use Cases
- Invoice and receipt generation - SaaS billing systems render HTML invoice templates styled with CSS into downloadable PDFs without a headless browser dependency
- Django/Flask report exports - web apps reuse existing HTML report templates to offer a “Download PDF” button by piping rendered HTML through
pisaDocument - Signed compliance documents - organizations that must produce digitally signed PDFs (contracts, certificates) use the pyHanko-based signing builder to attach signatures during generation
- Batch document conversion - CLI pipelines use the
pisa/xhtml2pdfscripts to convert directories of HTML files to PDF as part of build or export jobs
Under The Hood
Architecture
xhtml2pdf follows a linear pipeline architecture: document.py’s pisaDocument() orchestrates parsing (parser.py’s pisaParser, built on html5lib’s DOM tree walker), CSS resolution and state tracking (context.py’s pisaContext, a large class holding fonts, colors, page templates, and the running “story” list), and rendering (xhtml2pdf_reportlab.py’s PmlBaseDoc/PmlPageTemplate, which extend ReportLab’s platypus DocTemplate to add CSS-driven @page/@frame layout, headers, and footers). Tag-specific conversion logic lives in tags.py and tables.py, each tag mapped to a handler that appends ReportLab flowables (Paragraph, Table, Image, Spacer) to the context’s story; paragraph.py and reportlab_paragraph.py extend ReportLab’s own Paragraph flowable to support inline HTML-like styling. Cross-cutting concerns - PDF digital signing and watermarking - are isolated in the builders/ package (signs.py, watermarks.py) and invoked from document.py after the story is built. Because context.py is the shared mutable state object threaded through parsing and rendering, changes to its font/CSS resolution logic ripple through nearly every downstream module; separation of concerns is reasonable but tightly coupled through this central context object rather than through explicit interfaces.
Tech Stack
The library targets Python 3.8+ and depends on ReportLab for PDF primitives and layout, html5lib for spec-compliant HTML parsing, pypdf for PDF post-processing, Pillow for image handling, svglib for inline SVG support, and pyHanko/pyhanko-certvalidator for digital signatures; arabic-reshaper and python-bidi provide right-to-left and Arabic script shaping. Optional extras wire in a Cairo or RenderPM backend for advanced bitmap rendering. Packaging uses a standard setuptools/pyproject.toml build with a dynamic version pulled from xhtml2pdf.__version__, and the project ships two console-script entry points (pisa, xhtml2pdf). There is no database or network layer - it is a pure conversion library, though wsgi.py provides a small WSGI app wrapper and files.py handles temp-file and remote-resource fetching for embedded images and links.
Code Quality
Testing uses Python’s built-in unittest framework, with a dedicated tests/ suite covering CSS media rules, tables, paragraphs, selectors, RTL/Asian font support, and HTTP fetching, plus a separate testrender/ visual-regression suite that renders reference PDFs and compares output; both run through tox and are wired into CI. A dedicated linting workflow runs ruff (a large explicit rule selection) and mypy for static analysis, with a separate spelling-check workflow. Code has limited type-hint coverage across most modules and naming leans on legacy camelCase (pisaDocument, getBox) rather than PEP 8 snake_case, reflecting the codebase’s 2010-era origins; one comment in parser.py candidly flags an unresolved import dependency as unexplained, suggesting some organically-grown coupling. Overall: real test coverage and enforced linting/typing gates in CI, but the core modules are large and only partially typed.
API Design
The primary API surface is intentionally minimal - a single pisaDocument/CreatePDF call that accepts an HTML string, file, or URL and an output destination, returning a status object with error/warning logs - which lowers the barrier for teams that already produce HTML/CSS and don’t want to learn ReportLab’s native flowable API directly. Extension points like link_callback (for resolving relative asset URLs, documented for Django integration) and the default_css parameter suit framework integration, and example integrations for CherryPy, Django, TurboGears, and WSGI ship in demo/. Where the design shows its age is CSS/print-model fidelity: it does not implement a real browser rendering engine, so support is limited to CSS 2.1 plus partial CSS3, and the README itself points users to WeasyPrint as an alternative for more complete CSS3 support - an unusually candid trade-off disclosure rather than groundbreaking API design.