Migrating from dompdf / Already HTML and CSS
Migrating off dompdf, and getting flexbox back
A pure PHP renderer supporting a subset of CSS 2.1, where the migration's real content is everything your templates could not do and had to work around.
What ports and what does not
The markup ports. The CSS mostly ports, but that understates the situation, because dompdf's CSS support stops well short of what a browser engine does and your templates were written inside that limit. There is no flexbox and no grid, float support is partial, and the layout of anything non-trivial was built from tables. All of that continues to render, so nothing breaks on day one. What you are actually migrating is a set of constraints, and the templates that were shaped by them stay shaped by them until somebody rewrites them.
What the call becomes
The Dompdf object, its loadHtml call, its setPaper call and its render and output pair collapse into a single POST to /v1/pdf. setPaper's size becomes options.format and its orientation becomes options.landscape. dompdf's options array has no general counterpart: most of its entries were about the PHP environment, remote asset fetching and font directories, none of which exist on the other side. The one to look for is isRemoteEnabled, because if it was off, your templates have no remote assets at all and everything is inline or local, which is exactly the case that needs the most work.
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.
<?php
// Before
$dompdf = new \Dompdf\Dompdf(['isRemoteEnabled' => true]);
$dompdf->loadHtml($html);
$dompdf->setPaper('A4', 'portrait');
$dompdf->render();
$pdf = $dompdf->output();
// After
$response = $client->post('https://api.pdfpipe.xyz/v1/pdf', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('PDFPIPE_KEY'),
'Content-Type' => 'application/json',
],
'json' => [
'html' => $html,
'filename' => 'invoice.pdf',
'options' => [
'format' => 'A4',
'landscape' => false,
],
],
]);
$pdf = (string) $response->getBody();
// isRemoteEnabled has no counterpart. If it was false, every asset in
// your templates is local or inline, and local paths do not resolve
// from a hosted renderer. That is the work item this flag reveals.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.
- Flexbox and grid start working. Your table-based layouts still work too, which is why nothing appears to change, and why the migration's value is not realised until the templates are rewritten to use them.
- Float behaviour changes. dompdf's float support was partial and its interaction with page breaks was its own, so any layout built on floats around a break will move.
- @page support broadens considerably. dompdf handled a narrow slice of it; margin boxes, named pages and the page pseudo-classes are all available now, which means running headers and folios can move out of your PHP and into the stylesheet.
- Fonts change completely. dompdf resolved fonts from a configured font directory and often needed them installed into its own cache. A modern renderer wants @font-face pointing at a public URL, and the font directory concept has no counterpart.
- CSS that was written to avoid dompdf bugs is now working around nothing. Those are safe to leave and worth removing later, but they are not urgent and they are not where the risk is.
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.
- Text metrics, and therefore page count. dompdf's line breaking is its own implementation and a browser engine's is not, so the same paragraph occupies a different number of lines.
- Fonts, unless every one is declared with @font-face at a public URL. A family name that dompdf resolved from its font directory falls back silently.
- Table column widths. dompdf's table layout algorithm and a browser's disagree, particularly on tables without explicit widths, so a table that fitted can overflow.
- Images sized without explicit dimensions, because the two engines make different assumptions when an image's intrinsic size is unknown at layout time.
What to diff before cutting over
Extract the text of both outputs and diff it, because dompdf silently drops content it cannot lay out and you want to know whether anything was missing before the migration rather than discovering it after. Then compare page counts. Then look specifically at every table without fixed column widths, because that is where dompdf and a browser disagree most.
The one thing that always breaks
Fonts. dompdf's font handling is unlike anything else: families are registered into its own directory and cache, and templates refer to them by name with no @font-face at all. Those declarations mean nothing to a browser engine, so every custom font in the document falls back to a default at once, and the document is still perfectly readable, which is why it ships.
Frequently asked
Do I have to rewrite my templates to leave dompdf?
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 mPDF, and replacing its custom tags
A PHP renderer with its own HTML dialect, where the templates contain tags and attributes that exist only in mPDF and have to be translated rather than carried across.
Migrating off TCPDF, where half the document is drawn and half is HTML
A PHP library that is a drawing API with an HTML renderer bolted on, so most codebases using it are half template and half coordinates, and the two halves migrate differently.
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.
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.
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.