# 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.0.0.nupkg` | the library, a NuGet package |
| `Pd4ml.Net.Tool.1.0.0.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 the most recent PD4ML 4.x release from the PD4ML Maven repository
  (<https://pd4ml.com/maven2>);
- 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.

| Option | |
|---|---|
| *(none)* | the most recent 4.x release |
| `--snapshots` | the most recent 4.x build, including development snapshots |
| `pd4ml install 4.0.23fx2` | an exact version |
| `--jakarta` | the `-jakarta` variant, for Jakarta EE / Servlet 6 environments |
| `--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();                          // 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>` |
  | 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 can be made *editable*: positioned content becomes text
boxes and form fields instead of pictures. To enable it, call
`pd.SetParam(Constants.PD4ML_WORD_EDITABLE, "true")`.

### 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.ReadHtmlString("<h1>Statement</h1>");
pd.WritePDF("statement.pdf");
int pages = Convert.ToInt32(pd.GetLastRenderInfo(Constants.PD4ML_TOTAL_PAGES));
```

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).

---

## 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. Use `SetParam` with
  `Constants.PD4ML_ALLOWED_RESOURCE_LOCATION` and `PD4ML_DISABLE_EXTERNAL_ATTACHMENTS`.
- **Pin the build in production.** Use `pd4ml install <version>` or a `PD4ML_CLASSPATH` to a jar
  you keep, rather than following the latest release.
- **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/)) |
| `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 |
| `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. -->
