# PD4ML for .NET — User Guide

`pd4ml-dotnet` brings [PD4ML](https://pd4ml.com) to .NET: it converts HTML and CSS into **PDF**,
**PDF/A**, **PDF/UA**, **ZUGFeRD/Factur-X** invoices, **DOCX**, **RTF** and **page images**, and
gives .NET applications the complete public PD4ML API with typed C# classes.

PD4ML is a Java library. `pd4ml-dotnet` runs it in a small Java *bridge* process that .NET talks
to over a pipe (or a local TCP port). Every PD4ML class has a typed .NET wrapper (`PD4ML`,
`PageSize`, `PdfSpec`, `PdfDocument`...) with its methods in PascalCase. The library adds .NET I/O
on top: file paths, `byte[]`, strings and `Stream`s instead of Java streams.

> **An alternative to the native port.** A native .NET port of PD4ML is pending.
> `pd4ml-dotnet` lets .NET applications use PD4ML now. It runs the Java PD4ML itself, so you get
> the same features, output and release cadence as the Java library, at the cost of needing a
> Java runtime next to .NET (see [4.2](#4.2-the-bridge)).

The PD4ML manual, which this guide refers to throughout, is at <https://pd4ml.com/docs/>.
The generated reference of every class and method is `docs/api.md` in the source archive from [pd4ml.com/net](https://pd4ml.com/net/).

**Contents**

1. [Prerequisites](#1.-prerequisites)
2. [Installation](#2.-installation)
3. [Quick start](#3.-quick-start)
4. [How it works](#4.-how-it-works)
5. [Use cases](#5.-use-cases)
6. [Tips and hints](#6.-tips-and-hints)
7. [Troubleshooting](#7.-troubleshooting)
8. [API reference](#8.-api-reference)
9. [Further reading](#9.-further-reading)

---

## 1. Prerequisites

| Requirement | Notes |
|---|---|
| **.NET 6 or later**, or .NET Framework 4.6.1+ | The library targets .NET Standard 2.0, so it runs on .NET 6, 7, 8 and later, and on .NET Framework 4.6.1 and later. The `pd4ml` command-line tool needs .NET 6 or later. |
| **Java 8 or later** | A JDK or JRE. An LTS release (17, 21) is recommended. The library finds Java through `PD4ML_JAVA`, then `JAVA_HOME`, then `java` on the `PATH`. |
| **PD4ML** | Downloaded by `pd4ml install` or `Installer.Install()` (see below), or any PD4ML build you point the library at. |
| **Network access** | Needed only for the download. Conversions need no network access unless the HTML references remote resources. |
| **A PD4ML license** | Optional for evaluation. Without one, PD4ML runs in evaluation mode and watermarks its output. See [Licensing](https://pd4ml.com/docs/#3.-licensing) and [5.18](#5.18-licensing). |

Check with `dotnet --info` and `java -version`. On a server without Java, install a JRE package
(for example `apt install openjdk-21-jre-headless`). If it is not on the `PATH`, set `JAVA_HOME`
or `PD4ML_JAVA`. In containers, use an image that has both runtimes, or add a JRE to the .NET image.

## 2. Installation

### 2.1 Add the library

`pd4ml-dotnet` is available for download from **<https://pd4ml.com/net/>**:

| Download | |
|---|---|
| `Pd4ml.Net.1.1.1.nupkg` | the library, a NuGet package |
| `Pd4ml.Net.Tool.1.1.1.nupkg` | the `pd4ml` command-line tool, a .NET tool package |
| source archive | the projects, tests and this guide, to build or reference yourself |

Save the packages in a folder, for example `C:\pd4ml-net` (any folder works; a team can use a
shared folder or its own NuGet feed), and add the library to your project from there:

```
dotnet add package Pd4ml.Net --source C:\pd4ml-net
```

To restore from that folder later without `--source`, register it once:
`dotnet nuget add source C:\pd4ml-net --name pd4ml`. New versions are published on the same
page; `dotnet add package Pd4ml.Net --source C:\pd4ml-net` again picks up the most recent one.

With the source archive instead, reference the project directly:

```
dotnet add reference pd4ml-dotnet/src/Pd4ml.Net/Pd4ml.Net.csproj
```

Everything is in the `Pd4mlNet` namespace: `using Pd4mlNet;`.

### 2.2 Install PD4ML

With the `pd4ml` command-line tool, installed from the downloaded tool package:

```
dotnet tool install --global Pd4ml.Net.Tool --add-source C:\pd4ml-net
pd4ml install
```

(From the source archive, without installing the tool:
`dotnet run --project pd4ml-dotnet/src/Pd4ml.Cli -- install`.)

`pd4ml install` does the following:

- downloads PD4ML from the PD4ML Maven repository (<https://pd4ml.com/maven2>): the most
  recent release from 4.2.0 on, or, while there is none, the most recent 4.2.x development
  snapshot (see below);
- downloads the Bouncy Castle jars PD4ML needs for encryption and signing, from Maven Central;
- checks every file against its published SHA-1 checksum;
- stores them in a per-user cache directory: `%LOCALAPPDATA%\pd4ml-dotnet` on Windows,
  `~/.cache/pd4ml-dotnet` elsewhere.

The library then uses that build automatically. Run the command again to update.

> **Why a snapshot.** Parts of the API of this version of `pd4ml-dotnet` need PD4ML 4.2:
> `ReadEML`/`ReadMHT`, `AddAllowedResourceLocation`, XMP `AddMetadata`, the DOM API and the v42
> layout engine. Until PD4ML 4.2.0 is released (the newest release is 4.1.2), 4.2 exists only as
> the development snapshot `4.2.0-SNAPSHOT`, so that is what `pd4ml install` installs. Once 4.2.0
> is out, the same command installs it, and later 4.2.x or 4.x releases after it; it never moves to
> a newer snapshot line (4.3) by itself. To stay on the current public release, pin it:
> `pd4ml install 4.1.2`. The rest of the library works with 4.1.x as before.

| Option | |
|---|---|
| *(none)* | the most recent 4.x release from 4.2.0 on; until there is one, the most recent 4.2.x snapshot |
| `--snapshots` | the most recent 4.x build, including development snapshots |
| `pd4ml install 4.1.2` | an exact version (also a snapshot, such as `4.2.0-SNAPSHOT`) |
| `--jakarta` | for Jakarta EE / Servlet 6 environments: a `-jakarta` variant if one fits; PD4ML 4.2 has none (its builds contain the Jakarta tag library), so the same 4.2 build |
| `--home DIR` | install into `DIR` (default: `PD4ML_HOME`, else the cache directory) |
| `--force` | download again |
| `--no-bouncycastle` | skip the Bouncy Castle jars (encryption and signing will not work) |
| `pd4ml list` | list the available versions |

You can do the same from code, for example at application start:

```csharp
using Pd4mlNet;

Installer.Install();                          // the default version; downloads only when that build is not there yet
Console.WriteLine(Installer.InstalledVersion());
```

To check the installation, run `pd4ml version`.

> **Services and web servers**: the account the application runs as must be able to see the
> installed build. Either install it as that account, or install into a shared directory with
> `--home` and set `PD4ML_HOME` for the application too. On IIS, the application pool identity
> has its own `%LOCALAPPDATA%`.

### 2.3 Other ways to provide PD4ML

The library looks for PD4ML in this order:

1. `Bridge.Configure(o => o.Classpath = new[] { ... })`, called before the first conversion;
2. the `PD4ML_CLASSPATH` environment variable: jars and class directories separated by `;`
   (Windows) or `:` (elsewhere), with `dir/*` for all jars of a directory;
3. the `PD4ML_CLASSPATH_FILE` environment variable: a file with one entry per line;
4. the build that `pd4ml install` downloaded.

Typical reasons to use them:

- **Offline or air-gapped servers**: run `pd4ml install --home ./pd4ml-libs` on a machine with
  network access, copy the directory, and set `PD4ML_HOME` on the target. Alternatively, ship
  the directory with your application and point `PD4ML_HOME` at it.
- **A licensed or specific jar**: `PD4ML_CLASSPATH=/opt/pd4ml/pd4ml.jar:/opt/pd4ml/lib/*`.
- **A PD4ML development build** (for the v42 layout engine, see [4.3](#4.3-layout-engines)):
  put a classpath file listing the build's classes and its Maven dependencies in
  `PD4ML_CLASSPATH_FILE`.

## 3. Quick start

```csharp
using Pd4mlNet;

var pd = new PD4ML();
pd.SetPageSize(PageSize.A4);
pd.ReadHtmlString("<h1>Hello, PDF</h1><p>Generated with <b>PD4ML</b>.</p>");
pd.WritePDF("hello.pdf");
```

Or in one call:

```csharp
using System.Text;
using Pd4mlNet;

byte[] pdf = Pdf.HtmlToPdf(Encoding.UTF8.GetBytes("<h1>Invoice 42</h1>"));
Console.WriteLine(Encoding.ASCII.GetString(pdf, 0, 5));     // %PDF-
```

## 4. How it works

### 4.1 The two-phase model

A document is **read once and written any number of times**:

- `ReadHTML` parses the document and lays it out;
- each `WritePDF`, `WriteDOCX`, `WriteRTF` or `RenderAsImages` call then produces one output from
  that same layout.

Settings (page size, headers, fonts...) go before `ReadHTML`.
See [the two-phase model](https://pd4ml.com/docs/#6.1-the-two-phase-model).

```csharp
using Pd4mlNet;

var pd = new PD4ML();
pd.ReadHtmlString("<h1>Quarterly report</h1>");
pd.WritePDF("report.pdf");
pd.WriteDOCX("report.docx");
byte[][] thumbnails = pd.RenderPng();     // PNG images, one per page
```

### 4.2 The bridge

- **One bridge per application (default).** The first use of PD4ML starts a Java bridge
  process. It serves every conversion of the .NET process, from any thread, and ends with it.
  Starting Java takes roughly a second. After that, converting an ordinary document takes
  milliseconds.
- **Thread safety.** Calls to the bridge are serialized. Use one `PD4ML` object per document
  (each thread its own) and convert from as many threads as you like. The conversions run one
  at a time, so for parallel throughput use several processes or several bridges (see below).
- **Shared server.** Several processes, or short-lived ones (Azure Functions, CLI invocations),
  can use one long-running bridge instead of each starting Java:

  ```
  pd4ml serve --port 8723          # keep it running (a Windows service, systemd, a sidecar container)
  ```

  Then set `PD4ML_BRIDGE=tcp://127.0.0.1:8723` for the application, or call
  `Bridge.Configure(o => o.Server = "tcp://127.0.0.1:8723")`. The server listens on the loopback
  interface only, and each connection has its own objects.
- **JVM options** (memory, system properties): set `PD4ML_JVM_OPTIONS="-Xmx2g"`, or call
  `Bridge.Configure(o => o.JvmOptions.Add("-Xmx2g"))` before the first use. Java runs headless.
- **The bridge's console output** is discarded by default. To keep it, set
  `PD4ML_BRIDGE_LOG=path` or `o.LogFile`.
- `Bridge.Shutdown()` stops the bridge; the next use starts it again. A `new Bridge(options)`
  creates a separate bridge, with its own Java process and classpath, for low-level use.

### 4.3 Layout engines

PD4ML 4.2 introduces a second HTML layout engine, **v42**, written to match current browsers
closely (flexbox, grid, modern CSS). `new PD4ML()` uses it by default:

```csharp
using Pd4mlNet;

if (Runtime.HasLayoutEngines)
{
    var pd = new PD4ML();                          // v42
    var legacy = new PD4ML(LayoutEngine.v40);      // the previous engine
    Console.WriteLine($"{pd.Engine} {legacy.Engine}");
}
```

Builds before 4.2 have one engine only; this includes the current public release, 4.1.x. With
such a build:

- the library uses that build's engine and traces a warning once (`System.Diagnostics.Trace`);
- `Runtime.HasLayoutEngines` returns false;
- asking for an engine explicitly throws `InvalidOperationException`.

### 4.4 .NET and Java values

- Java methods appear in **PascalCase**: `setPageSize` becomes `SetPageSize`, `getVersion`
  becomes `GetVersion`. Static Java fields keep their names (`PageSize.A4`,
  `Constants.AllowPrint`, `LayoutEngine.v42`); instance fields become properties
  (`PageSize.A4.Width`).
- Java types map to .NET types:

  | Java | .NET |
  |---|---|
  | `String` | `string` |
  | `int` / `long` / `float` / `boolean` | `int` / `long` / `float` / `bool` |
  | `byte[]` | `byte[]` |
  | `InputStream` | `Stream` |
  | `OutputStream` | any `Stream` |
  | `URL` | `Uri` |
  | `File` | `FileInfo` |
  | `Map` | `IDictionary<string, string>` |
  | `List`, `Set` returned by the DOM API | `IReadOnlyList<T>` |
  | `Map` returned by the DOM API | `IReadOnlyDictionary<string, string?>` |
  | PD4ML classes | the wrappers |
  | any other object | `JavaObject` |

- Java objects that have no wrapper are `JavaObject`s. Call them with `obj.Call("method", args)`
  or `obj.Call<T>(...)`, or through `dynamic`:

  ```csharp
  using Pd4mlNet;

  dynamic locale = Java.Class("java.util.Locale").Call("getDefault");
  Console.WriteLine((string)locale.getDisplayName());
  ```

- `Java.Class("name")` gives you any Java class: `.New(...)` calls a constructor, `.Call(...)` a
  static method, and `.Get(...)` reads a static field. With `dynamic`, write Java's names as they
  are (`map.put(...)`).
- `ToString()`, `Equals()` and `GetHashCode()` call the Java methods of the same name. Java
  objects are released when their .NET wrappers are garbage-collected.

### 4.5 Exceptions

Java exceptions are thrown as `JavaException`, which carries the Java class and its
superclasses:

```csharp
using Pd4mlNet;

try
{
    var pd = new PD4ML();
    pd.ReadHTML("file:///no/such/document.html");
    pd.WritePDF();
}
catch (JavaException e) when (e.IsInstanceOf("java.io.IOException"))
{
    Console.WriteLine($"cannot read the document: {e.JavaMessage}");
}
```

`e.JavaClass`, `e.Causes` and `e.JavaTrace` hold the details. Other exception types:

| Exception | Thrown when |
|---|---|
| `BridgeException` | the bridge itself fails: no Java, no PD4ML build, or the process ended |
| `CallbackException` | a .NET callback throws, such as a progress listener; the original exception is the `InnerException` |
| `InstallException` | downloads fail |

---

## 5. Use cases

The snippets below are complete and runnable as C# scripts or in a `Main` method. Files they read
(`report.html`...) are assumed to exist in the working directory.

### 5.1 HTML string to PDF

This is the common case for web applications: render HTML with your template engine (Razor,
Scriban, a `StringBuilder`) and convert it in memory.

```csharp
using Pd4mlNet;

var html = "<html><body><h1>Order 1001</h1><p>Thank you for your order.</p></body></html>";

var pd = new PD4ML();
pd.ReadHtmlString(html);
byte[] pdf = pd.WritePDF();               // the PDF as bytes
```

`WritePDF`, `WriteDOCX` and `WriteRTF` have three forms:

- with no argument, they return the document as `byte[]`;
- given a file path, they write the document to that file;
- given a `Stream`, they write into it (a `FileStream`, `MemoryStream` or `Response.Body`).

### 5.2 Files and URLs

```csharp
using Pd4mlNet;

var pd = new PD4ML();
pd.ReadHTML("report.html");               // also: "https://example.com/report", a Uri, a FileInfo
pd.WritePDF("report.pdf");
```

**Relative links** (`<img src="logo.png">`, `<link href="style.css">`) resolve against the
document's own location when it is read from a path or URL. When the HTML comes from a string,
`byte[]` or a stream, pass the base location. Otherwise images and stylesheets go missing without
an error (see [reading the source](https://pd4ml.com/docs/#6.2-reading-the-source)):

```csharp
using Pd4mlNet;

var html = "<link rel='stylesheet' href='style.css'><p class='note'>Styled</p>";
var pd = new PD4ML();
pd.ReadHtmlString(html, ".");             // a directory, file path or URL
pd.WritePDF("styled.pdf");
```

For a document in a known encoding other than UTF-8, call
`pd.ReadHTML(bytes, baseUrl, "windows-1251")`. `ReadHTML(Stream, baseUrl, encoding)` works the
same way for streams.

### 5.3 Page size, orientation, margins and scaling

```csharp
using Pd4mlNet;

var pd = new PD4ML();
pd.SetPageSize(PageSize.LETTER.Rotate());                       // landscape
pd.SetPageMargins(new PageMargins(15, 20, 15, 20, Units.MM));   // left, top, right, bottom
pd.SetHtmlWidth(1000);              // the virtual browser width the page is laid out in
pd.ReadHtmlString("<table width='100%'><tr><td>wide content</td></tr></table>");
pd.WritePDF("landscape.pdf");
```

The layout is scaled to the page: `scale = printable width / html width`, so a smaller HTML width
makes everything larger. Related controls:

- `AdjustHtmlWidth(true)` lays the page out at the document's own natural width.
- `FitPageVertically(Constants.CENTER_ALIGN)` shrinks the whole document onto a single page.
- `SetPageSize` and `SetPageMargins` accept a page *scope* (`"1"`, `"2+"`, `"odd"`...) as a
  second argument.

More in [page layout](https://pd4ml.com/docs/#7.-page-layout-size-margins-and-scaling).

For a custom size, use `new PageSize(100f, 150f, Units.MM)`. For a named size, use
`PageSize.GetInstance("a5")` (the name in lower case).

### 5.4 Headers, footers and page numbers

Headers and footers are HTML fragments. PD4ML fills in `$[page]`, `$[total]` and `$[title]` on
each page, and `SetDynamicData` adds placeholders of your own. In C#, write them in a verbatim
string, or in an ordinary (not interpolated) string:

```csharp
using System.Linq;
using Pd4mlNet;

var pd = new PD4ML();
pd.SetDynamicData(new Dictionary<string, string> { ["customer"] = "Acme Corp.", ["invoice"] = "INV-2026-0031" });
pd.SetPageHeader("<div style='font-size:9pt'><b>$[title]</b></div>", 30, "1+");
pd.SetPageFooter(
    "<div style='font-size:8pt; text-align:right'>"
    + "Invoice $[invoice] &mdash; $[customer] &mdash; page $[page] of $[total]</div>",
    24, "1+");
pd.ReadHtmlString("<title>Invoice</title><h1>Invoice</h1>" + string.Concat(Enumerable.Repeat("<p>Line item</p>", 80)));
pd.WritePDF("invoice.pdf");
```

The second argument is the area height in pixels. The third is the page scope: `"1"` (first
page), `"2+"` (all but the first), `"odd"`, `"even"`, `"3-7"`... You can also write headers and
footers inline in the HTML with PD4ML's extension tags. See
[headers, footers, watermarks and backgrounds](https://pd4ml.com/docs/#8.-headers-footers-watermarks-and-backgrounds)
and the [usage examples](https://pd4ml.com/docs/usage-examples.html#set-page-header-and-footer).

### 5.5 Watermarks and page backgrounds

```csharp
using Pd4mlNet;

var pd = new PD4ML();
pd.SetWatermark("<span style='font-size:72pt; color:#c00'>DRAFT</span>",
    120f, 350f,     // left, top (px)
    0.15f,          // opacity
    45f,            // rotation, degrees
    1f,             // scale
    true, true,     // visible on screen, in print
    "1+");          // pages
pd.SetPageBackground("<div style='background:#f7f7f2; height:100%'></div>", "1+");
pd.ReadHtmlString("<h1>Proposal</h1>");
pd.WritePDF("draft.pdf");
```

### 5.6 Fonts

PD4ML's built-in fonts cover Unicode broadly. For a specific typeface (a corporate font, CJK
coverage), give it a font directory:

```csharp
using Pd4mlNet;

var pd = new PD4ML();
pd.UseTTF(@"C:\fonts", true);       // a directory of TTF/OTF fonts (indexed: see below)
pd.EmbedTTFs(true, false);          // embed only the glyphs used
pd.ApplyKerning(true);
pd.EnableLigatures(true);
```

Index a font directory once, ahead of time. PD4ML then reads the index (`pd4fonts.properties`)
instead of scanning all the fonts on every conversion
([font indexing](https://pd4ml.com/docs/#4.3-font-indexing)):

```
java -jar <pd4ml jar> -configure.fonts C:\fonts
```

`Installer.InstalledJar()` returns the jar's path. CSS `@font-face` web fonts in the document
work as well.

### 5.7 Bookmarks, links and tables of contents

```csharp
using Pd4mlNet;

var pd = new PD4ML();
pd.GenerateBookmarksFromHeadings(true);    // one PDF bookmark per <h1>..<h6>
pd.EnableHyperlinks(true);                 // <a href> become clickable links (the default)
pd.ReadHtmlString("<h1>Introduction</h1><p>...</p><h1 style='page-break-before:always'>Details</h1>");
pd.WritePDF("bookmarked.pdf");
```

PD4ML's `<pd4ml:toc>` tag generates a table of contents with page numbers from the headings; see
the [usage examples](https://pd4ml.com/docs/usage-examples.html#add-toc). For explicit page
breaks, use CSS `page-break-before` / `break-before`, or `<pd4ml:page.break>`.

### 5.8 PDF/A and PDF/UA

```csharp
using Pd4mlNet;

var pd = new PD4ML();
pd.ReadHtmlString("<html lang='en'><title>Annual report</title><h1>Annual report</h1>");
pd.WritePDF("archive.pdf", PdfSpec.PDFA_3B);                          // archival
pd.WritePDF("accessible.pdf", PdfSpec.PDFUA_1);                       // accessible
pd.WritePDF("both.pdf", PdfSpec.PDF_1_7.Combine(PdfSpec.PDFA_2A));

var status = pd.GetLastRenderInfo(Constants.PD4ML_PDFA_STATUS);      // after writing
```

You can also name the spec with a keyword: `Runtime.PdfSpecFor("pdf/a-3b")`.
`PdfSpec.ListKeywords()` lists the keywords. An unknown keyword throws `ArgumentException`;
`PdfSpec.GetInstance` alone would fall back to PDF 1.7 without an error.

- PDF/A embeds all fonts.
- PDF/UA needs well-structured HTML: real headings, `alt` texts, `lang`, table headers.
  Combine it with `pd.SetErrorPolicy(ErrorPolicy.PEDANTIC)` to be told when the source does not
  satisfy the standard.

Details: [PDF/A, PDF/UA and e-invoicing](https://pd4ml.com/docs/#11.2-pdfa-pdfua-and-e-invoicing-formats).

### 5.9 ZUGFeRD / Factur-X e-invoices

`PdfSpec.ZUGFeRD` and `PdfSpec.FacturX` select the PDF/A-3 container the standards require. The
HTML attaches the invoice XML with PD4ML's attachment tag. The
[ZUGFeRD and Factur-X page](https://pd4ml.com/docs/features/zugferd-and-factur-x-pdf-invoices.html)
has the complete recipe, including the required attachment names.

```csharp
using Pd4mlNet;

var pd = new PD4ML();
pd.ReadHTML("invoice.html");          // contains the <pd4ml:attachment> of factur-x.xml
pd.WritePDF("invoice.pdf", PdfSpec.FacturX);
```

### 5.10 Passwords and permissions

```csharp
using Pd4mlNet;

var pd = new PD4ML();
pd.SetPermissions("owner-secret", Constants.AllowPrint | Constants.AllowCopy, Constants.AES_256BIT);
pd.ReadHtmlString("<p>Confidential</p>");
pd.WritePDF("protected.pdf");
```

Combine the `Allow*` flags with `|`, or start from `Constants.DefaultPermissions` and remove
flags with `& ~`. Encryption needs the Bouncy Castle jars, which `pd4ml install` installs. See
[permissions and encryption](https://pd4ml.com/docs/#11.1-permissions-and-encryption).

### 5.11 Word (DOCX, RTF) and images

```csharp
using Pd4mlNet;

var pd = new PD4ML();
pd.ReadHtmlString("<h1>Letter</h1><p>Dear customer, ...</p>");
pd.WriteDOCX("letter.docx");
pd.WriteRTF("letter.rtf");
byte[][] pages = pd.RenderPng();                               // PNG per page
byte[][] tiffs = pd.RenderAsImages(Constants.TIFF);            // one TIFF per page
pd.RenderAsImages(".", "page", Constants.PNG24);               // page files in a directory
```

The image formats are `PNG8`, `PNG24`, `TIFF` and `TIFF_COMPRESSED` (constants in `Constants`).
The v42 engine does not support the multi-page TIFF formats yet; use the v40 engine for them.

With the v42 engine, DOCX/RTF output is *editable*: positioned content becomes text boxes
and form fields, not pictures.

### 5.12 Interactive forms and multi-column layout

```csharp
using System.Linq;
using Pd4mlNet;

var pd = new PD4ML();
pd.GenerateForms(true, null);         // <input>/<select>/<textarea> become PDF form fields
pd.ReadHtmlString("<form>Name: <input name='name' size='30'></form>");
pd.WritePDF("form.pdf");

var news = new PD4ML();
news.GenerateMulticolumn(2, 20, true); // two columns, 20 mm apart
news.ReadHtmlString("<h1>Newsletter</h1>" + string.Concat(Enumerable.Repeat("<p>Lorem ipsum dolor sit amet.</p>", 200)));
news.WritePDF("newsletter.pdf");
```

See [forms](https://pd4ml.com/docs/#10.1-interactive-pdf-forms) and
[multi-column pages](https://pd4ml.com/docs/#10.2-multi-column-pages). Multi-column pages need
PD4ML 4.0.24 / 4.1.2 or later.

### 5.13 Adding CSS and HTML programmatically

```csharp
using Pd4mlNet;

var pd = new PD4ML();
pd.AddStyle("body { font-family: sans-serif } h1 { color: #036 }", true);   // true: after the document's styles
pd.InjectHtml("<div style='font-size:8pt'>Generated by the reporting service</div>", false);  // before </body>
pd.ReadHtmlString("<h1>Report</h1>");
pd.WritePDF("report-styled.pdf");
```

`AddStyle` also takes the URL of a stylesheet: `pd.AddStyle(new Uri(Path.GetFullPath("print.css")), true)`.
See [injecting HTML](https://pd4ml.com/docs/#10.3-injecting-html-and-custom-resource-providers)
and [adding styles](https://pd4ml.com/docs/usage-examples.html#add-style-programmatically).

### 5.14 Progress, diagnostics and metadata

```csharp
using Pd4mlNet;

var pd = new PD4ML();
pd.MonitorProgressWith((messageId, percent, note, msec) =>
    Console.WriteLine($"{percent,3}% {note} ({msec} ms)"));
pd.SetErrorPolicy(ErrorPolicy.NORMAL);   // NONE, RELAXED, NORMAL, PEDANTIC, STRICT
pd.SetLogLevel(1);                       // 0 (silent) .. 5
pd.SetAuthorName("Reporting service");
pd.SetDocumentTitle("Monthly statement");
pd.AddMetadata("StatementId", "2026-10", false);
pd.AddMetadata("http://www.w3.org/ns/tdmrep/", "tdm", "reservation", "1");   // XMP, a namespace of your own
pd.ReadHtmlString("<h1>Statement</h1>");
pd.WritePDF("statement.pdf");
int pages = Convert.ToInt32(pd.GetLastRenderInfo(Constants.PD4ML_TOTAL_PAGES));
```

`AddMetadata(key, value, hide)` adds a custom document property;
`AddMetadata(namespaceURI, prefix, key, value)` (PD4ML 4.2) adds an XMP property in a namespace of
your own, such as the [TDM Reservation Protocol](https://www.w3.org/community/reports/tdmrep/CG-FINAL-tdmrep-20240510/)
properties above (PDF output only).

The callback runs in .NET while the conversion runs in Java. It may call PD4ML itself. An
exception it throws comes out of the call that caused it, wrapped in a `CallbackException`. The
v42 engine currently reports fewer progress stages than v40. More in
[extensibility and diagnostics](https://pd4ml.com/docs/#12.-extensibility-and-diagnostics).

### 5.15 Working with existing PDFs

```csharp
using System.Text;
using Pd4mlNet;

byte[] cover = Pdf.HtmlToPdf(Encoding.UTF8.GetBytes("<h1 style='margin-top:300px; text-align:center'>Annual Report</h1>"));
byte[] body = Pdf.HtmlToPdf(Encoding.UTF8.GetBytes("<h2>Chapter 1</h2><p>...</p>"));

var doc = Pdf.Open(cover);                // byte[], a Stream, a path or a URL (+ password)
doc.Append(Pdf.Open(body));               // pages of another document after these
doc.SetTitle("Annual Report");
Pdf.Save(doc, "annual-report.pdf");

byte[] stamp = Pdf.HtmlToPdf(Encoding.UTF8.GetBytes("<p style='color:#c00; font-size:40pt'>COPY</p>"));
var original = Pdf.Open("annual-report.pdf");
original.Overlay(Pdf.Open(stamp), 128);   // draw over every page, half opaque (0..255)
Pdf.Save(original, "annual-report-copy.pdf", "secret");

Console.WriteLine(Pdf.Open("annual-report.pdf").GetNumberOfPages());
```

`Pdf.Write(doc)` returns the document as `byte[]`. Other operations:

- `Underlay` draws another document *under* the pages (a letterhead, for example).
- `Pdf.Merge(a, b)` appends two documents in one call.
- `DeletePage`, `GetPageSize`, `GetMetadata` and `DumpOutlines` inspect and edit a document.

PD4ML's PDF toolkit (signing, optimisation, XFDF) is described in
[the PDF post-processing toolkit](https://pd4ml.com/docs/#15.-the-pdf-post-processing-toolkit)
and is reachable through `Java.Class(...)`.

### 5.16 Web applications

ASP.NET Core minimal API:

```csharp
using Pd4mlNet;

var app = WebApplication.Create(args);

app.MapGet("/invoice/{number:int}.pdf", (int number, IWebHostEnvironment env) =>
{
    var pd = new PD4ML();
    pd.ReadHtmlString(RenderInvoiceHtml(number), env.WebRootPath);   // base: where images/CSS live
    return Results.File(pd.WritePDF(), "application/pdf", $"invoice-{number}.pdf");
});

app.Run();
```

An MVC controller, with the HTML from a Razor view rendered to a string (for example with
`IRazorViewEngine`, or a library such as RazorLight):

```csharp
public async Task<IActionResult> Invoice(int number)
{
    string html = await _viewRenderer.RenderAsync("Invoice", await _invoices.GetAsync(number));
    var pd = new PD4ML();
    pd.ReadHtmlString(html, _env.WebRootPath);
    return File(pd.WritePDF(), "application/pdf");
}
```

Conversions are synchronous and serialized through the bridge. In ASP.NET Core you can run them
on the request thread, or with `await Task.Run(...)` for long documents. The bridge starts with
the first request. To pay that cost at startup instead, touch PD4ML when the application starts
(`_ = Runtime.Version;`).

### 5.17 Batch conversion

```csharp
using Pd4mlNet;

foreach (var source in Directory.GetFiles(".", "*.html"))
{
    var pd = new PD4ML();                    // one converter per document
    pd.ReadHTML(source);
    pd.WritePDF(Path.ChangeExtension(source, ".pdf"));
    Console.WriteLine($"written {source}");
}
```

One bridge serves the whole loop. To convert truly in parallel, run several worker processes or
give workers their own `Bridge` instances (a low-level API).

### 5.18 Licensing

```csharp
using Pd4mlNet;

var pd = new PD4ML("YOUR-SERIAL-NUMBER");   // or a license file URL; or pd4ml.lic in the working dir / classpath
if (pd.IsDemoMode())
    Console.WriteLine("unlicensed: output is watermarked");
Console.WriteLine($"PD4ML {Runtime.Version}{(pd.IsPro() ? " pro" : "")}");
```

Without a license, PD4ML is fully functional but marks its output. See
[licensing](https://pd4ml.com/docs/#3.-licensing).

### 5.19 The DOM API: modifying the document before conversion

With the v42 layout engine (the default), `GetDocument()` gives the HTML document PD4ML has parsed.
Between `ReadHTML` and the output it can be read, queried with CSS selectors and changed, and the
output is laid out as if the HTML had been written that way. `Layout()` lays the document out into
pages without writing it, and tells where each element ended up. The classes are in the
`Pd4mlNet.Dom` namespace: `Document`, `Element`, `Text`, `Comment`, `DocumentType` (all of them a
`Node`), `Layout` and `LayoutBox`.

> **Editions.** The DOM API needs PD4ML 4.2 and is part of the **DMS and UA editions**. Without a
> license it works, with the evaluation watermark; with an STD or PRO license the output of a
> document it was used on carries "PD4ML EVALUATION MODE" banners.

```csharp
using Pd4mlNet;
using Pd4mlNet.Dom;

var pd = new PD4ML();
pd.ReadHtmlString("<h1 id='total'>0.00</h1><img src='a.png'><img src='b.png' alt='B'>"
    + "<p class='draft-only'>Draft</p><table class='items'><tbody></tbody></table>");
Document doc = pd.GetDocument()!;

doc.GetElementById("total")!.SetTextContent("1 250,00 EUR");
foreach (Element img in doc.QuerySelectorAll("img:not([alt])"))   // a .NET list
    img.SetAttribute("alt", "");
foreach (Element e in doc.QuerySelectorAll(".draft-only"))
    e.Remove();
doc.QuerySelector("table.items tbody")!.SetInnerHTML("<tr><td>Consulting</td><td>8 h</td></tr>");

Element note = doc.CreateElement("p")!;
note.SetClassName("note");
note.SetTextContent("Prices include VAT.");
doc.GetBody()!.AppendChild(note);

Element style = doc.CreateElement("style")!;
style.SetTextContent("@page { size: A4; margin: 15mm } .note { color: #036 }");
doc.GetHead()!.AppendChild(style);

pd.WritePDF("invoice.pdf");
```

- `GetDocument()` and `Layout()` throw a `JavaException` (`IllegalStateException`) before
  `ReadHTML` and with the v40 engine.
- Nodes are views of the tree: compare them with `Equals`, not `==`.
  `GetDocument()` returns the same tree on every call.
- Lists (`QuerySelectorAll`, `GetElementsByTagName`, `GetChildNodes`, `GetChildren`,
  `GetClassList`, `Layout.GetBoxes`, `GetPages`) are .NET `IReadOnlyList`s, read in one call: they
  are snapshots, so removing their nodes while going through them is safe. `GetAttributes()` is an
  `IReadOnlyDictionary<string, string?>`.
- `SetTextContent` sets text (`<` stays a `<`); `SetInnerHTML` / `SetOuterHTML` parse HTML.
  `ToHtml()` writes the document (or a node) back out as HTML.
- Changes to `style`, `class`, `src` and the like take effect in the next output, as if the source
  had had them. A `<style>` or `<link rel="stylesheet">` added to `<head>` applies, `@page` rules
  included.

Reading the layout:

```csharp
using Pd4mlNet;
using Pd4mlNet.Dom;

var pd = new PD4ML();
pd.SetPageSize(PageSize.A4);
pd.ReadHtmlString("<h1>Report</h1><div id='payment' style='height: 200pt'>Payment details</div>");
var doc = pd.GetDocument()!;
var payment = doc.GetElementById("payment")!;

Layout layout = pd.Layout()!;
Console.WriteLine($"{layout.GetPageCount()} page(s)");
foreach (LayoutBox box in layout.GetBoxes(payment))   // page, x, y, width, height in points, from the top left
    Console.WriteLine($"page {box.GetPage()}: {box.GetX()}, {box.GetY()}, {box.GetWidth()} x {box.GetHeight()}");
Console.WriteLine(layout.GetComputedStyle(payment, "height"));

if (layout.GetPages(payment).Count > 1)            // split over two pages: start it on a new one
    payment.SetAttribute("style", "height: 200pt; page-break-before: always");
pd.WritePDF("report.pdf");
```

A `Layout` is a snapshot: after changing the document, call `Layout()` again. It also has
`GetPageWidth(page)`, `GetPageHeight(page)`, `GetPageNumber(page)` (the printed number) and
`ElementAt(page, x, y)`; pages count from 1. Every method is listed in `docs/api.md` in the source archive from [pd4ml.com/net](https://pd4ml.com/net/);
the PD4ML guide [Modifying the HTML document before conversion](https://pd4ml.com/docs/features/modifying-the-html-document-before-conversion.html)
explains them, with the selectors supported and the timing rules.

### 5.20 E-mail messages and saved web pages

> **Editions.** E-mail and saved web page input — `ReadEML`/`ReadMHT`, and `ReadHTML` for input it
> recognizes as one of them — is part of the PD4ML **DMS and UA editions**. With an STD or PRO license it
> works, but the output carries the "PD4ML EVALUATION MODE" banners.

PD4ML 4.2 reads e-mail messages (`.eml`) and web pages saved as a single file (`.mht`, `.mhtml`),
and writes them to any output format. `ReadHTML` recognizes them by the file name or by how the
content starts; when you know what the input is, say so with `ReadEML` / `ReadMHT`. That is the way
to go for mail from outside your control:

```csharp
using Pd4mlNet;

File.WriteAllText("message.eml", "From: Alice <alice@example.com>\r\nTo: Bob <bob@example.com>\r\n"
    + "Subject: Quarterly figures\r\nDate: Thu, 05 Nov 2026 10:00:00 +0100\r\nMIME-Version: 1.0\r\n"
    + "Content-Type: text/html; charset=utf-8\r\n\r\n<p>The figures are attached.</p>\r\n");

var pd = new PD4ML();
pd.ReadEML("message.eml");          // also: a Uri, a FileInfo, byte[], a Stream; ReadMHT(...) for saved pages
Console.WriteLine(pd.GetDocumentTitle());   // the Subject
pd.WritePDF("message.pdf");
```

- A message becomes a block with From, Date, To, Cc, Subject and the names of the attachments,
  followed by the body (its HTML version if it has one) with the images it embeds. The attached
  files are **embedded in the PDF**, and the PDF hands them on to whoever opens it.
- A saved page comes with the images, style sheets and fonts saved in it; its links keep leading
  where they led on the original page.
- Of resources that are not in the file, only `https://` locations are loaded.
  `AddAllowedResourceLocation` ([5.21](#5.21-restricting-resource-loading-for-untrusted-html))
  replaces that with the locations it allows.

See [converting e-mail and saved web pages](https://pd4ml.com/docs/features/converting-e-mail-and-saved-web-pages.html).

### 5.21 Restricting resource loading for untrusted HTML

HTML from users, customers or mail can refer to anything the converting machine can reach: local
files, intranet servers, cloud metadata endpoints. `AddAllowedResourceLocation` (PD4ML 4.2) limits
loading of style sheets, images and attachments to the locations you list:

```csharp
using Pd4mlNet;

var pd = new PD4ML();
pd.AddAllowedResourceLocation("https://cdn.example.com/assets/, https://*.example.org/");
pd.ReadHtmlString("<img src='file:///etc/passwd'><p>Untrusted content</p>");   // the image is not loaded
pd.WritePDF("untrusted.pdf");
```

- The argument is a comma-separated list of URL prefixes; `*` matches any run of characters except
  `/`, so `https://*.example.org/` covers every subdomain. The comparison ignores case.
- A local directory can be given as a `file:` URL, for example `Runtime.ToUrl(@"C:\app\templates").ToString()`.
- A resource outside of every location, or one whose path contains `..`, is not loaded. Each call
  adds locations; as long as none is added, resources load from anywhere.
- Inline `data:` URIs and classpath (`java:`) resources are not affected, nor are font directories
  set with `UseTTF`. Call it before `ReadHTML`.

See [restricting resource loading](https://pd4ml.com/docs/features/restricting-resource-loading-for-untrusted-html.html).

---

## 6. Tips and hints

- **Start the bridge early.** The first use starts Java, which takes about a second. Touch
  PD4ML at application start, or use a bridge server for short-lived processes.
- **One converter per document.** `new PD4ML()` is cheap; reuse the bridge, not the object.
- **Set the base location for HTML from strings.** Missing images and unstyled output are almost
  always caused by unresolved relative links ([details](https://pd4ml.com/docs/#6.2-reading-the-source)).
- **Settings go before `ReadHTML`.** Page size, headers, fonts and styles apply to the layout
  `ReadHTML` makes; writes afterwards only choose the output format.
- **Write several formats from one read.** PDF, DOCX and page images from a single `ReadHTML`
  cost one layout.
- **Print CSS works.** `@page { size: A4 landscape; margin: 15mm }`, `@media print` and
  `page-break-*` / `break-*` are honoured. They are often simpler than API calls.
- **Control the scale with the HTML width.** If text is too small, or a layout wraps
  differently than in the browser, set `SetHtmlWidth` to the width the page was designed for.
- **Index fonts once** with `-configure.fonts`, instead of pointing `UseTTF` at a large
  unindexed directory on every conversion.
- **Restrict what documents can load** when converting untrusted HTML: `AddAllowedResourceLocation`
  ([5.21](#5.21-restricting-resource-loading-for-untrusted-html)), and `SetParam` with
  `Constants.PD4ML_DISABLE_EXTERNAL_ATTACHMENTS`. Read mail with `ReadEML`, not `ReadHTML`.
- **Pin the build in production.** Use `pd4ml install <version>` or a `PD4ML_CLASSPATH` to a jar
  you keep, rather than following the latest release (until PD4ML 4.2.0 is released, the default
  is a development snapshot, which changes when it is rebuilt).
- **Containers**: install the build when you build the image
  (`pd4ml install --home /opt/pd4ml`), and set `PD4ML_HOME=/opt/pd4ml`.

## 7. Troubleshooting

| Symptom | Cause and remedy |
|---|---|
| `BridgeException: PD4ML not found` | No build is configured. Run `pd4ml install` as the account the application runs as, or set `PD4ML_CLASSPATH`. |
| `BridgeException: cannot start java` / "closed the connection" | Java is missing or failed. Check `java -version`, and set `JAVA_HOME` or `PD4ML_JAVA`. `PD4ML_BRIDGE_LOG` shows Java's output. |
| `BridgeException: cannot connect` | The bridge server is not running, or `PD4ML_BRIDGE` names another port. |
| `BridgeException: ... Configure() before the first use` | `Bridge.Configure` was called after PD4ML had been used. Call it first, or call `Bridge.Shutdown()` first. |
| Images or styles missing | Relative links have no base location, or the server cannot reach the resources (proxies, authentication). See `SetCookie` and `SetHttpOptions`. |
| Output is watermarked | No license is installed ([licensing](https://pd4ml.com/docs/#3.-licensing)). |
| Squares instead of characters | The fonts lack those glyphs. Provide fonts with `UseTTF` ([fonts](https://pd4ml.com/docs/#9.-fonts-bookmarks-and-hyperlinks)). |
| `NoClassDefFoundError: org/bouncycastle/...` with `SetPermissions` | The Bouncy Castle jars are missing. Install without `--no-bouncycastle`, or add them to the classpath. |
| Warning "has no v42 layout engine" | The PD4ML build predates 4.2, so the conversion uses its own engine. Use a 4.2 build for v42. |
| `JavaException: ... NoSuchMethodException` | Only with `JavaObject.Call` / `dynamic`: no Java method of that name takes those argument types. Pass `float`s as `10f`. |

## 8. API reference

### .NET classes

| Class | |
|---|---|
| `PD4ML` | the converter: `new PD4ML()`, `new PD4ML(license, htmlWidth, engine)`, `new PD4ML(LayoutEngine)`; every Java method of `com.pd4ml.PD4ML` |
| `PageSize`, `PageMargins`, `Units`, `Dimensions`, `Constants`, `LayoutEngine`, `ErrorPolicy`, `PdfSpec`, `PdfDocument`, `ProgressListener`, `StatusMessage`, `ResourceProvider`, `CustomTag` | typed wrappers of the PD4ML Java classes (`docs/api.md` in the source archive from [pd4ml.com/net](https://pd4ml.com/net/)) |
| `Node`, `Document`, `Element`, `Text`, `Comment`, `DocumentType`, `Layout`, `LayoutBox` | the DOM API, namespace `Pd4mlNet.Dom` ([5.19](#5.19-the-dom-api-modifying-the-document-before-conversion), `docs/api.md` in the source archive from [pd4ml.com/net](https://pd4ml.com/net/)); DMS and UA editions |
| `Pdf` | `HtmlToPdf(source, pageSize, margins, spec)`, `Open(source, password)`, `Write(doc, password, permissions)`, `Save(doc, path, ...)`, `Merge(a, b)` |
| `Runtime` | `Version`, `Install()`, `Configure()`, `HasLayoutEngines`, `ToUrl()`, `PdfSpecFor()` |
| `Installer` | `Install(version, options)`, `InstalledJar()`, `InstalledClasspath()`, `InstalledVersion()`, `AvailableVersions()`, `SelectVersion()` |
| `Bridge`, `BridgeOptions` | `Configure()`, `Default`, `Shutdown()`, `Command()`; low-level `New`, `Call`, `CallStatic`, `GetField`, `Proxy` |
| `Java`, `JavaClass`, `JavaObject` | any Java class or object, with `dynamic` support; `Java.Proxy(interface, callback)` |
| `JavaException`, `BridgeException`, `CallbackException`, `InstallException` | errors |

### `PD4ML` methods with .NET additions

| Method | |
|---|---|
| `ReadHTML(string pathOrUrl, encoding)`, `ReadHTML(Uri)`, `ReadHTML(FileInfo)` | a document from a path or URL |
| `ReadHTML(byte[] html, baseUrl, encoding)`, `ReadHTML(Stream html, baseUrl, encoding)` | a document from memory |
| `ReadHtmlString(string html, baseUrl)` | the document as a string |
| `ReadEML(...)`, `ReadMHT(...)` | an e-mail message, a saved web page (DMS and UA editions): from a path or URL, `Uri`, `FileInfo`, `byte[]` or `Stream` |
| `WritePDF()` → `byte[]`, `WritePDF(PdfSpec)`, `WritePDF(string path, PdfSpec?)`, `WritePDF(Stream, PdfSpec?)` | PDF output |
| `WriteDOCX(...)`, `WriteRTF(..., bool rtfWmf)` | the same forms as `WritePDF` |
| `RenderAsImages(format)` → `byte[][]`, `RenderPng()`, `RenderAsImages(dir, name, format)` | page images |
| `MonitorProgressWith(Action<int, int, string?, long>)` | progress callback |
| `UseHtmlRenderer(string or LayoutEngine, htmlWidth, parameters)`, `Engine` | the layout engine |

All other methods are those of the Java class `com.pd4ml.PD4ML` (`docs/api.md` in the source archive from [pd4ml.com/net](https://pd4ml.com/net/),
[Java API](https://pd4ml.com/docs/#6.-the-java-api)).

`ResourceProvider` and `CustomTag` are extended in **Java**: register yours with
`AddCustomResourceProvider` / `AddCustomTagHandler`, and put its classes on the classpath. Java
*interfaces* (such as `ProgressListener`) can be implemented in .NET with `Java.Proxy()`.
Examples: [resource loaders](https://github.com/zxfr/pd4ml-examples/blob/master/src/main/java/advanced/A005AddCustomResourceLoader.java),
[custom tags](https://github.com/zxfr/pd4ml-examples/blob/master/src/main/java/advanced/A008AddingCustomTagRenderer.java).

## 9. Further reading

- [PD4ML documentation](https://pd4ml.com/docs/) and the full [programmer's manual](https://pd4ml.com/docs/pd4ml-manual.html)
- [Usage examples](https://pd4ml.com/docs/usage-examples.html)
- [HTML5 element support matrix](https://pd4ml.com/docs/html5-element-support-matrix.html)
- [Command-line tool reference](https://pd4ml.com/docs/pd4cmd-command-line-reference.html)
- [ZUGFeRD and Factur-X invoices](https://pd4ml.com/docs/features/zugferd-and-factur-x-pdf-invoices.html)
- [PDF post-processing toolkit](https://pd4ml.com/docs/#15.-the-pdf-post-processing-toolkit)
- [Java examples on GitHub](https://github.com/zxfr/pd4ml-examples)

<!-- Copied from pd4ml-dotnet/docs/guide.md by docs-generator/sync-integrations.js: edit it there. -->
