Migrating from WeasyPrint / Already HTML and CSS
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.
What ports and what does not
Nearly everything. WeasyPrint implements paged CSS properly, so stylesheets written for it, including @page rules, margin boxes, named pages and counters, carry across with less friction than any other migration in this cluster. The interesting direction is the opposite one: WeasyPrint does not execute JavaScript, so your templates are guaranteed to contain no client-side rendering, no charting library and no dynamic content. Everything is server-rendered already, which means the migration is unusually safe and also that you are not yet using the main thing you have gained.
What the call becomes
HTML(string=...).write_pdf() becomes a POST to /v1/pdf with html in the body. WeasyPrint took page setup from CSS rather than from arguments, so there is nothing to translate: your @page rule keeps working and you can leave the options object nearly empty. Set options.prefer_css_page_size to true so the size in your @page rule governs, which is the behaviour you already had. The base_url argument, which WeasyPrint used to resolve relative asset paths against the local filesystem, has no counterpart and is the one thing that needs attention.
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
from weasyprint import HTML, CSS
HTML(string=html, base_url="/srv/app/static/").write_pdf(
"invoice.pdf",
stylesheets=[CSS(filename="/srv/app/static/print.css")],
)
# After
import os, requests
resp = requests.post(
"https://api.pdfpipe.xyz/v1/pdf",
headers={"Authorization": f"Bearer {os.environ['PDFPIPE_KEY']}"},
json={
"html": html, # with absolute asset URLs, or CSS inlined
"filename": "invoice.pdf",
"options": {
# Your @page rule already says the size. Let it govern, which
# is the behaviour WeasyPrint gave you by default.
"prefer_css_page_size": True,
},
},
timeout=60,
)
pdf = resp.content
# base_url has no counterpart. Either inline the stylesheet into the
# html string, or serve it from a public absolute URL. A separate
# stylesheets argument does not exist: there is one document.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.
- Very little changes, which is the point. Paged CSS that worked in WeasyPrint generally works, because both implement the same specifications rather than one of them approximating.
- JavaScript now runs. Nothing in your templates uses it, so nothing changes today, but it is the reason to do the migration and the thing to be deliberate about adding.
- A handful of WeasyPrint's paged-media features are ahead of what a browser engine implements, footnotes being the usual example. If your templates use one, that is the item to check first, because it is the only direction where you can lose something.
- Font handling changes from system font resolution to @font-face at a public URL, the same as every other migration here.
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.
- Fonts, if any family was resolved from the system rather than declared. WeasyPrint could use installed fonts and a hosted renderer cannot.
- Line breaking and hyphenation, which differ enough between implementations to move a page count even when the CSS is identical.
- Any paged-media feature WeasyPrint supports and a browser engine does not, which stops working rather than erroring.
What to diff before cutting over
This is the migration where a page-count diff is most likely to come out equal, so use it as the primary check and treat any difference as worth investigating rather than as expected drift. Then check the stylesheet loaded at all, since the base_url change is the one real hazard.
The one thing that always breaks
Asset resolution. WeasyPrint's base_url let templates reference stylesheets, images and fonts by relative path against the local filesystem, and that is precisely what a hosted renderer cannot do. The document renders unstyled and complete, which is the most visible failure available and the easiest to fix once seen.
Frequently asked
Do I have to rewrite my templates to leave WeasyPrint?
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 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.
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 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.
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.