An HTML-to-PDF comparison is easier to evaluate when readers can inspect the inputs and outputs. A screenshot of a successful invoice does not show whether a renderer can repeat table headers, handle a row taller than a page, or honor different page dimensions in one document.
This article introduces a downloadable fixture suite with HTML sources, generated PDFs, a rendering manifest, and a regression harness. The examples and synthetic data are MIT-licensed in the archive. Download the example projects, fixtures, and results.
What the suite contains
| Fixture | What it exercises | What to inspect |
|---|---|---|
| 72-row service report | Multipage table pagination | Repeated headers, intact short rows, final total |
| Deliberately enlarged report | A known layout regression | Three pages becoming four |
| Oversized table row | A row taller than printable height | How the engine fragments unavoidable overflow |
| SVG chart | Vector shapes and labels | Missing elements, clipping, readable labels |
| Font fallback | An unavailable first-choice font | Fallback glyphs, changed metrics, accented text |
| Mixed page sizes | Portrait document with landscape appendix | Per-page width and height |
| Three invoice layouts | Small business-document variants | Currency, line items, missing invoice number |
| Clean and degraded scans | Recognition-sensitive input | Text-layer recovery and failed phrase checks |
The oversized row is intentionally impossible to keep whole on one page. Its purpose is to show a limit, not to declare a renderer defective for breaking it. Similarly, the fallback fixture intentionally requests an unavailable font. It tests observable fallback behavior rather than successful embedding of a custom font.
Reproduce the baseline
Install Node 22 or newer, Chromium, and Poppler. Extract the archive and run:
npm ci
npm run fixtures
npm run compare
npm test
Set CHROMIUM_PATH if your Chromium executable is elsewhere. The source uses inline content and system fonts; no remote CSS or image host is required. Record your font environment when comparing results across machines.
render.mjs writes the HTML and PDF fixtures and records SHA-256 hashes, file sizes, page counts, and page dimensions. compare.mjs renders the shared report using Puppeteer and React PDF. regression.mjs verifies a separately generated clean report and rejects the intentionally broken one.
The rendering manifest records the original local run with Chromium 151.0.7922.173 and Node 26.7.0. PDF hashes identify those particular output files; they are not expected to stay constant when a renderer changes metadata or object ordering.
Inspect artifacts before interpreting scores
Download the standard report, broken report, SVG chart, font fallback output, and mixed-page document.
For the mixed-page fixture, the manifest records each page's actual dimensions. For the report, text extraction checks the first item, last item, and grand total. Raster comparisons then detect layout differences that those semantic checks miss.
The recorded clean comparison had zero changed pixels at the test settings. The broken candidate had four pages instead of three and exceeded the visual threshold. The CI article explains why the negative test is part of the suite.
What has been compared so far
The corpus has a Chromium baseline across the HTML fixtures. The shared service report also has a separately authored React PDF implementation. The React PDF versus Puppeteer article discloses its timing method, font differences, and header behavior.
This release does not contain measured Prince, wkhtmltopdf, or Gotenberg results. It does not rank untested engines. The fixture sources are supplied so another engine can be evaluated without relying on undocumented marketing screenshots.
API-based OCR and basic compression results were measured on RelayPDF staging and are labeled separately from the local renderer results. They demonstrate concrete outcomes on the provided files, including OCR failure on a degraded scan and compression that slightly enlarges image-based PDFs.
Add an engine without changing the question
Use the same HTML source and record every adaptation required by the engine. If an engine needs a different stylesheet for a feature, publish that stylesheet rather than silently replacing the input.
Record the engine version, operating environment, fonts, page options, and whether startup is included in timing. Separate local rendering time from network service latency. Run enough repetitions for the claim you intend to make and publish individual measurements, not only a selected best run.
Use distinct criteria for visual layout, searchable text, file size, and specialized conformance. One combined score can hide a tradeoff that matters more than the average to a particular reader.
Use the corpus in your own application
Replace the generic service rows with your difficult production-shaped examples, using synthetic or appropriately permitted data. Add a long description, an empty section, a large chart, and non-English text in a font you actually deploy.
Keep an approved baseline under review and compare candidate outputs against it. Generating both baseline and candidate from the same changed template can produce a green test that proves nothing about regression.
For specific layout problems, the page-break guide, custom-font guide, and chart guide explain the corresponding controls. For managed rendering of the same HTML, see HTML-to-PDF and the print options reference.