# PD4ML Programmer's Manual

_This document applies to PD4ML v4.1.1_

PD4ML is a pure-Java document generation engine that turns HTML and CSS into production-quality **PDF**, **DOCX**, and **RTF** — no browser, no native dependencies, no external rendering service. It runs anywhere a JVM does: inside a web application, a batch job, a desktop tool, or straight from the command line.

Beyond the core HTML-to-PDF conversion covered in this manual, PD4ML also ships a small suite of PDF _post-processing_ packages -- signing, merging, optimizing, COS-level editing, and XFDF annotation import/export -- each with its own dedicated manual; see §15.

Every class, method and constant this manual mentions is described in full in the [PD4ML API reference (Javadoc)](https://pd4ml.com/javadoc/); the method names below link to their entries there.

## 1. What PD4ML does

At its core, PD4ML is a layout engine: it parses HTML/CSS (or arbitrary XML plus an XSL stylesheet) the same way a browser would, paginates the result, and writes it out in one of several formats. What sets it apart from "print to PDF" approaches built on a headless browser:

* **Deterministic, print-focused pagination** -- explicit page breaks, repeating headers/footers, running page numbers, widow/orphan control, and multi-column layout, all driven by ordinary CSS plus a handful of PD4ML-specific extensions.
* **Standards-aware output** -- PDF/A-1/2/3/4 (long-term archival), PDF/UA (accessible, tagged PDF), and ZUGFeRD/Factur-X (machine-readable e-invoices embedded in a human-readable PDF) are first-class output targets, not an afterthought (§11).
* **Full Unicode and font control** -- TrueType/OpenType embedding, glyph subsetting, kerning, and ligature substitution, with correct shaping for Latin, Cyrillic, CJK, Arabic, and Hebrew scripts.
* **Multiple output formats from one conversion** -- the same parsed document can be written as PDF, DOCX, RTF, or rendered to raster images, without re-parsing the source (§6.3).
* **Deployment flexibility** -- a plain Java API, a self-contained command-line tool (§4), a GUI previewer (§5), a JSP tag library for web applications (§13), and integrations that bring the same API to [Python](https://pd4ml.com/docs/integrations/pd4ml-python.html), [PHP](https://pd4ml.com/docs/integrations/pd4ml-php.html), [.NET](https://pd4ml.com/docs/integrations/pd4ml-dotnet.html), [Node.js](https://pd4ml.com/docs/integrations/pd4ml-node.html), and [Ruby](https://pd4ml.com/docs/integrations/pd4ml-ruby.html).

PD4ML is commercially licensed, with a free evaluation mode (watermarked output, no restriction on functionality) so an integration can be built and tested before a license is purchased.

## 2. Obtaining PD4ML

PD4ML is distributed as a single, self-contained jar plus a required Bouncy Castle companion ([`com.pd4ml.pdf.encryption`](https://pd4ml.com/javadoc/com/pd4ml/pdf/encryption/package-summary.html) -- standard PDF permissions/ protection, §11.1 -- and [`com.pd4ml.pdf.sign`](https://pd4ml.com/javadoc/com/pd4ml/pdf/sign/package-summary.html), §15.2, both depend on it; as of v4.1.0 it is no longer an optional, signing-only add-on) through PD4ML's own Maven repository:

```xml
<dependency>
    <groupId>com.pd4ml</groupId>
    <artifactId>pd4ml</artifactId>
    <version>4.1.1</version>
</dependency>

<!-- Required as of v4.1.0 -- com.pd4ml.pdf.encryption (document protection,
     &sect;11.1) and com.pd4ml.pdf.sign (&sect;15.2) both depend on it. A Maven
     build already pulls these in transitively via the dependency above;
     declare them explicitly only to pin a specific version. -->
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk18on</artifactId>
    <version>1.84</version>
</dependency>
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcpkix-jdk18on</artifactId>
    <version>1.84</version>
</dependency>

<repository>
    <id>pd4ml</id>
    <url>https://pd4ml.com/maven2/</url>
</repository>
```

A separate, credentialed repository (`https://pd4ml.com/maven2-src/`) also publishes source jars, for stepping through PD4ML's own code in a debugger during integration work.

Without Maven, the plain jar plus its `bouncycastle-runtime.jar` companion (a single jar merging the Bouncy Castle artifacts above -- see the note in §15.2) can be downloaded directly and placed on the classpath; nothing else needs to be installed to run it. PD4ML runs on Java 8 or later; TIFF image output (§6.3) needs Java 9 or later, whose built-in TIFF writer it uses.

From other languages, PD4ML's integrations download the jar from this same repository, and its companion jars, themselves, and expose the complete Java API to the host language (a Java runtime is still needed alongside):

| Language | Install | Guide |
|---|---|---|
| Python | `pip install pd4ml` | [PD4ML for Python](https://pd4ml.com/docs/integrations/pd4ml-python.html) |
| PHP | `composer require pd4ml/pd4ml-php` | [PD4ML for PHP](https://pd4ml.com/docs/integrations/pd4ml-php.html) |
| .NET | packages from [pd4ml.com/net](https://pd4ml.com/net/) | [PD4ML for .NET](https://pd4ml.com/docs/integrations/pd4ml-dotnet.html) |
| Node.js | `npm install pd4ml` | [PD4ML for Node.js](https://pd4ml.com/docs/integrations/pd4ml-node.html) |
| Ruby | `gem install pd4ml` | [PD4ML for Ruby](https://pd4ml.com/docs/integrations/pd4ml-ruby.html) |

## 3. Licensing

```java
PD4ML pd4ml = new PD4ML();                 // looks for pd4ml.lic on the classpath / working directory
PD4ML pd4ml = new PD4ML("SERIAL-NUMBER");   // the serial number from the license email
PD4ML pd4ml = new PD4ML("https://internal.example.com/pd4ml.lic");  // license file URI, instead of a serial number
```

With no license installed, PD4ML runs in **evaluation mode**: every feature is available, but output carries a watermark. This is normal and expected during development -- there is nothing to configure to unlock full, unwatermarked output beyond installing a valid license file or serial number.

What a license covers depends on its edition (STD, PRO, DMS, UA) -- see [PD4ML editions and prices](https://pd4ml.com/products/). The DOM API (§6.5: [`getDocument()`](https://pd4ml.com/javadoc/com/pd4ml/PD4ML.html#getDocument()), [`layout()`](https://pd4ml.com/javadoc/com/pd4ml/PD4ML.html#layout()) and the [`com.pd4ml.dom`](https://pd4ml.com/javadoc/com/pd4ml/dom/package-summary.html) package) is part of the **DMS and UA editions**. With an STD or PRO license it still works, but the output of a document it was used on carries the "PD4ML EVALUATION MODE" banners. The same holds for e-mail (`.eml`) and saved web page (`.mht`, `.mhtml`) input -- [`readEML()`](https://pd4ml.com/javadoc/com/pd4ml/PD4ML.html#readEML(java.io.InputStream)), [`readMHT()`](https://pd4ml.com/javadoc/com/pd4ml/PD4ML.html#readMHT(java.io.InputStream)), and `readHTML()` for input it recognizes as a message or a saved page (see [Converting E-mail Messages and Saved Web Pages](../features/converting-e-mail-and-saved-web-pages)).

```java
if (pd4ml.isDemoMode()) {
    log.warn("PD4ML is running unlicensed -- output will be watermarked");
}
System.out.println("PD4ML " + PD4ML.getVersion() + " built " + PD4ML.getVersionBuildDate());
```

## 4. Command-line tool: `Pd4Cmd`

[`com.pd4ml.tools.Pd4Cmd`](https://pd4ml.com/javadoc/com/pd4ml/tools/Pd4Cmd.html) is `pd4ml.jar`'s own `Main-Class` -- run it directly with `java -jar`, no classpath assembly required:

```
java -Djava.awt.headless=true -Xmx512m -jar pd4ml.jar "https://example.com" 1200
```

The two required positional arguments are the **source** (a URL, or a local `file:` path) and the **`htmlWidth`** in pixels -- the virtual browser viewport width PD4ML lays the page out against before scaling it to the target paper size (see §7 for the scaling math). Everything else is an optional flag.

### 4.1 Common flags

| Flag                                             | Purpose                                                |
| ------------------------------------------------ | ------------------------------------------------------ |
| `-out <file>`                                    | Output file (default: stdout)                          |
| `LETTER`, `A4`, `LEGAL`, ... (positional)        | Target page format                                     |
| `-orientation PORTRAIT\|LANDSCAPE`               | Page orientation                                       |
| `-insets <l,t,r,b[,mm\|points]>`                 | Page margins                                           |
| `-bookmarks HEADINGS\|ANCHORS`                   | Generate a bookmark tree                               |
| `-header "<html>"`, `-footer "<html>"`           | Repeating page header/footer HTML                      |
| `-watermark "<url,left,top,width,height,angle>"` | Image watermark                                        |
| `-bgcolor <#rrggbb>`, `-bgimage <url>`           | Page background                                        |
| `-pdfforms`                                      | Convert HTML `<form>` controls to real PDF form fields |
| `-outformat pdf\|pdfa\|pdfua\|...`               | PDF/A / PDF/UA / ZUGFeRD conformance (§11.2)           |
| `-password <pw>`                                 | Owner password + default permissions                   |
| `-permissions <n>`                               | Explicit permission bitmask (§11.1)                    |
| `-ttf <dir>`                                     | TrueType/OpenType font directory to embed from         |
| `-kerning`, `-ligatures`                         | Font shaping refinements (§9)                          |
| `-threads <n>`                                   | Async resource-loader thread pool size                 |
| `-nohyperlinks`                                  | Disable hyperlink conversion                           |
| `-adjustwidth`                                   | Auto-fit `htmlWidth` to the document's natural width   |
| `-fitapage`                                      | Downscale the whole layout to fit one page vertically  |
| `-multicolumn <columns,gap[,mm]>`                | Multi-column page layout                               |
| `-debug <level>`                                 | Verbosity (0-5)                                        |

Run `java -jar pd4ml.jar` with no arguments for the complete, always-current flag reference.

### 4.2 Examples

Convert a live page to a bookmarked, form-enabled Letter PDF:

```
java -Djava.awt.headless=true -Xmx512m -jar pd4ml.jar \
    "https://example.com/report.html" 1200 LETTER \
    -bookmarks HEADINGS -pdfforms -out report.pdf
```

Render a local file with a running header/footer and page numbers:

```
java -jar pd4ml.jar "file:/data/invoice.html" 900 A4 \
    -header "<div style='text-align:right'>$[title]</div>" \
    -footer "<div style='text-align:center'>Page $[page] of $[total]</div>" \
    -out invoice.pdf
```

`Pd4Cmd` also carries a handful of built-in, single-document PDF **post-processing** flags for quick one-off tasks -- `-tools <file> ...` switches it from HTML conversion into edit mode:

```
# Keep only pages 2-3 and everything from page 5 onward
java -jar pd4ml.jar -tools file:/docs/source.pdf -pagerange 2-3,5+ -out subset.pdf

# Append another PDF after the source document
java -jar pd4ml.jar -tools file:/docs/source.pdf -merge file:/docs/appendix.pdf after -out combined.pdf

# Read back permission flags, author, title and page count
java -jar pd4ml.jar -tools file:/docs/source.pdf -printpermissions -printauthor -printtitle -printpagenum
```

For anything beyond these simple cases -- selecting page ranges from _two_ independent source documents with accessibility-tag reconciliation, reachability-based cleanup and duplicate-content dedup, or arbitrary COS-level structural edits -- reach for the dedicated `pd4mergecli`, `pd4optimizecli` and `pd4coscli` tools instead (§15).

### 4.3 Font indexing

```
java -Xmx512m -jar pd4ml.jar -configure.fonts /path/to/fonts [pd4fonts.properties]
```

Scans a directory of TrueType/OpenType font files and writes (or updates) a font-mapping properties file PD4ML uses to resolve CSS `font-family` names to actual font files at conversion time -- run once per font directory, ahead of time, rather than paying the directory scan cost on every conversion.

## 5. GUI application: `PD4Browser`

A lightweight preview tool bundled in the same jar -- useful for iterating on CSS/layout without a full round trip through your application:

```
java -jar pd4ml.jar -gui
```

It shows the document page by page as PD4ML lays it out -- the window width is the HTML width, so resizing the window shows the effect of `setHtmlWidth()` directly -- and converts it to PDF, DOCX or RTF with **Render**. Its **Fonts Tool** builds font mapping files and font JARs for `useTTF()`. Its settings (page format, margins, fonts, headers and footers, bookmarks, proxy, ...) live in a `pd4browser.properties` file in the working directory -- the same settings a Java integration would set via the API in §6-9, as plain properties for quick experimentation. [PD4Browser: Interactive Converter and Fonts Tool](pd4browser-interactive-converter) describes the window, the Fonts Tool and every setting of the file.

## 6. The Java API

### 6.1 The two-phase model

Every conversion is **read** once, then **written** one or more times: a [`readHTML(...)`](https://pd4ml.com/javadoc/com/pd4ml/PD4ML.html#readHTML(java.io.InputStream)) call parses and lays out the source; any number of subsequent `write*(...)`/[`renderAsImages()`](https://pd4ml.com/javadoc/com/pd4ml/PD4ML.html#renderAsImages()) calls then serialize that same parsed document to different formats or destinations, without re-parsing.

```java
import com.pd4ml.PD4ML;
import java.io.*;

PD4ML pd4ml = new PD4ML();
String html = "<html><body><h1>Hello, World!</h1></body></html>";
pd4ml.readHTML(new ByteArrayInputStream(html.getBytes("UTF-8")), null, "UTF-8");

try (FileOutputStream out = new FileOutputStream("hello.pdf")) {
    pd4ml.writePDF(out);
}
```

### 6.2 Reading the source

```java
pd4ml.readHTML(inputStream);                          // encoding auto-detected
pd4ml.readHTML(inputStream, baseUrl);                  // relative links/images resolve against baseUrl
pd4ml.readHTML(inputStream, baseUrl, "windows-1251");  // explicit source encoding
pd4ml.readHTML(new URL("https://example.com/report"));  // fetch directly
```

A relative-URL `baseUrl` matters whenever the source HTML references external images, stylesheets, or fonts by relative path -- omitting it (or getting it wrong) is the most common cause of a converted document missing images that render fine in a browser.

Runnable example: [A003ReadHtmlFromClasspath.java](https://github.com/zxfr/pd4ml-examples/blob/master/src/main/java/advanced/A003ReadHtmlFromClasspath.java) -- reading the HTML source from the classpath via `java:` URLs.

### 6.3 Writing the output

```java
pd4ml.writePDF(pdfOutputStream);
pd4ml.writeRTF(rtfOutputStream, false);   // false: keep raster images as PNG/JPEG rather than converting to WMF
pd4ml.writeDOCX(docxOutputStream);

BufferedImage[] pages = pd4ml.renderAsImages();                     // one image per page, in memory
byte[][] pngBytes     = pd4ml.renderAsImages(Constants.PNG24);      // ... or already PNG-encoded
File dir              = pd4ml.renderAsImages(outDir, "page", Constants.PNG24);  // ... or written straight to disk
```

The encoded formats are `Constants.PNG24`, `PNG8`, `TIFF`, `TIFF_COMPRESSED` (PackBits), `MULTIPAGE_TIFF` and `MULTIPAGE_TIFF_COMPRESSED` (one file with all pages); `"png"` means `PNG24`, letter case does not matter, and any other name throws an [`InvokeException`](https://pd4ml.com/javadoc/com/pd4ml/InvokeException.html). PNG works on any Java version; the TIFF formats need Java 9 or later, and throw an `IOException` on Java 8.

Because reading happens once, converting the same source to PDF _and_ a page-image thumbnail set costs one HTML parse, not two:

```java
pd4ml.readHTML(new URL("https://example.com/report"));
pd4ml.writePDF(new FileOutputStream("report.pdf"));
BufferedImage[] thumbnails = pd4ml.renderAsImages();
```

Runnable example: [E001GettingStarted.java](https://github.com/zxfr/pd4ml-examples/blob/master/src/main/java/basics/E001GettingStarted.java)

### 6.4 Choosing the layout engine

_From PD4ML v4.2.0._ PD4ML has two HTML layout engines, named by [`Constants.LayoutEngine`](https://pd4ml.com/javadoc/com/pd4ml/Constants.LayoutEngine.html): `v40`, the engine of PD4ML v4.0 and v4.1 and the default, and `v42`, with browser-compatible layout, CSS `@page` rules beyond the page size and margins, and the DOM API (§6.5). Select the engine with [`useHtmlRenderer(...)`](https://pd4ml.com/javadoc/com/pd4ml/PD4ML.html#useHtmlRenderer(com.pd4ml.Constants.LayoutEngine)) before `readHTML(...)`:

```java
pd4ml.useHtmlRenderer(Constants.LayoutEngine.v42);
pd4ml.useHtmlRenderer(Constants.LayoutEngine.v42, 1024);             // ... and the HTML width, as setHtmlWidth() sets it
pd4ml.useHtmlRenderer(Constants.LayoutEngine.v42, 1024, settings);   // ... and v42 settings, a Map<String, String>
```

The v42 settings are `"media"` (the CSS media type to lay out for, `"screen"` by default), `"dark"` (`"true"` matches `@media (prefers-color-scheme: dark)`), `"externalResources"` (`"false"` loads no images or style sheets), `"webFonts"` (`"false"` loads no `@font-face` fonts), `"rejectUnitlessLengths"` (`"true"` drops lengths without a unit, such as `width: 600`, outside quirks mode, as browsers do) and `"bevelColorScheme"` (`"chrome"` draws inset/outset/groove/ridge borders in Chrome's colours). Unknown keys are ignored.

### 6.5 The DOM API: reading and changing the document

_From PD4ML v4.2.0, with the v42 layout engine. DMS and UA editions._ Between `readHTML(...)` and the output, [`getDocument()`](https://pd4ml.com/javadoc/com/pd4ml/PD4ML.html#getDocument()) returns the parsed HTML document, to read, query with CSS selectors and change; the output is laid out as if the HTML had been written that way. [`layout()`](https://pd4ml.com/javadoc/com/pd4ml/PD4ML.html#layout()) lays the document out into pages without writing it and tells where each element ended up:

```java
import com.pd4ml.dom.Document;
import com.pd4ml.dom.Element;
import com.pd4ml.dom.Layout;

pd4ml.useHtmlRenderer(Constants.LayoutEngine.v42);
pd4ml.readHTML(new URL("https://example.com/invoice.html"));

Document doc = pd4ml.getDocument();
doc.getElementById("total").setTextContent("1 250,00 EUR");
for (Element img : doc.querySelectorAll("img:not([alt])")) {
    img.setAttribute("alt", "");
}

Layout layout = pd4ml.layout();
if (layout.getPages(doc.getElementById("totals")).size() > 1) {   // split over two pages?
    doc.getElementById("totals").setAttribute("style", "page-break-before: always");
}
pd4ml.writePDF(out);
```

The classes, in the package [`com.pd4ml.dom`](https://pd4ml.com/javadoc/com/pd4ml/dom/package-summary.html):

| Class | What it gives |
|---|---|
| `Node` | the tree: `getParentNode()`, `getChildNodes()`, siblings, `appendChild()`, `insertBefore()`, `replaceChild()`, `removeChild()`, `remove()`, `cloneNode()`, `getTextContent()`/`setTextContent()`, `toHtml()` |
| `Document` | `getElementById()`, `querySelector()`/`querySelectorAll()`, `getElementsByTagName()`/`getElementsByClassName()`, `getHead()`, `getBody()`, `getTitle()`, `createElement()`, `createTextNode()`, `createComment()` |
| `Element` | attributes (`getAttribute()`, `setAttribute()`, `removeAttribute()`, `getAttributes()`), classes (`addClass()`, `removeClass()`, `toggleClass()`, `hasClass()`), `getInnerHTML()`/`setInnerHTML()`, `getOuterHTML()`/`setOuterHTML()`, `matches()`, `closest()`, child and sibling elements |
| `Text`, `Comment`, `DocumentType` | text (`getData()`, `setData()`, `isWhitespace()`), comments and the doctype |
| `Layout` | from `layout()`: `getPageCount()`, page sizes and numbers, `getBoxes(element)`, `getPages(element)`, `elementAt(page, x, y)`, `getComputedStyle(element, property)` |
| `LayoutBox` | one box of an element: its page, `getX()`, `getY()`, `getWidth()`, `getHeight()` in points from the top left of the page |

Make the changes before the first output: writing a document moves footnotes and endnotes into place in the tree. `layout()` is a snapshot -- after changing the document, call it again. With the v40 engine, or before `readHTML(...)`, both methods throw an `IllegalStateException`.

The full guide, with more examples: [DOM API: Modifying the HTML Document Before Conversion](https://pd4ml.com/docs/features/modifying-the-html-document-before-conversion.html). Runnable examples: [A009ModifyDocumentWithDom.java](https://github.com/zxfr/pd4ml-examples/blob/master/src/main/java/advanced/A009ModifyDocumentWithDom.java), [A010ReadLayout.java](https://github.com/zxfr/pd4ml-examples/blob/master/src/main/java/advanced/A010ReadLayout.java).

## 7. Page layout: size, margins, and scaling

Three settings govern how source content maps onto printed pages:

| Setting       | Meaning                                                    | Default         |
| ------------- | ---------------------------------------------------------- | --------------- |
| `pageSize`    | Target paper format                                        | A4, portrait    |
| `pageMargins` | Blank border reserved on every page                        | 10 mm all sides |
| `htmlWidth`   | Virtual viewport width (px) the source is laid out against | 727 px          |

```java
import com.pd4ml.PageSize;
import com.pd4ml.PageMargins;
import com.pd4ml.Dimensions.Units;

pd4ml.setPageSize(PageSize.LETTER);
pd4ml.setPageMargins(new PageMargins(15, 20, 15, 20, Units.MM));
pd4ml.setHtmlWidth(1000);
```

Content is scaled uniformly to fit the printable area:

```
scale = (pageWidth - marginLeft - marginRight) / htmlWidth
```

A narrower `htmlWidth` means a larger scale factor -- text and images appear bigger on the page, at the cost of how much content fits horizontally before wrapping. [`pd4ml.adjustHtmlWidth(true)`](https://pd4ml.com/javadoc/com/pd4ml/PD4ML.html#adjustHtmlWidth(boolean)) (or the CLI's `-adjustwidth`) instead measures the document's own natural width and uses that, useful for source HTML that isn't already responsive-width-aware.

The scale applies to every size, including those given in physical units: with an `htmlWidth` of 1600 px on A4, `font-size: 12pt` prints at about 5.5pt. [`protectPhysicalUnitDimensions(true)`](https://pd4ml.com/javadoc/com/pd4ml/PD4ML.html#protectPhysicalUnitDimensions(boolean)) keeps sizes in `mm`, `cm`, `in`, `pt` and `pc` at their true size while pixel-sized content still scales (v40 engine; the v42 engine always converts physical units at 96 dpi, as browsers do). From PD4ML v4.2.0 it works for DOCX and RTF output too - before, it made physical sizes there about four times too small.

[`setPageSize(...)`](https://pd4ml.com/javadoc/com/pd4ml/PD4ML.html#setPageSize(com.pd4ml.PageSize))/[`setPageMargins(...)`](https://pd4ml.com/javadoc/com/pd4ml/PD4ML.html#setPageMargins(com.pd4ml.PageMargins)) also accept an optional **scope** string (`"1"`, `"2+"`, `"3-7,odd"`, ...) to vary page size or margins across the document -- the same scope grammar used by headers/footers (§8) and by `Pd4Cmd -pagerange`.

[`fitPageVertically(Constants.CENTER_ALIGN)`](https://pd4ml.com/javadoc/com/pd4ml/PD4ML.html#fitPageVertically(int)) downscales the entire layout to guarantee it fits one single page, rather than paginating -- handy for "print this as a single-page summary" use cases; it's mutually exclusive with multi-column mode (§10.1).

Runnable example: [E002SetPageFormat.java](https://github.com/zxfr/pd4ml-examples/blob/master/src/main/java/basics/E002SetPageFormat.java)

## 8. Headers, footers, watermarks, and backgrounds

Repeating page decorations are themselves HTML/CSS fragments, not a separate mini-format to learn:

```java
pd4ml.setPageHeader("<b>$[title]</b>", 30, "1");             // first page only
pd4ml.setPageHeader("<b>$[title]</b> &mdash; $[page]/$[total]", 30, "2+");  // every page after
pd4ml.setPageFooter(
    "<div style='width:100%; text-align:right; font-size:8pt'>Page $[page] of $[total]</div>",
    24, "1+");
```

Three placeholders are always available: `$[page]` (current page number), `$[total]` (total page count), and `$[title]` (document title). Arbitrary additional placeholders come from [`setDynamicData(...)`](https://pd4ml.com/javadoc/com/pd4ml/PD4ML.html#setDynamicData(java.util.Map)):

```java
Map<String, String> data = new HashMap<>();
data.put("customer", "Acme Corp.");
data.put("invoiceNo", "INV-2026-0031");
pd4ml.setDynamicData(data);
pd4ml.setPageFooter("<div>Invoice $[invoiceNo] &mdash; $[customer]</div>", 24, "1+");
```

A watermark, and a full-bleed page background rendered underneath every page's content, follow the same HTML-fragment-plus-scope model -- the watermark content is itself HTML (an `<img>`, styled text, or anything else CSS can lay out), not a bare image URL:

```java
pd4ml.setWatermark("<img src='https://example.com/draft-stamp.png'/>",
        50, 50,      // left, top
        0.15f,       // opacity
        30,          // rotation angle, degrees
        1.0f,        // scale
        true, true,  // visible on screen and in print
        "1+");

pd4ml.setPageBackground("<div style='background:#f7f7f7; height:100%'></div>", "1+");
```

Watermarks also appear in RTF and DOCX output (from v4.2.0): with the v40 engine as a Word text watermark, with v42 as a picture of the watermark - see [RTF and DOCX Output: Watermarks](https://pd4ml.com/docs/features/rtf-and-docx-output.md#watermarks).

Runnable examples: [E003SetPageHeaderFooter.java](https://github.com/zxfr/pd4ml-examples/blob/master/src/main/java/basics/E003SetPageHeaderFooter.java), [E004SetPageHeaderFooterInline.java](https://github.com/zxfr/pd4ml-examples/blob/master/src/main/java/basics/E004SetPageHeaderFooterInline.java) (headers/footers), [E005SetPageBackground.java](https://github.com/zxfr/pd4ml-examples/blob/master/src/main/java/basics/E005SetPageBackground.java), [E006SetPageBackgroundInline.java](https://github.com/zxfr/pd4ml-examples/blob/master/src/main/java/basics/E006SetPageBackgroundInline.java) (backgrounds), [E007SetPageWatermark.java](https://github.com/zxfr/pd4ml-examples/blob/master/src/main/java/basics/E007SetPageWatermark.java), [E008SetPageWatermarkInline.java](https://github.com/zxfr/pd4ml-examples/blob/master/src/main/java/basics/E008SetPageWatermarkInline.java) (watermarks) -- each via the API and inline tags respectively.

**CSS gradient backgrounds.** _From PD4ML v4.2.0, both layout engines._ `linear-gradient()`, `radial-gradient()`, `conic-gradient()` and their `repeating-` forms work as backgrounds, in PDF and image output:

```css
.banner  { background: linear-gradient(to right, #f80, #08f); border-radius: 12px; }
.badge   { background: radial-gradient(circle at 30% 30%, #fff, #fc0 40%, #c60); }
.chart   { background: conic-gradient(#36c 0 40%, #f80 0 75%, #ccc 0); border-radius: 50%; }
.stripes { background: repeating-linear-gradient(45deg, #eee 0 10px, #fff 10px 20px); }
.fade    { background: linear-gradient(transparent, rgba(0,0,0,.6)), url(photo.jpg); }
```

- **The full CSS syntax:** angles and `to <side>` / `to <corner>`; radial shapes, sizes (`closest-side`, `farthest-corner`, ...) and `at <position>`; conic `from <angle>` and `at <position>`; color stops anywhere along the gradient, hard stops and double-position stops. Transparent stops fade without turning grey.
- **Sized and tiled like images:** `background-size`, `background-position` and `background-repeat` apply, so `linear-gradient(...) 0 0 / 20px 20px` tiles a pattern.
- **Boxes:** gradients follow rounded corners, and show through the gaps of dashed and dotted borders. On a box that breaks across pages, the gradient continues from one page to the next.
- **In PDF, gradients are vector shadings, not images:** sharp at any zoom, and small. Transparent stops become a soft mask. PDF/A-1 does not allow transparency, so there the colors are blended over white instead.

The v40 layout engine draws one background layer per box (the first gradient or image), reads `background-size` only from the property itself (not from the `/ size` part of the `background` shorthand), and draws a `border-radius: 50%` box as a pill rather than an ellipse.

A background image or gradient on `<body>` -- or on `<html>`, when `<body>` has no background -- fills every page of the v42 layout engine's output, as in v40.

## 9. Fonts, bookmarks, and hyperlinks

```java
pd4ml.useTTF("/opt/fonts");                 // TrueType/OpenType directory to embed glyphs from
pd4ml.embedTTFs(true, false);               // embed only the glyphs actually used, not the full font
pd4ml.applyKerning(true);                   // honor the font's own kerning pairs
pd4ml.enableLigatures(true);                // build compound glyphs (fi, fl, ...) where the font defines them

pd4ml.generateBookmarksFromHeadings(true);  // one bookmark per <h1>-<h6>
pd4ml.generateBookmarksFromAnchors(true);   // ... or from named <a name="..."> anchors instead
pd4ml.enableHyperlinks(true);               // convert <a href> into live PDF link annotations (default: on)
```

PD4ML ships with broad Unicode coverage out of the box; [`useTTF(...)`](https://pd4ml.com/javadoc/com/pd4ml/PD4ML.html#useTTF(java.lang.String)) matters when the source document needs a _specific_ font family (a corporate typeface, a CJK font with particular glyph coverage, ...) rather than PD4ML's own font substitution.

**Emoji and color fonts.** _From PD4ML v4.2.0, both layout engines._ Emoji are drawn in color, in PDF and image output, from a color font:

| Color font format | Example fonts | Support |
|---|---|---|
| COLR (version 0) + CPAL: layered vector glyphs | Twemoji Mozilla, Segoe UI Emoji | supported |
| CBDT/CBLC: PNG bitmap glyphs | Noto Color Emoji | supported |
| sbix | Apple Color Emoji | not supported |
| OpenType-SVG, COLR version 1 | | not supported |

Make an emoji font available like any other font -- with [`useTTF(...)`](https://pd4ml.com/javadoc/com/pd4ml/PD4ML.html#useTTF(java.lang.String)) or `@font-face`:

```java
pd4ml.useTTF("/opt/fonts");   // a directory that also holds NotoColorEmoji.ttf
```

```css
@font-face { font-family: 'Noto Color Emoji'; src: url(fonts/NotoColorEmoji.ttf); }
```

No `font-family` is needed in the document: an emoji the text font lacks comes from a color emoji font of the font set (Twemoji Mozilla, Noto Color Emoji, Segoe UI Emoji, ...). Host fonts are never used, so the output does not depend on the machine it is made on.

Alternatively, put the optional `pd4ml-emoji` add-on (Twemoji Mozilla, about 0.7 MB) on the class path next to `pd4ml.jar`: emoji then work with no setup. It is in PD4ML's Maven repository (§2), with the same version as PD4ML, and its jar is part of the distribution zip:

```xml
<dependency>
    <groupId>com.pd4ml</groupId>
    <artifactId>pd4ml-emoji</artifactId>
    <version>4.2.0</version>
</dependency>
```

Its emoji graphics are licensed under CC-BY 4.0 (attribution in the jar's `META-INF/NOTICE.txt`). A font of the same family given with `useTTF(...)` or `@font-face` takes precedence. Any jar can add fonts this way: a `META-INF/pd4ml-fonts.properties` file in it lists `family=/resource/path` lines.

- **Emoji sequences stay one glyph:** families and professions joined with zero-width joiners, skin tones, flags, keycaps -- by the font's own ligatures.
- **Text stays text:** in PDF, the emoji are drawn as vector shapes or images, with invisible text underneath, so they can be selected, copied, pasted and searched. The color font itself is not embedded.
- **DOCX and RTF:** emoji are written as text; Word draws them with its own emoji font.
- **Known gap:** Twemoji's keycap emoji (1️⃣, #️⃣) are not formed and are left blank; Noto Color Emoji's keycaps work.

Runnable examples: [N001TtfFonts.java](https://github.com/zxfr/pd4ml-examples/blob/master/src/main/java/i18n/N001TtfFonts.java), [N002TtfFonts.java](https://github.com/zxfr/pd4ml-examples/blob/master/src/main/java/i18n/N002TtfFonts.java) (TTF fonts), [H003CreateBookmarks.java](https://github.com/zxfr/pd4ml-examples/blob/master/src/main/java/html/H003CreateBookmarks.java) (bookmarks from headings).

## 10. Forms, multi-column layout, and dynamic content

### 10.1 Interactive PDF forms

```java
pd4ml.generateForms(true, null);   // convert <input>/<select>/<textarea> to real PDF form fields
```

The resulting fields are ordinary AcroForm widgets -- readable and fillable in any PDF viewer, and, downstream, exactly what [`com.pd4ml.pdf.xfdf`](https://pd4ml.com/javadoc/com/pd4ml/pdf/xfdf/package-summary.html) (§15.5) reads values out of and writes values back into via XFDF.

The second argument names the font of the field values: a TrueType font made available with [`useTTF(...)`](https://pd4ml.com/javadoc/com/pd4ml/PD4ML.html#useTTF(java.lang.String)) or `@font-face`, or `null` for the default.

**Field appearances.** _From PD4ML v4.0.25 and v4.2.0._ Every field carries its own appearance stream -- what the field shows before anyone edits it:

| Field | Shows |
|---|---|
| `<input type="text">` | the value, aligned as its `text-align` says, in the field's text color |
| `<input type="password">` | one bullet per character -- never the value itself |
| `<textarea>` | the value, wrapped at the field width, from the top |
| `<select>` (drop-down) | the selected option |
| `<select size="...">`, `<select multiple>` (list box) | the options, the selected ones highlighted |
| `<input type="checkbox">`, `<input type="radio">` | the check mark or dot when checked |

So the values are visible in every viewer, including those that do not draw form fields themselves: printing, flattening, browser previews, PDF libraries. And since the document no longer asks the viewer to regenerate the fields (`NeedAppearances`), Acrobat no longer offers to save a document that was only read. The text is laid out the way Acrobat lays out a field while it is edited, so it does not move when a field gets the focus.

**Editing.** While a user edits a field, the viewer draws the text itself with the field's font, referenced by name and not embedded -- as Acrobat does for its own forms: the viewer uses the installed font, or a similar one where it is missing. Users can type Latin-1 (WinAnsi) characters. The appearances PD4ML writes use the embedded form font, so a value in any script is shown exactly until it is edited.

### 10.2 Multi-column pages

_Available in PD4ML v4.0.24 and v4.1.2 and newer (PDF output)._

```java
pd4ml.generateMulticolumn(2, 20, true);   // 2 columns, 20mm gap
```

The document is laid out to the width of a column; every page break moves on to the next column, and a new page starts after the last one. `<pd4ml:page.break multicolumn="true"/>` starts a new page right away. Headers, footers, backgrounds and watermarks span the page, page numbers count pages, and footnotes go to the bottom of their column.

Not combinable with `fitPageVertically(...)` (§7) or `adjustHtmlWidth(...)` -- they compete to control the overall page layout; with either of them the document is output in a single column.

Runnable examples: [H008MulticolumnNewsletter.java](https://github.com/zxfr/pd4ml-examples/blob/master/src/main/java/html/H008MulticolumnNewsletter.java) (two columns, header/footer, footnote), [H009MulticolumnGlossary.java](https://github.com/zxfr/pd4ml-examples/blob/master/src/main/java/html/H009MulticolumnGlossary.java) (three columns, landscape).

### 10.3 Injecting HTML and custom resource providers

```java
pd4ml.injectHtml("<div class='cover-note'>Generated " + LocalDate.now() + "</div>", true);
```

`injectHtml` splices a fragment right after the opening `<body>` (or, with `prependBodyContent=false`, right before the closing `</body>`) -- useful for adding a fixed cover note or trailer without having to reassemble the whole source document string.

A custom [`ResourceProvider`](https://pd4ml.com/javadoc/com/pd4ml/ResourceProvider.html) lets PD4ML resolve resources through a scheme of your own choosing -- a CMS attachment ID, a database BLOB, a signed internal URL -- rather than only `http(s)://`/`file:`:

```java
public class DatabaseImageProvider extends ResourceProvider {

    public static final String PROTOCOL = "db-image";

    @Override
    public boolean canLoad(String resource, FileCache cache) {
        return resource.toLowerCase().startsWith(PROTOCOL);
    }

    @Override
    public BufferedInputStream getResourceAsStream(String resource, FileCache cache) throws IOException {
        String imageId = resource.substring(PROTOCOL.length() + 1);
        byte[] bytes = imageRepository.loadBytes(imageId);   // your own lookup
        return new BufferedInputStream(new ByteArrayInputStream(bytes));
    }
}
```

```java
pd4ml.addCustomResourceProvider("com.example.DatabaseImageProvider");
```

```html
<img src="db-image:41208" alt="Signature" />
```

Runnable examples: [E010InjectHtml.java](https://github.com/zxfr/pd4ml-examples/blob/master/src/main/java/basics/E010InjectHtml.java), [A005AddCustomResourceLoader.java](https://github.com/zxfr/pd4ml-examples/blob/master/src/main/java/advanced/A005AddCustomResourceLoader.java), [A005AddImageConvertingResourceLoader.java](https://github.com/zxfr/pd4ml-examples/blob/master/src/main/java/advanced/A005AddImageConvertingResourceLoader.java)

## 11. Document security, PDF/A, PDF/UA, and ZUGFeRD/Factur-X

### 11.1 Permissions and encryption

[`setPermissions(...)`](https://pd4ml.com/javadoc/com/pd4ml/PD4ML.html#setPermissions(java.lang.String,int)) is implemented by `com.pd4ml.pdf.encryption`, which depends on Bouncy Castle -- see §2 for the required Maven artifacts (or `bouncycastle-runtime.jar` alongside the plain jar).

```java
import com.pd4ml.Constants;

// Owner password "secret"; readers can view and print, nothing else
pd4ml.setPermissions("secret", Constants.AllowPrint);

// Everything allowed except modifying the document
pd4ml.setPermissions(null, Constants.DefaultPermissions ^ Constants.AllowModify);

// AES-256 instead of the default encryption method (AES-128)
pd4ml.setPermissions("secret", Constants.DefaultPermissions, Constants.AES_256BIT);
```

[`Constants`](https://pd4ml.com/javadoc/com/pd4ml/Constants.html) defines one bit flag per PDF permission (`AllowPrint`, `AllowModify`, `AllowCopy`, `AllowAnnotate`, `AllowFillingForms`, `AllowContentExtraction`, `AllowAssembly`, `AllowDegradedPrint`, ...) -- combine with `|`, or start from `DefaultPermissions` (everything allowed) and subtract with `^`. The optional third argument to `setPermissions` picks the encryption method: `RC4_40BIT`, `RC4_128BIT`, `AES_128BIT` (the default), or `AES_256BIT`.

Runnable examples: [E009SetDocumentPassword.java](https://github.com/zxfr/pd4ml-examples/blob/master/src/main/java/basics/E009SetDocumentPassword.java), [E009SetDocumentPermissions.java](https://github.com/zxfr/pd4ml-examples/blob/master/src/main/java/basics/E009SetDocumentPermissions.java)

### 11.2 PDF/A, PDF/UA, and e-invoicing formats

```java
pd4ml.writePDF(out, Constants.PDFA);    // PDF/A-1b: long-term archival, all fonts embedded
pd4ml.writePDF(out, Constants.PDFUA);   // PDF/UA + PDF/A-2a: accessible, tagged PDF
```

As of v4.1.0, the `Constants` shortcuts above are just two fixed points on a much larger surface exposed by [`writePDF(OutputStream, PdfSpec)`](https://pd4ml.com/javadoc/com/pd4ml/PD4ML.html#writePDF(java.io.OutputStream,com.pd4ml.PdfSpec)). A [`PdfSpec`](https://pd4ml.com/javadoc/com/pd4ml/PdfSpec.html) names one target specification -- a PDF version, a PDF/A or PDF/UA conformance level, or one of the e-invoicing presets below -- and [`combine()`](https://pd4ml.com/javadoc/com/pd4ml/PdfSpec.html#combine(com.pd4ml.PdfSpec)) layers any number of them onto a single call:

```java
import com.pd4ml.PdfSpec;

pd4ml.writePDF(out, PdfSpec.PDFA_3B);
pd4ml.writePDF(out, PdfSpec.PDF_1_7_8.combine(PdfSpec.PDFA_3B));
```

**PDF version.** `PdfSpec` names every PDF version from 1.4 through 2.0, including the numbered ISO extension levels that Acrobat itself has historically used to track incremental additions to the 1.7 line:

```java
PdfSpec.PDF_1_4
PdfSpec.PDF_1_5
PdfSpec.PDF_1_6
PdfSpec.PDF_1_7
PdfSpec.PDF_1_7_1   // through
PdfSpec.PDF_1_7_11  // ISO 32000-1 extension levels 1-11
PdfSpec.PDF_2_0     // ISO 32000-2
```

_From PD4ML v4.2.0:_ `pd4ml.setParam(Constants.PD4ML_PDF_BROTLI, "true")` compresses the streams of PDF 2.0 output with Brotli (`/Filter /BrotliDecode`) instead of Flate -- smaller files, but BrotliDecode is a recent PDF Association extension, not part of ISO 32000-2:2020, and many viewers cannot read it yet. It is ignored below PDF 2.0 and for PDF/A and PDF/UA output, which must use standard filters only.

**PDF/A (long-term archival).** Ten constants cover PDF/A-1 through PDF/A-4, each named for its conformance level: `a` (**A**ccessible -- adds tagged-PDF structure on top of `b`), `b` (**B**asic -- visual reproducibility only), and, from PDF/A-2 onward, `u` (**U**nicode -- guarantees text is extractable/searchable without necessarily being fully tagged). PDF/A-4 drops the letter scheme in favor of a plain baseline plus an `e` (**E**mbedded-files) variant, continuing PDF/A-3's relaxation of the earlier prohibition on arbitrary embedded files:

```java
PdfSpec.PDFA_1A
PdfSpec.PDFA_1B
PdfSpec.PDFA_2A
PdfSpec.PDFA_2B
PdfSpec.PDFA_2U
PdfSpec.PDFA_3A
PdfSpec.PDFA_3B
PdfSpec.PDFA_3U
PdfSpec.PDFA_4
PdfSpec.PDFA_4E
```

**PDF/UA (accessibility).** Two levels are available, tied to the two ISO base specifications: `PDFUA_1` (built on PDF 1.7 / ISO 32000-1) and `PDFUA_2` (built on PDF 2.0 / ISO 32000-2 -- it can only be combined with `PdfSpec.PDF_2_0`, not an earlier version):

```java
PdfSpec.PDFUA_1
PdfSpec.PDFUA_2   // requires PdfSpec.PDF_2_0
```

Any of these can be layered together in a single call, as long as the combination is one the PDF/A and PDF/UA specifications themselves permit -- PDF version, archival level, and accessibility level are independent axes:

```java
pd4ml.writePDF(out, PdfSpec.PDFUA_1);
pd4ml.writePDF(out, PdfSpec.PDF_1_7_8.combine(PdfSpec.PDFUA_1));
pd4ml.writePDF(out, PdfSpec.PDF_1_7_11.combine(PdfSpec.PDFA_3A).combine(PdfSpec.PDFUA_1));
```

**ZUGFeRD and Factur-X.** Four further constants bundle the exact PDF version and PDF/A level each e-invoicing standard requires into one name, so the underlying `combine()` call never needs to be written out by hand. The plain and `_a` forms differ only in PDF/A conformance level -- `_a` produces a PDF/A-3**a** (accessible, tagged) container instead of the baseline PDF/A-3**b**:

| Constant            | Equivalent to                              |
| ------------------- | ------------------------------------------ |
| `PdfSpec.ZUGFeRD`   | `PdfSpec.PDF_1_7.combine(PdfSpec.PDFA_3B)` |
| `PdfSpec.ZUGFeRD_a` | `PdfSpec.PDF_1_7.combine(PdfSpec.PDFA_3A)` |
| `PdfSpec.FacturX`   | `PdfSpec.PDF_1_7.combine(PdfSpec.PDFA_3B)` |
| `PdfSpec.FacturX_a` | `PdfSpec.PDF_1_7.combine(PdfSpec.PDFA_3A)` |

```java
pd4ml.writePDF(os, PdfSpec.ZUGFeRD);
pd4ml.writePDF(os, PdfSpec.FacturX);
```

These presets only select the container's PDF/A conformance level -- they don't attach the invoice XML itself, which is a separate step covered in full, including the required attachment filenames and a working end-to-end example, on the [ZUGFeRD and Factur-X PDF Invoices page](https://pd4ml.com/docs/features/zugferd-and-factur-x-pdf-invoices.md) in the Features section of this site.

Not every combination is meaningful -- pairing `PDFUA_2` with a pre-2.0 PDF version is the clearest example -- and `combine()` rejects an invalid pairing by throwing `PdfSpecViolationException` or `PdfSpecUnsupportedException` rather than silently producing a non-conformant file; catch (or let propagate) whichever of the two applies while the exact target spec for a given document is still being worked out.

PDF/UA conformance depends on the _source_ HTML being well-structured (real headings, `<table>` markup for tabular data, `alt` text on images, ...) as much as on PD4ML's own output -- pairing any of the targets above with [`setErrorPolicy(Constants.ErrorPolicy.PEDANTIC)`](https://pd4ml.com/javadoc/com/pd4ml/PD4ML.html#setErrorPolicy(com.pd4ml.Constants.ErrorPolicy)) surfaces a source document that doesn't actually satisfy the requested standard, rather than silently emitting non-conformant output.

Runnable example: [Z001ConvertHtml.java](https://github.com/zxfr/pd4ml-examples/blob/master/src/main/java/standards/Z001ConvertHtml.java) with [Z001.htm](https://github.com/zxfr/pd4ml-examples/blob/master/src/main/java/standards/Z001.htm) -- a PDF/A-3b ZUGFeRD/Factur-X invoice.

### 11.3 Restricting where resources are loaded from

PD4ML loads every stylesheet, image, web font and attachment the HTML refers to, with the network access and file permissions of your server. When the HTML comes from a source you do not fully control, limit loading to locations you trust with [`addAllowedResourceLocation(...)`](https://pd4ml.com/javadoc/com/pd4ml/PD4ML.html#addAllowedResourceLocation(java.lang.String)) (from v4.2.0) - otherwise the HTML can make your server read local files or call internal services:

```java
pd4ml.addAllowedResourceLocation("https://cdn.example.com/assets/, /opt/app/templates/");
pd4ml.addAllowedResourceLocation("https://*.example.com/");   // adds to the locations above
```

Resources outside the listed URL prefixes are not loaded; `*` matches any characters except `/`. The same restriction can be set with `setParam(Constants.PD4ML_ALLOWED_RESOURCE_LOCATION, ...)`. Details, what is checked and recommendations: [Restricting Resource Loading for Untrusted HTML](../features/restricting-resource-loading-for-untrusted-html).

E-mail messages and saved web pages load only from `https://` unless you list locations this way: [Converting E-mail Messages and Saved Web Pages](../features/converting-e-mail-and-saved-web-pages).

## 12. Extensibility and diagnostics

```java
pd4ml.setErrorPolicy(Constants.ErrorPolicy.PEDANTIC);  // fail fast on malformed source / non-conformant output
pd4ml.setLogLevel(3);                                   // 0 (silent) .. 5 (very verbose)

// ProgressListener callback: messageID identifies the conversion phase (see
// ProgressListener's own constants), progress is a 0-50 estimate, msec is
// elapsed time since conversion start
pd4ml.monitorProgressWith((messageID, progress, message, msec) ->
        System.out.println(progress + "% - " + message));

// Fetch images/attachments with up to 8 concurrent threads (avoid in a
// Java EE container that restricts application-managed thread creation)
pd4ml.asyncResourceLoader(8);

pd4ml.addMetadata("InvoiceNumber", "INV-2026-0031", false);            // custom PDF document-info entry
pd4ml.addDocumentActionHandler("OpenAction", "app.alert('Welcome');"); // PDF-embedded JavaScript
```

[`getLastRenderInfo(Constants.PD4ML_TOTAL_PAGES)`](https://pd4ml.com/javadoc/com/pd4ml/PD4ML.html#getLastRenderInfo(java.lang.String)) and `getLastRenderInfo(Constants.PD4ML_PDFA_STATUS)` retrieve information about the just-completed render -- how many pages it produced, or whether the requested PDF/A conformance was actually achieved -- after [`writePDF(...)`](https://pd4ml.com/javadoc/com/pd4ml/PD4ML.html#writePDF(java.io.OutputStream)) returns.

Runnable examples: [A004AddProgressListener.java](https://github.com/zxfr/pd4ml-examples/blob/master/src/main/java/advanced/A004AddProgressListener.java), [A007RenderingStatusInfo.java](https://github.com/zxfr/pd4ml-examples/blob/master/src/main/java/advanced/A007RenderingStatusInfo.java), [A008AddingCustomTagRenderer.java](https://github.com/zxfr/pd4ml-examples/blob/master/src/main/java/advanced/A008AddingCustomTagRenderer.java)

## 13. Web integration: the JSP tag library

Copy `pd4ml*.jar` into `WEB-INF/lib` and the tag library is available to any JSP page, no further deployment step needed. Two taglib URIs are published, for a classic `javax.servlet.jsp` container and a Jakarta EE 9+/`jakarta.servlet.jsp` one respectively -- pick the one matching your servlet container:

```jsp
<%@ taglib uri="http://pd4ml.com/tlds/4.2" prefix="pd4tl" %>      <%-- javax: Tomcat 9 and earlier --%>
<%@ taglib uri="http://pd4ml.com/tlds/4.2-jk" prefix="pd4tl" %>   <%-- jakarta: Tomcat 10 and later --%>
```

```jsp
<%@ taglib uri="http://pd4ml.com/tlds/4.2" prefix="pd4tl" %>
<pd4tl:transform
    screenWidth="1000"
    pageFormat="A4"
    pageInsets="15,20,15,20,mm">
<html>
<head><style>body { font-family: "Helvetica", sans-serif; }</style></head>
<body>
    <h1>${invoice.number}</h1>
    <pd4ml:page.footer height="24">Page $[page] of $[total]</pd4ml:page.footer>
    <!-- ... document body ... -->
</body>
</html>
</pd4tl:transform>
```

The tag resets the response when it starts, so output before it (white space included) is discarded, and it sets the document's content type itself; the page after `</pd4tl:transform>` is not processed. The response must not be committed before the tag: frameworks that wrap every JSP in a shared layout (Struts, JSF, ...) should render the PD4ML page from its own, undecorated JSP, or use the tag's forward mode. The `pd4tl` prefix keeps `pd4ml:` free for PD4ML's own tags in the HTML, such as `<pd4ml:page.footer>` above.

To save the generated file server-side instead of streaming it to the client, nest `savefile` in the `transform` tag (the directory must exist):

```jsp
<pd4tl:transform screenWidth="1000">
    <pd4tl:savefile
        dir="/var/spool/generated-pdfs"
        redirect="/app/download.jsp" />
    ...
</pd4tl:transform>
```

`redirect` receives a `filename` request parameter naming the file just written under `dir` -- `download.jsp` (or a plain servlet) then streams, emails, or archives it as your application requires. A `redirect` path starting with `/` is relative to the server root, so it includes the application's context path.

The complete reference of the tags - headers and footers, permissions, fonts, metadata, output formats, forward mode, troubleshooting - is the [PD4ML JSP Tag Library guide](https://pd4ml.com/docs/integrations/pd4ml-jsp-taglib.html).

## 14. IBM Notes/Domino integration

A Notes/Domino document can be converted the same way any other HTML source is -- either fetched live over HTTP:

```
http://<host>/<database>/<view>/<documentUNID>?OpenDocument
```

or exported to **DXL** (Domino's XML document representation) and converted through an XSL stylesheet PD4ML ships for that purpose:

```
java -Djava.awt.headless=true -Xmx512m -jar pd4ml.jar document.dxl 1200 -xsl notesdefault
```

Either approach works equally well from a scheduled Domino agent (Java or LotusScript, shelling out to `Pd4Cmd`) or an external batch process with network access to the Domino server. The URL patterns, the DXL export, and both deployment options are described in [PD4ML and IBM Notes/Domino](https://pd4ml.com/docs/integrations/pd4ml-and-ibm-notes-domino.html).

## 15. The PDF post-processing toolkit

Alongside HTML-to-PDF conversion, PD4ML ships five focused packages for working with _existing_ PDFs -- each is a complete, independently-documented feature with its own manual, runnable examples, and command-line tool; this section only orients you to which one covers which job.

### 15.1 `com.pd4ml.pdf.cos` -- the PDF object model

The low-level PDF object model, parser, COS-path query language, and incremental-update writer everything below is built on. Reach for it directly for structural edits nothing else here covers -- rotating a page, patching an arbitrary dictionary key, auditing a document's object graph. From v4.2.0 it also reads streams compressed with Brotli ([`BrotliDecodeFilter`](https://pd4ml.com/javadoc/com/pd4ml/pdf/cos/filter/BrotliDecodeFilter.html), the PDF 2.0 extension `/BrotliDecode`), and [`COSDocumentWriter.isPdfA1(document)`](https://pd4ml.com/javadoc/com/pd4ml/pdf/cos/writer/COSDocumentWriter.html#isPdfA1(com.pd4ml.pdf.cos.COSDocument)) tells whether a document declares PDF/A-1 conformance in its XMP metadata. See the [PD4ML COS API Manual](https://pd4ml.com/docs/pdf-tools/pd4ml-pdf-cos/pd4ml-cos-api-manual.md) and [pd4coscli Reference](https://pd4ml.com/docs/pdf-tools/pd4ml-pdf-cos/pd4coscli-reference.md).

Runnable examples: [`com.pd4ml.pdf.cos.example.usecases`](https://github.com/zxfr/pd4ml-examples/tree/master/src/main/java/com/pd4ml/pdf/cos/example/usecases)

### 15.2 `com.pd4ml.pdf.sign` -- digital signatures

PKCS#12/PKCS#11/HSM/cloud-KMS signing, RFC 3161 timestamping, DocMDP certification, and PAdES-LT/LTV long-term validation. The only one of the five post-processing packages that needs Bouncy Castle on the classpath -- already required regardless by the core engine's own document protection, §11.1 -- see the [PD4ML Signing Manual](https://pd4ml.com/docs/pdf-tools/pd4ml-pdf-signing/pd4ml-signing-manual.md) and [pdfsigncli Reference](https://pd4ml.com/docs/pdf-tools/pd4ml-pdf-signing/pd4signcli-reference.md).

Runnable examples: [`com.pd4ml.pdf.sign.example.usecases`](https://github.com/zxfr/pd4ml-examples/tree/master/src/main/java/com/pd4ml/pdf/sign/example/usecases)

### 15.3 `com.pd4ml.pdf.merge` -- combining PDFs

Selects independent page ranges from two or more source PDFs and merges them into one structurally-correct document, reconciling tagged (accessibility) structure across sources. See the [PD4ML PDF Merge Manual](https://pd4ml.com/docs/pdf-tools/pd4ml-pdf-merge/pd4ml-merge-manual.md) and [pd4mergecli Reference](https://pd4ml.com/docs/pdf-tools/pd4ml-pdf-merge/pd4mergecli-reference.md).

Runnable examples: [`com.pd4ml.pdf.merge.example.usecases`](https://github.com/zxfr/pd4ml-examples/tree/master/src/main/java/com/pd4ml/pdf/merge/example/usecases)

### 15.4 `com.pd4ml.pdf.optimizer` -- cleanup and size reduction

Drops unreferenced objects, deduplicates byte-identical content, and collapses incremental-update history into one fresh, compact file -- optionally linearized for Fast Web View (from v4.2.0: `PdfOptimizer.Options.linearize(true)`; a viewer reading the file over a network shows the first page before the whole file has arrived; not with encrypted output). See the [PD4ML PDF Optimizer Manual](https://pd4ml.com/docs/pdf-tools/pd4ml-pdf-optimizer/pd4ml-optimize-manual.md) and [pd4optimizecli Reference](https://pd4ml.com/docs/pdf-tools/pd4ml-pdf-optimizer/pd4optimizecli-reference.md).

Runnable examples: [`com.pd4ml.pdf.optimizer.example.usecases`](https://github.com/zxfr/pd4ml-examples/tree/master/src/main/java/com/pd4ml/pdf/optimizer/example/usecases)

### 15.5 `com.pd4ml.pdf.xfdf` -- annotation & form-value exchange

Exports a PDF's annotations and AcroForm field values to XFDF (ISO 19444-1), or imports an XFDF file's back in -- the same format most PDF viewers use for "export/import comments", including tagging newly-added annotations into an already-accessible target. See the [PD4ML XFDF Manual](https://pd4ml.com/docs/pdf-tools/pd4ml-pdf-xfdf/pd4ml-xfdf-manual.md) and [pd4xfdfcli Reference](https://pd4ml.com/docs/pdf-tools/pd4ml-pdf-xfdf/pd4xfdfcli-reference.md).

Runnable examples: [`com.pd4ml.pdf.xfdf.example.usecases`](https://github.com/zxfr/pd4ml-examples/tree/master/src/main/java/com/pd4ml/pdf/xfdf/example/usecases)

## 16. See also

* [PD4ML COS API Manual](https://pd4ml.com/docs/pdf-tools/pd4ml-pdf-cos/pd4ml-cos-api-manual.md) / [pd4coscli Reference](https://pd4ml.com/docs/pdf-tools/pd4ml-pdf-cos/pd4coscli-reference.md)
* [PD4ML Signing Manual](https://pd4ml.com/docs/pdf-tools/pd4ml-pdf-signing/pd4ml-signing-manual.md) / [pdfsigncli Reference](https://pd4ml.com/docs/pdf-tools/pd4ml-pdf-signing/pd4signcli-reference.md)
* [PD4ML PDF Merge Manual](https://pd4ml.com/docs/pdf-tools/pd4ml-pdf-merge/pd4ml-merge-manual.md) / [pd4mergecli Reference](https://pd4ml.com/docs/pdf-tools/pd4ml-pdf-merge/pd4mergecli-reference.md)
* [PD4ML PDF Optimizer Manual](https://pd4ml.com/docs/pdf-tools/pd4ml-pdf-optimizer/pd4ml-optimize-manual.md) / [pd4optimizecli Reference](https://pd4ml.com/docs/pdf-tools/pd4ml-pdf-optimizer/pd4optimizecli-reference.md)
* [PD4ML XFDF Manual](https://pd4ml.com/docs/pdf-tools/pd4ml-pdf-xfdf/pd4ml-xfdf-manual.md) / [pd4xfdfcli Reference](https://pd4ml.com/docs/pdf-tools/pd4ml-pdf-xfdf/pd4xfdfcli-reference.md)
* [ZUGFeRD and Factur-X PDF Invoices](https://pd4ml.com/docs/features/zugferd-and-factur-x-pdf-invoices.md) -- the full e-invoicing walkthrough referenced in §11.2
* [Usage Examples](https://pd4ml.com/docs/usage-examples.md) / [`pd4ml-examples`](https://github.com/zxfr/pd4ml-examples) on GitHub -- runnable, buildable sources for the API shown in this manual
* [PD4ML for Python](https://pd4ml.com/docs/integrations/pd4ml-python.html) / [PHP](https://pd4ml.com/docs/integrations/pd4ml-php.html) / [.NET](https://pd4ml.com/docs/integrations/pd4ml-dotnet.html) / [Node.js](https://pd4ml.com/docs/integrations/pd4ml-node.html) / [Ruby](https://pd4ml.com/docs/integrations/pd4ml-ruby.html) -- the API of this manual from other languages
* [pd4ml.com](https://pd4ml.com) -- product site, licensing, and online Javadoc
