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 +88 -3
- package/bin/pd4ml.js +131 -0
- package/docs/api.md +546 -0
- package/docs/guide.md +795 -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/lib/installer.js
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Downloads a PD4ML build from the PD4ML Maven repository and makes it the module's default:
|
|
3
|
+
//
|
|
4
|
+
// pd4ml install the most recent 4.* release (also: npx pd4ml install)
|
|
5
|
+
// pd4ml install --snapshots the most recent 4.* build, snapshots included
|
|
6
|
+
// pd4ml install 4.0.23fx2 an exact version
|
|
7
|
+
//
|
|
8
|
+
// or install() / installAsync() from code. The jar is stored in the user's cache directory
|
|
9
|
+
// (PD4ML_HOME to choose another) with the Bouncy Castle jars PD4ML needs for encryption and
|
|
10
|
+
// signing, every file verified against its published SHA-1 checksum. With no build installed
|
|
11
|
+
// or configured, the first use of PD4ML downloads one (PD4ML_AUTO_INSTALL=0 turns that off).
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const os = require('os');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const crypto = require('crypto');
|
|
16
|
+
const { execFileSync } = require('child_process');
|
|
17
|
+
const { InstallError } = require('./errors');
|
|
18
|
+
|
|
19
|
+
const REPOSITORY = 'https://pd4ml.com/maven2';
|
|
20
|
+
const CENTRAL = 'https://repo1.maven.org/maven2';
|
|
21
|
+
const GROUP_PATH = 'com/pd4ml/pd4ml';
|
|
22
|
+
/** The Bouncy Castle version PD4ML is built against (https://pd4ml.com/docs/#2.-obtaining-pd4ml). */
|
|
23
|
+
const BOUNCYCASTLE_VERSION = '1.84';
|
|
24
|
+
const BOUNCYCASTLE = ['bcprov-jdk18on', 'bcpkix-jdk18on', 'bcutil-jdk18on'];
|
|
25
|
+
const MANIFEST = 'installed.json';
|
|
26
|
+
const VERSION = /^(\d+)\.(\d+)\.(\d+)(?:fx(\d+)(?:a(\d+))?)?(-jakarta)?(-SNAPSHOT)?$/;
|
|
27
|
+
|
|
28
|
+
// ------------------------------------------------------------------------------------- versions
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* An ordering key for a PD4ML version (4.1.1, 4.0.23fx2, 4.0.23fx1a1, 4.1.2-SNAPSHOT), or null
|
|
32
|
+
* for an unknown form. A snapshot sorts below its release, an fxNaM pre-release below fxN.
|
|
33
|
+
*/
|
|
34
|
+
function versionKey(version) {
|
|
35
|
+
const m = VERSION.exec(version);
|
|
36
|
+
if (!m) return null;
|
|
37
|
+
const n = i => (m[i] === undefined ? 0 : Number(m[i]));
|
|
38
|
+
return [n(1), n(2), n(3), n(4), m[5] === undefined ? 1 : 0, n(5), m[7] === undefined ? 1 : 0];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function compareKeys(a, b) {
|
|
42
|
+
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return a[i] - b[i];
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Versions sorted oldest first (unknown forms dropped). */
|
|
47
|
+
function sortVersions(versions) {
|
|
48
|
+
return versions.filter(v => versionKey(v)).sort((a, b) => compareKeys(versionKey(a), versionKey(b)));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The most recent `major`.* version: releases only unless `snapshots`, -jakarta only with `jakarta`. */
|
|
52
|
+
function selectVersion(versions, { major = 4, snapshots = false, jakarta = false } = {}) {
|
|
53
|
+
const candidates = sortVersions(versions.filter(v => {
|
|
54
|
+
const k = versionKey(v);
|
|
55
|
+
return k && k[0] === major && (snapshots || !v.endsWith('-SNAPSHOT')) && v.includes('-jakarta') === jakarta;
|
|
56
|
+
}));
|
|
57
|
+
if (!candidates.length) throw new InstallError(`no ${major}.* version in the repository`);
|
|
58
|
+
return candidates[candidates.length - 1];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ------------------------------------------------------------------------------------- download
|
|
62
|
+
|
|
63
|
+
async function fetchBytes(url) {
|
|
64
|
+
let response;
|
|
65
|
+
try {
|
|
66
|
+
response = await fetch(url, { headers: { 'User-Agent': 'pd4ml-node' } });
|
|
67
|
+
} catch (e) {
|
|
68
|
+
throw new InstallError(`cannot download ${url}: ${e.cause ? e.cause.message : e.message}`);
|
|
69
|
+
}
|
|
70
|
+
if (!response.ok) throw new InstallError(`cannot download ${url} (HTTP ${response.status})`);
|
|
71
|
+
return Buffer.from(await response.arrayBuffer());
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** The versions the repository lists. */
|
|
75
|
+
async function availableVersionsAsync(repository = REPOSITORY) {
|
|
76
|
+
const xml = (await fetchBytes(`${repository}/${GROUP_PATH}/maven-metadata.xml`)).toString('utf8');
|
|
77
|
+
return [...xml.matchAll(/<version>([^<]+)<\/version>/g)].map(m => m[1].trim());
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function jarName(version, repository) {
|
|
81
|
+
if (!version.endsWith('-SNAPSHOT')) return `pd4ml-${version}.jar`;
|
|
82
|
+
const xml = (await fetchBytes(`${repository}/${GROUP_PATH}/${version}/maven-metadata.xml`)).toString('utf8');
|
|
83
|
+
for (const sv of xml.matchAll(/<snapshotVersion>([\s\S]*?)<\/snapshotVersion>/g)) {
|
|
84
|
+
const tag = t => (sv[1].match(new RegExp(`<${t}>([^<]*)</${t}>`)) || [])[1];
|
|
85
|
+
if (tag('extension') === 'jar' && !tag('classifier') && tag('value')) return `pd4ml-${tag('value')}.jar`;
|
|
86
|
+
}
|
|
87
|
+
const ts = (xml.match(/<timestamp>([^<]+)</) || [])[1];
|
|
88
|
+
const build = (xml.match(/<buildNumber>([^<]+)</) || [])[1];
|
|
89
|
+
if (ts && build) return `pd4ml-${version.slice(0, -'-SNAPSHOT'.length)}-${ts}-${build}.jar`;
|
|
90
|
+
throw new InstallError(`no jar listed for ${version}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function download(url, file, { force, quiet }) {
|
|
94
|
+
if (!force && fs.existsSync(file)) return;
|
|
95
|
+
if (!quiet) process.stderr.write(`downloading ${url}\n`);
|
|
96
|
+
const data = await fetchBytes(url);
|
|
97
|
+
const expected = (await fetchBytes(url + '.sha1')).toString('ascii').trim().split(/\s+/)[0].toLowerCase();
|
|
98
|
+
const actual = crypto.createHash('sha1').update(data).digest('hex');
|
|
99
|
+
if (expected && actual !== expected) throw new InstallError(`checksum mismatch for ${url}: ${actual} != ${expected}`);
|
|
100
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
101
|
+
const tmp = `${file}.${process.pid}.part`;
|
|
102
|
+
fs.writeFileSync(tmp, data);
|
|
103
|
+
fs.renameSync(tmp, file);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Downloads a PD4ML build (unless already there) and makes it the default one; resolves to the
|
|
108
|
+
* jar's path. Options: snapshots, jakarta, repository, home, force, quiet, bouncycastle (default true).
|
|
109
|
+
*/
|
|
110
|
+
async function installAsync(version = 'latest', options = {}) {
|
|
111
|
+
const repository = options.repository || REPOSITORY;
|
|
112
|
+
const home = options.home || defaultHome();
|
|
113
|
+
if (version === 'latest') {
|
|
114
|
+
version = selectVersion(await availableVersionsAsync(repository), { snapshots: options.snapshots, jakarta: options.jakarta });
|
|
115
|
+
}
|
|
116
|
+
const name = await jarName(version, repository);
|
|
117
|
+
const jar = path.join(home, name);
|
|
118
|
+
await download(`${repository}/${GROUP_PATH}/${version}/${name}`, jar, options);
|
|
119
|
+
const libs = [];
|
|
120
|
+
if (options.bouncycastle !== false) {
|
|
121
|
+
for (const artifact of BOUNCYCASTLE) {
|
|
122
|
+
const lib = `${artifact}-${BOUNCYCASTLE_VERSION}.jar`;
|
|
123
|
+
await download(`${CENTRAL}/org/bouncycastle/${artifact}/${BOUNCYCASTLE_VERSION}/${lib}`, path.join(home, 'lib', lib), options);
|
|
124
|
+
libs.push(`lib/${lib}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const manifest = { ...readManifest(home), version, jar: name, libs, repository, installed: new Date().toISOString() };
|
|
128
|
+
const tmp = path.join(home, `${MANIFEST}.${process.pid}.part`); // several processes may install at once
|
|
129
|
+
fs.writeFileSync(tmp, JSON.stringify(manifest, null, 2));
|
|
130
|
+
fs.renameSync(tmp, path.join(home, MANIFEST));
|
|
131
|
+
if (!options.quiet) process.stderr.write(`PD4ML ${version}: ${jar}\n`);
|
|
132
|
+
return jar;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Synchronous forms: the download runs in a child Node process, so they can be used from the
|
|
136
|
+
// synchronous API (and from the first use of PD4ML).
|
|
137
|
+
function runSync(task, args) {
|
|
138
|
+
try {
|
|
139
|
+
const out = execFileSync(process.execPath, [__filename, '--run', task, JSON.stringify(args)], {
|
|
140
|
+
stdio: ['ignore', 'pipe', 'inherit'],
|
|
141
|
+
windowsHide: true,
|
|
142
|
+
});
|
|
143
|
+
return JSON.parse(out.toString('utf8'));
|
|
144
|
+
} catch (e) {
|
|
145
|
+
const message = e.stdout && e.stdout.length ? e.stdout.toString('utf8').trim() : e.message;
|
|
146
|
+
throw new InstallError(message);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Synchronous installAsync(): downloads a PD4ML build and returns the jar's path. */
|
|
151
|
+
function install(version = 'latest', options = {}) {
|
|
152
|
+
return runSync('install', [version, options]);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** The versions the repository lists (synchronous). */
|
|
156
|
+
function availableVersions(repository = REPOSITORY) {
|
|
157
|
+
return runSync('versions', [repository]);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// -------------------------------------------------------------------------------- installation
|
|
161
|
+
|
|
162
|
+
/** Where downloaded builds go: PD4ML_HOME, else the user's cache directory. */
|
|
163
|
+
function defaultHome() {
|
|
164
|
+
if (process.env.PD4ML_HOME) return process.env.PD4ML_HOME;
|
|
165
|
+
if (process.platform === 'win32') {
|
|
166
|
+
return path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'), 'pd4ml-node');
|
|
167
|
+
}
|
|
168
|
+
return path.join(process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache'), 'pd4ml-node');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function readManifest(home) {
|
|
172
|
+
try {
|
|
173
|
+
return JSON.parse(fs.readFileSync(path.join(home, MANIFEST), 'utf8'));
|
|
174
|
+
} catch (e) {
|
|
175
|
+
return {};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** The jar install() made the default, or null. */
|
|
180
|
+
function installedJar(home = defaultHome()) {
|
|
181
|
+
const { jar } = readManifest(home);
|
|
182
|
+
const p = jar ? path.join(home, jar) : null;
|
|
183
|
+
return p && fs.existsSync(p) ? p : null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** The installed build's classpath: the PD4ML jar and its companion jars; empty if none. */
|
|
187
|
+
function installedClasspath(home = defaultHome()) {
|
|
188
|
+
const jar = installedJar(home);
|
|
189
|
+
if (!jar) return [];
|
|
190
|
+
const libs = (readManifest(home).libs || []).map(l => path.join(home, l)).filter(p => fs.existsSync(p));
|
|
191
|
+
return [jar, ...libs];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** The version install() made the default, or null. */
|
|
195
|
+
function installedVersion(home = defaultHome()) {
|
|
196
|
+
return installedJar(home) ? readManifest(home).version || null : null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Whether the first use of PD4ML may download a build (PD4ML_AUTO_INSTALL, default on). */
|
|
200
|
+
function autoInstallEnabled() {
|
|
201
|
+
return !['0', 'false', 'no', 'off'].includes(String(process.env.PD4ML_AUTO_INSTALL || '1').trim().toLowerCase());
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
module.exports = {
|
|
205
|
+
REPOSITORY, BOUNCYCASTLE_VERSION,
|
|
206
|
+
versionKey, sortVersions, selectVersion,
|
|
207
|
+
install, installAsync, availableVersions, availableVersionsAsync,
|
|
208
|
+
defaultHome, installedJar, installedClasspath, installedVersion, autoInstallEnabled,
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// the child process of runSync()
|
|
212
|
+
if (require.main === module && process.argv[2] === '--run') {
|
|
213
|
+
const [task, json] = process.argv.slice(3);
|
|
214
|
+
const args = JSON.parse(json);
|
|
215
|
+
const work = task === 'install' ? installAsync(...args) : availableVersionsAsync(...args);
|
|
216
|
+
work.then(result => process.stdout.write(JSON.stringify(result)),
|
|
217
|
+
e => { process.stdout.write(e.message); process.exitCode = 1; });
|
|
218
|
+
}
|
package/lib/java.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Java objects and classes as JavaScript proxies.
|
|
3
|
+
//
|
|
4
|
+
// const map = new (Java.type('java.util.HashMap'))();
|
|
5
|
+
// map.put('k', 'v'); // any Java method, by its Java name
|
|
6
|
+
// PageSize.A4.$get('width') // a public field
|
|
7
|
+
// PageSize.A4.getWidth() // a method
|
|
8
|
+
// Units.MM.$enum // an enum constant's name
|
|
9
|
+
//
|
|
10
|
+
// Helpers are $-prefixed ($get, $set, $call, $class, $enum, $instanceOf) so they can never hide
|
|
11
|
+
// a Java method of the same name. Objects are released in Java when the JavaScript object is
|
|
12
|
+
// garbage collected.
|
|
13
|
+
const util = require('util');
|
|
14
|
+
const { Bridge, JavaFloat } = require('./bridge');
|
|
15
|
+
|
|
16
|
+
const HANDLE = Symbol('pd4ml.handle');
|
|
17
|
+
const BRIDGE = Symbol('pd4ml.bridge');
|
|
18
|
+
|
|
19
|
+
function isJavaObject(v) {
|
|
20
|
+
return v !== null && (typeof v === 'object' || typeof v === 'function') && v[HANDLE] !== undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function handleOf(v) { return v[HANDLE]; }
|
|
24
|
+
|
|
25
|
+
function wrapObject(bridge, handle, className, enumName) {
|
|
26
|
+
const target = { [HANDLE]: handle, [BRIDGE]: bridge, className, enumName: enumName || null };
|
|
27
|
+
let proxy;
|
|
28
|
+
const javaToString = () => bridge.call(proxy, 'toString', []);
|
|
29
|
+
proxy = new Proxy(target, {
|
|
30
|
+
get(t, prop) {
|
|
31
|
+
if (prop === HANDLE || prop === BRIDGE) return t[prop];
|
|
32
|
+
if (typeof prop === 'symbol') {
|
|
33
|
+
if (prop === Symbol.toPrimitive) return () => javaToString();
|
|
34
|
+
if (prop === util.inspect.custom) return () => `JavaObject<${className}> ${t.enumName || javaToString()}`;
|
|
35
|
+
if (prop === Symbol.toStringTag) return 'JavaObject';
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
switch (prop) {
|
|
39
|
+
case 'then': return undefined; // not a thenable: `await obj` gives the object
|
|
40
|
+
case '$class': return className;
|
|
41
|
+
case '$enum': return t.enumName;
|
|
42
|
+
case '$get': return field => bridge.getField(proxy, field);
|
|
43
|
+
case '$set': return (field, value) => bridge.setField(proxy, field, value);
|
|
44
|
+
case '$call': return (method, ...args) => bridge.call(proxy, method, args);
|
|
45
|
+
case '$instanceOf': return cls => bridge.instanceOf(proxy, cls);
|
|
46
|
+
case 'toString': return javaToString;
|
|
47
|
+
case 'toJSON': return () => (t.enumName !== null ? t.enumName : javaToString());
|
|
48
|
+
default: return (...args) => bridge.call(proxy, prop, args);
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
set() {
|
|
52
|
+
throw new TypeError('set Java fields with obj.$set(name, value)');
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
bridge.track(proxy, handle);
|
|
56
|
+
return proxy;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ------------------------------------------------------------------------------------ classes
|
|
60
|
+
|
|
61
|
+
const descriptions = new WeakMap(); // bridge -> Map(class -> describe() result + constant cache)
|
|
62
|
+
|
|
63
|
+
function described(bridge, name) {
|
|
64
|
+
let byClass = descriptions.get(bridge);
|
|
65
|
+
if (!byClass) {
|
|
66
|
+
byClass = new Map();
|
|
67
|
+
descriptions.set(bridge, byClass);
|
|
68
|
+
}
|
|
69
|
+
let d = byClass.get(name);
|
|
70
|
+
if (!d) {
|
|
71
|
+
const info = bridge.describe(name);
|
|
72
|
+
d = {
|
|
73
|
+
className: info.class,
|
|
74
|
+
fields: new Set(info.fields),
|
|
75
|
+
constants: new Set(info.constants),
|
|
76
|
+
methods: new Set(info.methods),
|
|
77
|
+
values: new Map(),
|
|
78
|
+
};
|
|
79
|
+
byClass.set(name, d);
|
|
80
|
+
}
|
|
81
|
+
return d;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* A Java class: `type('java.util.Locale').getDefault()` (static methods), `PageSize.A4` (static
|
|
86
|
+
* fields), `new PageMargins(10, 10, 10, 10, Units.MM)` (constructors). Nested classes by '.'
|
|
87
|
+
* or '$' (`com.pd4ml.Constants.LayoutEngine`).
|
|
88
|
+
*/
|
|
89
|
+
function type(name) {
|
|
90
|
+
const target = function JavaClass() {};
|
|
91
|
+
return new Proxy(target, {
|
|
92
|
+
get(t, prop) {
|
|
93
|
+
if (typeof prop === 'symbol') {
|
|
94
|
+
if (prop === util.inspect.custom) return () => `JavaClass<${name}>`;
|
|
95
|
+
if (prop === Symbol.toStringTag) return 'JavaClass';
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
switch (prop) {
|
|
99
|
+
case 'then': return undefined;
|
|
100
|
+
case '$name': return name;
|
|
101
|
+
case '$new': return (...args) => Bridge.get().newObject(name, args);
|
|
102
|
+
case '$get': return field => Bridge.get().getField(name, field);
|
|
103
|
+
case '$set': return (field, value) => Bridge.get().setField(name, field, value);
|
|
104
|
+
case '$call': return (method, ...args) => Bridge.get().callStatic(name, method, args);
|
|
105
|
+
case 'toString': return () => name;
|
|
106
|
+
case 'prototype': return t.prototype;
|
|
107
|
+
default: break;
|
|
108
|
+
}
|
|
109
|
+
const bridge = Bridge.get();
|
|
110
|
+
const d = described(bridge, name);
|
|
111
|
+
if (d.fields.has(prop)) {
|
|
112
|
+
if (!d.constants.has(prop)) return bridge.getField(name, prop);
|
|
113
|
+
if (!d.values.has(prop)) d.values.set(prop, bridge.getField(name, prop));
|
|
114
|
+
return d.values.get(prop);
|
|
115
|
+
}
|
|
116
|
+
if (d.methods.has(prop)) return (...args) => bridge.callStatic(name, prop, args);
|
|
117
|
+
return undefined;
|
|
118
|
+
},
|
|
119
|
+
set(t, prop, value) {
|
|
120
|
+
Bridge.get().setField(name, prop, value);
|
|
121
|
+
return true;
|
|
122
|
+
},
|
|
123
|
+
has(t, prop) {
|
|
124
|
+
const d = described(Bridge.get(), name);
|
|
125
|
+
return d.fields.has(prop) || d.methods.has(prop);
|
|
126
|
+
},
|
|
127
|
+
construct(t, args) {
|
|
128
|
+
return Bridge.get().newObject(name, args);
|
|
129
|
+
},
|
|
130
|
+
apply() {
|
|
131
|
+
throw new TypeError(`${name} is a Java class: use new`);
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* A Java object implementing the interface `iface`, its methods calling `fn(method, args)` on
|
|
138
|
+
* this thread while PD4ML runs (`Java.proxy('com.pd4ml.ProgressListener', (m, a) => ...)`).
|
|
139
|
+
*/
|
|
140
|
+
function proxy(iface, fn) {
|
|
141
|
+
return Bridge.get().proxy(iface, fn);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** A number passed to Java as a float/double: `Java.float(10)` (a plain 10 is an int). */
|
|
145
|
+
function float(value) {
|
|
146
|
+
return new JavaFloat(value);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
module.exports = { type, proxy, float, isJavaObject, handleOf, wrapObject };
|
|
Binary file
|
package/lib/pd4ml.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// PD4ML: com.pd4ml.PD4ML with Node I/O and the v42 layout engine by default.
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const java = require('./java');
|
|
5
|
+
const runtime = require('./runtime');
|
|
6
|
+
const { Bridge, Output } = require('./bridge');
|
|
7
|
+
|
|
8
|
+
const PD4MLClass = java.type('com.pd4ml.PD4ML');
|
|
9
|
+
const DEFAULT_ENGINE = 'v42';
|
|
10
|
+
let warnedNoEngines = false;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* An HTML to PDF/DOCX/RTF/image converter. Every method of the Java class com.pd4ml.PD4ML is
|
|
14
|
+
* available by its Java name (setPageSize, addStyle, useTTF...); these also take Node I/O:
|
|
15
|
+
* readHTML (a path, URL or Buffer), readHtmlString, writePDF/writeDOCX/writeRTF (a Buffer is
|
|
16
|
+
* returned, or written to a path, file descriptor or stream), renderAsImages and
|
|
17
|
+
* monitorProgressWith (a JavaScript function).
|
|
18
|
+
*
|
|
19
|
+
* const pd = new PD4ML();
|
|
20
|
+
* pd.setPageSize(PageSize.A4);
|
|
21
|
+
* pd.readHtmlString('<h1>Hello</h1>');
|
|
22
|
+
* const pdf = pd.writePDF();
|
|
23
|
+
*/
|
|
24
|
+
class PD4ML {
|
|
25
|
+
/**
|
|
26
|
+
* @param {object} [options]
|
|
27
|
+
* @param {string} [options.license] a license serial number or license file URL (default:
|
|
28
|
+
* pd4ml.lic on the classpath or in the working directory, else evaluation mode)
|
|
29
|
+
* @param {number} [options.htmlWidth] the width in pixels the HTML is laid out in
|
|
30
|
+
* @param {string|object|false} [options.engine] 'v42', 'v40' or a LayoutEngine; default v42
|
|
31
|
+
* where the build has layout engines, else the build's own; false: the Java default
|
|
32
|
+
*/
|
|
33
|
+
constructor(options = {}) {
|
|
34
|
+
const { license, htmlWidth } = options;
|
|
35
|
+
let { engine } = options;
|
|
36
|
+
const args = [];
|
|
37
|
+
if (license !== undefined && license !== null) args.push(license);
|
|
38
|
+
if (htmlWidth !== undefined && htmlWidth !== null) args.push(htmlWidth);
|
|
39
|
+
/** The Java object (com.pd4ml.PD4ML). */
|
|
40
|
+
this.java = Bridge.get().newObject('com.pd4ml.PD4ML', args);
|
|
41
|
+
/** The layout engine chosen ('v42', 'v40'), or null for the build's own. */
|
|
42
|
+
this.engine = null;
|
|
43
|
+
if (engine === undefined || engine === null) {
|
|
44
|
+
if (runtime.hasLayoutEngines()) {
|
|
45
|
+
engine = DEFAULT_ENGINE;
|
|
46
|
+
} else {
|
|
47
|
+
engine = false;
|
|
48
|
+
if (!warnedNoEngines) {
|
|
49
|
+
warnedNoEngines = true;
|
|
50
|
+
process.emitWarning(`PD4ML ${runtime.version()} has no ${DEFAULT_ENGINE} layout engine (from 4.2): its own engine is used`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (engine !== false) this.useHtmlRenderer(engine);
|
|
55
|
+
return new Proxy(this, {
|
|
56
|
+
get(t, prop, receiver) {
|
|
57
|
+
if (typeof prop === 'symbol' || prop in t) return Reflect.get(t, prop, receiver);
|
|
58
|
+
if (prop === 'then') return undefined;
|
|
59
|
+
return (...a) => t.java.$call(prop, ...a);
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ---------------------------------------------------------------------------- layout engine
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Chooses the layout engine: 'v42', 'v40' or a LayoutEngine, optionally with the HTML width
|
|
68
|
+
* and engine parameters (an object of strings).
|
|
69
|
+
*/
|
|
70
|
+
useHtmlRenderer(engine = DEFAULT_ENGINE, htmlWidth = null, params = null) {
|
|
71
|
+
if (!runtime.hasLayoutEngines()) {
|
|
72
|
+
throw new Error(`PD4ML ${runtime.version()} has no layout engines to choose from (Constants.LayoutEngine is new in 4.2)`);
|
|
73
|
+
}
|
|
74
|
+
const e = typeof engine === 'string' ? java.type('com.pd4ml.Constants$LayoutEngine').valueOf(engine) : engine;
|
|
75
|
+
if (params) {
|
|
76
|
+
const strings = Object.fromEntries(Object.entries(params).map(([k, v]) => [k, String(v)]));
|
|
77
|
+
this.java.useHtmlRenderer(e, htmlWidth === null ? -1 : htmlWidth, strings);
|
|
78
|
+
} else if (htmlWidth !== null) {
|
|
79
|
+
this.java.useHtmlRenderer(e, htmlWidth);
|
|
80
|
+
} else {
|
|
81
|
+
this.java.useHtmlRenderer(e);
|
|
82
|
+
}
|
|
83
|
+
this.engine = e.$enum;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ------------------------------------------------------------------------------------ input
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Reads the document to convert.
|
|
90
|
+
* @param {string|URL|Buffer|Uint8Array|object} source a URL ('https://...', 'file:...'), a local
|
|
91
|
+
* path, the document's bytes, or a Java URL/InputStream
|
|
92
|
+
* @param {string} [baseUrl] where relative links resolve (a URL or a local directory/file); for
|
|
93
|
+
* a path or URL source, its own location
|
|
94
|
+
* @param {string} [encoding] the document's encoding when it does not declare one
|
|
95
|
+
*/
|
|
96
|
+
readHTML(source, baseUrl = null, encoding = null) {
|
|
97
|
+
if (typeof source === 'string' || source instanceof URL
|
|
98
|
+
|| (java.isJavaObject(source) && source.$class === 'java.net.URL')) {
|
|
99
|
+
const url = runtime.toUrl(source);
|
|
100
|
+
encoding === null ? this.java.readHTML(url) : this.java.readHTML(url, encoding);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const content = java.isJavaObject(source) ? source : runtime.bytes(source);
|
|
104
|
+
const base = baseUrl === null ? null : runtime.toUrl(baseUrl);
|
|
105
|
+
if (baseUrl === null && encoding === null) {
|
|
106
|
+
this.java.readHTML(content);
|
|
107
|
+
} else if (encoding === null) {
|
|
108
|
+
this.java.readHTML(content, base);
|
|
109
|
+
} else {
|
|
110
|
+
this.java.readHTML(content, base, encoding);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Reads the document from a string of HTML. */
|
|
115
|
+
readHtmlString(html, baseUrl = null) {
|
|
116
|
+
this.readHTML(Buffer.from(String(html), 'utf8'), baseUrl, 'UTF-8');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ----------------------------------------------------------------------------------- output
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Writes the PDF.
|
|
123
|
+
* @param {string|number|object} [target] a file path, a file descriptor or a writable stream
|
|
124
|
+
* (an HTTP response...); none: the PDF is returned as a Buffer
|
|
125
|
+
* @param {string|object} [spec] a PdfSpec (PdfSpec.PDFA_2B), one of its keywords ('pdf/a-2b',
|
|
126
|
+
* see PdfSpec.listKeywords()), or 'PDF'/'PDFA'/'PDFUA'
|
|
127
|
+
*/
|
|
128
|
+
writePDF(target = null, spec = null) {
|
|
129
|
+
const s = runtime.pdfSpec(spec);
|
|
130
|
+
const outs = [];
|
|
131
|
+
Bridge.get().call(this.java, 'writePDF', s === null ? [new Output()] : [new Output(), s], outs);
|
|
132
|
+
return runtime.deliver(outs[0] || Buffer.alloc(0), target);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Writes a DOCX document (see writePDF for `target`). */
|
|
136
|
+
writeDOCX(target = null) {
|
|
137
|
+
const outs = [];
|
|
138
|
+
Bridge.get().call(this.java, 'writeDOCX', [new Output()], outs);
|
|
139
|
+
return runtime.deliver(outs[0] || Buffer.alloc(0), target);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Writes an RTF document; `rtfWmf`: images as WMF (see writePDF for `target`). */
|
|
143
|
+
writeRTF(target = null, rtfWmf = false) {
|
|
144
|
+
const outs = [];
|
|
145
|
+
Bridge.get().call(this.java, 'writeRTF', [new Output(), rtfWmf], outs);
|
|
146
|
+
return runtime.deliver(outs[0] || Buffer.alloc(0), target);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* renderAsImages(format): the pages as encoded images (Constants.PNG24...), an array of Buffers;
|
|
151
|
+
* renderAsImages(dir, name, format): image files written to a directory;
|
|
152
|
+
* renderAsImages(): Java BufferedImage objects.
|
|
153
|
+
*/
|
|
154
|
+
renderAsImages(...args) {
|
|
155
|
+
if (args.length === 3 && typeof args[0] === 'string') {
|
|
156
|
+
const File = java.type('java.io.File');
|
|
157
|
+
fs.mkdirSync(args[0], { recursive: true });
|
|
158
|
+
return this.java.renderAsImages(new File(args[0]), args[1], args[2]);
|
|
159
|
+
}
|
|
160
|
+
return this.java.renderAsImages(...args);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** The pages as 24-bit PNG images (Buffers). */
|
|
164
|
+
renderPng() {
|
|
165
|
+
return this.java.renderAsImages(java.type('com.pd4ml.Constants').PNG24);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// --------------------------------------------------------------------------------- progress
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Reports conversion progress to a function (messageId, progress, note, msec) - it runs on
|
|
172
|
+
* this thread while PD4ML works - or to a Java ProgressListener.
|
|
173
|
+
*/
|
|
174
|
+
monitorProgressWith(listener) {
|
|
175
|
+
const l = typeof listener === 'function'
|
|
176
|
+
? java.proxy('com.pd4ml.ProgressListener', (method, a) => listener(Number(a[0]), Number(a[1]), a[2], Number(a[3])))
|
|
177
|
+
: listener;
|
|
178
|
+
this.java.monitorProgressWith(l);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
PD4ML.DEFAULT_ENGINE = DEFAULT_ENGINE;
|
|
183
|
+
|
|
184
|
+
// static Java members (PD4ML.getVersion()) on the class itself
|
|
185
|
+
const PD4MLExport = new Proxy(PD4ML, {
|
|
186
|
+
get(t, prop, receiver) {
|
|
187
|
+
if (typeof prop === 'symbol' || prop in t) return Reflect.get(t, prop, receiver);
|
|
188
|
+
return PD4MLClass[prop];
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
module.exports = { PD4ML: PD4MLExport };
|
package/lib/pdf.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// One-call conversion and PDF documents.
|
|
3
|
+
//
|
|
4
|
+
// const pdf = htmlToPdf('report.html'); // a path, URL or Buffer
|
|
5
|
+
// const doc = openPdf(pdf); // a com.pd4ml.PdfDocument
|
|
6
|
+
// writePdf(doc, 'copy.pdf', { password: 'secret' });
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const java = require('./java');
|
|
9
|
+
const runtime = require('./runtime');
|
|
10
|
+
const { Bridge, Output } = require('./bridge');
|
|
11
|
+
const { PD4ML } = require('./pd4ml');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Converts one document to PDF. `source` as for PD4ML#readHTML, `target` as for PD4ML#writePDF.
|
|
15
|
+
* Options: baseUrl, pageSize, pageMargins, spec, engine, htmlWidth.
|
|
16
|
+
*/
|
|
17
|
+
function htmlToPdf(source, target = null, options = {}) {
|
|
18
|
+
const pd = new PD4ML({ htmlWidth: options.htmlWidth, engine: options.engine });
|
|
19
|
+
if (options.pageSize) pd.setPageSize(options.pageSize);
|
|
20
|
+
if (options.pageMargins) pd.setPageMargins(options.pageMargins);
|
|
21
|
+
pd.readHTML(source, options.baseUrl === undefined ? null : options.baseUrl);
|
|
22
|
+
return pd.writePDF(target, options.spec === undefined ? null : options.spec);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** PDF content from a path, a Buffer/typed array, or a string holding a PDF. */
|
|
26
|
+
function pdfBytes(source) {
|
|
27
|
+
if (typeof source === 'string' && !source.startsWith('%PDF')) return fs.readFileSync(source);
|
|
28
|
+
return runtime.bytes(source);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A com.pd4ml.PdfDocument from a path, URL, Buffer or PDF string (+ password). */
|
|
32
|
+
function openPdf(source, password = null) {
|
|
33
|
+
const PdfDocument = java.type('com.pd4ml.PdfDocument');
|
|
34
|
+
if ((typeof source === 'string' && /^[A-Za-z][A-Za-z0-9+.-]*:\/\//.test(source)) || source instanceof URL) {
|
|
35
|
+
return new PdfDocument(runtime.toUrl(source), password);
|
|
36
|
+
}
|
|
37
|
+
return new PdfDocument(pdfBytes(source), password);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Writes a PdfDocument (after append, overlay...): returned as a Buffer, or written to a path,
|
|
42
|
+
* fd or stream. Options: password (encrypts), permissions (Constants flags; default
|
|
43
|
+
* Constants.DefaultPermissions).
|
|
44
|
+
*/
|
|
45
|
+
function writePdf(document, target = null, options = {}) {
|
|
46
|
+
const outs = [];
|
|
47
|
+
if (!options.password) {
|
|
48
|
+
Bridge.get().call(document, 'write', [new Output()], outs);
|
|
49
|
+
} else {
|
|
50
|
+
const permissions = options.permissions === undefined ? java.type('com.pd4ml.Constants').DefaultPermissions : options.permissions;
|
|
51
|
+
Bridge.get().call(document, 'write', [new Output(), options.password, permissions], outs);
|
|
52
|
+
}
|
|
53
|
+
return runtime.deliver(outs[0] || Buffer.alloc(0), target);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** PdfDocument.mergePDFs: two PDF documents appended into one. */
|
|
57
|
+
function mergePdfs(first, second, target = null) {
|
|
58
|
+
const outs = [];
|
|
59
|
+
Bridge.get().callStatic('com.pd4ml.PdfDocument', 'mergePDFs', [pdfBytes(first), pdfBytes(second), new Output()], outs);
|
|
60
|
+
return runtime.deliver(outs[0] || Buffer.alloc(0), target);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = { htmlToPdf, openPdf, writePdf, mergePdfs };
|