Migrating from Flying Saucer / Already HTML and CSS
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.
What ports and what does not
Everything ports, and more easily than the templates expect. Flying Saucer parsed XHTML strictly, so your markup is well-formed, every tag is closed and every attribute is quoted, which is exactly the input a browser engine is happiest with. Its CSS support stopped at 2.1, so the templates contain no modern layout at all. Nothing breaks and nothing improves until the templates are rewritten, which is the same shape as the dompdf migration with a different language around it.
What the call becomes
ITextRenderer, setDocumentFromString, layout and createPDF collapse into a POST to /v1/pdf with html. The renderer's shared context configuration, its user agent callback for resolving resources and its font directory registration have no counterpart: resource resolution becomes ordinary HTTP from the markup, and fonts become @font-face at a public URL. Page setup was already CSS in Flying Saucer, so set options.prefer_css_page_size to true and leave the rest alone.
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
ITextRenderer renderer = new ITextRenderer();
renderer.getFontResolver().addFont("/fonts/brand.ttf", true);
renderer.setDocumentFromString(xhtml, baseUrl);
renderer.layout();
ByteArrayOutputStream out = new ByteArrayOutputStream();
renderer.createPDF(out);
// After
String body = mapper.writeValueAsString(Map.of(
"html", xhtml,
"filename", "invoice.pdf",
"options", Map.of("prefer_css_page_size", true)
));
HttpResponse<byte[]> resp = client.send(
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))
.build(),
HttpResponse.BodyHandlers.ofByteArray());
/* addFont has no counterpart. The font becomes an @font-face rule in
the document pointing at a public URL:
@font-face {
font-family: "Brand";
src: url("https://assets.example.com/brand.woff2") format("woff2");
}
The user agent callback that resolved images and stylesheets from the
classpath also has no counterpart. Those assets have to be reachable
over HTTP or inlined. */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.
- CSS 2.1 is a subset of what is available now, so nothing in the stylesheets stops working. Flexbox and grid become available and the table-based layouts continue to render.
- The XHTML strictness requirement is gone. That is a relaxation rather than a change, and it is worth keeping the discipline: well-formed markup is easier to reason about and the templates already have it.
- Resource resolution changes from a classpath callback to ordinary HTTP, which is the substantive difference and the one that needs work.
- Font registration changes from a renderer call to @font-face, which also means the font file has to move somewhere public.
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, because addFont registrations vanish and the family names in the CSS resolve to nothing.
- Images and stylesheets loaded through the user agent callback, which stop loading and produce an unstyled or incomplete document.
- Page count, from metric differences, though less than in most migrations because CSS 2.1 layout is comparatively predictable.
What to diff before cutting over
List every resource the user agent callback was resolving, because that list is invisible in the markup: the URLs in the templates may look like ordinary paths while being served from the classpath. Then diff extracted text and page counts.
The one thing that always breaks
Resource loading through the callback. Flying Saucer let an application intercept every resource request and serve it from anywhere, typically the classpath or a database, and templates written against it reference resources by paths that mean nothing over HTTP. Everything they point at fails to load at once.
Frequently asked
Do I have to rewrite my templates to leave Flying Saucer?
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 iText, and the licensing question underneath it
A mature PDF library with a document model and a low-level API, where the technical migration is ordinary and the reason for it is frequently the licence rather than the code.
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.