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/README.md CHANGED
@@ -1,3 +1,88 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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 github:zxfr/pd4ml-node
9
+ ```
10
+
11
+ or a release package from <https://github.com/zxfr/pd4ml-node/releases>
12
+ (`npm install https://github.com/zxfr/pd4ml-node/releases/download/v1.0.0/pd4ml-1.0.0.tgz`). Either way the module is `pd4ml`.
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
+ }