Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
For print-oriented HTML5 and CSS3, start with WeasyPrint. Its Python API accepts an HTML string, file, readable object or URL and writes a PDF. If you need a real browser page, JavaScript-driven layout or browser-compatible rendering, use Playwright’s Python API and page.pdf(). ReportLab is better when you are composing a PDF programmatically rather than converting an existing website.
This guide shows complete implementations, explains CSS and asset limitations, and gives a decision path for choosing the renderer that matches your document.
Choose the renderer before writing code
| Option | Best fit | Important considerations |
|---|---|---|
| WeasyPrint | Print-oriented documents authored in HTML and CSS | Python API, broad W3C CSS support, PDF links and bookmarks; verify unsupported CSS and URL-fetching limits. |
| Playwright (Python) | Existing browser pages and workflows that need browser behavior | page.pdf() uses print media by default; emulate screen media when needed and account for print color adjustment. |
| ReportLab | Programmatically composed PDFs | Its documented RML workflow is not a direct HTML/CSS conversion pipeline. |
Neither the documentation nor the APIs establish a universal winner for speed or pixel fidelity. Test your own templates, fonts, images, page breaks and external assets in the deployment environment.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Generate a PDF with WeasyPrint
Install and render a minimal document
Install WeasyPrint according to its current platform instructions, then call the API:
#1 Best Overall
- EDIT text, images & designs in PDF documents. ORGANIZE PDFs. Convert PDFs to Word, Excel & ePub.
- READ and Comment PDFs – Intuitive reading modes & document commenting and mark up.
- CREATE, COMBINE, SCAN and COMPRESS PDFs
- FILL forms & Digitally Sign PDFs. PROTECT and Encrypt PDFs
- LIFETIME License for 1 Windows PC or Laptop. 5GB MobiDrive Cloud Storage Included.
from weasyprint import HTML
html = """
Report
Quarterly report
Generated from HTML5 and CSS3 in Python.
Total: $12,450
"""
HTML(string=html).write_pdf("report.pdf")
Pass the markup explicitly with string=. The documentation warns that a bare string positional argument can be interpreted as a filename. The same API accepts a filename, URL or readable object, and can write to a filename or file-like destination (WeasyPrint First Steps).
Keep HTML and CSS in separate files
from weasyprint import HTML, CSS
pdf = HTML(filename="templates/invoice.html", base_url="templates/").write_pdf(
stylesheets=[CSS(filename="static/print.css")]
)
with open("invoice.pdf", "wb") as output:
output.write(pdf)
A correct base_url is important for relative images, stylesheets and fonts. Without it, an HTML string has no reliable location from which relative URLs can be resolved.
Use custom fonts
from weasyprint import HTML, CSS
from weasyprint.text.fonts import FontConfiguration
font_config = FontConfiguration()
css = CSS(filename="static/print.css", font_config=font_config)
HTML(filename="templates/report.html", base_url="templates/").write_pdf(
"report.pdf",
stylesheets=[css],
font_config=font_config,
)
Define your @font-face rules in the stylesheet and ensure the font files are readable in the rendering environment. A missing font can change line wrapping and therefore page breaks.
Control page layout with print CSS
@page {
size: Letter;
margin: 20mm 16mm 22mm;
@bottom-right { content: counter(page); }
}
h1, h2 { break-after: avoid; }
.chapter { break-before: page; }
table { break-inside: avoid; }
@media print {
.screen-only { display: none; }
}
Use physical page sizes, margins and break properties deliberately. WeasyPrint supports many W3C CSS specifications, but its support is not complete browser parity. The current support and unsupported-feature list is maintained in the API reference; check it for features such as advanced layout, bidirectional text and particular table or page-box behavior before depending on them.
Rank #2
- Edit PDFs with Ease. Modify text, images, and layouts directly within your PDF documents.
- Convert & Organize. Export PDFs to Word, Excel, or ePub, and organize files with ease.
- Read & Annotate. Enjoy intuitive reading modes and powerful tools to comment, highlight, and mark up PDFs.
- Create & Manage PDFs. Create new PDFs, combine multiple files, scan documents, and compress for easy sharing.
- Fill & Sign Forms. Complete forms and digitally sign documents with secure e-signature tools.
Render an existing page with Playwright
Basic browser PDF export
from pathlib import Path
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://example.com", wait_until="networkidle")
page.pdf(path="page.pdf", format="A4", print_background=True)
browser.close()
Install the Python package and the browser binaries using Playwright’s current installation instructions. The page API documents that page.pdf() generates a PDF using print CSS media by default (Playwright Python Page API).
Choose screen or print media
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page(viewport={"width": 1440, "height": 900})
page.goto("https://example.com/dashboard", wait_until="networkidle")
page.emulate_media(media="screen")
page.pdf(path="screen-styled.pdf", format="A4", print_background=True)
browser.close()
Call emulate_media(media="screen") before page.pdf() when the screen rules, rather than @media print, are the ones you need. Browser PDF output also changes colors for printing by default. When exact colors matter, use the CSS adjustment described in the Playwright documentation:
* {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
Wait for application content and assets
page.goto(url, wait_until="domcontentloaded")
page.locator("#report-ready").wait_for(state="visible")
page.wait_for_function("() => Array.from(document.images).every(i => i.complete)")
page.pdf(path="ready.pdf", prefer_css_page_size=True)
Choose a readiness condition that belongs to your application: a marker element, an API result, a stable network state or image completion. The API behavior does not guarantee that your site’s JavaScript, fonts or lazy images have finished simply because navigation returned.
When ReportLab is the better tool
ReportLab’s official guide documents RML and the rml2pdf workflow (ReportLab PDF Generation User Guide). Choose it when your system owns the document model and lays out paragraphs, tables and graphics directly. If designers already deliver HTML templates and CSS, converting those templates with WeasyPrint or a browser is usually a more direct fit; moving them to RML is a different authoring model.
Rank #3
- Create and edit PDFs. Collaborate with ease. E-sign documents and collect signatures. Get everything done in one app, wherever you go.
- Edit text and images without jumping to another app.
- E-sign documents or request e-signatures on any device. Recipients don’t need to log in to e-sign.
- Convert PDFs to editable Microsoft Word, Excel, or PowerPoint documents.
- Share PDFs for collaboration. Commenting features make it easy for reviewers to comment, mark up, and annotate.
Handle assets, URLs and security deliberately
Relative and remote resources
Images, CSS, fonts and other resources are part of the rendering input. Use a filesystem base_url for local templates and ensure the process can read every referenced file. WeasyPrint can fetch local and HTTP resources, but its default HTTP client does not provide advanced cookies or authentication. For protected assets, configure a suitable custom URL fetcher rather than assuming a browser session will be available (WeasyPrint API reference).
Sanitize untrusted input
Do not pass arbitrary user HTML or CSS directly to a renderer. WeasyPrint explicitly warns: “Using WeasyPrint with untrusted HTML or untrusted CSS may lead to various security problems” (First Steps). Sanitize markup, constrain CSS, restrict allowed URL schemes and hosts, and isolate rendering jobs when documents originate outside your trust boundary.
Validate links, fonts and page breaks
- Open the PDF in more than one viewer and inspect every page.
- Check that external images and web fonts are present rather than silently replaced.
- Verify headings, tables and code blocks do not split in unacceptable places.
- Confirm Unicode, right-to-left text and long unbroken strings with your actual data.
- Compare output in the same operating-system image used in production.
Performance and operational guidance
Do not apply a generic speed claim to either renderer. Measure your templates, asset sizes and concurrency. For batches, WeasyPrint notes that a long-lived process can avoid repeated startup costs (WeasyPrint First Steps). Reuse a worker process where practical, but put limits on document size, external requests and execution time.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Playwright starts a full browser, so manage browser and context lifetimes rather than launching a new browser for every page. Use isolated contexts for separate users, close pages promptly and set application-level timeouts. In either approach, cache immutable assets, avoid unnecessarily huge images and record the input URL, renderer settings and output errors so a failed document can be reproduced.
Rank #4
- Perfect Adobe Acrobat Pro alternative – lifetime license for Windows 10 and 11.
- EDIT text, images, pages, hyperlinks, designs in PDF documents. ORGANIZE PDFs.
- READ and Comment on PDFs – Intuitive reading modes & document commenting and mark up tools!
- CREATE, COMBINE, SCAN and COMPRESS PDFs.
- FILL forms & Digitally Sign PDFs. Work with Digital certificates
Troubleshooting common failures
“The PDF is blank”
For WeasyPrint, verify that you used HTML(string=...), supplied a valid base_url, and that the document actually contains body content. For Playwright, wait for the application’s ready marker and check that content is not hidden by a print stylesheet.
Images or CSS are missing
Resolve relative URLs against the correct base directory, check file permissions and inspect HTTP responses. Protected resources may need a custom WeasyPrint URL fetcher or authenticated browser context in Playwright.
Fonts change or text wraps differently
Confirm that font files exist in the deployment image and that your @font-face declarations use usable URLs. A fallback font changes metrics, which can move content across page boundaries.
Free tools Windows power users keep installed
One-click scans. No signup required.
Colors look washed out in Playwright
This is the documented print-color behavior. Add -webkit-print-color-adjust: exact (and the standard property) when preserving screen colors is required, then verify the result in your target PDF viewer.
Best Value
- ALL-IN-ONE SOLUTION – read, edit, convert, merge and protect your PDF files
- MAXIMUM FUNCIONALITY – create interactive forms, compare PDFs, bates numbering, find and replace text or colors, convert documents, OCR engine, comment, highlight, fill out and print forms, document protection and others
- EASY TO INSTALL AND USE – well-structured user-interface, in-program instructions, free tech support whenever you need it
- GREAT VALUE FOR MONEY - why spend a fortune if you can have maximum functionality at a reasonable price - this also fits the requirements of companies very well
CSS works in Chrome but not WeasyPrint
Browser support is not evidence of WeasyPrint support. Check the current unsupported-feature list, replace the unsupported rule with print-oriented CSS, or switch that document to Playwright if browser layout is a hard requirement.
Rendering is slow or times out
Profile asset fetches, JavaScript readiness waits and document size. Reuse long-lived workers, set explicit timeouts, and prevent untrusted documents from requesting arbitrary slow or internal URLs.
Or skip the browser setup
If your actual requirement is a clean PDF or image of a public webpage rather than conversion of your own HTML template, ScreenshotNeo provides a single API endpoint. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups and chat widgets. Bot checks, blank pages, timeouts, failed loads and cache hits are not billed, and the response identifies the page verdict and billing status in X-Page-Verdict and X-Billed headers.
Python:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
cURL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
See the ScreenshotNeo documentation for output and options. Its MCP server lets Claude, Cursor and other MCP clients call take_screenshot, get_page_info and capture_pdf. Every plan includes its features; the free plan provides 1,000 screenshots per month without a card, and paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.
A practical decision checklist
- Use WeasyPrint when your source is controlled HTML/CSS and the target is a print document.
- Use Playwright when the source is a live browser page, requires JavaScript or depends on browser layout.
- Use ReportLab when your application already models content as PDF-oriented drawing and flowables.
- Render representative documents in production-like environments before committing to a renderer.
- Record asset, font, media and page-break assumptions as tests so template changes do not silently damage output.
Frequently Asked Questions
Can WeasyPrint execute JavaScript from my HTML?
The documented WeasyPrint workflow is HTML/CSS rendering, not a full browser runtime. If your page depends on JavaScript to construct its content, render it with Playwright or produce the final HTML before passing it to WeasyPrint.
How do I add headers and footers to a WeasyPrint PDF?
Use print-specific CSS such as an @page rule and generated page-margin content, then test the result with your actual page sizes and fonts.
Should I use a URL or an HTML string?
Use HTML(string=…) for markup held in Python, HTML(filename=…) for a local template, or a URL when the source is already addressable. Supply base_url when relative resources must resolve from a string or local template.
Can I guarantee identical PDFs across machines?
No. Fonts, operating-system libraries, resource availability and renderer behavior affect pagination and appearance. Pin your deployment environment and run visual checks on representative documents.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

