Fullbleed, WeasyPrint, and Chromium: compare the PDFs first
A PDF benchmark is useful when you can inspect the documents behind its numbers. This comparison gives Fullbleed, WeasyPrint, and Chromium the same static HTML/CSS, font, and synthetic records, then checks the output before accepting a timing.
The workload is deliberately small: a one-page invoice, a 100-row ledger, and a three-page report. The run used Fullbleed 2.5.6, WeasyPrint 70.0, and Playwright 1.63.0 with Chromium 153.0.8010.12 on one Ubuntu/WSL2 development machine.
Read the complete comparison and download the PDFs. The report includes all 315 latency samples, separate memory measurements, source code, and the initial setup failures.
Different jobs favor different tools
Fullbleed had the lowest fresh-process median for all three fixtures. Its warm medians were 12.9 milliseconds for the invoice, 87.2 milliseconds for the ledger, and 27.5 milliseconds for the report.
Chromium had the lowest warm ledger median at 64.5 milliseconds. Its invoice and report medians were 38.7 and 42.8 milliseconds. WeasyPrint's corresponding warm medians were 78.5, 290.7, and 164.9 milliseconds.
WeasyPrint produced the smallest PDF files in all three fixtures. Its invoice was about 8.7 KiB, compared with Chromium's 9.5 KiB and Fullbleed's 51.3 KiB. File size matters when an application retains or sends large numbers of documents, so it belongs beside latency in the evaluation.
The full-size chart shows medians and the entire observed range. The report also includes sampled process-tree memory, with browser children included and the limitations of summed RSS explained.
Check the output contract first
Every generated PDF had to preserve all body-text segments, every record ID exactly once and in order, the line-item values, and the totals. The checks also covered A4 dimensions, declared page counts, embedded Inter fonts, repeated table headings, and text boxes inside the page inset.
These checks used pypdf and PDFium separately from the three rendering engines. Deliberately wrong record expectations, a removed page, missing font embedding, and shifted text had to fail. All 414 PDFs from the measured run, including warm-ups and memory passes, qualified. All 22 representative page images matched the pages inspected for readable layout and page transitions.
The ledger still has a visible difference: Chromium puts the final note on page four, while Fullbleed and WeasyPrint keep it on page three. The original contract allowed three or four pages. All 100 records and the total survived, and the timing table reports the page counts. These are complete-document timings, not a claim of pixel equality.
Two setup details changed the experiment
The first inputs used Inter's contextual alternates. In this Chromium version, the hyphen in an uppercase ID such as RUN-000001 extracted as a private-use character. A static font instance reproduced it. A focused probe showed that disabling the calt feature preserved those IDs, so every measured fixture uses the same static font and the same feature setting in all three engines.
WSL also inherited 82 Windows PATH entries. WeasyPrint's shared-library discovery searched them during import. A diagnostic took about 11.8 seconds with that PATH and about 1 second with standard Linux executable directories. Every measured worker received the same Linux PATH policy. The retained setup diagnostics make both adjustments inspectable.
Separate startup from warm rendering
The fresh-process measurement starts before Python launches and ends when it exits. It includes imports, font setup, a browser launch where needed, rendering, writing the PDF, and cleanup. Operating-system caches were not flushed.
The warm measurement reuses an engine, a WeasyPrint stylesheet and font configuration, or a Chromium browser and page. Each render receives changed HTML with a unique fixed-length record token. It includes parsing, layout, PDF creation, and the same Python file-writing step. Chromium waits for its fonts to load.
Each renderer/document pair has five fresh-process samples and thirty warm samples, split across three independently started blocks. Jobs run sequentially in seeded shuffled order, and every sample is retained. Memory sampling runs separately so it cannot add work to the latency measurements.
Use the experiment as a starting point
Three fixtures on one development host cannot predict every workload. Browser JavaScript, live website layout, HTTP services, concurrency, compiled templates, and high-volume VDP are outside this run. The output checks do not certify PDF/A, PDF/UA, or PDF/X.
Start with the reproducible source and methodology. Replace the synthetic fixtures with your application's longest records and required features, declare what correct output means, and inspect the PDFs before using a timing result to choose a library.
Download the complete measured evidence or browse the results and individual PDFs.
This maintainer-run comparison and write-up were prepared by an AI coding agent under Fullbleed's direction. The measurements came from executed code; raw samples, failed setup outputs, source revisions, and verification evidence are retained. Measured October 5, 2026 UTC.