@lpdfio/lpdf 0.31.0 → 0.33.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 +2 -0
- package/dist/L.d.ts +8 -2
- package/dist/L.js +7 -2
- package/dist/_shared.d.ts +23 -3
- package/dist/_shared.js +19 -3
- package/dist/attrs.d.ts +600 -13
- package/dist/browser.js +282 -33
- package/dist/constants.d.ts +7 -0
- package/dist/constants.js +7 -1
- package/dist/engine.d.ts +58 -13
- package/dist/engine.js +185 -102
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/kit-to-xml.js +5 -4
- package/dist/kit.d.ts +87 -5
- package/dist/kit.js +11 -2
- package/dist/render-error.d.ts +11 -0
- package/dist/render-error.js +19 -0
- package/dist/render-job.d.ts +67 -0
- package/dist/render-job.js +74 -0
- package/dist/render-queue.d.ts +52 -0
- package/dist/render-queue.js +116 -0
- package/dist/render-worker.d.ts +1 -0
- package/dist/render-worker.js +16 -0
- package/dist/stamp.d.ts +51 -0
- package/dist/stamp.js +30 -0
- package/dist/wasm/lpdf-web.js +73 -2
- package/dist/wasm/lpdf.js +73 -2
- package/dist/wasm/lpdf_bg.wasm +0 -0
- package/package.json +2 -2
package/dist/engine.js
CHANGED
|
@@ -3,13 +3,44 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.PdfEngine = exports.LpdfRenderError = void 0;
|
|
4
4
|
const node_crypto_1 = require("node:crypto");
|
|
5
5
|
const node_fs_1 = require("node:fs");
|
|
6
|
+
const node_path_1 = require("node:path");
|
|
7
|
+
const node_worker_threads_1 = require("node:worker_threads");
|
|
6
8
|
const _shared_1 = require("./_shared");
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
const render_error_1 = require("./render-error");
|
|
10
|
+
const stamp_1 = require("./stamp");
|
|
11
|
+
const render_queue_1 = require("./render-queue");
|
|
12
|
+
var render_error_2 = require("./render-error");
|
|
13
|
+
Object.defineProperty(exports, "LpdfRenderError", { enumerable: true, get: function () { return render_error_2.LpdfRenderError; } });
|
|
14
|
+
/**
|
|
15
|
+
* The engine's license check on this thread, loaded on first use: a render runs on the worker, which loads the engine
|
|
16
|
+
* itself, so an app that only renders loads it once.
|
|
17
|
+
*/
|
|
18
|
+
function checkLicense(token, nowUnix) {
|
|
19
|
+
// require() path is relative to the compiled output at dist/engine.js.
|
|
20
|
+
// In the published package, wasm-pack artifacts live in dist/wasm/.
|
|
21
|
+
// In the monorepo, `npm run build` copies them there from <root>/dist/node/.
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
23
|
+
const wasm = require('./wasm/lpdf.js');
|
|
24
|
+
return wasm.check_license(token, nowUnix);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Starts the worker of `render-worker.js`. It keeps the process running only while it renders, so a script that has
|
|
28
|
+
* rendered ends by itself.
|
|
29
|
+
*/
|
|
30
|
+
function startNodeWorker(events) {
|
|
31
|
+
const worker = new node_worker_threads_1.Worker((0, node_path_1.join)(__dirname, 'render-worker.js'));
|
|
32
|
+
worker.on('message', (reply) => events.replied(reply));
|
|
33
|
+
worker.on('error', error => events.failed(error.message));
|
|
34
|
+
worker.on('messageerror', error => events.failed(error.message));
|
|
35
|
+
worker.on('exit', code => events.failed(`The render worker stopped with exit code ${code}.`));
|
|
36
|
+
return {
|
|
37
|
+
post: job => worker.postMessage(job),
|
|
38
|
+
terminate: () => void worker.terminate(),
|
|
39
|
+
hold: busy => (busy ? worker.ref() : worker.unref()),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
// One worker for the process, as renders ran one at a time on the calling thread before they moved to a worker.
|
|
43
|
+
let renderQueue;
|
|
13
44
|
/**
|
|
14
45
|
* The clock the engine checks a key's expiry against.
|
|
15
46
|
*
|
|
@@ -22,26 +53,28 @@ const WasmEngine = wasmModule.LpdfEngine;
|
|
|
22
53
|
function nowUnix() {
|
|
23
54
|
return BigInt(Math.floor(Date.now() / 1000));
|
|
24
55
|
}
|
|
25
|
-
/** Thrown when the lpdf engine returns a layout or parse error. */
|
|
26
|
-
class LpdfRenderError extends Error {
|
|
27
|
-
constructor(message) {
|
|
28
|
-
super(message);
|
|
29
|
-
this.name = 'LpdfRenderError';
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
exports.LpdfRenderError = LpdfRenderError;
|
|
33
56
|
/** Node's secure generator, for the entropy of AES-256. */
|
|
34
57
|
function secureRandomBytes(count) {
|
|
35
58
|
return new Uint8Array((0, node_crypto_1.randomBytes)(count));
|
|
36
59
|
}
|
|
37
60
|
class PdfEngine {
|
|
38
|
-
|
|
61
|
+
/**
|
|
62
|
+
* @param options - Engine-level options: `timeout`, the seconds a render may run before it rejects with an
|
|
63
|
+
* `LpdfRenderError` that names the limit, 5 when left out and no limit at 0; `maxMemoryMb`, the megabytes a render
|
|
64
|
+
* may allocate before it rejects with an `LpdfRenderError` that names the cap, 1024 when left out.
|
|
65
|
+
* @throws {RangeError} when `timeout` is negative or not a finite number, or `maxMemoryMb` is not a whole number
|
|
66
|
+
* from 1 to 4096.
|
|
67
|
+
*/
|
|
68
|
+
constructor(options = {}) {
|
|
39
69
|
this._licenseKey = '';
|
|
40
70
|
this._fonts = new Map();
|
|
41
71
|
this._images = new Map();
|
|
42
72
|
this._files = new Map();
|
|
73
|
+
this._profiles = new Map();
|
|
43
74
|
this._markdownStyles = new Map();
|
|
44
75
|
this._disposed = false;
|
|
76
|
+
this._timeout = (0, render_queue_1.renderTimeout)(options.timeout);
|
|
77
|
+
this._maxMemoryMb = (0, _shared_1.maxMemoryMb)(options.maxMemoryMb);
|
|
45
78
|
}
|
|
46
79
|
/**
|
|
47
80
|
* Set the license key. Returns `this` for chaining.
|
|
@@ -64,7 +97,7 @@ class PdfEngine {
|
|
|
64
97
|
*/
|
|
65
98
|
checkLicenseKey(key) {
|
|
66
99
|
this._throwIfDisposed();
|
|
67
|
-
return JSON.parse(
|
|
100
|
+
return JSON.parse(checkLicense(key ?? this._licenseKey, nowUnix()));
|
|
68
101
|
}
|
|
69
102
|
/**
|
|
70
103
|
* Register raw TTF/OTF bytes for a custom font name used in `<font src="…">`.
|
|
@@ -93,6 +126,16 @@ class PdfEngine {
|
|
|
93
126
|
this._files.set(name, bytes);
|
|
94
127
|
return this;
|
|
95
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Register the bytes of an ICC color profile, for the `<profile>` of the document's `<assets>` whose `ref`, or with
|
|
131
|
+
* no ref whose name, is `name`: the press profile a PDF/X document names in its output. Call before `render`.
|
|
132
|
+
* Returns `this` for chaining.
|
|
133
|
+
*/
|
|
134
|
+
loadProfile(name, bytes) {
|
|
135
|
+
this._throwIfDisposed();
|
|
136
|
+
this._profiles.set(name, bytes);
|
|
137
|
+
return this;
|
|
138
|
+
}
|
|
96
139
|
/**
|
|
97
140
|
* Register the text of a file of Markdown rules, the root of which is a `<markdown-styles>`, for the `src` that
|
|
98
141
|
* names it, as `<markdown-styles src="…">` does. Call before `render`. Returns `this` for chaining.
|
|
@@ -111,10 +154,10 @@ class PdfEngine {
|
|
|
111
154
|
}
|
|
112
155
|
[Symbol.dispose]() { this.dispose(); }
|
|
113
156
|
/**
|
|
114
|
-
*
|
|
115
|
-
*
|
|
157
|
+
* The files of Markdown rules that a document names: the ones loaded here, and the ones read from their `src`. A
|
|
158
|
+
* file that can't be read is left for the engine to report, naming the `src`.
|
|
116
159
|
*/
|
|
117
|
-
|
|
160
|
+
_markdownStylesFor(srcs) {
|
|
118
161
|
const files = new Map(this._markdownStyles);
|
|
119
162
|
for (const src of srcs) {
|
|
120
163
|
if (!files.has(src)) {
|
|
@@ -124,15 +167,13 @@ class PdfEngine {
|
|
|
124
167
|
catch { /* not found; the engine says so */ }
|
|
125
168
|
}
|
|
126
169
|
}
|
|
127
|
-
|
|
128
|
-
engine.load_markdown_styles(src, text);
|
|
129
|
-
}
|
|
170
|
+
return files;
|
|
130
171
|
}
|
|
131
172
|
/**
|
|
132
|
-
*
|
|
133
|
-
*
|
|
173
|
+
* The files the document carries: the ones loaded here, and the ones read from the `src` of a file that wasn't. A
|
|
174
|
+
* `src` that can't be read is an error that names the file and the path.
|
|
134
175
|
*/
|
|
135
|
-
|
|
176
|
+
_filesFor(srcs) {
|
|
136
177
|
const files = new Map(this._files);
|
|
137
178
|
for (const [key, src] of srcs) {
|
|
138
179
|
if (files.has(key))
|
|
@@ -141,12 +182,28 @@ class PdfEngine {
|
|
|
141
182
|
files.set(key, (0, node_fs_1.readFileSync)(src));
|
|
142
183
|
}
|
|
143
184
|
catch (e) {
|
|
144
|
-
throw new LpdfRenderError(`file '${key}' of <attachments>: cannot read its src '${src}': ${e instanceof Error ? e.message : String(e)}`);
|
|
185
|
+
throw new render_error_1.LpdfRenderError(`file '${key}' of <attachments>: cannot read its src '${src}': ${e instanceof Error ? e.message : String(e)}`);
|
|
145
186
|
}
|
|
146
187
|
}
|
|
147
|
-
|
|
148
|
-
|
|
188
|
+
return files;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* The color profiles the document declares: the ones loaded here, and the ones read from the `src` of a profile
|
|
192
|
+
* that wasn't. A `src` that can't be read is an error that names the profile and the path.
|
|
193
|
+
*/
|
|
194
|
+
_profilesFor(srcs) {
|
|
195
|
+
const profiles = new Map(this._profiles);
|
|
196
|
+
for (const [key, src] of srcs) {
|
|
197
|
+
if (profiles.has(key))
|
|
198
|
+
continue;
|
|
199
|
+
try {
|
|
200
|
+
profiles.set(key, (0, node_fs_1.readFileSync)(src));
|
|
201
|
+
}
|
|
202
|
+
catch (e) {
|
|
203
|
+
throw new render_error_1.LpdfRenderError(`profile '${key}' of <assets>: cannot read its src '${src}': ${e instanceof Error ? e.message : String(e)}`);
|
|
204
|
+
}
|
|
149
205
|
}
|
|
206
|
+
return profiles;
|
|
150
207
|
}
|
|
151
208
|
_throwIfDisposed() {
|
|
152
209
|
if (this._disposed)
|
|
@@ -154,89 +211,115 @@ class PdfEngine {
|
|
|
154
211
|
}
|
|
155
212
|
async render(input, callOptions = {}) {
|
|
156
213
|
this._throwIfDisposed();
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
214
|
+
const job = this._job(input, callOptions);
|
|
215
|
+
renderQueue ?? (renderQueue = new render_queue_1.RenderQueue(startNodeWorker));
|
|
216
|
+
return renderQueue.render(job, this._timeout);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Draw PNG images over the pages of an existing PDF, and return the PDF with them (Node.js). Nothing is laid out:
|
|
220
|
+
* each image is drawn in its stamp's box, over the pages its `page` names.
|
|
221
|
+
*
|
|
222
|
+
* The output is the input, byte for byte, with an incremental update after it, so an owner password and its
|
|
223
|
+
* permissions stay, and so does a digital signature, for the revision it signed. A PDF/A file stays PDF/A. Without a
|
|
224
|
+
* license key each stamped page gets the attribution line in its top-right corner. A stamp covers what is under it
|
|
225
|
+
* and does not remove it: the text under an image can still be selected and found, so a stamp is not a redaction.
|
|
226
|
+
*
|
|
227
|
+
* The stamp runs on a worker thread, as a render does, with its `timeout` and `maxMemoryMb`. It rejects with an
|
|
228
|
+
* `LpdfRenderError` when the input is not a PDF, needs a password to open, or doesn't have a page a stamp names, and
|
|
229
|
+
* when an image is not a PNG or a stamp's options are wrong.
|
|
230
|
+
*
|
|
231
|
+
* @throws {TypeError} when a stamp's `image` is not the PNG's bytes.
|
|
232
|
+
*/
|
|
233
|
+
async stamp(pdf, stamps) {
|
|
234
|
+
this._throwIfDisposed();
|
|
235
|
+
const { stamps: json, images } = (0, stamp_1.stampRequest)(stamps);
|
|
236
|
+
const job = {
|
|
237
|
+
licenseKey: this._licenseKey,
|
|
238
|
+
now: nowUnix(),
|
|
239
|
+
maxMemoryMb: this._maxMemoryMb,
|
|
240
|
+
createdOn: undefined,
|
|
241
|
+
fonts: new Map(),
|
|
242
|
+
images,
|
|
243
|
+
files: new Map(),
|
|
244
|
+
profiles: new Map(),
|
|
245
|
+
markdownStyles: new Map(),
|
|
246
|
+
document: { pdf, stamps: json },
|
|
247
|
+
encryption: [null, null, false, new Uint8Array()],
|
|
248
|
+
};
|
|
249
|
+
renderQueue ?? (renderQueue = new render_queue_1.RenderQueue(startNodeWorker));
|
|
250
|
+
return renderQueue.render(job, this._timeout);
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* A render of `input` with every file it needs, read here from the `src` of each font, image, file, profile and
|
|
254
|
+
* file of rules the document names and that wasn't loaded: the worker reads nothing itself.
|
|
255
|
+
*/
|
|
256
|
+
_job(input, callOptions) {
|
|
257
|
+
const fonts = new Map(this._fonts);
|
|
258
|
+
const images = new Map(this._images);
|
|
259
|
+
let document;
|
|
260
|
+
let markdownStyles;
|
|
261
|
+
let files;
|
|
262
|
+
let profiles;
|
|
263
|
+
if (typeof input === 'string') {
|
|
264
|
+
// XML path — auto-load fonts and images declared via src="…".
|
|
265
|
+
for (const [key, src] of extractAssetSrcs(input, 'font')) {
|
|
266
|
+
if (!fonts.has(key)) {
|
|
267
|
+
try {
|
|
268
|
+
fonts.set(key, (0, node_fs_1.readFileSync)(src));
|
|
176
269
|
}
|
|
270
|
+
catch { /* not found; Rust falls back to Helvetica */ }
|
|
177
271
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
this._loadMarkdownStyles(engine, markdownStylesSrcs(xml));
|
|
185
|
-
this._loadFiles(engine, extractAssetSrcs(xml, 'file'));
|
|
186
|
-
// Auto-load images declared via <image src="…"> that weren't pre-loaded.
|
|
187
|
-
for (const [key, src] of extractAssetSrcs(xml, 'image')) {
|
|
188
|
-
if (!this._images.has(key)) {
|
|
189
|
-
try {
|
|
190
|
-
engine.load_image(key, (0, node_fs_1.readFileSync)(src));
|
|
191
|
-
}
|
|
192
|
-
catch { /* skip unresolvable image */ }
|
|
272
|
+
}
|
|
273
|
+
for (const [key, src] of extractAssetSrcs(input, 'image')) {
|
|
274
|
+
if (!images.has(key)) {
|
|
275
|
+
try {
|
|
276
|
+
images.set(key, (0, node_fs_1.readFileSync)(src));
|
|
193
277
|
}
|
|
278
|
+
catch { /* skip unresolvable image */ }
|
|
194
279
|
}
|
|
195
|
-
const dataJson = callOptions.data != null ? JSON.stringify(callOptions.data) : null;
|
|
196
|
-
pdf = engine.render_pdf(xml, dataJson, ...(0, _shared_1.encryptionArguments)(callOptions, secureRandomBytes));
|
|
197
280
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
281
|
+
markdownStyles = this._markdownStylesFor(markdownStylesSrcs(input));
|
|
282
|
+
files = this._filesFor(extractAssetSrcs(input, 'file'));
|
|
283
|
+
profiles = this._profilesFor(extractAssetSrcs(input, 'profile'));
|
|
284
|
+
document = { xml: input, data: callOptions.data != null ? JSON.stringify(callOptions.data) : null };
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
// JSON (Kit tree) path. Fonts and images the document declares with a src are read from there, as on the XML
|
|
288
|
+
// path.
|
|
289
|
+
for (const [key, src] of assetSrcsOfDocument(input, 'fonts')) {
|
|
290
|
+
if (!fonts.has(key)) {
|
|
291
|
+
try {
|
|
292
|
+
fonts.set(key, (0, node_fs_1.readFileSync)(src));
|
|
209
293
|
}
|
|
294
|
+
catch { /* not found; Rust falls back to Helvetica */ }
|
|
210
295
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if (!allImages.has(key)) {
|
|
217
|
-
try {
|
|
218
|
-
allImages.set(key, (0, node_fs_1.readFileSync)(src));
|
|
219
|
-
}
|
|
220
|
-
catch { /* skip unresolvable image */ }
|
|
296
|
+
}
|
|
297
|
+
for (const [key, src] of assetSrcsOfDocument(input, 'images')) {
|
|
298
|
+
if (!images.has(key)) {
|
|
299
|
+
try {
|
|
300
|
+
images.set(key, (0, node_fs_1.readFileSync)(src));
|
|
221
301
|
}
|
|
302
|
+
catch { /* skip unresolvable image */ }
|
|
222
303
|
}
|
|
223
|
-
for (const [name, bytes] of allImages) {
|
|
224
|
-
engine.load_image(name, bytes);
|
|
225
|
-
}
|
|
226
|
-
this._loadMarkdownStyles(engine, markdownStylesSrcsOfDocument(input));
|
|
227
|
-
this._loadFiles(engine, fileSrcsOfDocument(input));
|
|
228
|
-
pdf = engine.render_tree_pdf(json, ...(0, _shared_1.encryptionArguments)(callOptions, secureRandomBytes));
|
|
229
304
|
}
|
|
305
|
+
markdownStyles = this._markdownStylesFor(markdownStylesSrcsOfDocument(input));
|
|
306
|
+
files = this._filesFor(fileSrcsOfDocument(input));
|
|
307
|
+
profiles = this._profilesFor(assetSrcsOfDocument(input, 'profiles'));
|
|
308
|
+
document = { tree: JSON.stringify(input) };
|
|
230
309
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
310
|
+
return {
|
|
311
|
+
licenseKey: this._licenseKey,
|
|
312
|
+
now: nowUnix(),
|
|
313
|
+
maxMemoryMb: this._maxMemoryMb,
|
|
314
|
+
createdOn: callOptions.createdOn,
|
|
315
|
+
fonts,
|
|
316
|
+
images,
|
|
317
|
+
files,
|
|
318
|
+
profiles,
|
|
319
|
+
markdownStyles,
|
|
320
|
+
document,
|
|
321
|
+
encryption: (0, _shared_1.encryptionArguments)(callOptions, secureRandomBytes),
|
|
322
|
+
};
|
|
240
323
|
}
|
|
241
324
|
}
|
|
242
325
|
exports.PdfEngine = PdfEngine;
|
|
@@ -269,7 +352,7 @@ function xmlElements(xml, tag) {
|
|
|
269
352
|
}
|
|
270
353
|
return elements;
|
|
271
354
|
}
|
|
272
|
-
/** The `ref ?? name → src` pairs of the `<font>`, `<image>` or `<
|
|
355
|
+
/** The `ref ?? name → src` pairs of the `<font>`, `<image>`, `<file>` or `<profile>` elements in XML. */
|
|
273
356
|
function extractAssetSrcs(xml, tag) {
|
|
274
357
|
const result = new Map();
|
|
275
358
|
for (const attributes of xmlElements(xml, tag)) {
|
|
@@ -315,7 +398,7 @@ function fileSrcsOfDocument(document) {
|
|
|
315
398
|
}
|
|
316
399
|
return result;
|
|
317
400
|
}
|
|
318
|
-
/** The `ref ?? name → src` pairs of the fonts or
|
|
401
|
+
/** The `ref ?? name → src` pairs of the fonts, images or color profiles a document declares in `attrs.assets`. */
|
|
319
402
|
function assetSrcsOfDocument(document, kind) {
|
|
320
403
|
const result = new Map();
|
|
321
404
|
const declared = document.attrs['assets']?.[kind] ?? [];
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export { PdfEngine, LpdfRenderError } from './engine';
|
|
2
2
|
export type { RenderOptions, EngineOptions } from './_shared';
|
|
3
|
-
export {
|
|
3
|
+
export type { StampAttr } from './stamp';
|
|
4
|
+
export { FieldType, Pin, Orientation, SectionType, FootnoteCounter, ColumnFill, Cipher, Permission, FileRelationship, BuiltinFont, PageScope, } from './constants';
|
|
4
5
|
export type { PdfDocument, LpdfSectionNode, LpdfSectionChild, LpdfLayoutBlock, DocumentTokens, TokenSlot, TokenRow, DocumentAssets, DocumentMeta, DocumentOutput, SectionAttr, DocumentAttr, } from './kit';
|
|
5
|
-
export type { ColumnAttr, RowAttr, GridAttr, ListAttr, LiAttr, FrameAttr, LinkAttr, TableAttr, TheadAttr, TrAttr, TdAttr, TextAttr, CodeAttr, SpanAttr, CodeSpanAttr, DividerAttr, ImgAttr, ShapeAttr, BarcodeAttr, RegionAttr, FieldAttr, BookmarkAttr, LayerAttr, RectAttr, CircleAttr, EllipseAttr, LineAttr, PathAttr, TextAtAttr, ImgAtAttr, BarcodeAtAttr, FontAttr, ImageAttr, FileAttr, LayoutAttr, GradientToken, GradientStop, MarkdownAttr, MarkdownStylesAttr, MarkdownH1Attr, MarkdownH2Attr, MarkdownH3Attr, MarkdownH4Attr, MarkdownH5Attr, MarkdownH6Attr, MarkdownPAttr, MarkdownCodeAttr, MarkdownQuoteAttr, MarkdownLiAttr, MarkdownAAttr, MarkdownStrongAttr, MarkdownEmAttr, MarkdownDelAttr, MarkdownTableAttr, MarkdownThAttr, MarkdownTdAttr, } from './attrs';
|
|
6
|
+
export type { ColumnAttr, RowAttr, GridAttr, ListAttr, LiAttr, FrameAttr, LinkAttr, TableAttr, TheadAttr, TrAttr, TdAttr, TextAttr, CodeAttr, SpanAttr, CodeSpanAttr, DividerAttr, ImgAttr, ShapeAttr, BarcodeAttr, RegionAttr, FieldAttr, BookmarkAttr, LayerAttr, RectAttr, CircleAttr, EllipseAttr, LineAttr, PathAttr, TextAtAttr, ImgAtAttr, BarcodeAtAttr, FontAttr, ImageAttr, FileAttr, ProfileAttr, LayoutAttr, GradientToken, GradientStop, RulingToken, RulingLines, MarkdownAttr, MarkdownStylesAttr, MarkdownH1Attr, MarkdownH2Attr, MarkdownH3Attr, MarkdownH4Attr, MarkdownH5Attr, MarkdownH6Attr, MarkdownPAttr, MarkdownCodeAttr, MarkdownQuoteAttr, MarkdownLiAttr, MarkdownAAttr, MarkdownStrongAttr, MarkdownEmAttr, MarkdownDelAttr, MarkdownTableAttr, MarkdownThAttr, MarkdownTdAttr, } from './attrs';
|
|
6
7
|
export type { LpdfNode, LpdfContainerNode, LpdfListNode, LpdfLiNode, LpdfTextNode, LpdfCodeNode, LpdfSpanNode, LpdfCodeSpanNode, LpdfFnNode, LpdfDividerNode, LpdfImgNode, LpdfShapeNode, LpdfBarcodeNode, LpdfTableNode, LpdfTheadNode, LpdfTrNode, LpdfTdNode, LpdfRegionNode, LpdfFieldNode, LpdfBookmarkNode, } from './layout';
|
|
7
8
|
export { LMarkdown } from './markdown';
|
|
8
9
|
export type { LpdfMarkdownNode, LpdfMarkdownStylesNode, LpdfMarkdownContentNode, LpdfMarkdownRuleNode, } from './markdown';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.NoAttr = exports.L = exports.CanvasTransform = exports.LMarkdown = exports.PageScope = exports.BuiltinFont = exports.FileRelationship = exports.Permission = exports.Cipher = exports.FootnoteCounter = exports.SectionType = exports.Orientation = exports.Pin = exports.FieldType = exports.LpdfRenderError = exports.PdfEngine = void 0;
|
|
3
|
+
exports.NoAttr = exports.L = exports.CanvasTransform = exports.LMarkdown = exports.PageScope = exports.BuiltinFont = exports.FileRelationship = exports.Permission = exports.Cipher = exports.ColumnFill = exports.FootnoteCounter = exports.SectionType = exports.Orientation = exports.Pin = exports.FieldType = exports.LpdfRenderError = exports.PdfEngine = void 0;
|
|
4
4
|
// ── Engine ────────────────────────────────────────────────────────────────────
|
|
5
5
|
var engine_1 = require("./engine");
|
|
6
6
|
Object.defineProperty(exports, "PdfEngine", { enumerable: true, get: function () { return engine_1.PdfEngine; } });
|
|
@@ -12,6 +12,7 @@ Object.defineProperty(exports, "Pin", { enumerable: true, get: function () { ret
|
|
|
12
12
|
Object.defineProperty(exports, "Orientation", { enumerable: true, get: function () { return constants_1.Orientation; } });
|
|
13
13
|
Object.defineProperty(exports, "SectionType", { enumerable: true, get: function () { return constants_1.SectionType; } });
|
|
14
14
|
Object.defineProperty(exports, "FootnoteCounter", { enumerable: true, get: function () { return constants_1.FootnoteCounter; } });
|
|
15
|
+
Object.defineProperty(exports, "ColumnFill", { enumerable: true, get: function () { return constants_1.ColumnFill; } });
|
|
15
16
|
Object.defineProperty(exports, "Cipher", { enumerable: true, get: function () { return constants_1.Cipher; } });
|
|
16
17
|
Object.defineProperty(exports, "Permission", { enumerable: true, get: function () { return constants_1.Permission; } });
|
|
17
18
|
Object.defineProperty(exports, "FileRelationship", { enumerable: true, get: function () { return constants_1.FileRelationship; } });
|
package/dist/kit-to-xml.js
CHANGED
|
@@ -19,9 +19,6 @@
|
|
|
19
19
|
*/
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
21
|
exports.kitToXml = kitToXml;
|
|
22
|
-
// Load the standalone kit_to_xml export from the compiled WASM module.
|
|
23
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
24
|
-
const { kit_to_xml: wasmKitToXml } = require('./wasm/lpdf.js');
|
|
25
22
|
/**
|
|
26
23
|
* Convert an `LpdfDocument` tree (built with `LpdfKit`) into an lpdf XML
|
|
27
24
|
* string.
|
|
@@ -33,5 +30,9 @@ const { kit_to_xml: wasmKitToXml } = require('./wasm/lpdf.js');
|
|
|
33
30
|
* @returns A well-formed XML string with an `<?xml ...?>` declaration.
|
|
34
31
|
*/
|
|
35
32
|
function kitToXml(doc) {
|
|
36
|
-
|
|
33
|
+
// The engine is loaded on this thread on first use: a render runs on a worker, which loads it itself, so an app that
|
|
34
|
+
// only renders loads it once.
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
36
|
+
const wasm = require('./wasm/lpdf.js');
|
|
37
|
+
return wasm.kit_to_xml(JSON.stringify(doc));
|
|
37
38
|
}
|
package/dist/kit.d.ts
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
import type { LpdfBookmarkNode, LpdfNode } from './layout';
|
|
23
23
|
import type { LpdfCanvasLayerNode } from './canvas';
|
|
24
24
|
import type { LpdfMarkdownNode } from './markdown';
|
|
25
|
-
import type { FileAttr, FontAttr, GradientToken, ImageAttr, LayoutAttr } from './attrs';
|
|
25
|
+
import type { FileAttr, FontAttr, GradientToken, ImageAttr, LayoutAttr, ProfileAttr, RulingToken } from './attrs';
|
|
26
26
|
import { type AttrValue } from './_shared';
|
|
27
27
|
/** A slot of a token row, from the smallest to the largest. */
|
|
28
28
|
export type TokenSlot = 'xs' | 's' | 'm' | 'l' | 'xl' | 'xxl';
|
|
@@ -31,6 +31,11 @@ export type TokenRow<T> = Partial<Record<TokenSlot, T>>;
|
|
|
31
31
|
export interface DocumentTokens {
|
|
32
32
|
colors?: Record<string, string>;
|
|
33
33
|
gradients?: Record<string, GradientToken>;
|
|
34
|
+
/**
|
|
35
|
+
* Lines a section draws on every page with `ruling: 'name'`, by name: `{ graph: { origin: 'margin', lines: [{ step:
|
|
36
|
+
* '5mm' }, { step: '25mm', color: '#b5c4d6' }] } }`. Each set of lines is cut by a step or a count, in x, in y or both.
|
|
37
|
+
*/
|
|
38
|
+
rulings?: Record<string, RulingToken>;
|
|
34
39
|
/**
|
|
35
40
|
* Names for the slots of the scale, each to the slot it stands for: `{ caption: 'xs' }` lets `fontSize: 'caption'`
|
|
36
41
|
* mean the text size `xs`, and `gap: 'caption'` the space `xs`. A name is lowercase letters, digits and `-`,
|
|
@@ -49,12 +54,18 @@ export interface DocumentTokens {
|
|
|
49
54
|
lineHeight?: TokenRow<number>;
|
|
50
55
|
}
|
|
51
56
|
/**
|
|
52
|
-
* The fonts and
|
|
53
|
-
* is picked by its `name`: text sets `font` to a font's name, an `img` sets `name` to an image's
|
|
57
|
+
* The fonts, images and color profiles the document declares, as the `assets` element of the XML does. A font or
|
|
58
|
+
* image is picked by its `name`: text sets `font` to a font's name, an `img` sets `name` to an image's, and the
|
|
59
|
+
* output's `profile` names a profile.
|
|
54
60
|
*/
|
|
55
61
|
export interface DocumentAssets {
|
|
56
62
|
fonts?: FontAttr[];
|
|
57
63
|
images?: ImageAttr[];
|
|
64
|
+
/**
|
|
65
|
+
* ICC color profiles: the profile of the printing press a PDF/X document is made for, which the printer gives out.
|
|
66
|
+
* Each is read from its `src`, or loaded with `loadProfile` under its `ref` or name.
|
|
67
|
+
*/
|
|
68
|
+
profiles?: ProfileAttr[];
|
|
58
69
|
}
|
|
59
70
|
export interface DocumentMeta {
|
|
60
71
|
title?: string;
|
|
@@ -62,6 +73,11 @@ export interface DocumentMeta {
|
|
|
62
73
|
subject?: string;
|
|
63
74
|
keywords?: string;
|
|
64
75
|
creator?: string;
|
|
76
|
+
/**
|
|
77
|
+
* The language the document is written in, as a language tag such as `en-US`. A PDF/UA document needs it, and a
|
|
78
|
+
* PDF/A document of level a takes it only as a language tag.
|
|
79
|
+
*/
|
|
80
|
+
lang?: string;
|
|
65
81
|
}
|
|
66
82
|
/**
|
|
67
83
|
* How the PDF file is written, as the `output` element of the XML is: whether it is encrypted, and what a reader
|
|
@@ -78,24 +94,90 @@ export interface DocumentOutput {
|
|
|
78
94
|
allow?: string;
|
|
79
95
|
/**
|
|
80
96
|
* Writes the PDF as PDF/A, the standard for files kept for the long term: `2b`, or `2u` when its text must also
|
|
81
|
-
* be searchable
|
|
82
|
-
*
|
|
97
|
+
* be searchable, or `2a` when it must also be tagged, and `3a`, `3b` or `3u` to carry attachments of any kind. The
|
|
98
|
+
* render needs `createdOn`, and every text a font of `assets`. Level a tags the file as `pdfua` does, without
|
|
99
|
+
* PDF/UA's checks: add `pdfua` for a file a screen reader can use. What PDF/A cannot hold is an error that says what
|
|
100
|
+
* to change: a built-in font, a CMYK color, a form field, a barcode's digits, `encrypt`, attachments in part 2, and
|
|
101
|
+
* in level a a `lang` that is not a language tag and a character of Unicode's private use area. With `3a`, `3b` or
|
|
102
|
+
* `3u`, an attachment named `factur-x.xml`, or `xrechnung.xml` for the XRECHNUNG profile, makes the file a Factur-X
|
|
103
|
+
* or ZUGFeRD e-invoice: the engine reads the profile from BT-24 of that XML and names the invoice in the file's
|
|
104
|
+
* metadata. The XML is attached as given, and its content is not checked.
|
|
83
105
|
*/
|
|
84
106
|
pdfa?: string;
|
|
107
|
+
/**
|
|
108
|
+
* Writes the PDF as PDF/UA-1, a tagged PDF a screen reader can read: `1`. It combines with `pdfa`. The render
|
|
109
|
+
* needs `createdOn`, the meta a `title` and a `lang`, every image and barcode of the layout an `alt`, and every
|
|
110
|
+
* text a font of `assets`. What PDF/UA cannot hold is an error that says what to change.
|
|
111
|
+
*/
|
|
112
|
+
pdfua?: string;
|
|
113
|
+
/**
|
|
114
|
+
* Writes the PDF as PDF/X-4, for a commercial printer: `4`. It needs `profile`, and combines with `pdfua`. Every
|
|
115
|
+
* page gets a TrimBox and a BleedBox, RGB is tied to sRGB, and links print as text, as PDF/X allows nothing
|
|
116
|
+
* clickable. The render needs `createdOn`, the meta a `title`, and every text a font of `assets`. What PDF/X cannot
|
|
117
|
+
* hold is an error that says what to change: a profile that is not a printer profile, a built-in font, a form
|
|
118
|
+
* field, a CMYK color with a profile that is not a CMYK one, `encrypt`, `pdfa`, attachments.
|
|
119
|
+
*/
|
|
120
|
+
pdfx?: string;
|
|
121
|
+
/** The profile of `assets` that a PDF/X file is made for: the ICC profile of the printing press. It needs `pdfx`. */
|
|
122
|
+
profile?: string;
|
|
123
|
+
/**
|
|
124
|
+
* Writes the PDF linearized, for fast web view: page 1 comes first in the file, with hints that say where every
|
|
125
|
+
* other page is, so a viewer that fetches the file by byte ranges, as a browser does, shows page 1 while the rest
|
|
126
|
+
* downloads. Each page then names only the fonts and images it draws. With `encrypt` the file is encrypted first
|
|
127
|
+
* and linearized last. Any change to the file afterwards, such as a stamp, ends the linearization.
|
|
128
|
+
*/
|
|
129
|
+
linearize?: boolean;
|
|
130
|
+
/**
|
|
131
|
+
* Refuses, in a Factur-X e-invoice, the attachments the Factur-X spec does not allow: an invoice file whose `mime`
|
|
132
|
+
* is not `text/xml`, an invoice file whose `relationship` its profile does not allow, and another file with no
|
|
133
|
+
* `mime` or one that is not of a format the spec lists (PDF, TXT, GIF, TIFF, JPG, CSV, XML, JSON, XLS, XLSX, ODS).
|
|
134
|
+
* Without it, every file is written as the document gives it. It does nothing in a document without
|
|
135
|
+
* `factur-x.xml` or `xrechnung.xml`. It is `facturx-strict` in the XML.
|
|
136
|
+
*/
|
|
137
|
+
facturxStrict?: boolean;
|
|
85
138
|
}
|
|
86
139
|
export interface SectionAttr {
|
|
87
140
|
size?: string;
|
|
88
141
|
orientation?: string;
|
|
142
|
+
/** How far this section's pages reach past their trim edge, such as `5mm`. Overrides the document's; `0mm` takes it away. */
|
|
143
|
+
bleed?: string;
|
|
89
144
|
background?: string;
|
|
90
145
|
title?: string;
|
|
91
146
|
debug?: boolean;
|
|
92
147
|
type?: string;
|
|
93
148
|
tocIndentStep?: string;
|
|
94
149
|
footnoteCounter?: string;
|
|
150
|
+
/**
|
|
151
|
+
* The number of the section's first page, which starts a new run of pages: `1` restarts the count. Without it the
|
|
152
|
+
* pages count on from the section before. `{page}` prints a page's number, and `{pages}` how many pages its run has.
|
|
153
|
+
*/
|
|
154
|
+
pageStart?: number;
|
|
155
|
+
/**
|
|
156
|
+
* How the section's page numbers are written: `decimal`, `lower-alpha`, `upper-alpha`, `lower-roman` or
|
|
157
|
+
* `upper-roman`. Without it the section keeps the format before.
|
|
158
|
+
*/
|
|
159
|
+
pageFormat?: string;
|
|
160
|
+
/** Text before the section's page numbers, such as `A-`. Without it the section keeps the prefix before; `''` takes it away. */
|
|
161
|
+
pagePrefix?: string;
|
|
162
|
+
/** Sets the section's flow in this many columns, 1 to 12, each filled like a small page, left to right. */
|
|
163
|
+
columns?: number;
|
|
164
|
+
/** The space between two columns, a space token or a length; `l` by default. */
|
|
165
|
+
columnGap?: string;
|
|
166
|
+
/** A line in the middle of each gap between two columns, as a border, such as `0.5pt #ccc`. */
|
|
167
|
+
columnRule?: string;
|
|
168
|
+
/** How the last page's columns fill: `ColumnFill.Auto`, in order, the default, or `ColumnFill.Balance`, levelled. */
|
|
169
|
+
columnFill?: string;
|
|
170
|
+
/** The name of a ruling of the tokens, whose lines are drawn on every page of the section, under everything else. */
|
|
171
|
+
ruling?: string;
|
|
95
172
|
}
|
|
96
173
|
export interface DocumentAttr {
|
|
97
174
|
size?: string;
|
|
98
175
|
orientation?: string;
|
|
176
|
+
/**
|
|
177
|
+
* How far each page reaches past its trim edge, such as `3mm`, for a background or an image that runs off the page
|
|
178
|
+
* when it is cut. The page grows by it and its background fills it; the layout keeps to the trimmed page.
|
|
179
|
+
*/
|
|
180
|
+
bleed?: string;
|
|
99
181
|
background?: string;
|
|
100
182
|
font?: string;
|
|
101
183
|
assets?: DocumentAssets;
|
package/dist/kit.js
CHANGED
|
@@ -34,6 +34,13 @@ function section(attrs, nodes = []) {
|
|
|
34
34
|
nodes,
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* The rulings of the tokens as the document tree takes them: a set of lines names its attributes as an element does,
|
|
39
|
+
* so `stepX` is `step-x`.
|
|
40
|
+
*/
|
|
41
|
+
function rulingsTree(rulings) {
|
|
42
|
+
return Object.fromEntries(Object.entries(rulings).map(([name, { lines, ...ruling }]) => [name, { ...ruling, lines: (lines ?? []).map((set) => (0, _shared_1.buildAttrs)(set)) }]));
|
|
43
|
+
}
|
|
37
44
|
function document(attrs, nodes = []) {
|
|
38
45
|
const { assets, tokens, meta, output, attachments, ...restOpts } = attrs ?? {};
|
|
39
46
|
const attrsObj = {
|
|
@@ -44,17 +51,19 @@ function document(attrs, nodes = []) {
|
|
|
44
51
|
if (tokens !== undefined) {
|
|
45
52
|
// A factor of the line-height row and a gradient's angle are numbers, which JSON can't hold as NaN or infinity.
|
|
46
53
|
(0, _shared_1.assertFiniteIn)('tokens', tokens);
|
|
47
|
-
const { textSize, lineHeight, ...rest } = tokens;
|
|
54
|
+
const { textSize, lineHeight, rulings, ...rest } = tokens;
|
|
48
55
|
attrsObj['tokens'] = {
|
|
49
56
|
...rest,
|
|
50
57
|
...(textSize !== undefined ? { 'text-size': textSize } : {}),
|
|
51
58
|
...(lineHeight !== undefined ? { 'line-height': lineHeight } : {}),
|
|
59
|
+
...(rulings !== undefined ? { rulings: rulingsTree(rulings) } : {}),
|
|
52
60
|
};
|
|
53
61
|
}
|
|
54
62
|
if (meta !== undefined)
|
|
55
63
|
attrsObj['meta'] = meta;
|
|
64
|
+
// The engine reads the schema's names: `facturxStrict` is `facturx-strict`.
|
|
56
65
|
if (output !== undefined)
|
|
57
|
-
attrsObj['output'] = output;
|
|
66
|
+
attrsObj['output'] = (0, _shared_1.buildAttrs)(output);
|
|
58
67
|
if (attachments !== undefined)
|
|
59
68
|
attrsObj['attachments'] = attachments;
|
|
60
69
|
return {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** What a render that crashes the engine rejects with, the same in every SDK of Lpdf. */
|
|
2
|
+
export declare const ENGINE_CRASHED: string;
|
|
3
|
+
/**
|
|
4
|
+
* Thrown when the lpdf engine returns a layout or parse error, a render runs past its timeout or its memory cap, or
|
|
5
|
+
* the engine crashes.
|
|
6
|
+
*/
|
|
7
|
+
export declare class LpdfRenderError extends Error {
|
|
8
|
+
/** For an engine that crashed, the error the engine itself gave, such as `RuntimeError: unreachable`. */
|
|
9
|
+
readonly cause?: string;
|
|
10
|
+
constructor(message: string, cause?: string);
|
|
11
|
+
}
|