# PD4ML JSP Tag Library

The PD4ML JSP tag library converts the output of a JSP page to **PDF**, **PDF/A**, **PDF/UA**,
**DOCX**, **RTF** or an **image** without a line of Java: wrap the page's HTML in
`<pd4tl:transform>`, and the browser receives the document instead of the HTML. Further tags
add page headers and footers, passwords and permissions, TrueType fonts, document metadata,
dynamic placeholders, and saving the result on the server.

The tags are part of the PD4ML jar, for both servlet containers in use today: the classic
`javax.servlet` ones (Tomcat 9 and earlier) and Jakarta EE (Tomcat 10 and later). They use the
PD4ML Java API described in the [PD4ML Programmer's Manual](https://pd4ml.com/docs/pd4ml-manual.html);
this guide covers tag library version **1.2**.

**Contents**

1. [Setup](#1.-setup)
2. [Quick start](#2.-quick-start)
3. [How it works](#3.-how-it-works)
4. [Tag reference](#4.-tag-reference)
5. [Use cases](#5.-use-cases)
6. [Tips and gotchas](#6.-tips-and-gotchas)
7. [Troubleshooting](#7.-troubleshooting)
8. [Older tag library versions](#8.-older-tag-library-versions)
9. [See also](#9.-see-also)

---

## 1. Setup

### 1.1 Libraries

Put the PD4ML jar into the web application's `WEB-INF/lib` (or the container's shared library
directory), together with the Bouncy Castle jar PD4ML ships with, `bouncycastle-runtime.jar` (see
[Obtaining PD4ML](https://pd4ml.com/docs/pd4ml-manual.html#2.-obtaining-pd4ml)). With Maven, the
`com.pd4ml:pd4ml` dependency brings both.

Nothing has to be added to `web.xml`: the tag library descriptors are in the jar's `META-INF`
directory, where JSP containers find them by their URI. The only exception is the optional
forwarder servlet ([3.5](#3.5-forward-mode-pd4forwarderservlet)).

### 1.2 The taglib directive

| Container | Directive |
|---|---|
| Jakarta EE 9 and later: Tomcat 10+, Jetty 11+, WildFly 27+... | `<%@ taglib uri="http://pd4ml.com/tlds/4.2-jk" prefix="pd4tl" %>` |
| Java EE / `javax.servlet`: Tomcat 9 and earlier... | `<%@ taglib uri="http://pd4ml.com/tlds/4.2" prefix="pd4tl" %>` |

The URI must be written exactly as above (`http://`, not `https://`): containers match it as a
string. Using the URI of the other servlet flavour fails with a class-not-found error when the page
is compiled.

**Use the prefix `pd4tl`, not `pd4ml`.** PD4ML's own extension tags in the converted HTML
(`<pd4ml:page.break>`, `<pd4ml:page.header>`, `<pd4ml:toc>`...) use the `pd4ml:` prefix. If the tag
library is imported with the same prefix, the JSP compiler takes those tags for tag library tags
and rejects the page ("No tag [page.break] defined in tag library imported with prefix [pd4ml]").
With `pd4tl`, both work side by side.

### 1.3 License

The tags create their PD4ML converter with the default constructor, which looks for the license
file `pd4ml.lic` in the working directory of the container's JVM and then on the class path. In a
web application, put it into **`WEB-INF/classes/pd4ml.lic`**. Without a license, PD4ML runs in
evaluation mode and marks every page (see [Licensing](https://pd4ml.com/docs/pd4ml-manual.html#3.-licensing));
PDF/A, PDF/UA, DOCX/RTF and image output need a license that covers them.

## 2. Quick start

```jsp
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib uri="http://pd4ml.com/tlds/4.2-jk" prefix="pd4tl" %>
<pd4tl:transform screenWidth="800" pageFormat="A4" pageInsets="15,15,15,15,mm">
<html>
<head>
  <title>Invoice <%= request.getParameter("id") %></title>
  <style>body { font-family: sans-serif } h1 { color: #036 }</style>
</head>
<body>
  <h1>Invoice <%= request.getParameter("id") %></h1>
  <p>Generated on <%= new java.util.Date() %>.</p>
</body>
</html>
</pd4tl:transform>
```

Requesting the page returns an A4 PDF (`application/pdf`, shown in the browser). Everything inside
`<pd4tl:transform>` is ordinary JSP: scriptlets, EL, JSTL, includes. Only the HTML it produces is
converted.

`screenWidth` is required: it is the width, in pixels, of the virtual browser window the HTML is
laid out in, and it sets the scale of the page (see [7. Page layout](https://pd4ml.com/docs/pd4ml-manual.html#7.-page-layout-size-margins-and-scaling)
in the manual). Set it to the width the HTML was designed for.

## 3. How it works

### 3.1 Request processing

1. When `<pd4tl:transform>` starts, it **resets the response**: whatever the page wrote or set
   before it (content, headers, cookies) is discarded, and the document's headers are set:
   `Content-Type` for the chosen output format, `Content-disposition` (`inline` or `attachment`,
   with the file name), and cache headers.
2. The tag's body is evaluated as usual, but its output is collected instead of being sent. Tags
   such as `<pd4tl:header>` or `<pd4tl:permissions>` inside it record their settings.
3. When the tag ends, PD4ML converts the collected HTML with these settings and writes the
   document to the response (or saves it, or redirects - see `savefile` and `forward`).
4. **The rest of the page after `</pd4tl:transform>` is not processed.**

Because the response is reset, white space or other output before the tag does no harm, as long
as the response has not been committed yet (by flushing, or by writing more than the page buffer,
8 KB by default). For the same reason, the page cannot add its own response headers.

### 3.2 Images, style sheets and other resources

Relative references in the HTML (`<img src="images/logo.png">`, `<link href="../css/print.css">`)
are resolved against the **JSP file's location on disk**, so files next to the JSP are read
directly. If the web application is not unpacked (no file system path is available), they are
resolved against the request URL and loaded over HTTP from the server itself.

Server-absolute paths (`/myapp/images/logo.png`) are not resolved automatically. Use absolute
URLs, or let [`<pd4tl:fix_base_path>`](#4.9-pd4tlfixbasepath) prefix them with the server address.

When PD4ML loads resources over HTTP, it appends the current session id (`;jsessionid=...`) to the
URLs, so that pages and images behind a login are loaded within the user's session. To keep the
session id away from other hosts, switch this off:
`<pd4tl:property name="pd4ml.sessionid.append" value="false"/>`.

### 3.3 Layout engine

The tag library uses PD4ML's established **v40** layout engine. The v42 engine is currently
available through the Java API only.

### 3.4 Output formats

`outputFormat` chooses what the tag produces:

| Value | Output | Content-Type |
|---|---|---|
| *(not set)*, `pdf` | PDF 1.7 | `application/pdf` |
| `pdfa` | PDF/A-1b | `application/pdf` |
| `pdfua` | PDF/UA-1 combined with PDF/A-2a (tagged PDF) | `application/pdf` |
| `docx` | Microsoft Word document | `application/vnd.openxmlformats-officedocument.wordprocessingml.document` |
| `rtf` | RTF, images in their original format | `application/rtf` |
| `rtfwmf` | RTF, images converted to WMF (for WordPad) | `application/rtf` |
| `png24` | PNG image of the first page, with transparency | `image/png` |
| `png8` | PNG image of the first page, without transparency | `image/png` |
| `tiff`, `tiffcomp` | TIFF image of the first page, uncompressed or compressed (PackBits) | `image/tiff` |
| `mpagetiff`, `mpagetiffcomp` | TIFF with all pages, uncompressed or compressed | `image/tiff` |

The value is not case-sensitive. The image formats show the first page of the document; combine
them with `outputRange` to render another page. TIFF output needs Java 9 or later.

**Set `fileName` to match the format** (`fileName="report.docx"`): it defaults to `file.pdf`
whatever the format is.

### 3.5 Forward mode (PD4ForwarderServlet)

In some environments - frameworks that write the page through `response.getWriter()`, or pages
that include other content - writing the document from the JSP fails with an
`IllegalStateException`. For those cases the tag can hand the delivery over to a servlet:
with `forward="..."`, the tag stores the conversion request in the HTTP session and redirects the
browser to `PD4ForwarderServlet`, which produces and sends the document.

Register the servlet in `web.xml`:

```xml
<servlet>
  <servlet-name>pd4forward</servlet-name>
  <!-- javax containers: com.pd4ml.taglib.javax.PD4ForwarderServlet -->
  <servlet-class>com.pd4ml.taglib.jakarta.PD4ForwarderServlet</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>pd4forward</servlet-name>
  <url-pattern>/pd4forward</url-pattern>
</servlet-mapping>
```

and point the tag to it:

```jsp
<pd4tl:transform screenWidth="800" fileName="report.pdf"
                 forward='<%= request.getContextPath() + "/pd4forward" %>'>
...
</pd4tl:transform>
```

The redirect needs the browser's session (cookies, or URL rewriting). The stored request stays in
the session until the session expires, so for large documents prefer the direct mode where
possible. A forward path that starts with `/` is relative to the **server**, not to the
application: include `request.getContextPath()` as above.

## 4. Tag reference

All attributes accept request-time values (`<%= ... %>` expressions and `${...}` EL). Values that
are switches take `"true"` (not case-sensitive) for on; any other value means off.

The tags other than `transform`, `fix_base_path`, `clean_xhtml` and `resolve_entities` must be
**nested inside `<pd4tl:transform>`**; they can appear anywhere in its body, before or after the
HTML, and produce no output of their own. Outside a `transform` they have no effect.

### 4.1 pd4tl:transform

Converts its body (or the page given by `url`) and sends the result.

**Page layout**

| Attribute | Value | Default |
|---|---|---|
| `screenWidth` | **Required.** Width of the virtual browser window, in pixels | |
| `pageFormat` | A page size name: `A0`...`A10`, `B4`, `B5`, `ISOB0`...`ISOB5`, `JIS_B4`, `JIS_B5`, `LETTER`, `HALFLETTER`, `LEGAL`, `LEDGER`, `TABLOID`, `NOTE`, `ArchA`...`ArchE`, `ArchE1`. **Case-sensitive** (`a4` is an error). Or a custom size `"<width>x<height>"` in pixels, at 1.35 px per point (`"810x1215"` is 600×900 pt) | `A4` |
| `pageOrientation` | `landscape` or `portrait` | `portrait` |
| `pageInsets` | Page margins `"top,left,bottom,right,units"`: whole numbers, units `mm` or `points` | `10,20,30,40,mm` |
| `adjustScreenWidth` | `true`: widen the layout to the content's actual width, if it is wider than `screenWidth` (so that wide content is not cut off) | `false` |
| `fitPageVertically` | `true`: scale the whole document down to a single page | `false` |
| `multicolumn` | Multi-column pages: `"<columns>,<gap>"` with the gap in `mm` or `pt`, e.g. `"2,10mm"` or `"3,25pt"` (no spaces). Not combined with `adjustScreenWidth` or `fitPageVertically` | |
| `protectPhysicalUnitDimensions` | `true`: sizes in physical units (`mm`, `cm`, `in`, `pt`) keep their size on paper instead of being scaled with the layout | `false` |
| `outputRange` | The pages to output: `"1"`, `"2+"` (all from the second), `"1-3"`, `"odd"`, `"even"`, `"skiplast"`, or combinations like `"3-7,odd"` | all |
| `initialPageNumber` | Number of the first page, for `$[page]` and `$[total]` | `1` |

The default margins are deliberately not symmetric (top 10, left 20, bottom 30, right 40 mm):
set `pageInsets` explicitly in most documents.

**Output**

| Attribute | Value | Default |
|---|---|---|
| `outputFormat` | See [3.4](#3.4-output-formats) | `pdf` |
| `pdfa` | `true`: PDF/A-1b output. Takes precedence over `outputFormat` | `false` |
| `fileName` | File name offered to the browser. Spaces are replaced with `_` | `file.pdf` |
| `inline` | `true`: show the document in the browser; `false`: offer it as a download | `true` |
| `http` | `noheader`: do not set the `Content-Type` and cache headers | |
| `forward` | URL of `PD4ForwarderServlet`, see [3.5](#3.5-forward-mode-pd4forwarderservlet) | |
| `enable` | `false`: do not convert; the body is sent as HTML (useful to check the generated HTML). The rest of the page after the tag is skipped in this case too | `true` |

**Document content**

| Attribute | Value | Default |
|---|---|---|
| `documentTitle` | Document title in the PDF properties; replaces the HTML `<title>` | the HTML `<title>` |
| `authorName` | Author in the PDF properties | |
| `outline` | PDF bookmarks: `headings` (from `<h1>`...`<h6>`), `anchors` (from named anchors) or `none` | `none` |
| `generateHyperlinks` | `false`: do not turn HTML links into PDF links | `true` |
| `generatePdfForms` | `true`: turn HTML form controls into PDF form fields | `false` |
| `formFont` | Font family of the PDF form field values; a TrueType font made available with `<pd4tl:usettf>` or `@font-face` | |
| `kerning` | `true`: apply the fonts' kerning | `false` |
| `ligatures` | `true`: use the ligature glyphs ("fi", "fl"...) of TrueType/OpenType fonts | `false` |

**Source and loading**

| Attribute | Value | Default |
|---|---|---|
| `url` | Convert this page instead of the tag's body (the body is then ignored). An absolute `http://` or `https://` URL, or a path starting with `/`, which is completed with the current server address - **without the application's context path**: use `'<%= request.getContextPath() + "/report.jsp" %>'` | |
| `proxy` | HTTP proxy `"host:port"` for loading `url` and the document's resources | |
| `basicAuthentication` | `"login:password"` for HTTP Basic authentication when loading `url` and the document's resources | |
| `encoding` | Leave unset (the body is converted as text, its encoding is irrelevant) | |
| `debug` | `true` or a PD4ML log level: log the conversion, including resource loading, to the server's console output | |
| `useAFM` | Obsolete, has no effect | |

### 4.2 pd4tl:header and pd4tl:footer

Page header and page footer. Each can be written in one of two ways.

**With an HTML body** (recommended): the tag's body is the header's HTML, with the placeholders
`$[page]`, `$[total]` and `$[title]`. Style it with CSS; JSP expressions in it are evaluated first.

```jsp
<pd4tl:header>
  <div style="font-size: 9pt; border-bottom: 1px solid #999">
    <b>$[title]</b> <span style="float: right">Page $[page] of $[total]</span>
  </div>
</pd4tl:header>
```

**With attributes** (the PD4ML v3 way): a title and a page number, each aligned left, center or
right.

```jsp
<pd4tl:footer areaHeight="24" fontSize="9" color="#666666"
              titleTemplate="Confidential" titleAlignment="left"
              pageNumberTemplate="Page $[page] of $[total]" pageNumberAlignment="right"/>
```

| Attribute | Value | Default |
|---|---|---|
| `titleTemplate` | Text with the placeholders `$[title]`, `$[page]`, `$[total]` (HTML markup allowed) | |
| `titleAlignment` | `left`, `center` or `right` | `center` |
| `pageNumberTemplate` | Text with the placeholders `$[page]`, `$[total]` | |
| `pageNumberAlignment` | `left`, `center` or `right` | `right` |
| `fontSize` | Text size (CSS pixels of the layout) | `10` |
| `fontFace` | Font family | `Courier New` |
| `color` | Text color, `#rrggbb` | black |
| `areaHeight` | Height of the header or footer area (CSS pixels of the layout). `0`: no header/footer text, only the watermark and background below | `12` |
| `pagesToSkip` | Number of pages at the beginning without the header or footer | `0` |
| `initialPageNumber` | Number of the first page (applies to the whole document; the `transform` attribute of the same name takes precedence) | `1` |
| `watermarkUrl` | Image to show on every page, as an absolute URL | |
| `watermarkBounds` | Position and size of the watermark image, `"x,y,width,height"`; required with `watermarkUrl` | |
| `watermarkOpacity` | Watermark opacity, `0`...`100` | `100` |
| `pageBackgroundColor` | Page background color, `#rrggbb` | |
| `pageBackgroundImageUrl` | Page background image (a letterhead or form), stretched to the page | |

With an HTML body, the text attributes (`titleTemplate`, `pageNumberTemplate`, the alignments,
`fontFace`, `fontSize`, `fontStyle`, `color`) are not used, and the area takes the height of its
content. `fontStyle` (`plain`, `bold`, `italic`, `bolditalic`) is accepted for compatibility but
does not change the text: use the HTML body for bold or italic text. The watermark and the page
background appear on every page, `pagesToSkip` does not apply to them.

Headers and footers can also be defined in the HTML itself, with `<pd4ml:page.header>` and
`<pd4ml:page.footer>` - see [Headers, footers, watermarks and backgrounds](https://pd4ml.com/docs/pd4ml-manual.html#8.-headers-footers-watermarks-and-backgrounds).

### 4.3 pd4tl:permissions

Encrypts the PDF and sets what its users may do.

| Attribute | Value |
|---|---|
| `password` | **Required.** Password to open (and change) the document. `empty` (or an empty value): no password to open it, but the rights still apply |
| `rights` | **Required.** The permitted operations: names separated with `,`, `+` or spaces - `print`, `degradedprint`, `modify`, `copy`, `annotate`, `fillingforms`, `contentextraction`, `assembly` (the `Allow` prefix of the [Constants](https://pd4ml.com/docs/pd4ml-manual.html#11.1-permissions-and-encryption) is optional: `"AllowPrint+AllowCopy"` works too) - or their numeric sum (`2068` = print and copy, `-1` = everything) |
| `encryption` | `rc4-40`, `rc4-128`, `aes-128` or `aes-256`. Default: `aes-128` |

```jsp
<pd4tl:permissions password="s3cret" rights="print,copy" encryption="aes-256"/>
```

PDF/A documents cannot be encrypted: with `pdfa` or `outputFormat="pdfa"` the permissions are
ignored.

### 4.4 pd4tl:usettf

Makes a directory of TrueType/OpenType fonts available to the document and embeds the fonts it uses.

| Attribute | Value | Default |
|---|---|---|
| `from` | **Required.** The font directory: a path on the server, a `file:` or `http(s):` URL, or `java:` and a class path location (`java:fonts` for `WEB-INF/classes/fonts`) | |
| `embed` | `false`: reference the fonts by name without embedding them (they must then be installed on the reader's computer) | `true` |
| `allGlyphs` | `true`: embed the complete fonts instead of the glyphs used | `false` |

```jsp
<pd4tl:usettf from='<%= application.getRealPath("/WEB-INF/fonts") %>'/>
```

The directory needs a font index, `pd4fonts.properties`. If it has none, PD4ML creates it there on
first use, which requires write access to the directory and takes a moment for large directories:
for production, ship the index with the fonts (`java -jar pd4ml.jar -configure.fonts <dir>`, see
[Font indexing](https://pd4ml.com/docs/pd4ml-manual.html#4.3-font-indexing)). A relative path is
resolved against the JVM's working directory, so prefer `application.getRealPath(...)` or `java:`.

### 4.5 pd4tl:savefile

Saves the document on the server, and optionally redirects the browser instead of sending it.

| Attribute | Value | Default |
|---|---|---|
| `dir` | **Required.** Directory to save the document in. **It must exist** | |
| `name` | File name. If a file of that name exists, a name with a number added is chosen (`report1234567.pdf`) | `pd4<number>.pdf` |
| `redirect` | `disabled`: send the document to the browser as well. `pdf`: redirect the browser to the saved file (requires `uri`). A URL: redirect to it, with the saved file's name as parameter `filename` | `disabled` |
| `uri` | The URL under which `dir` is reachable; required with `redirect="pdf"` | |
| `redirectMethod` | `redirect`: an HTTP redirect (the browser loads the new URL). `forward`: a server-side forward within the application | `redirect` |
| `debug` | `true`: log the file operations to the console (and create a missing directory) | |

```jsp
<pd4tl:transform screenWidth="800">
  <pd4tl:savefile dir='<%= application.getRealPath("/archive") %>'
                  redirect='<%= request.getContextPath() + "/saved.jsp" %>'/>
  ...
</pd4tl:transform>
```

`saved.jsp` then receives the file name: `<%= request.getParameter("filename") %>`.

Notes:

- With `redirectMethod="redirect"` (the default), a URL starting with `/` is relative to the
  **server**: include `request.getContextPath()`. With `forward`, it is relative to the application.
- The automatic name ends in `.pdf` whatever the output format: set `name` for other formats.
- `name` is used as a path below `dir`: never pass user input to it unchecked.

### 4.6 pd4tl:parameters and pd4tl:parameter

Values for placeholders `$[name]` in the HTML. Useful for content that is written in a static
template but known only at request time.

```jsp
<pd4tl:parameters>
  <pd4tl:parameter key="customer" value="ACME Corp"/>
  <pd4tl:parameter key="order" value='<%= request.getParameter("order") %>'/>
</pd4tl:parameters>
...
<p>Order $[order] for $[customer]</p>
```

| Tag | Attributes |
|---|---|
| `parameters` | none; contains the `parameter` tags. Use one `parameters` block per page |
| `parameter` | `key` (**required**): the placeholder name. `value` (**required**): its value |

The values are inserted as text (HTML markup in them is shown, not interpreted). A parameter whose
value is `null` is left out, and its placeholder then appears literally in the document. The
names `page`, `total` and `title` are reserved for headers and footers. See also
[setDynamicData()](https://pd4ml.com/docs/usage-examples.html) in the manual.

### 4.7 pd4tl:metadata

A custom metadata property of the PDF (XMP), for example a document number for an archive.

| Attribute | Value |
|---|---|
| `key` | **Required.** Property name: a valid XML element name (not one of the standard properties `Title`, `Author`, `Subject`, `Keywords`, `Creator`, `Producer`, `CreationDate`, `ModDate`, `Trapped`) |
| `value` | **Required.** Property value |
| `hidden` | `true`: store the property in PD4ML's own namespace, which PDF viewers do not show. Default: shown among the document's custom properties |

```jsp
<pd4tl:metadata key="DocumentId" value='<%= invoice.getNumber() %>'/>
```

### 4.8 pd4tl:property

Sets one of PD4ML's advanced conversion parameters (see `setParam()` and the `PD4ML_...`
[constants](https://pd4ml.com/docs/pd4ml-manual.html)).

| Attribute | Value |
|---|---|
| `name` | **Required.** Parameter name |
| `value` | **Required.** Parameter value |

Useful ones:

| `name` | `value` | Effect |
|---|---|---|
| `pd4ml.sessionid.append` | `false` | Do not append the session id to resource URLs ([3.2](#3.2-images-style-sheets-and-other-resources)) |
| `pd4ml.document.view.mode` | e.g. `TwoColumnLeft`, `SinglePage` | Initial page layout in PDF viewers |
| `pd4ml.allowed.resource.location` | a URL prefix | Load resources from this location only (for HTML from untrusted sources) |
| `pd4ml.disable.external.attachments` | `true` | Ignore attachments that refer to external files |
| `pd4ml.html.in.dynamic.data` | `true` | Interpret HTML in `<pd4tl:parameter>` values |
| `pd4ml.media.type.print` | `true` | Apply `@media print` style sheet rules |
| `pd4ml.socket.timeout` | milliseconds | Timeout for loading resources |
| `pd4ml.document.id` | a string | The PDF's document identifier |

Unknown names are ignored.

### 4.9 pd4tl:fix_base_path

Rewrites server-absolute paths in its body into full URLs that PD4ML can load. Can be used inside
or outside `transform`.

| Attribute | Value | Default |
|---|---|---|
| `pattern` | **Required.** The path prefix to complete, e.g. `/myapp/images/` | |
| `basePath` | What to put in front of it, e.g. `https://cdn.example.com` | the current server: `http(s)://host:port` |
| `addSessionID` | `true`: add `;jsessionid=...` to the paths (write the attribute values in double quotes) | `false` |
| `debug` | `true`: log the body before and after to the console | |

```jsp
<pd4tl:fix_base_path pattern="/myapp/images/">
  <img src="/myapp/images/chart.png">
</pd4tl:fix_base_path>
```

Every occurrence of `pattern` in the body is completed - in attributes and in text alike - so
choose a pattern that occurs only in the paths.

### 4.10 pd4tl:clean_xhtml and pd4tl:resolve_entities

Two filters for their body, kept for compatibility with older pages:

- `clean_xhtml` turns XHTML-style empty elements (`<br/>`) into HTML (`<br>`) by replacing every
  `/>` in the body - including any in text or attribute values. PD4ML reads XHTML directly, so
  this is rarely needed.
- `resolve_entities` replaces numeric character references (`&#1055;`, `&#x440;`) with the
  characters, which helped right-to-left scripts in older versions. It also turns escaped markup
  (`&#60;`) into real markup, so do not apply it to user-supplied text, and it does not handle
  characters beyond the Basic Multilingual Plane (emoji).

## 5. Use cases

The examples use the Jakarta URI; on `javax` containers use `http://pd4ml.com/tlds/4.2`.

### 5.1 A report as a download, landscape Letter

```jsp
<%@ taglib uri="http://pd4ml.com/tlds/4.2-jk" prefix="pd4tl" %>
<pd4tl:transform screenWidth="1000" pageFormat="LETTER" pageOrientation="landscape"
                 pageInsets="36,36,36,36,points" outline="headings"
                 fileName="Quarterly Report.pdf" inline="false">
<html>
<head><title>Quarterly Report</title></head>
<body>
  <h1>Quarterly report</h1>
  <jsp:include page="/WEB-INF/report/table.jsp"/>
</body>
</html>
</pd4tl:transform>
```

The browser saves `Quarterly_Report.pdf`, with bookmarks from the headings.

### 5.2 Headers, footers and a watermark

```jsp
<% String base = request.getScheme() + "://" + request.getServerName() + ":"
               + request.getServerPort() + request.getContextPath(); %>
<pd4tl:transform screenWidth="800" pageInsets="20,15,20,15,mm">
  <pd4tl:header pagesToSkip="1">
    <div style="font-size: 8pt; color: #666">$[title]</div>
  </pd4tl:header>
  <pd4tl:footer>
    <div style="font-size: 8pt; text-align: right">Page $[page] of $[total]</div>
  </pd4tl:footer>
  <pd4tl:header areaHeight="0" watermarkUrl='<%= base + "/images/draft.png" %>'
                watermarkBounds="150,300,300,300" watermarkOpacity="20"/>
<html><head><title>Contract draft</title></head><body>...</body></html>
</pd4tl:transform>
```

### 5.3 Archive copy as PDF/A with embedded fonts

```jsp
<pd4tl:transform screenWidth="800" outputFormat="pdfa" fileName="invoice-1042.pdf">
  <pd4tl:usettf from="java:fonts"/>
  <pd4tl:metadata key="InvoiceNumber" value="1042"/>
<html><head><title>Invoice 1042</title></head>
<body style="font-family: 'DejaVu Sans'">...</body></html>
</pd4tl:transform>
```

PDF/A requires all fonts to be embedded: provide them with `<pd4tl:usettf>` (here from
`WEB-INF/classes/fonts`) and use them in the CSS. For accessible documents, use
`outputFormat="pdfua"` with well-structured HTML (headings, `lang`, `alt` texts) - see
[PDF/A, PDF/UA and e-invoicing](https://pd4ml.com/docs/pd4ml-manual.html#11.2-pdfa-pdfua-and-e-invoicing-formats).

### 5.4 A protected document

```jsp
<pd4tl:transform screenWidth="800">
  <pd4tl:permissions password="empty" rights="print"/>
<html><body><p>Can be printed, but not copied or changed.</p></body></html>
</pd4tl:transform>
```

### 5.5 Save on the server and show a confirmation page

```jsp
<pd4tl:transform screenWidth="800">
  <pd4tl:savefile dir="/var/app/statements" name="statement.pdf"
                  redirect="/confirm.jsp" redirectMethod="forward"/>
<html><body>...</body></html>
</pd4tl:transform>
```

`confirm.jsp`:

```jsp
<p>Your statement was saved as <%= request.getParameter("filename") %>.</p>
```

### 5.6 Converting another page or an external site

```jsp
<%-- a page of this application --%>
<pd4tl:transform screenWidth="1000" url='<%= request.getContextPath() + "/catalog.jsp" %>'/>

<%-- an intranet page behind a proxy and Basic authentication --%>
<pd4tl:transform screenWidth="1200" url="https://intranet.example.com/status.html"
                 proxy="proxy.example.com:3128" basicAuthentication="reporter:secret">
  <pd4tl:property name="pd4ml.sessionid.append" value="false"/>
</pd4tl:transform>
```

### 5.7 Word document and page thumbnail

```jsp
<pd4tl:transform screenWidth="800" outputFormat="docx" fileName="letter.docx" inline="false">
<html><body><h1>Letter</h1><p>Dear customer, ...</p></body></html>
</pd4tl:transform>

<pd4tl:transform screenWidth="800" outputFormat="png24" outputRange="2" fileName="page2.png">
...
</pd4tl:transform>
```

### 5.8 Placeholders filled at request time

```jsp
<pd4tl:transform screenWidth="800">
  <pd4tl:parameters>
    <pd4tl:parameter key="customer" value='<%= user.getCompany() %>'/>
  </pd4tl:parameters>
  <jsp:include page="/WEB-INF/templates/offer.html"/>
</pd4tl:transform>
```

`offer.html` contains `$[customer]` wherever the customer's name belongs.

## 6. Tips and gotchas

- **Nothing after `</pd4tl:transform>` runs.** Put all logic before or inside the tag.
- **The response is reset when the tag starts**: headers or cookies the page set before are lost.
  Set cookies in a filter or another request; the tag sets the document's own headers.
- **Use `prefix="pd4tl"`**, so that PD4ML's in-HTML tags (`<pd4ml:page.break/>`...) can be used in
  the page.
- **Paths starting with `/`** in `url`, `forward` and `savefile`'s `redirect` are relative to the
  server root: add `request.getContextPath()`.
- **Set `pageInsets` and `fileName`** explicitly: the defaults (10/20/30/40 mm, `file.pdf`) rarely
  fit.
- **`pageFormat` names are case-sensitive** (`A4`, `LETTER`, `ArchA`); numbers in `pageInsets`,
  `watermarkBounds` and `multicolumn` must be whole numbers.
- **Create the `savefile` directory in advance**, and check `name` if it comes from a request.
- **Keep the session id private**: with resources on other hosts, add
  `<pd4tl:property name="pd4ml.sessionid.append" value="false"/>`.
- **Check the HTML**: `enable="false"` shows the HTML the tag would convert, as the browser sees it.
- **Ship `pd4fonts.properties`** with font directories used by `<pd4tl:usettf>`.
- **Large documents**: the HTML and the result are held in memory; forward mode also keeps the
  request in the session. Prefer direct mode, and paginate very large reports.

## 7. Troubleshooting

| Symptom | Cause and remedy |
|---|---|
| "The absolute uri ... cannot be resolved" | The jar is not in `WEB-INF/lib`, the URI is misspelled (`https://` instead of `http://`), or the container skips the jar when scanning for tag libraries (Tomcat: `jarsToSkip` in `catalina.properties` - add the PD4ML jar to `tldScan`/`jarsToScan`). |
| `ClassNotFoundException` / `NoClassDefFoundError` for `javax.servlet...` or `jakarta.servlet...` | The URI does not match the container: `4.2-jk` for Jakarta (Tomcat 10+), `4.2` for `javax` (Tomcat 9 and earlier). |
| "No tag [page.break] defined in tag library imported with prefix [pd4ml]" | The tag library uses the prefix `pd4ml`: change it to `pd4tl`. |
| `IllegalStateException` | The response was committed before the tag (flushed, or more than the page buffer written), or the page writes through `getWriter()`: move the tag to the top of the page, or use [forward mode](#3.5-forward-mode-pd4forwarderservlet). |
| HTTP 500 "Invalid page format", "Invalid screen width", "Invalid page insets", "Invalid multicolumn definition" | An attribute value in the wrong form; see [4.1](#4.1-pd4tltransform). |
| HTTP 500 "can not create: [...]" | The `savefile` directory does not exist or is not writable. |
| Images or styles missing | Relative references resolve against the JSP's file; server-absolute paths need absolute URLs or `<pd4tl:fix_base_path>`; resources behind a login need the session id (default) or `basicAuthentication`. `debug="true"` logs what is loaded. |
| The PDF says "invalid url: ..." | `url` is not an absolute `http(s)://` URL or a `/`-path. |
| The browser shows "Hello no cached PDF generation data found in the session context" | Forward mode lost the session between the redirect and the servlet (cookies off and no URL rewriting, or a different host name). |
| Output is watermarked "PD4ML DEMO MODE" | No license: put `pd4ml.lic` into `WEB-INF/classes`. |
| Fonts are replaced, or squares instead of characters | The fonts are not available to PD4ML: provide them with `<pd4tl:usettf>` and use their family names in the CSS. |
| Downloaded file has the wrong extension | Set `fileName` (and `savefile`'s `name`) to match `outputFormat`. |

## 8. Older tag library versions

Pages written for earlier versions keep working: the older URIs use the same tags.

| URI | Servlet API | Version |
|---|---|---|
| `http://pd4ml.com/tlds/4.2-jk` | Jakarta | **1.2** (current) |
| `http://pd4ml.com/tlds/4.2` | `javax` | **1.2** (current) |
| `http://pd4ml.com/tlds/4.1-jk` | Jakarta | 1.0 |
| `http://pd4ml.com/tlds/4.1`, `http://pd4ml.com/tlds/4.0` | `javax` | 1.0 |

Version 1.0 lacks the `metadata` and `property` tags, the `transform` attributes `documentTitle`,
`initialPageNumber`, `kerning`, `ligatures`, `protectPhysicalUnitDimensions`, `formFont`, `proxy`
and `basicAuthentication`, the `permissions` attribute `encryption` (and permission names in
`rights`), and the `usettf` attribute `allGlyphs`. To use them, change the URI to `4.2` or
`4.2-jk`; nothing else in the page needs to change.

Pages written for PD4ML v3 (`http://pd4ml.com/tlds/3.0`, prefix `pd4ml`) need the new URI and the
`pd4tl` prefix; the tags and their attributes are the same. See the
[v3 to v4 migration guide](https://pd4ml.com/docs/legacy/pd4ml-v3-to-v4-migration-guide.html).

## 9. See also

- [PD4ML Programmer's Manual](https://pd4ml.com/docs/pd4ml-manual.html), in particular
  [13. Web integration: the JSP tag library](https://pd4ml.com/docs/pd4ml-manual.html#13.-web-integration-the-jsp-tag-library)
- [PD4ML proprietary CSS media types, properties and tags](https://pd4ml.com/docs/features/pd4ml-proprietary-css-media-types-properties-and-tags.html) -
  the `<pd4ml:...>` extension tags usable in the converted HTML
- [PDF header and footer generation](https://pd4ml.com/docs/features/pdf-header-and-footer-generation.html)
- [RTF and DOCX output](https://pd4ml.com/docs/features/rtf-and-docx-output.html),
  [HTML to raster image conversion](https://pd4ml.com/docs/features/html-to-raster-image-conversion.html)
- [Usage examples](https://pd4ml.com/docs/usage-examples.html)
