PDFPipe

Migrating from Prince / Already HTML and CSS

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.

What ports and what does not

The markup ports and most of the CSS ports. This is the migration that needs an honest feature audit first, because Prince implements parts of the paged media specifications that browser engines do not, and templates that use them will lose that behaviour rather than degrade gracefully. Footnotes, cross-references resolved with target-counter and target-text, leaders, named page groups, PDF bookmarks generated from the document and hyphenation dictionaries are the features to look for. If your templates use none of them, this migration is straightforward. If they use several, the question is not how to migrate but whether to.

What the call becomes

The prince command or a binding call becomes a POST to /v1/pdf with html. Prince took nearly all configuration from CSS, so as with WeasyPrint there is little to translate: set options.prefer_css_page_size to true and let your @page rule govern. The command line options that generated PDF metadata, bookmarks and attachments have no counterpart on the render, and are the concrete part of the feature audit.

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.

bash
# Before
prince invoice.html -o invoice.pdf --javascript

# After
curl -X POST https://api.pdfpipe.xyz/v1/pdf \
  -H "Authorization: Bearer $PDFPIPE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "html": "<!doctype html>...",
    "filename": "invoice.pdf",
    "options": { "prefer_css_page_size": true }
  }'

# Audit the stylesheets for these before committing to the migration.
# Each is a Prince feature with no browser-engine equivalent, and each
# stops working rather than degrading:
#
#   float: footnote            and the ::footnote-call pseudo-element
#   target-counter(...)        cross-references resolved to a page number
#   target-text(...)           cross-references resolved to the target text
#   leader(...)                dot leaders in a contents listing
#   prince-bookmark-level      PDF bookmarks generated from headings
#   @prince-pdf               document-level PDF settings
#
# A contents page built with target-counter is the common case. There is
# no replacement for it in CSS: the page numbers have to be resolved
# elsewhere, or the contents page has to be produced differently.

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.

  • Footnotes stop working. Prince implements the footnote model from the paged media specification and browser engines do not, so float: footnote produces an ordinary float and the note appears inline where it was written.
  • Cross-references resolved with target-counter or target-text stop resolving. A contents page built this way renders with empty page numbers rather than wrong ones, which at least fails visibly.
  • Leaders stop being generated, so a contents line loses its dots and the page number moves next to the title.
  • Hyphenation changes. Prince ships language dictionaries and a browser engine's hyphenation is its own, so justified text rebreaks throughout.
  • PDF bookmarks generated from the document structure stop being generated, because that was a Prince extension rather than CSS.
  • In the other direction, very little is gained. Prince is ahead on paged media, so this is the one migration in the cluster where the CSS delta is mostly a list of losses, and the reasons to do it are operational rather than typographic.

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.

  • Contents pages, which render with the structure intact and the numbers missing.
  • Justified text throughout, from the hyphenation change, which moves the page count on any long document.
  • Footnote placement, which moves from the foot of the page to the middle of the text.
  • Document outline, which disappears from the reader's navigation pane without changing a single printed page.

What to diff before cutting over

Do the feature audit before the diff, because if the templates use footnotes or target-counter the diff will only tell you what you already know. Grep the stylesheets for the Prince-only property names first. Then, if the audit is clean, diff page counts and extracted text as usual and expect the hyphenation change to move both slightly.

The one thing that always breaks

The table of contents. Almost every document long enough to have been given to Prince has one, and almost every one of those was built with target-counter and leader. Both stop working together, so the contents page keeps its entries, loses its dots and shows nothing where the page numbers were.

Frequently asked

Do I have to rewrite my templates to leave Prince?

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.

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.