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.
@@ -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 };