pd4ml 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/guide.md ADDED
@@ -0,0 +1,795 @@
1
+ # PD4ML for Node.js — User Guide
2
+
3
+ `pd4ml` brings [PD4ML](https://pd4ml.com) to Node.js: it converts HTML and CSS into **PDF**,
4
+ **PDF/A**, **PDF/UA**, **ZUGFeRD/Factur-X** invoices, **DOCX**, **RTF** and **page images**, and
5
+ gives Node applications the complete public PD4ML API.
6
+
7
+ PD4ML is a Java library. The module runs it in a small Java *bridge* process that Node talks to.
8
+ Every PD4ML class and method can be called from JavaScript, and the module adds Node I/O on top:
9
+ paths, URLs and Buffers instead of Java streams. The API is synchronous: `pd.writePDF()` returns
10
+ the PDF.
11
+
12
+ The PD4ML manual, which this guide refers to throughout, is at <https://pd4ml.com/docs/>.
13
+ The generated reference of every class and method is [api.md](api.md); TypeScript declarations
14
+ come with the package.
15
+
16
+ **Contents**
17
+
18
+ 1. [Prerequisites](#1-prerequisites)
19
+ 2. [Installation](#2-installation)
20
+ 3. [Quick start](#3-quick-start)
21
+ 4. [How it works](#4-how-it-works)
22
+ 5. [Use cases](#5-use-cases)
23
+ 6. [Tips and hints](#6-tips-and-hints)
24
+ 7. [Troubleshooting](#7-troubleshooting)
25
+ 8. [API reference](#8-api-reference)
26
+ 9. [Further reading](#9-further-reading)
27
+
28
+ ---
29
+
30
+ ## 1. Prerequisites
31
+
32
+ | Requirement | Notes |
33
+ |---|---|
34
+ | **Node.js 18 or later** | Any platform Node and Java run on. |
35
+ | **Java 8 or later** | A JDK or JRE; an LTS release (17, 21) is recommended. The module finds it through `PD4ML_JAVA`, then `JAVA_HOME`, then `java` on the `PATH`. |
36
+ | **PD4ML** | Downloaded automatically on first use, or ahead of time with `npx pd4ml install` (see below); or any PD4ML build you point the module at. |
37
+ | **Network access** | Only for downloading PD4ML. Conversions need none, unless the HTML references remote resources. |
38
+ | **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](#518-licensing). |
39
+
40
+ Check Java with `java -version`. On a server without Java, install a JRE package (for example
41
+ `apt install openjdk-21-jre-headless`) and, if it is not on the `PATH`, set `JAVA_HOME`. In
42
+ containers, use an image with both Node and Java, or add a JRE to the Node image.
43
+
44
+ ## 2. Installation
45
+
46
+ ### 2.1 Install the module
47
+
48
+ ```
49
+ npm install github:zxfr/pd4ml-node
50
+ ```
51
+
52
+ or install a release package from <https://github.com/zxfr/pd4ml-node/releases>, by its URL or
53
+ after downloading it:
54
+
55
+ ```
56
+ npm install https://github.com/zxfr/pd4ml-node/releases/download/v1.0.0/pd4ml-1.0.0.tgz
57
+ ```
58
+
59
+ Either way the module is named `pd4ml` (`require('pd4ml')`, `import ... from 'pd4ml'`).
60
+
61
+ This installs the module, its TypeScript declarations and the `pd4ml` command (`npx pd4ml ...`).
62
+ The module has no npm dependencies.
63
+
64
+ ### 2.2 Install PD4ML
65
+
66
+ The package contains the Node module and the bridge only. The PD4ML library itself comes from the
67
+ PD4ML Maven repository (<https://pd4ml.com/maven2>), in one of two ways.
68
+
69
+ **Automatically, on first use.** If no PD4ML build is installed or configured
70
+ ([2.3](#23-other-ways-to-provide-pd4ml)), the first use of PD4ML in a process downloads the most
71
+ recent PD4ML 4.x release before starting Java. A notice on standard error reports it. So once the module
72
+ is installed, this is all it takes:
73
+
74
+ <!-- no-test -->
75
+ ```js
76
+ const { htmlToPdf } = require('pd4ml');
77
+
78
+ const pdf = htmlToPdf(Buffer.from('<h1>Hello</h1>')); // the first call downloads PD4ML (once)
79
+ ```
80
+
81
+ **Ahead of time, with the `pd4ml` command.** This is better for servers, CI and container
82
+ images, where a download in the middle of a request is unwelcome:
83
+
84
+ ```
85
+ npx pd4ml install
86
+ ```
87
+
88
+ Either way, the module:
89
+
90
+ - downloads the most recent PD4ML 4.x release;
91
+ - downloads the Bouncy Castle jars PD4ML needs for encryption and signing, from Maven Central;
92
+ - checks every file against its published SHA-1 checksum;
93
+ - stores them in a per-user cache directory: `%LOCALAPPDATA%\pd4ml-node` on Windows,
94
+ `~/.cache/pd4ml-node` elsewhere, or `PD4ML_HOME`.
95
+
96
+ The module then uses that build. Later runs do not check for newer PD4ML releases: run
97
+ `npx pd4ml install` again to update. Updating the npm package does not change the PD4ML build.
98
+
99
+ | Option | |
100
+ |---|---|
101
+ | *(none)* | the most recent 4.x release, or an update to it |
102
+ | `--snapshots` | the most recent 4.x build, including development snapshots |
103
+ | `npx pd4ml install 4.0.23fx2` | an exact version |
104
+ | `--jakarta` | the `-jakarta` variant (for Jakarta EE / Servlet 6 environments) |
105
+ | `--home DIR` | install into `DIR` (default: `PD4ML_HOME`, else the cache directory) |
106
+ | `--force` | download again |
107
+ | `--no-bouncycastle` | skip the Bouncy Castle jars (encryption and signing will not work) |
108
+ | `npx pd4ml list` | list the available versions |
109
+
110
+ The same is available from code, which suits an application's start-up:
111
+
112
+ <!-- no-test -->
113
+ ```js
114
+ const pd4ml = require('pd4ml');
115
+
116
+ pd4ml.install(); // downloads only when that build is not there yet
117
+ console.log(pd4ml.installedVersion(), pd4ml.installedClasspath());
118
+ ```
119
+
120
+ To check the installation, run `npx pd4ml version`.
121
+
122
+ **Turning the automatic download off.** Set `PD4ML_AUTO_INSTALL=0`, for example on servers that
123
+ must not reach the internet, or where the build is provisioned separately. The module then
124
+ throws a `BridgeError` until a build is installed or configured. The automatic download never
125
+ runs when a build is configured through [2.3](#23-other-ways-to-provide-pd4ml).
126
+
127
+ > **Services and containers**: the account the application runs as must see the installed
128
+ > build. Install it as that account, or install into a shared directory and set `PD4ML_HOME`
129
+ > for the application too. In a Dockerfile:
130
+ >
131
+ > ```
132
+ > RUN npm ci && PD4ML_HOME=/opt/pd4ml npx pd4ml install
133
+ > ENV PD4ML_HOME=/opt/pd4ml PD4ML_AUTO_INSTALL=0
134
+ > ```
135
+
136
+ ### 2.3 Other ways to provide PD4ML
137
+
138
+ The module looks for PD4ML in this order:
139
+
140
+ 1. `configure({ classpath: [...] })`, called before the first conversion;
141
+ 2. the `PD4ML_CLASSPATH` environment variable: jars and class directories separated by `;`
142
+ (Windows) or `:` (elsewhere), with `dir/*` for all jars of a directory;
143
+ 3. the `PD4ML_CLASSPATH_FILE` environment variable: a file with one entry per line;
144
+ 4. the build `npx pd4ml install` (or the first use) downloaded.
145
+
146
+ Typical reasons to use them:
147
+
148
+ - **Offline or air-gapped servers**: run `npx pd4ml install --home ./pd4ml-libs` on a machine
149
+ with network access, copy the directory, and set `PD4ML_HOME` and `PD4ML_AUTO_INSTALL=0` on
150
+ the target.
151
+ - **A licensed or specific jar**: `PD4ML_CLASSPATH=/opt/pd4ml/pd4ml.jar:/opt/pd4ml/lib/*`.
152
+ - **A PD4ML development build** (for the v42 layout engine, see [4.3](#43-layout-engines)): a
153
+ classpath file listing the build's classes and its Maven dependencies, in
154
+ `PD4ML_CLASSPATH_FILE`.
155
+
156
+ ## 3. Quick start
157
+
158
+ ```js
159
+ const { PD4ML, PageSize } = require('pd4ml');
160
+
161
+ const pd = new PD4ML();
162
+ pd.setPageSize(PageSize.A4);
163
+ pd.readHtmlString('<h1>Hello, PDF</h1><p>Generated with <b>PD4ML</b>.</p>');
164
+ pd.writePDF('hello.pdf');
165
+ ```
166
+
167
+ Or in one call:
168
+
169
+ ```js
170
+ const { htmlToPdf } = require('pd4ml');
171
+
172
+ const pdf = htmlToPdf(Buffer.from('<h1>Invoice 42</h1>'));
173
+ console.log(pdf.subarray(0, 5).toString()); // %PDF-
174
+ ```
175
+
176
+ With TypeScript or ES modules:
177
+
178
+ <!-- no-test -->
179
+ ```ts
180
+ import { PD4ML, PageSize } from 'pd4ml';
181
+
182
+ const pd = new PD4ML();
183
+ pd.setPageSize(PageSize.A4);
184
+ pd.readHtmlString('<h1>Hello</h1>');
185
+ const pdf: Buffer = pd.writePDF();
186
+ ```
187
+
188
+ ## 4. How it works
189
+
190
+ ### 4.1 The two-phase model
191
+
192
+ A document is **read once and written any number of times**:
193
+
194
+ - `readHTML` parses the document and lays it out;
195
+ - each `writePDF`, `writeDOCX`, `writeRTF` or `renderAsImages` call then produces one output from
196
+ that same layout.
197
+
198
+ Settings (page size, headers, fonts...) go before `readHTML`. See
199
+ [the two-phase model](https://pd4ml.com/docs/#6.1-the-two-phase-model).
200
+
201
+ ```js
202
+ const { PD4ML } = require('pd4ml');
203
+
204
+ const pd = new PD4ML();
205
+ pd.readHtmlString('<h1>Quarterly report</h1>');
206
+ pd.writePDF('report.pdf');
207
+ pd.writeDOCX('report.docx');
208
+ const thumbnails = pd.renderPng(); // PNG images, one Buffer per page
209
+ ```
210
+
211
+ ### 4.2 The bridge, threads and the event loop
212
+
213
+ - **One bridge per process.** The first use of PD4ML starts a Java bridge process. It serves
214
+ every conversion of the Node process and ends with it. Starting Java takes a fraction of a
215
+ second; after that, converting an ordinary document takes milliseconds.
216
+ - **Synchronous calls.** Each call waits for Java's answer, like any other CPU-bound work in
217
+ Node: a long conversion blocks the event loop while it runs. Behind the scenes a worker thread
218
+ does the communication, so callbacks such as progress listeners
219
+ ([5.14](#514-progress-diagnostics-and-metadata)) still run on your thread.
220
+ - **Servers with concurrent requests** stay responsive by converting in worker threads: each
221
+ worker (`worker_threads`, or a pool such as `piscina`) can load `pd4ml` and convert on its own.
222
+ Each worker starts its own bridge, unless all of them use one bridge server (next item).
223
+ - **Shared bridge server.** Several processes or workers, or short-lived ones (serverless,
224
+ CLI tools), can use one long-running bridge instead of each starting Java:
225
+
226
+ ```
227
+ npx pd4ml serve --port 8723 # keep it running (systemd, a sidecar container, pm2)
228
+ ```
229
+
230
+ Then set `PD4ML_BRIDGE=tcp://127.0.0.1:8723`, or call
231
+ `configure({ server: 'tcp://127.0.0.1:8723' })`. The server listens on the loopback
232
+ interface only, and each connection has its own objects.
233
+ - **JVM options** (memory, system properties): set `PD4ML_JVM_OPTIONS="-Xmx2g"`, or call
234
+ `configure({ jvmOptions: ['-Xmx2g'] })` before the first use. Java runs headless.
235
+ - **The bridge's console output** is discarded unless `PD4ML_BRIDGE_LOG=path` (or
236
+ `configure({ logFile })`) keeps it. `shutdown()` stops the bridge; the next use starts it again.
237
+
238
+ ### 4.3 Layout engines
239
+
240
+ PD4ML 4.2 introduces a second HTML layout engine, **v42**, written to match current browsers
241
+ closely (flexbox, grid, modern CSS). `new PD4ML()` uses it by default:
242
+
243
+ ```js
244
+ const { PD4ML, hasLayoutEngines } = require('pd4ml');
245
+
246
+ if (hasLayoutEngines()) {
247
+ const pd = new PD4ML(); // v42
248
+ const legacy = new PD4ML({ engine: 'v40' }); // the previous engine
249
+ console.log(pd.engine, legacy.engine);
250
+ }
251
+ ```
252
+
253
+ Builds before 4.2 have one engine only; this includes the current public release, 4.1.x. With
254
+ such a build:
255
+
256
+ - the module uses that build's engine and emits a warning once (`process.emitWarning`);
257
+ - `hasLayoutEngines()` returns false;
258
+ - asking for an engine explicitly throws.
259
+
260
+ ### 4.4 JavaScript and Java values
261
+
262
+ - Java methods keep their **Java names** (`setPageSize`, `readHTML`...) and take JavaScript
263
+ values: strings, numbers, booleans, `null`; arrays for Java arrays and lists; plain objects
264
+ for `Map`s; `Buffer`/`Uint8Array` for `byte[]` and input streams.
265
+ - **Classes**: the PD4ML classes are exported under their own names (`PageSize`, `PageMargins`,
266
+ `Units`, `Constants`, `PdfSpec`, `LayoutEngine`...). Static fields and methods are properties
267
+ (`PageSize.A4`, `PdfSpec.getInstance('pdf/a-2b')`), constructors use `new`. Any other class:
268
+ `Java.type('java.util.Locale')`.
269
+ - **Objects**: a Java object comes back as a proxy with all its Java methods. Helpers start with
270
+ `$`, so they never hide a Java method: `$get('width')` and `$set(...)` for public fields,
271
+ `$class`, `$enum` (an enum constant's name), `$instanceOf(...)`. `String(obj)` is Java's
272
+ `toString()`.
273
+ - **Numbers**: JavaScript has one number type. An integer goes to Java as an `int`; where a
274
+ method has both `int` and `float` overloads and you want the float one, pass
275
+ `Java.float(10)`. Java numbers come back as JavaScript numbers, `byte[]` as a `Buffer`.
276
+ - Java objects are released when JavaScript garbage-collects them.
277
+
278
+ ### 4.5 Exceptions
279
+
280
+ Java exceptions are thrown as `JavaError`, which knows the Java class and its superclasses:
281
+
282
+ ```js
283
+ const { PD4ML, JavaError } = require('pd4ml');
284
+
285
+ try {
286
+ const pd = new PD4ML();
287
+ pd.readHTML('file:///no/such/document.html');
288
+ pd.writePDF();
289
+ } catch (e) {
290
+ if (e instanceof JavaError && e.isInstanceOf('java.io.IOException')) {
291
+ console.log('cannot read the document:', e.javaMessage);
292
+ } else {
293
+ throw e;
294
+ }
295
+ }
296
+ ```
297
+
298
+ `e.javaClass`, `e.causes` and `e.javaTrace` hold the details. Other errors:
299
+
300
+ | Error | Thrown when |
301
+ |---|---|
302
+ | `BridgeError` | the bridge itself fails: no Java, no PD4ML build, or the process ended |
303
+ | `CallbackError` | a JavaScript callback threw, such as a progress listener; the original error is `cause` |
304
+ | `InstallError` | downloading PD4ML fails |
305
+
306
+ ---
307
+
308
+ ## 5. Use cases
309
+
310
+ The snippets below are complete and runnable. Files they read (`report.html`...) are assumed to
311
+ exist in the working directory.
312
+
313
+ ### 5.1 HTML string to PDF
314
+
315
+ This is the common case for web applications: render HTML with your template engine
316
+ (Handlebars, EJS, Nunjucks, JSX to string) and convert it in memory.
317
+
318
+ ```js
319
+ const { PD4ML } = require('pd4ml');
320
+
321
+ const html = '<html><body><h1>Order 1001</h1><p>Thank you for your order.</p></body></html>';
322
+
323
+ const pd = new PD4ML();
324
+ pd.readHtmlString(html);
325
+ const pdf = pd.writePDF(); // the PDF as a Buffer
326
+ ```
327
+
328
+ `writePDF`, `writeDOCX` and `writeRTF` return the document as a `Buffer` when given no target.
329
+ Given a file path, a file descriptor, or a writable stream (an HTTP response, an
330
+ `fs.WriteStream`), they write into it.
331
+
332
+ ### 5.2 Files and URLs
333
+
334
+ ```js
335
+ const { PD4ML } = require('pd4ml');
336
+
337
+ const pd = new PD4ML();
338
+ pd.readHTML('report.html'); // also: 'https://example.com/report', a URL object
339
+ pd.writePDF('report.pdf');
340
+ ```
341
+
342
+ **Relative links** (`<img src="logo.png">`, `<link href="style.css">`) resolve against the
343
+ document's own location when it is read from a path or URL. When the HTML comes from a string or
344
+ a `Buffer`, pass the base location. Otherwise images and stylesheets go missing without an error
345
+ (see [reading the source](https://pd4ml.com/docs/#6.2-reading-the-source)):
346
+
347
+ ```js
348
+ const { PD4ML } = require('pd4ml');
349
+
350
+ const html = '<link rel="stylesheet" href="style.css"><p class="note">Styled</p>';
351
+ const pd = new PD4ML();
352
+ pd.readHtmlString(html, '.'); // a directory, file path or URL
353
+ pd.writePDF('styled.pdf');
354
+ ```
355
+
356
+ For a document in a known encoding other than UTF-8, call
357
+ `pd.readHTML(buffer, baseUrl, 'windows-1251')`. To read from a stream, collect it into a Buffer
358
+ first (`Buffer.concat(chunks)`, or `await new Response(stream).arrayBuffer()`).
359
+
360
+ ### 5.3 Page size, orientation, margins and scaling
361
+
362
+ ```js
363
+ const { PD4ML, PageSize, PageMargins, Units } = require('pd4ml');
364
+
365
+ const pd = new PD4ML();
366
+ pd.setPageSize(PageSize.LETTER.rotate()); // landscape
367
+ pd.setPageMargins(new PageMargins(15, 20, 15, 20, Units.MM)); // left, top, right, bottom
368
+ pd.setHtmlWidth(1000); // the virtual browser width the page is laid out in
369
+ pd.readHtmlString("<table width='100%'><tr><td>wide content</td></tr></table>");
370
+ pd.writePDF('landscape.pdf');
371
+ ```
372
+
373
+ The layout is scaled to the page: `scale = printable width / html width`, so a smaller HTML width
374
+ makes everything larger. Related controls:
375
+
376
+ - `adjustHtmlWidth(true)` lays the page out at the document's own natural width.
377
+ - `fitPageVertically(Constants.CENTER_ALIGN)` shrinks the whole document onto a single page.
378
+ - `setPageSize` and `setPageMargins` accept a page *scope* (`'1'`, `'2+'`, `'odd'`...) as a
379
+ second argument.
380
+
381
+ More in [page layout](https://pd4ml.com/docs/#7.-page-layout-size-margins-and-scaling).
382
+
383
+ For a custom size, use `new PageSize(Java.float(100), Java.float(150), Units.MM)`. For a named
384
+ size, use `PageSize.getInstance('a5')` (the name in lower case).
385
+
386
+ ### 5.4 Headers, footers and page numbers
387
+
388
+ Headers and footers are HTML fragments. PD4ML fills in `$[page]`, `$[total]` and `$[title]` on
389
+ each page, and `setDynamicData` adds placeholders of your own. In JavaScript, write them in
390
+ ordinary quotes; inside a template literal, `$[...]` is safe too (only `${...}` is interpolated).
391
+
392
+ ```js
393
+ const { PD4ML } = require('pd4ml');
394
+
395
+ const pd = new PD4ML();
396
+ pd.setDynamicData({ customer: 'Acme Corp.', invoice: 'INV-2026-0031' });
397
+ pd.setPageHeader("<div style='font-size:9pt'><b>$[title]</b></div>", 30, '1+');
398
+ pd.setPageFooter(
399
+ "<div style='font-size:8pt; text-align:right'>"
400
+ + 'Invoice $[invoice] &mdash; $[customer] &mdash; page $[page] of $[total]</div>',
401
+ 24, '1+');
402
+ pd.readHtmlString('<title>Invoice</title><h1>Invoice</h1>' + '<p>Line item</p>'.repeat(80));
403
+ pd.writePDF('invoice.pdf');
404
+ ```
405
+
406
+ The second argument is the area height in pixels. The third is the page scope: `'1'` (first
407
+ page), `'2+'` (all but the first), `'odd'`, `'even'`, `'3-7'`... You can also write headers and
408
+ footers inline in the HTML with PD4ML's extension tags. See
409
+ [headers, footers, watermarks and backgrounds](https://pd4ml.com/docs/#8.-headers-footers-watermarks-and-backgrounds)
410
+ and the [usage examples](https://pd4ml.com/docs/usage-examples.html#set-page-header-and-footer).
411
+
412
+ ### 5.5 Watermarks and page backgrounds
413
+
414
+ ```js
415
+ const { PD4ML } = require('pd4ml');
416
+
417
+ const pd = new PD4ML();
418
+ pd.setWatermark("<span style='font-size:72pt; color:#c00'>DRAFT</span>",
419
+ 120, 350, // left, top (px)
420
+ 0.15, // opacity
421
+ 45, // rotation, degrees
422
+ 1, // scale
423
+ true, true, // visible on screen, in print
424
+ '1+'); // pages
425
+ pd.setPageBackground("<div style='background:#f7f7f2; height:100%'></div>", '1+');
426
+ pd.readHtmlString('<h1>Proposal</h1>');
427
+ pd.writePDF('draft.pdf');
428
+ ```
429
+
430
+ ### 5.6 Fonts
431
+
432
+ PD4ML's built-in fonts cover Unicode broadly. For a specific typeface (a corporate font, CJK
433
+ coverage), give it a font directory:
434
+
435
+ <!-- no-test -->
436
+ ```js
437
+ const { PD4ML } = require('pd4ml');
438
+
439
+ const pd = new PD4ML();
440
+ pd.useTTF('/opt/fonts', true); // a directory of TTF/OTF fonts (indexed: see below)
441
+ pd.embedTTFs(true, false); // embed only the glyphs used
442
+ pd.applyKerning(true);
443
+ pd.enableLigatures(true);
444
+ ```
445
+
446
+ Index a font directory once, ahead of time. PD4ML then reads the index (`pd4fonts.properties`)
447
+ instead of scanning all the fonts on every conversion
448
+ ([font indexing](https://pd4ml.com/docs/#4.3-font-indexing)):
449
+
450
+ ```
451
+ java -jar <pd4ml jar> -configure.fonts /opt/fonts
452
+ ```
453
+
454
+ `require('pd4ml').installedJar()` returns the jar's path. CSS `@font-face` web fonts in the
455
+ document work as well.
456
+
457
+ ### 5.7 Bookmarks, links and tables of contents
458
+
459
+ ```js
460
+ const { PD4ML } = require('pd4ml');
461
+
462
+ const pd = new PD4ML();
463
+ pd.generateBookmarksFromHeadings(true); // one PDF bookmark per <h1>..<h6>
464
+ pd.enableHyperlinks(true); // <a href> become clickable links (the default)
465
+ pd.readHtmlString("<h1>Introduction</h1><p>...</p><h1 style='page-break-before:always'>Details</h1>");
466
+ pd.writePDF('bookmarked.pdf');
467
+ ```
468
+
469
+ PD4ML's `<pd4ml:toc>` tag generates a table of contents with page numbers from the headings; see
470
+ the [usage examples](https://pd4ml.com/docs/usage-examples.html#add-toc). For explicit page
471
+ breaks, use CSS `page-break-before` / `break-before`, or `<pd4ml:page.break>`.
472
+
473
+ ### 5.8 PDF/A and PDF/UA
474
+
475
+ ```js
476
+ const { PD4ML, PdfSpec, Constants } = require('pd4ml');
477
+
478
+ const pd = new PD4ML();
479
+ pd.readHtmlString("<html lang='en'><title>Annual report</title><h1>Annual report</h1>");
480
+ pd.writePDF('archive.pdf', PdfSpec.PDFA_3B); // archival
481
+ pd.writePDF('accessible.pdf', PdfSpec.PDFUA_1); // accessible
482
+ pd.writePDF('both.pdf', PdfSpec.PDF_1_7.combine(PdfSpec.PDFA_2A));
483
+ pd.writePDF('keyword.pdf', 'pdf/a-2b'); // by keyword
484
+
485
+ const status = pd.getLastRenderInfo(Constants.PD4ML_PDFA_STATUS); // after writing
486
+ ```
487
+
488
+ `PdfSpec.listKeywords()` lists the keywords; an unknown one throws a `RangeError`
489
+ (`PdfSpec.getInstance` alone would fall back to PDF 1.7 without an error).
490
+
491
+ - PDF/A embeds all fonts.
492
+ - PDF/UA needs well-structured HTML: real headings, `alt` texts, `lang`, table headers.
493
+ Combine it with `pd.setErrorPolicy(ErrorPolicy.PEDANTIC)` to be told when the source does not
494
+ satisfy the standard.
495
+
496
+ Details: [PDF/A, PDF/UA and e-invoicing](https://pd4ml.com/docs/#11.2-pdfa-pdfua-and-e-invoicing-formats).
497
+
498
+ ### 5.9 ZUGFeRD / Factur-X e-invoices
499
+
500
+ `PdfSpec.ZUGFeRD` and `PdfSpec.FacturX` select the PDF/A-3 container the standards require. The
501
+ HTML attaches the invoice XML with PD4ML's attachment tag. The
502
+ [ZUGFeRD and Factur-X page](https://pd4ml.com/docs/features/zugferd-and-factur-x-pdf-invoices.html)
503
+ has the complete recipe, including the required attachment names.
504
+
505
+ <!-- no-test -->
506
+ ```js
507
+ const { PD4ML, PdfSpec } = require('pd4ml');
508
+
509
+ const pd = new PD4ML();
510
+ pd.readHTML('invoice.html'); // contains the <pd4ml:attachment> of factur-x.xml
511
+ pd.writePDF('invoice.pdf', PdfSpec.FacturX);
512
+ ```
513
+
514
+ ### 5.10 Passwords and permissions
515
+
516
+ ```js
517
+ const { PD4ML, Constants } = require('pd4ml');
518
+
519
+ const pd = new PD4ML();
520
+ pd.setPermissions('owner-secret', Constants.AllowPrint | Constants.AllowCopy, Constants.AES_256BIT);
521
+ pd.readHtmlString('<p>Confidential</p>');
522
+ pd.writePDF('protected.pdf');
523
+ ```
524
+
525
+ Combine the `Allow*` flags with `|`, or start from `Constants.DefaultPermissions` and remove flags
526
+ with `& ~`. Encryption needs the Bouncy Castle jars, which the installation downloads. See
527
+ [permissions and encryption](https://pd4ml.com/docs/#11.1-permissions-and-encryption).
528
+
529
+ ### 5.11 Word (DOCX, RTF) and images
530
+
531
+ ```js
532
+ const { PD4ML, Constants } = require('pd4ml');
533
+
534
+ const pd = new PD4ML();
535
+ pd.readHtmlString('<h1>Letter</h1><p>Dear customer, ...</p>');
536
+ pd.writeDOCX('letter.docx');
537
+ pd.writeRTF('letter.rtf');
538
+ const pages = pd.renderPng(); // a PNG Buffer per page
539
+ const tiffs = pd.renderAsImages(Constants.TIFF); // one TIFF per page
540
+ pd.renderAsImages('pages', 'page', Constants.PNG24); // page files in a directory
541
+ ```
542
+
543
+ The image formats are `PNG8`, `PNG24`, `TIFF` and `TIFF_COMPRESSED` (constants in `Constants`).
544
+ The v42 engine does not support the multi-page TIFF formats yet; use the v40 engine for them.
545
+
546
+ With the v42 engine, DOCX/RTF output can be made *editable*: positioned content becomes text
547
+ boxes and form fields instead of pictures. To enable it, call
548
+ `pd.setParam(Constants.PD4ML_WORD_EDITABLE, 'true')`.
549
+
550
+ ### 5.12 Interactive forms and multi-column layout
551
+
552
+ ```js
553
+ const { PD4ML } = require('pd4ml');
554
+
555
+ const pd = new PD4ML();
556
+ pd.generateForms(true, null); // <input>/<select>/<textarea> become PDF form fields
557
+ pd.readHtmlString("<form>Name: <input name='name' size='30'></form>");
558
+ pd.writePDF('form.pdf');
559
+
560
+ const news = new PD4ML();
561
+ news.generateMulticolumn(2, 20, true); // two columns, 20 mm apart
562
+ news.readHtmlString('<h1>Newsletter</h1>' + '<p>Lorem ipsum dolor sit amet.</p>'.repeat(200));
563
+ news.writePDF('newsletter.pdf');
564
+ ```
565
+
566
+ See [forms](https://pd4ml.com/docs/#10.1-interactive-pdf-forms) and
567
+ [multi-column pages](https://pd4ml.com/docs/#10.2-multi-column-pages). Multi-column pages need
568
+ PD4ML 4.0.24 / 4.1.2 or later.
569
+
570
+ ### 5.13 Adding CSS and HTML programmatically
571
+
572
+ ```js
573
+ const { PD4ML } = require('pd4ml');
574
+
575
+ const pd = new PD4ML();
576
+ pd.addStyle('body { font-family: sans-serif } h1 { color: #036 }', true); // true: after the document's styles
577
+ pd.injectHtml("<div style='font-size:8pt'>Generated by the reporting service</div>", false); // before </body>
578
+ pd.readHtmlString('<h1>Report</h1>');
579
+ pd.writePDF('report-styled.pdf');
580
+ ```
581
+
582
+ `addStyle` also takes the URL of a stylesheet: `pd.addStyle(toUrl('print.css'), true)`. See
583
+ [injecting HTML](https://pd4ml.com/docs/#10.3-injecting-html-and-custom-resource-providers) and
584
+ [adding styles](https://pd4ml.com/docs/usage-examples.html#add-style-programmatically).
585
+
586
+ ### 5.14 Progress, diagnostics and metadata
587
+
588
+ ```js
589
+ const { PD4ML, ErrorPolicy, Constants } = require('pd4ml');
590
+
591
+ const pd = new PD4ML();
592
+ pd.monitorProgressWith((messageId, percent, note, msec) =>
593
+ console.log(`${String(percent).padStart(3)}% ${note || ''} (${msec} ms)`));
594
+ pd.setErrorPolicy(ErrorPolicy.NORMAL); // NONE, RELAXED, NORMAL, PEDANTIC, STRICT
595
+ pd.setLogLevel(1); // 0 (silent) .. 5
596
+ pd.setAuthorName('Reporting service');
597
+ pd.setDocumentTitle('Monthly statement');
598
+ pd.addMetadata('StatementId', '2026-10', false);
599
+ pd.readHtmlString('<h1>Statement</h1>');
600
+ pd.writePDF('statement.pdf');
601
+ const pages = Number(pd.getLastRenderInfo(Constants.PD4ML_TOTAL_PAGES));
602
+ ```
603
+
604
+ The callback runs on your thread while the conversion runs in Java, and it may call PD4ML
605
+ itself. An error it throws comes out of the call that caused it, wrapped in a `CallbackError`.
606
+ The v42 engine currently reports fewer progress stages than v40. More in
607
+ [extensibility and diagnostics](https://pd4ml.com/docs/#12.-extensibility-and-diagnostics).
608
+
609
+ ### 5.15 Working with existing PDFs
610
+
611
+ ```js
612
+ const { htmlToPdf, openPdf, writePdf, mergePdfs } = require('pd4ml');
613
+
614
+ const cover = htmlToPdf(Buffer.from("<h1 style='margin-top:300px; text-align:center'>Annual Report</h1>"));
615
+ const body = htmlToPdf(Buffer.from('<h2>Chapter 1</h2><p>...</p>'));
616
+
617
+ const doc = openPdf(cover); // a Buffer, a path or a URL (+ password)
618
+ doc.append(openPdf(body)); // pages of another document after these
619
+ doc.setTitle('Annual Report');
620
+ writePdf(doc, 'annual-report.pdf');
621
+
622
+ const stamp = htmlToPdf(Buffer.from("<p style='color:#c00; font-size:40pt'>COPY</p>"));
623
+ const original = openPdf('annual-report.pdf');
624
+ original.overlay(openPdf(stamp), 128); // draw over every page, half opaque (0..255)
625
+ writePdf(original, 'annual-report-copy.pdf', { password: 'secret' });
626
+
627
+ console.log(openPdf('annual-report.pdf').getNumberOfPages());
628
+ const both = mergePdfs(cover, body); // two documents appended in one call
629
+ ```
630
+
631
+ `underlay` draws another document *under* the pages (a letterhead, for example);
632
+ `deletePage`, `getPageSize`, `getMetadata` and `dumpOutlines` inspect and edit a document.
633
+ PD4ML's PDF toolkit (signing, optimisation, XFDF) is described in
634
+ [the PDF post-processing toolkit](https://pd4ml.com/docs/#15.-the-pdf-post-processing-toolkit)
635
+ and is reachable through `Java.type(...)`.
636
+
637
+ ### 5.16 Web applications
638
+
639
+ Express:
640
+
641
+ <!-- no-test -->
642
+ ```js
643
+ const express = require('express');
644
+ const { PD4ML } = require('pd4ml');
645
+
646
+ const app = express();
647
+ app.get('/invoice/:number.pdf', (req, res) => {
648
+ const pd = new PD4ML();
649
+ pd.readHtmlString(renderInvoiceHtml(req.params.number), __dirname + '/public'); // base: images/CSS
650
+ res.type('application/pdf');
651
+ res.send(pd.writePDF());
652
+ });
653
+ app.listen(3000);
654
+ ```
655
+
656
+ Plain `http`, streaming into the response:
657
+
658
+ <!-- no-test -->
659
+ ```js
660
+ const http = require('http');
661
+ const { PD4ML } = require('pd4ml');
662
+
663
+ http.createServer((req, res) => {
664
+ const pd = new PD4ML();
665
+ pd.readHtmlString(renderReportHtml(req.url), __dirname);
666
+ res.writeHead(200, { 'Content-Type': 'application/pdf' });
667
+ pd.writePDF(res); // writes into the response
668
+ res.end();
669
+ }).listen(3000);
670
+ ```
671
+
672
+ Each conversion holds the event loop while it runs ([4.2](#42-the-bridge-threads-and-the-event-loop)).
673
+ For a server under concurrent load, convert in worker threads, and touch PD4ML when the
674
+ application starts (`require('pd4ml').version()`), so the first request does not pay for
675
+ starting Java.
676
+
677
+ ### 5.17 Batch conversion
678
+
679
+ ```js
680
+ const fs = require('fs');
681
+ const { PD4ML } = require('pd4ml');
682
+
683
+ for (const source of fs.readdirSync('.').filter(f => f.endsWith('.html'))) {
684
+ const pd = new PD4ML(); // one converter per document
685
+ pd.readHTML(source);
686
+ pd.writePDF(source.replace(/\.html$/, '.pdf'));
687
+ console.log(`written ${source}`);
688
+ }
689
+ ```
690
+
691
+ One bridge serves the whole loop. To convert in parallel, run several worker threads or
692
+ processes (each with its own bridge), or point them all at one bridge server.
693
+
694
+ ### 5.18 Licensing
695
+
696
+ <!-- no-test -->
697
+ ```js
698
+ const { PD4ML, version } = require('pd4ml');
699
+
700
+ const pd = new PD4ML({ license: 'YOUR-SERIAL-NUMBER' }); // or a license file URL; or pd4ml.lic in the working dir / classpath
701
+ if (pd.isDemoMode()) console.log('unlicensed: output is watermarked');
702
+ console.log(`PD4ML ${version()}${pd.isPro() ? ' pro' : ''}`);
703
+ ```
704
+
705
+ Without a license, PD4ML is fully functional but marks its output. See
706
+ [licensing](https://pd4ml.com/docs/#3.-licensing).
707
+
708
+ ---
709
+
710
+ ## 6. Tips and hints
711
+
712
+ - **Start the bridge early.** The first use starts Java. Touch PD4ML when the application
713
+ starts, or use a bridge server for short-lived processes.
714
+ - **One converter per document.** `new PD4ML()` is cheap; reuse the bridge, not the object.
715
+ - **Set the base location for HTML from strings.** Missing images and unstyled output are almost
716
+ always caused by unresolved relative links ([details](https://pd4ml.com/docs/#6.2-reading-the-source)).
717
+ - **Settings go before `readHTML`.** Page size, headers, fonts and styles apply to the layout
718
+ `readHTML` makes; writes afterwards only choose the output format.
719
+ - **Write several formats from one read.** PDF, DOCX and page images from a single `readHTML`
720
+ cost one layout.
721
+ - **Print CSS works.** `@page { size: A4 landscape; margin: 15mm }`, `@media print` and
722
+ `page-break-*` / `break-*` are honoured. They are often simpler than API calls.
723
+ - **Control the scale with the HTML width.** If text is too small, or a layout wraps
724
+ differently than in the browser, set `setHtmlWidth` to the width the page was designed for.
725
+ - **Index fonts once** with `-configure.fonts`, instead of pointing `useTTF` at a large
726
+ unindexed directory on every conversion.
727
+ - **Restrict what documents can load** when converting untrusted HTML. Use `setParam` with
728
+ `Constants.PD4ML_ALLOWED_RESOURCE_LOCATION` and `PD4ML_DISABLE_EXTERNAL_ATTACHMENTS`.
729
+ - **TypeScript**: the package's declarations cover the whole PD4ML API, so editors complete the
730
+ Java methods too.
731
+ - **Pin the build in production.** Use `npx pd4ml install <version>` or a `PD4ML_CLASSPATH` to a
732
+ jar you keep, rather than following the latest release.
733
+
734
+ ## 7. Troubleshooting
735
+
736
+ | Symptom | Cause and remedy |
737
+ |---|---|
738
+ | `BridgeError: PD4ML not found` | No build is configured and the automatic download is off (`PD4ML_AUTO_INSTALL=0`) or failed: run `npx pd4ml install`, or set `PD4ML_CLASSPATH`. |
739
+ | `BridgeError: cannot start java` / "the PD4ML bridge exited" | Java is missing or failed. Check `java -version`, set `JAVA_HOME` or `PD4ML_JAVA`; `PD4ML_BRIDGE_LOG=file` keeps Java's output. |
740
+ | `BridgeError: cannot connect to the PD4ML bridge` | The bridge server is not running, or `PD4ML_BRIDGE` names another port. |
741
+ | A pause and a "downloading" notice on the first conversion | The automatic first-use download ([2.2](#22-install-pd4ml)). Run `npx pd4ml install` at deployment instead. |
742
+ | The server stops responding during conversions | Conversions are synchronous; convert in worker threads ([4.2](#42-the-bridge-threads-and-the-event-loop)). |
743
+ | Images or styles missing | Relative links have no base location, or the server cannot reach the resources (proxies, authentication). See `setCookie` and `setHttpOptions`. |
744
+ | Output is watermarked | No license is installed ([licensing](https://pd4ml.com/docs/#3.-licensing)). |
745
+ | Squares instead of characters | The fonts lack those glyphs. Provide fonts with `useTTF` ([fonts](https://pd4ml.com/docs/#9.-fonts-bookmarks-and-hyperlinks)). |
746
+ | `NoClassDefFoundError: org/bouncycastle/...` with `setPermissions` | The Bouncy Castle jars are missing. Install without `--no-bouncycastle`, or add them to the classpath. |
747
+ | 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. |
748
+ | `JavaError: ... NoSuchMethodException` | No Java method of that name takes those argument types. Check [api.md](api.md); pass floats with `Java.float(10)`, binary data as a `Buffer`. |
749
+
750
+ ## 8. API reference
751
+
752
+ ### Module exports
753
+
754
+ | Export | |
755
+ |---|---|
756
+ | `PD4ML` | the converter: `new PD4ML({ license, htmlWidth, engine })`; every Java method of `com.pd4ml.PD4ML` |
757
+ | `PageSize`, `PageMargins`, `Units`, `Dimensions`, `Constants`, `LayoutEngine`, `ErrorPolicy`, `PdfSpec`, `PdfDocument`, `ProgressListener`, `StatusMessage`, `ResourceProvider`, `CustomTag` | the PD4ML Java classes ([api.md](api.md)) |
758
+ | `htmlToPdf(source, target, options)`, `openPdf(source, password)`, `writePdf(doc, target, { password, permissions })`, `mergePdfs(a, b, target)` | one-call conversion and PDF documents |
759
+ | `version()`, `hasLayoutEngines()`, `toUrl(location)`, `pdfSpec(keyword)` | runtime helpers |
760
+ | `configure(options)`, `shutdown()` | the bridge: `classpath`, `java`, `jvmOptions`, `server`, `logFile` |
761
+ | `install(version, options)`, `installedJar()`, `installedClasspath()`, `installedVersion()`, `availableVersions()` | PD4ML builds |
762
+ | `Java.type(name)`, `Java.proxy(iface, fn)`, `Java.float(n)` | any Java class, Java interfaces implemented in JavaScript, float arguments |
763
+ | `JavaError`, `BridgeError`, `CallbackError`, `InstallError` | errors |
764
+
765
+ ### `PD4ML` methods with Node additions
766
+
767
+ | Method | |
768
+ |---|---|
769
+ | `readHTML(source, baseUrl, encoding)` | `source`: a path, URL string or object, `Buffer`/`Uint8Array`, or a Java `URL`/`InputStream` |
770
+ | `readHtmlString(html, baseUrl)` | the document as a string |
771
+ | `writePDF(target, spec)` | `target`: a path, file descriptor or writable stream; none: returns a `Buffer`. `spec`: a `PdfSpec`, a keyword (`'pdf/a-2b'`) or `'PDF'`/`'PDFA'`/`'PDFUA'` |
772
+ | `writeDOCX(target)`, `writeRTF(target, rtfWmf)` | as `writePDF` |
773
+ | `renderAsImages(format)`, `renderPng()` | pages as encoded images (Buffers); `renderAsImages(dir, name, format)` writes files |
774
+ | `monitorProgressWith(fn)` | a function `(messageId, progress, note, msec)` or a Java `ProgressListener` |
775
+ | `useHtmlRenderer(engine, htmlWidth, params)` | the layout engine by name; `pd.engine` tells which one is in use |
776
+
777
+ All other methods are those of the Java class `com.pd4ml.PD4ML` ([api.md](api.md#pd4ml),
778
+ [Java API](https://pd4ml.com/docs/#6.-the-java-api)); static ones are called on the class
779
+ (`PD4ML.getVersion()`). The Java object itself is `pd.java`.
780
+
781
+ `ResourceProvider` and `CustomTag` are extended in **Java**: register yours with
782
+ `addCustomResourceProvider` / `addCustomTagHandler`, and put its classes on the classpath. Java
783
+ *interfaces* (such as `ProgressListener`) can be implemented in JavaScript with `Java.proxy()`.
784
+ Examples: [resource loaders](https://github.com/zxfr/pd4ml-examples/blob/master/src/main/java/advanced/A005AddCustomResourceLoader.java),
785
+ [custom tags](https://github.com/zxfr/pd4ml-examples/blob/master/src/main/java/advanced/A008AddingCustomTagRenderer.java).
786
+
787
+ ## 9. Further reading
788
+
789
+ - [PD4ML documentation](https://pd4ml.com/docs/) and the full [programmer's manual](https://pd4ml.com/docs/pd4ml-manual.html)
790
+ - [Usage examples](https://pd4ml.com/docs/usage-examples.html)
791
+ - [HTML5 element support matrix](https://pd4ml.com/docs/html5-element-support-matrix.html)
792
+ - [Command-line tool reference](https://pd4ml.com/docs/pd4cmd-command-line-reference.html)
793
+ - [ZUGFeRD and Factur-X invoices](https://pd4ml.com/docs/features/zugferd-and-factur-x-pdf-invoices.html)
794
+ - [PDF post-processing toolkit](https://pd4ml.com/docs/#15.-the-pdf-post-processing-toolkit)
795
+ - [Java examples on GitHub](https://github.com/zxfr/pd4ml-examples)