WeasyPrint in Practice: Generating High-Quality PDFs from HTML/CSS in Python

Python tutorial - IT technology blog
Python tutorial - IT technology blog

Background: A Tearful Breakup with wkhtmltopdf

It was 2 AM. A customer’s invoicing system suddenly started throwing errors. The reason? The frontend had updated the UI to use Flexbox, but the aging wkhtmltopdf engine (based on a 2014 version of WebKit) was completely helpless at rendering modern layouts. Faced with the choice of reverting CSS back to the “Stone Age” using tables or finding a new tool, I chose WeasyPrint.

Why WeasyPrint? Unlike pdfkit or wkhtmltopdf, which often face missing dependency issues on Ubuntu 22.04, WeasyPrint is much more Python-friendly. It supports CSS3, Flexbox, and even Grid smoothly. Most importantly, you don’t need to install a heavy headless browser like Chrome, saving about 300-500MB of RAM on your server.

However, don’t mistake this for an “instant” solution. To run it stably in production, you need to understand how it interacts with system libraries.

Installation: Overcoming System Library Barriers

The most common mistake is simply running pip install weasyprint. If you do that, you’ll soon encounter the frustrating OSError: cannot load library 'gobject-2.0' error. WeasyPrint requires graphic rendering engines like Cairo and Pango to function.

1. Installation on Linux (Ubuntu/Debian)

Just one command to prepare your server’s arsenal:

sudo apt-get update
sudo apt-get install build-essential python3-dev python3-pip python3-setuptools python3-wheel python3-cffi libcairo2 libpango-1.0-0 libpangocairo-1.0-0 libgdk-pixbuf2.0-0 libffi-dev shared-mime-info

2. The Windows Nightmare

Installing WeasyPrint on Windows is often a nightmare due to DLL errors. The simplest method today is to download the GTK for Windows Runtime. After installation, remember to add the bin folder path to your PATH environment variables. Without this step, Python won’t be able to find the necessary executables.

In Practice: From Basic Code to Professional PDFs

In reality, cramming CSS into HTML (inline styles) is a maintenance disaster. When a client wants to change the brand color from blue to red, you’ll have to dig through hundreds of lines of code. Separate them from the start.

Basic Rendering Method

This is the fastest way to convert a simple HTML string:

from weasyprint import HTML

html_content = """
<h1 style='color: #1a73e8; font-family: sans-serif;'>Service Invoice</h1>
<p>Thank you for using our products.</p>
"""

# Export file with just one line of code
HTML(string=html_content).write_pdf("invoice.pdf")

Handling Vietnamese Fonts and Advanced CSS

The most headache-inducing issue is font errors turning into squares (tofu). WeasyPrint uses Pango to manage fonts, so you need to define the font-face clearly and ensure the font is installed on the system.

from weasyprint import HTML, CSS
from weasyprint.text.fonts import FontConfiguration

font_config = FontConfiguration()
css = CSS(string="""
    @font-face {
        font-family: 'Roboto';
        src: url(https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Mu4mxKKTU1Kg.woff2);
    }
    body { font-family: 'Roboto', Arial, sans-serif; }
    .header { display: flex; justify-content: space-between; border-bottom: 2px solid #eee; }
""")

html = HTML(string='<div class="header"><h1>Report</h1><span>No: #123</span></div>')
html.write_pdf('report.pdf', stylesheets=[css], font_config=font_config)

Production Optimization: Keeping Your Server Responsive

Rendering PDFs is resource-intensive. A complex PDF can cause CPU spikes of 80-90% for several seconds. If 50 users click “Export” simultaneously, your server will crash instantly.

  • Use Workers: Never render PDFs directly within the request-handling thread. Offload the task to Celery or Redis Queue. Users should receive a “Processing” notification and download the file later via email or an S3 link.
  • Smart Image Management: Instead of forcing WeasyPrint to download images from external URLs (which often causes timeouts), use Base64 images or local file paths. This can reduce rendering time from 5 seconds to less than 1 second.
  • Resize Images: Inserting a 4K image into an A4 PDF is wasteful. Resize images to the exact dimensions needed. In my experience, a PDF file can be reduced from 10MB to 200KB just by optimizing images.

Conclusion

WeasyPrint is not just a tool; it’s a lifesaver for those who want beautiful PDF prints without returning to the era of CSS tables. Although the initial setup is a bit tricky, its stability and support for modern CSS make it well worth the investment. Remember: install all libraries, configure your fonts correctly, and always run it as a background task for the best experience!

Share: