PD4ML v3 to v4 Migration Guide
PD4ML v4 is a substantial rewrite of the engine underneath a largely familiar API surface. Most call sites carry over with only a renamed method or a changed parameter type, but a handful of areas -- most notably the conversion flow itself, and the classes used for page geometry -- changed enough in shape that a mechanical find-and-replace won't get an existing v3 integration all the way to a working v4 build. This guide walks through what changed and why, section by section, and closes with a full method-by-method correspondence table for the rest. It complements the PD4ML Programmer's Manual (written against v4 throughout) and the Usage Examples page (which shows v3 and v4 code side by side for the majority of common tasks).
1. Activation
Software activation is new in v4. Where v3 shipped without any license-file mechanism, v4 looks for a pd4ml.lic file -- containing an activation code obtained from the vendor's licensing page -- on the classpath or in the working directory, unless a code or a license-file URL is passed explicitly to the PD4ML constructor instead. The activation code encodes both the licensed feature set and the maintenance/upgrade window it's valid for. Until a valid license is installed, PD4ML runs in evaluation mode: fully functional, but with watermarked output. If activation isn't taking effect as expected, pd4ml.setLogLevel(255) produces verbose diagnostic output that typically pinpoints why (an expired maintenance window, a code that doesn't match the running version, a pd4ml.lic that isn't actually on the classpath, and so on).
Existing license codes and account details are managed from the View My Licenses page.
2. New Conversion Flow
The single biggest structural change is that conversion is no longer one call. v3's render(source, output) parsed the source and produced output in a single step, called once per output artifact. v4 splits this into an explicit two-phase model: readHTML(...) parses the source exactly once, after which any number of writePDF(...), writeRTF(...), writeDOCX(...), or renderAsImages(...) calls can serialize that same parsed document to different formats or destinations -- without re-parsing. This also means metadata about the parsed document (via getLastRenderInfo(...)) becomes available for inspection before -- or instead of -- writing any output at all.
PD4ML pd4ml = new PD4ML();
String html = "TEST<pd4ml:page.break><b>Hello, World!</b>";
ByteArrayInputStream bais = new ByteArrayInputStream(html.getBytes());
// phase 1: parse
pd4ml.readHTML(bais);
File pdf = File.createTempFile("result", ".pdf");
FileOutputStream fos = new FileOutputStream(pdf);
// phase 2: render -- can be called more than once, against different
// write*()/renderAsImages() methods, without re-parsing
pd4ml.writePDF(fos);
PD4ML pd4ml = new PD4ML();
String html = "TEST<b>Hello, World!</b>";
StringReader bais = new StringReader(html);
File pdf = File.createTempFile("result", ".pdf");
FileOutputStream fos = new FileOutputStream(pdf);
// a single call both parses the source and writes the output
pd4ml.render(bais, fos);
3. PDF Document Defaults: Scale Factor, Margins, and View Mode
Three settings govern how source content maps onto the printed page, and their defaults changed between versions: pageSize defaults to A4 in both, pageMargins defaults to 10 mm on every side in v4 versus the more granular 50/25/25/25 pt (top/right/bottom/left) in v3, and htmlWidth (the virtual viewport width the source is laid out against before scaling) defaults to 640px in both. An htmlWidth of 727px is worth knowing about specifically: at that value, and only that value, PD4ML's internal scale factor works out to a 1:1 pixel-to-point correspondence at the PDF-standard 72 DPI -- convenient when reasoning about how a CSS pixel dimension will translate to printed size.
An integration that needs to reproduce v3's original defaults exactly under v4, rather than adopt the new ones, can do so explicitly:
PD4ML pd4ml = new PD4ML();
pd4ml.setParam(Constants.PD4ML_DOCUMENT_VIEW_MODE, "OneColumn");
pd4ml.setHtmlWidth(640);
pd4ml.addStyle("BODY { margin: 8px }", true);
pd4ml.setPageSize(PageSize.A4);
pd4ml.setPageMargins(new PageMargins(50, 25, 25, 25, Units.PT));
4. Document Headers and Footers
v3 modeled a header or footer as a PD4PageMark object, whose getHtmlTemplate(int pageNumber) method was invoked once per page to compute that page's markup. v4 replaces this with a direct HTML string passed to setPageHeader()/setPageFooter(), with an optional scope parameter targeting a specific page range in place of per-page conditional logic in code -- and the same content can equally well be declared inline in the source HTML with the <pd4ml:page.header>/<pd4ml:page.footer> tags. All three forms support the $[page], $[total], and $[title] placeholders.
pd4ml.setPageHeader("$[title]", 30, "1");
pd4ml.setPageFooter("Total pages: $[total]", 30, "1");
pd4ml.setPageHeader("<b>$[title]</b> $[page]/$[total]", 30, "2+");
pd4ml.setPageFooter("<div style='width: 100%; text-align: right'>Page: $[page]</div>", 30, "2+");
<html>
<head>
<title>Header/Footer example</title>
<style>BODY {font-family: Arial}</style>
</head>
<body>
<pd4ml:page.header height=30>$[title]</pd4ml:page.header>
<pd4ml:page.footer height=30>Total pages: $[total]</pd4ml:page.footer>
First Page
<pd4ml:page.break>
<pd4ml:page.header height=30><b>$[title]</b> $[page]/$[total]</pd4ml:page.header>
<pd4ml:page.footer height=30><div style='width: 100%; text-align: right'>Page: $[page]</div></pd4ml:page.footer>
Second Page
</body>
</html>
PD4PageMark header = new PD4PageMark() {
public String getHtmlTemplate(int pageNumber) {
if (pageNumber == 1) {
return "<html><body>$[title]";
} else {
return "<html><body><b>$[title]</b> $[page]/$[total]";
}
}
};
PD4PageMark footer = new PD4PageMark() {
public String getHtmlTemplate(int pageNumber) {
if (pageNumber == 1) {
return "<html><body>Total pages: $[total]";
} else {
return "<html><body><div style='width: 100%; text-align: right'>Page: $[page]</div>";
}
}
};
header.setAreaHeight(30);
footer.setAreaHeight(30);
pd4ml.setPageHeader(header);
pd4ml.setPageFooter(footer);
See also the Programmer's Manual's JSP taglib section for the same placeholders used from a JSP page.
5. JSP Taglib
The JSP taglib now ships as an integral part of the main library rather than a separate artifact. The recommended custom-tag prefix also changed, from pd4ml: to pd4tl: -- freeing pd4ml: to unambiguously refer only to PD4ML's own proprietary tags (<pd4ml:page.break> and the like) within the same page, rather than being shared between the taglib's own directives and PD4ML's markup extensions.
<%@ taglib uri="https://pd4ml.com/tlds/4.0" prefix="pd4tl"%>
<%@page contentType="text/html; charset=ISO8859_1"%>
<pd4tl:transform
screenWidth="400"
pageFormat="A5"
pageOrientation="landscape"
pageInsets="100,100,100,100,points">
<html>
<head>
<title>pd4ml test</title>
<style type="text/css">
body {
color: red;
font-family: Tahoma, "Sans-Serif";
font-size: 10pt;
}
</style>
</head>
<body>
<p>Hello, World!</p>
<pd4ml:page.break />
<table style="border: 1px solid gray; border-radius: 5px; background-color: #f8f8f8; color: #000000">
<tr>
<td>Hello, New Page!</td>
</tr>
</table>
</body>
</html>
</pd4tl:transform>
6. Full API Correspondence Table
The table below maps every v3 PD4ML method referenced in the vendor's migration notes to its v4 successor, linking each to its respective Javadoc (v3's now-archived Javadoc at old.pd4ml.com, and v4's current one). Where a v3 method has no listed v4 successor, that most often means its functionality was folded into a more general mechanism described elsewhere in this guide, or the behavior it controlled is now automatic; treat those as "no longer applicable" rather than "still there under a different name."
Two entries in this table conflict with tested example code published elsewhere on pd4ml.com, and are worth double-checking against the actual Javadoc for the PD4ML build in use before relying on them:
monitorProgress/monitorProgressWith-- this migration guide lists v3 asmonitorProgress(PD4ProgressListener)and v4 asmonitorProgressWith(ProgressListener). The Usage Examples' Add Progress Listener entry, however, shows the opposite pairing in its tested v3/v4 code samples: v3 callingmonitorProgressWith(ProgressListener)and v4 callingmonitorProgress(PD4ProgressListener).setDynamicParams-- this migration guide lists it as v3-only, superseded in v4 bysetDynamicData/setParam/setRenderingHints. The Usage Examples' Add Custom Resource Loader entry, however, callspd4ml.setDynamicParams(map)directly in its v4 code sample.
Both discrepancies come from pd4ml.com's own published documentation rather than from anything in this rewrite -- they're surfaced here rather than silently resolved one way or the other.
See also
- PD4ML Programmer's Manual -- the current (v4) API, described from scratch rather than as a diff against v3.
- Usage Examples -- runnable examples with v3 and v4 code shown side by side for most common tasks.
- Support Forums and Extended Support Plans -- for migration questions not covered here.
