Documentation page: PD4ML v3 to v4 Migration Guide. This page is also available as Markdown at pd4ml-v3-to-v4-migration-guide.md.

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);

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+");

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."

v3 method v4 method What changed
addDocumentActionHandler(String, String) addDocumentActionHandler(String, String) Unchanged
addMetadata(String, String, boolean) addMetadata(String, String, boolean) Unchanged
addStyle(String, boolean) addStyle(String, boolean) Unchanged
addStyle(URL, boolean) addStyle(URL, boolean) Unchanged
adjustHtmlWidth() adjustHtmlWidth(boolean) Now takes an explicit boolean instead of being an always-on toggle call
changePageOrientation(Dimension) PageSize.rotate() Moved from a PD4ML instance method to a method on PageSize itself
clearCache() terminate() Renamed
disableHyperlinks() enableHyperlinks(boolean) Inverted: call enableHyperlinks(false)
enableDebugInfo() setLogLevel(int) Replaced by a graduated log level; use setLogLevel(255) for maximum verbosity
enableImgSplit(boolean) addStyle(String, boolean) Superseded by ordinary CSS page-break control passed through addStyle() rather than a dedicated flag
enableRenderingPatch(boolean) -- No v4 successor listed
enableSmartTableBreaks(boolean) -- No v4 successor listed
enableTableBreaks(boolean) -- No v4 successor listed
fitPageVertically() fitPageVertically(int) Now takes an explicit alignment constant
generateMulticolumn(int, int, boolean) generateMulticolumn(int, int, boolean) Unchanged
generateOutlines(boolean) generateBookmarksFromHeadings(boolean) / generateBookmarksFromAnchors(boolean) Split into two purpose-specific methods (see the Usage Examples' Create Bookmarks entry)
generatePdfa(boolean) writePDF(OutputStream, String) with Constants.PDFA Folded into the write call itself rather than a standalone flag set beforehand
generatePdfForms(boolean, String) generateForms(boolean, String) Renamed
getCache() getCache() Unchanged
getLastRenderInfo(String) getLastRenderInfo(String) Unchanged
getVersion() getVersion() Unchanged
interpolateImages(boolean) -- No v4 successor listed (image interpolation is presumably automatic now)
isDemoMode() isDemoMode() Unchanged
isPro() isPro() Unchanged
merge(InputStream, int, int, boolean) / merge(Reader, int, int, boolean) merge(PdfDocument, int, int, boolean) Both v3 overloads consolidate onto one v4 overload taking a PdfDocument
monitorProgress(PD4ProgressListener) monitorProgressWith(ProgressListener) Renamed -- but see the caveat immediately below the table
outputFormat(String) / outputFormat(String, int, int) writePDF(...) / writeRTF(...) / renderAsImages() The v3 "set a format flag, then render" model is replaced by calling the write method for the desired format directly
outputRange(String) outputRange(String) Unchanged
overrideDocumentEncoding(String) overrideDocumentEncoding(String) Unchanged
predictPageHeight(Insets, Dimension, int) predictPageHeight(PageMargins, PageSize, int) Same purpose; parameter types updated to PageMargins/PageSize
predictScale(Insets, Dimension, int) predictScale(PageMargins, PageSize, int) Same purpose; parameter types updated to PageMargins/PageSize
protectPhysicalUnitDimensions() protectPhysicalUnitDimensions(boolean) Now takes an explicit boolean instead of being an always-on toggle call
render(InputStreamReader, OutputStream[, URL]) readHTML(InputStream[, URL]) + writePDF(OutputStream) Splits into the two-phase model (§2)
render(String, OutputStream) / render(URL, OutputStream) readHTML(URL) + writePDF(OutputStream) Splits into the two-phase model, source addressed as a URL
render(StringReader, OutputStream[, URL[, String]]) readHTML(InputStream, URL[, String]) + writePDF(OutputStream) Splits into the two-phase model, with an optional explicit source encoding
render(StringReader[], OutputStream, URL) / render(URL[], OutputStream) readHTML(...) + writePDF(...) per source, combined with PdfDocument.mergePDFs(InputStream, InputStream, OutputStream) v3's "render several sources and merge them" array overloads are replaced by converting each source individually and merging the resulting PDFs explicitly
renderAsImages(StringReader, URL, int, int) / renderAsImages(URL, int, int) readHTML(...) + one of renderAsImages() / renderAsImages(File, String, String) / renderAsImages(String) Splits into the two-phase model, with a choice of in-memory, to-disk, or encoded-bytes image output
resetAddedStyles() -- No v4 successor listed
setAuthorName(String) setAuthorName(String) Unchanged
setCache(PD4Cache) setCache(Object) Same purpose; parameter type loosened to Object
setCookie(String, String) setCookie(String, String) Unchanged
setDefaultTTFs(String, String, String) addStyle(String, boolean) Superseded by an ordinary CSS @font-face rule passed to addStyle() (see the Usage Examples' Add Style Programmatically entry)
setDocumentTitle(String) setDocumentTitle(String) Unchanged
setDynamicParams(Map) setDynamicData(Map) / setParam(String, String) / setRenderingHints(Map) v3's single, catch-all method splits into three narrower ones -- placeholder substitution values, individual named parameters, and rendering hints, respectively. See the caveat below the table, though: at least one other official example still calls setDynamicParams() under v4
setHtmlWidth(int) setHtmlWidth(int) Unchanged
setPageHeader(PD4PageMark) / setPageFooter(PD4PageMark) setPageHeader(String, int[, String]) / setPageFooter(String, int[, String]) Replaced the callback object with a direct HTML string plus scope (§4)
setPageInsets(Insets) / setPageInsetsMM(Insets) setPageMargins(PageMargins[, String]) The separate points/millimeters methods collapse into one call; units are now chosen via the PageMargins constructor instead of which method you call
setPageSize(Dimension) / setPageSizeMM(Dimension) setPageSize(PageSize[, String]) Same collapse as setPageMargins, for page size instead of margins
setPermissions(String, int, boolean) setPermissions(String, int) Trailing legacy-mode boolean dropped (see the Usage Examples' Set Document Password entry)
setSessionID(String) setSessionID(String) Unchanged
translate(int) translateToPt(float) Renamed, and now takes/returns a float rather than an int
useAdobeFontMetrics(boolean) -- No v4 successor listed
useHttpRequest(HttpServletRequest, HttpServletResponse) useHttpRequest(HttpServletRequest, HttpServletResponse) Unchanged
useServletContext(ServletContext) useHttpRequest(...) Folded into useHttpRequest()
useTTF(String, boolean) useTTF(String) / useTTF(String, boolean) / useTTF(String, String) + embedTTFs(boolean, boolean) The single v3 overload expands into three v4 overloads for different addressing needs, with glyph-embedding behavior configured separately via embedTTFs() (see the Usage Examples' i18n section)

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 as monitorProgress(PD4ProgressListener) and v4 as monitorProgressWith(ProgressListener). The Usage Examples' Add Progress Listener entry, however, shows the opposite pairing in its tested v3/v4 code samples: v3 calling monitorProgressWith(ProgressListener) and v4 calling monitorProgress(PD4ProgressListener).
  • setDynamicParams -- this migration guide lists it as v3-only, superseded in v4 by setDynamicData/setParam/setRenderingHints. The Usage Examples' Add Custom Resource Loader entry, however, calls pd4ml.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