pd4ml 0.0.0-stage → 1.0.1
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/README.md +88 -3
- package/bin/pd4ml.js +131 -0
- package/docs/api.md +546 -0
- package/docs/guide.md +793 -0
- package/index.d.ts +655 -0
- package/index.js +61 -0
- package/index.mjs +40 -0
- package/lib/bridge-worker.js +64 -0
- package/lib/bridge.js +309 -0
- package/lib/errors.js +55 -0
- package/lib/installer.js +218 -0
- package/lib/java.js +149 -0
- package/lib/pd4ml-bridge.jar +0 -0
- package/lib/pd4ml.js +192 -0
- package/lib/pdf.js +63 -0
- package/lib/runtime.js +87 -0
- package/package.json +50 -6
package/README.md
CHANGED
|
@@ -1,3 +1,88 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# pd4ml — PD4ML for Node.js
|
|
2
|
+
|
|
3
|
+
HTML and CSS to **PDF**, **PDF/A**, **PDF/UA**, **ZUGFeRD/Factur-X**, **DOCX**, **RTF** and page
|
|
4
|
+
**images**, with the full public [PD4ML](https://pd4ml.com) API (Java package `com.pd4ml`)
|
|
5
|
+
available to Node.js applications. New converters use PD4ML's **v42 layout engine** by default.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
npm install pd4ml
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The package is on npm (<https://www.npmjs.com/package/pd4ml>); its contents and releases are also
|
|
12
|
+
on GitHub (<https://github.com/zxfr/pd4ml-node>).
|
|
13
|
+
|
|
14
|
+
```js
|
|
15
|
+
const { PD4ML, PageSize, PageMargins, Units, PdfSpec } = require('pd4ml');
|
|
16
|
+
|
|
17
|
+
const pd = new PD4ML(); // v42 layout engine
|
|
18
|
+
pd.setPageSize(PageSize.A4);
|
|
19
|
+
pd.setPageMargins(new PageMargins(15, 15, 15, 15, Units.MM));
|
|
20
|
+
pd.readHTML('invoice.html'); // a path, URL or Buffer
|
|
21
|
+
pd.writePDF('invoice.pdf', PdfSpec.PDFA_3B); // a path, fd or stream; no target: a Buffer
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The first use downloads the most recent PD4ML build (see *Installation* below). TypeScript
|
|
25
|
+
declarations of the whole API are included.
|
|
26
|
+
|
|
27
|
+
## Requirements
|
|
28
|
+
|
|
29
|
+
- Node.js 18 or later.
|
|
30
|
+
- Java 8 or later (17 or 21 recommended), found through `PD4ML_JAVA`, `JAVA_HOME` or the `PATH`.
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
The package contains the Node module only; PD4ML itself comes from the PD4ML Maven repository
|
|
35
|
+
(<https://pd4ml.com/maven2>):
|
|
36
|
+
|
|
37
|
+
- **Automatically.** If no PD4ML build is installed or configured, the first use of PD4ML
|
|
38
|
+
downloads the most recent 4.x release, with the Bouncy Castle jars (for encryption and
|
|
39
|
+
signing). Every file is verified against its SHA-1 checksum and stored in a per-user cache
|
|
40
|
+
directory (`%LOCALAPPDATA%\pd4ml-node`, `~/.cache/pd4ml-node`, or `PD4ML_HOME`).
|
|
41
|
+
- **Ahead of time**, better for servers and container images: `npx pd4ml install`. Run it again
|
|
42
|
+
to update to a newer release.
|
|
43
|
+
|
|
44
|
+
`PD4ML_AUTO_INSTALL=0` turns the automatic download off. `npx pd4ml list` shows the available
|
|
45
|
+
versions, `npx pd4ml install 4.0.23fx2` installs a specific one, and `PD4ML_CLASSPATH` /
|
|
46
|
+
`PD4ML_CLASSPATH_FILE` point the module at any other build.
|
|
47
|
+
|
|
48
|
+
## How it works
|
|
49
|
+
|
|
50
|
+
PD4ML runs in a small Java bridge process started on first use, one per Node process (or one
|
|
51
|
+
shared server: `npx pd4ml serve`, `PD4ML_BRIDGE=tcp://127.0.0.1:8723`). The API is synchronous:
|
|
52
|
+
`pd.writePDF()` returns the PDF. Like any CPU-bound call, a conversion blocks the event loop
|
|
53
|
+
while it runs; servers under concurrent load convert in worker threads.
|
|
54
|
+
|
|
55
|
+
- Java methods keep their Java names; the PD4ML classes are exported under their own names
|
|
56
|
+
(`PageSize.A4`, `new PageMargins(...)`, `PdfSpec.getInstance('pdf/a-2b')`), any other Java class
|
|
57
|
+
through `Java.type(name)`.
|
|
58
|
+
- Helpers on Java objects start with `$`: `$get(field)`, `$set(field, value)`, `$class`, `$enum`.
|
|
59
|
+
- `Java.float(10)` passes a number as a Java float; Java's `byte[]` is a `Buffer`.
|
|
60
|
+
- Java exceptions are `JavaError`s (`e.isInstanceOf('java.io.IOException')`).
|
|
61
|
+
|
|
62
|
+
## Command line
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
npx pd4ml convert page.html page.pdf [--spec pdf/a-2b] [--engine v40] # also .docx, .rtf, .png
|
|
66
|
+
npx pd4ml version
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Documentation
|
|
70
|
+
|
|
71
|
+
- **User guide**: [docs/guide.md](docs/guide.md) — prerequisites, installation, how it works,
|
|
72
|
+
use cases with code, tips, troubleshooting.
|
|
73
|
+
- **API reference**: [docs/api.md](docs/api.md) — every class and method, generated from PD4ML.
|
|
74
|
+
- The PD4ML manual: <https://pd4ml.com/docs/>.
|
|
75
|
+
- Licensing: without a license, PD4ML runs in evaluation mode and watermarks its output. See
|
|
76
|
+
<https://pd4ml.com/docs/#3.-licensing>.
|
|
77
|
+
|
|
78
|
+
## Development
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
npm test # node --test; PD4ML_NETWORK_TESTS=1 adds the download test
|
|
82
|
+
npm run gen-types # index.d.ts and docs/api.md from PD4ML's classes (javap)
|
|
83
|
+
npm run build-bridge # lib/pd4ml-bridge.jar from java/src
|
|
84
|
+
node tools/dist.js # branch dist (pushed as github.com/zxfr/pd4ml-node main) + pd4ml-<version>.tgz
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The tests use `./pd4ml.classpath` (a PD4ML development build) when present, else the installed
|
|
88
|
+
build; they include every JavaScript snippet of the guide.
|
package/bin/pd4ml.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// The pd4ml command: install/list PD4ML builds, show the version, run a bridge server, convert.
|
|
4
|
+
const { spawn } = require('child_process');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const pd4ml = require('..');
|
|
7
|
+
const installer = require('../lib/installer');
|
|
8
|
+
const { Bridge } = require('../lib/bridge');
|
|
9
|
+
|
|
10
|
+
const USAGE = `usage: pd4ml <command> [options]
|
|
11
|
+
|
|
12
|
+
install [VERSION] [--snapshots] [--jakarta] [--home DIR] [--force] [--no-bouncycastle]
|
|
13
|
+
download a PD4ML build (default: the most recent 4.* release), or update to it
|
|
14
|
+
list [--snapshots] [--jakarta]
|
|
15
|
+
the versions in the PD4ML repository (* the installed one)
|
|
16
|
+
version the PD4ML version the module runs (downloads it on first use)
|
|
17
|
+
serve [--port N] run a bridge server (default port 8723) for PD4ML_BRIDGE=tcp://127.0.0.1:N
|
|
18
|
+
convert INPUT OUTPUT [--format pdf|docx|rtf|png] [--page-size A4] [--landscape]
|
|
19
|
+
[--margins 10,10,10,10] [--width PX] [--engine v42|v40] [--spec pdf/a-2b]
|
|
20
|
+
[--footer HTML] [--bookmarks]
|
|
21
|
+
convert a file or URL; the format follows OUTPUT's extension by default
|
|
22
|
+
|
|
23
|
+
PD4ML_CLASSPATH / PD4ML_CLASSPATH_FILE / PD4ML_HOME / PD4ML_JAVA choose the build and Java.
|
|
24
|
+
https://pd4ml.com/docs/`;
|
|
25
|
+
|
|
26
|
+
const FLAGS = new Set(['snapshots', 'jakarta', 'force', 'no-bouncycastle', 'landscape', 'bookmarks']);
|
|
27
|
+
|
|
28
|
+
function parse(argv) {
|
|
29
|
+
const positional = [];
|
|
30
|
+
const named = {};
|
|
31
|
+
for (let i = 0; i < argv.length; i++) {
|
|
32
|
+
const a = argv[i];
|
|
33
|
+
if (!a.startsWith('--')) { positional.push(a); continue; }
|
|
34
|
+
const eq = a.indexOf('=');
|
|
35
|
+
const name = eq > 0 ? a.slice(2, eq) : a.slice(2);
|
|
36
|
+
if (eq > 0) named[name] = a.slice(eq + 1);
|
|
37
|
+
else if (!FLAGS.has(name) && i + 1 < argv.length && !argv[i + 1].startsWith('--')) named[name] = argv[++i];
|
|
38
|
+
else named[name] = true;
|
|
39
|
+
}
|
|
40
|
+
return { positional, named };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function convert({ positional, named }) {
|
|
44
|
+
if (positional.length !== 2) throw new Error('convert: INPUT and OUTPUT expected');
|
|
45
|
+
const [input, output] = positional;
|
|
46
|
+
const format = String(named.format || path.extname(output).slice(1)).toLowerCase();
|
|
47
|
+
const pd = new pd4ml.PD4ML({ htmlWidth: named.width ? Number(named.width) : undefined, engine: named.engine });
|
|
48
|
+
if (named['page-size']) {
|
|
49
|
+
const size = pd4ml.PageSize.getInstance(String(named['page-size']).toLowerCase());
|
|
50
|
+
if (!size) throw new Error(`unknown page size '${named['page-size']}'`);
|
|
51
|
+
pd.setPageSize(named.landscape ? size.rotate() : size);
|
|
52
|
+
} else if (named.landscape) {
|
|
53
|
+
pd.setPageSize(pd4ml.PageSize.A4.rotate());
|
|
54
|
+
}
|
|
55
|
+
if (named.margins) {
|
|
56
|
+
let mm = String(named.margins).split(',').map(s => Number(s.trim()));
|
|
57
|
+
if (mm.length === 1) mm = [mm[0], mm[0], mm[0], mm[0]];
|
|
58
|
+
if (mm.length !== 4 || mm.some(Number.isNaN)) throw new Error('--margins: one or four values (mm)');
|
|
59
|
+
pd.setPageMargins(new pd4ml.PageMargins(mm[0], mm[1], mm[2], mm[3], pd4ml.Units.MM));
|
|
60
|
+
}
|
|
61
|
+
if (named.footer) pd.setPageFooter(String(named.footer), 30, '1+');
|
|
62
|
+
if (named.bookmarks) pd.generateBookmarksFromHeadings(true);
|
|
63
|
+
pd.readHTML(input);
|
|
64
|
+
switch (format) {
|
|
65
|
+
case 'pdf': pd.writePDF(output, named.spec || null); break;
|
|
66
|
+
case 'docx': pd.writeDOCX(output); break;
|
|
67
|
+
case 'rtf': pd.writeRTF(output); break;
|
|
68
|
+
case 'png': {
|
|
69
|
+
const pages = pd.renderPng();
|
|
70
|
+
if (pages.length === 1) {
|
|
71
|
+
require('fs').writeFileSync(output, pages[0]);
|
|
72
|
+
} else {
|
|
73
|
+
const stem = output.replace(/\.png$/i, '');
|
|
74
|
+
pages.forEach((p, i) => require('fs').writeFileSync(`${stem}-${i + 1}.png`, p));
|
|
75
|
+
}
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
default: throw new Error(`unknown format '${format}' (pdf, docx, rtf, png)`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function main(argv) {
|
|
83
|
+
const [command, ...rest] = argv;
|
|
84
|
+
if (!command || ['-h', '--help', 'help'].includes(command)) {
|
|
85
|
+
console.log(USAGE);
|
|
86
|
+
return command ? 0 : 2;
|
|
87
|
+
}
|
|
88
|
+
const args = parse(rest);
|
|
89
|
+
const n = args.named;
|
|
90
|
+
switch (command) {
|
|
91
|
+
case 'install':
|
|
92
|
+
console.log(installer.install(args.positional[0] || 'latest', {
|
|
93
|
+
snapshots: !!n.snapshots, jakarta: !!n.jakarta, home: n.home, force: !!n.force, bouncycastle: !n['no-bouncycastle'],
|
|
94
|
+
}));
|
|
95
|
+
return 0;
|
|
96
|
+
case 'list': {
|
|
97
|
+
const installed = installer.installedVersion();
|
|
98
|
+
for (const v of installer.sortVersions(installer.availableVersions())) {
|
|
99
|
+
if (!n.snapshots && v.endsWith('-SNAPSHOT')) continue;
|
|
100
|
+
if (v.includes('-jakarta') !== !!n.jakarta) continue;
|
|
101
|
+
console.log((v === installed ? '* ' : ' ') + v);
|
|
102
|
+
}
|
|
103
|
+
return 0;
|
|
104
|
+
}
|
|
105
|
+
case 'version':
|
|
106
|
+
console.log(pd4ml.version());
|
|
107
|
+
return 0;
|
|
108
|
+
case 'serve': {
|
|
109
|
+
const port = Number(n.port || 8723);
|
|
110
|
+
const cmd = Bridge.command(undefined, port);
|
|
111
|
+
console.error(`PD4ML bridge on tcp://127.0.0.1:${port} (Ctrl+C to stop)`);
|
|
112
|
+
const child = spawn(cmd[0], cmd.slice(1), { stdio: 'inherit' });
|
|
113
|
+
child.on('exit', code => process.exit(code === null ? 1 : code));
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
case 'convert':
|
|
117
|
+
convert(args);
|
|
118
|
+
return 0;
|
|
119
|
+
default:
|
|
120
|
+
console.error(`pd4ml: unknown command '${command}'\n\n${USAGE}`);
|
|
121
|
+
return 2;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const code = main(process.argv.slice(2));
|
|
127
|
+
if (code !== null) process.exitCode = code;
|
|
128
|
+
} catch (e) {
|
|
129
|
+
console.error('pd4ml: ' + e.message);
|
|
130
|
+
process.exitCode = 1;
|
|
131
|
+
}
|