Migrating from PDFreactor / Already HTML and CSS
Migrating off PDFreactor, and auditing its CSS extensions first
A commercial formatter with strong paged CSS support and its own extensions, where the migration is a feature audit followed by a fairly ordinary call change.
What ports and what does not
The markup and the standard CSS port. The extensions do not, and finding them is the first task. PDFreactor supports paged media features that browser engines do not, and it adds its own properties for PDF-level concerns such as bookmarks, document metadata and tagging. It also runs JavaScript, so unlike a WeasyPrint migration you cannot assume the templates are free of client-side rendering, and the timing of when scripts have finished becomes something to verify rather than something you know.
What the call becomes
The Configuration object and the convert call become a POST to /v1/pdf. Configuration.setDocument with a string becomes html, and with a URL becomes url. The properties governing page size and margins are largely CSS-driven already, so set options.prefer_css_page_size to true. The configuration entries with no counterpart are the PDF-level ones: bookmark generation, document metadata beyond what your markup carries, tagging and conformance settings. The only conformance option here is options.pdf_a, which writes a PDF/A-1b identification packet and returns a warning saying the marking is best-effort, and that is a much narrower thing than a formatter's conformance mode.
Side by side
The old shape and the new one, with the parts that have no counterpart called out in comments rather than quietly omitted.
// Before (Java)
Configuration config = new Configuration();
config.setDocument(html);
config.setJavaScriptSettings(...);
PDFreactor reactor = new PDFreactor();
Result result = reactor.convert(config);
byte[] pdf = result.getDocument();
// After
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.pdfpipe.xyz/v1/pdf"))
.header("Authorization", "Bearer " + System.getenv("PDFPIPE_KEY"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body)) // {"html": ..., "options": {...}}
.build();
/* Audit for these before committing. Each is a PDFreactor capability
with no counterpart on a render call:
bookmark generation from the document structure
document-level metadata set through configuration
tagging and accessibility conformance modes
conformance output beyond options.pdf_a, which writes a PDF/A-1b
identification packet and tells you in the response that the
marking is best-effort
If the pipeline depended on any of those, they move to a step after
the render rather than disappearing, and that step has to exist
before the cutover rather than after. */The CSS delta, in both directions
What the old engine accepted that a current one does not, and what it refused that a current one wants. The second direction is the one people forget, and it is where the value of the migration is.
- PDFreactor's own prefixed properties stop applying. They do not error, so a stylesheet full of them renders as though they were never there.
- Paged media features beyond what a browser engine implements stop working, in the same places as the Prince migration: footnotes, cross-reference resolution and leaders are the ones to check.
- JavaScript still runs, but the point at which the page is considered ready is decided differently, so a document that depended on a script finishing needs that assumption re-tested rather than assumed.
- Standard CSS is unaffected, and anything modern that the templates avoided is available.
What changes without anything erroring
The dangerous list. Each of these produces a different document and no diagnostic, so none of them are caught by a test that only checks the render succeeded.
- Anything driven by a prefixed extension property, which stops applying with no diagnostic.
- The document outline, if bookmarks were generated by configuration rather than being part of the markup.
- Content produced by scripts, if the readiness point differs, which shows up as intermittent rather than consistent absence and is the hardest kind of change to catch.
- Conformance claims, which is the one to be careful about: a pipeline that produced a conforming file now produces one that does not, and nothing about the document looks different.
What to diff before cutting over
Grep the stylesheets for the vendor prefix and list every property that comes back. That list is the audit. Then, because JavaScript is involved, render the same document ten times rather than once and compare the outputs to each other, which is the only way to see an intermittent readiness problem before it reaches production.
The one thing that always breaks
Conformance, when the pipeline had any. PDFreactor could produce tagged and conforming output as part of the render, and that is not something a general rendering API does. The replacement is a conversion step after the render, and until it exists the documents look identical and no longer satisfy the requirement that justified buying the formatter.
Frequently asked
Do I have to rewrite my templates to leave PDFreactor?
No. The templates are HTML and CSS and they carry across. What needs attention is the delta above: the places the two engines disagree, and the configuration that used to live outside the document and now lives inside it, or the other way round.
Can I run both for a while?
Yes, and it is the safest way to do it. Put both behind one internal function that takes your data and returns bytes, switch on an environment variable, and run the new path on real traffic while the old one still serves. You get a diff on real documents rather than on fixtures, and you keep a way back that does not involve a deploy.
What about the documents already generated?
Nothing here changes them. They are files that already exist. What is worth deciding before the cutover is whether a regenerated document has to match the original byte for byte or merely say the same thing, because for anything with a legal or audit character the answer is usually to keep the original file rather than to be able to reproduce it.
Other migrations
The nearest neighbours first, then others that started from the same kind of tool, because the model matters more than the language.
Migrating off Prince, and the paged CSS features you lose
A commercial formatter with the most complete paged CSS implementation available, which makes this the one migration in the cluster where you should expect to give something up.
Migrating off WeasyPrint, and what happens to your JavaScript
A Python renderer with genuinely good paged CSS support and no JavaScript engine, so the migration adds a capability you never had and changes very little else.
Migrating off Flying Saucer, and leaving XHTML strictness behind
A Java renderer that required well-formed XHTML and implemented CSS 2.1, where the migration relaxes both constraints and the templates keep obeying them anyway.
Migrating off wkhtmltopdf without changing what the pages look like
A command line tool built on a fork of an old browser engine, driven entirely by flags, where almost every flag you were passing has a different home on the other side.
Migrating off wicked_pdf or Snappy, the wkhtmltopdf wrappers
Language wrappers that shell out to the wkhtmltopdf binary, where the framework integration is the thing you are actually replacing and the engine change comes along with it.
Every migration, grouped by what you are coming from
The full list, sorted by the model the old tool used rather than by its name.
What this API actually does
One page per option and endpoint that exists, which is what the mappings above point at.
Render one of your existing documents through the playground before changing any code. That comparison is the whole of the risk assessment for this migration.