@lpdfio/lpdf 0.32.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 +22 -2
- package/dist/_shared.js +19 -3
- package/dist/attrs.d.ts +191 -20
- package/dist/browser.js +265 -36
- package/dist/constants.d.ts +7 -0
- package/dist/constants.js +7 -1
- package/dist/engine.d.ts +49 -16
- package/dist/engine.js +160 -110
- 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 +36 -4
- 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 +60 -2
- package/dist/wasm/lpdf.js +60 -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,20 +53,19 @@ 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();
|
|
@@ -43,6 +73,8 @@ class PdfEngine {
|
|
|
43
73
|
this._profiles = new Map();
|
|
44
74
|
this._markdownStyles = new Map();
|
|
45
75
|
this._disposed = false;
|
|
76
|
+
this._timeout = (0, render_queue_1.renderTimeout)(options.timeout);
|
|
77
|
+
this._maxMemoryMb = (0, _shared_1.maxMemoryMb)(options.maxMemoryMb);
|
|
46
78
|
}
|
|
47
79
|
/**
|
|
48
80
|
* Set the license key. Returns `this` for chaining.
|
|
@@ -65,7 +97,7 @@ class PdfEngine {
|
|
|
65
97
|
*/
|
|
66
98
|
checkLicenseKey(key) {
|
|
67
99
|
this._throwIfDisposed();
|
|
68
|
-
return JSON.parse(
|
|
100
|
+
return JSON.parse(checkLicense(key ?? this._licenseKey, nowUnix()));
|
|
69
101
|
}
|
|
70
102
|
/**
|
|
71
103
|
* Register raw TTF/OTF bytes for a custom font name used in `<font src="…">`.
|
|
@@ -122,10 +154,10 @@ class PdfEngine {
|
|
|
122
154
|
}
|
|
123
155
|
[Symbol.dispose]() { this.dispose(); }
|
|
124
156
|
/**
|
|
125
|
-
*
|
|
126
|
-
*
|
|
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`.
|
|
127
159
|
*/
|
|
128
|
-
|
|
160
|
+
_markdownStylesFor(srcs) {
|
|
129
161
|
const files = new Map(this._markdownStyles);
|
|
130
162
|
for (const src of srcs) {
|
|
131
163
|
if (!files.has(src)) {
|
|
@@ -135,15 +167,13 @@ class PdfEngine {
|
|
|
135
167
|
catch { /* not found; the engine says so */ }
|
|
136
168
|
}
|
|
137
169
|
}
|
|
138
|
-
|
|
139
|
-
engine.load_markdown_styles(src, text);
|
|
140
|
-
}
|
|
170
|
+
return files;
|
|
141
171
|
}
|
|
142
172
|
/**
|
|
143
|
-
*
|
|
144
|
-
*
|
|
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.
|
|
145
175
|
*/
|
|
146
|
-
|
|
176
|
+
_filesFor(srcs) {
|
|
147
177
|
const files = new Map(this._files);
|
|
148
178
|
for (const [key, src] of srcs) {
|
|
149
179
|
if (files.has(key))
|
|
@@ -152,18 +182,16 @@ class PdfEngine {
|
|
|
152
182
|
files.set(key, (0, node_fs_1.readFileSync)(src));
|
|
153
183
|
}
|
|
154
184
|
catch (e) {
|
|
155
|
-
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)}`);
|
|
156
186
|
}
|
|
157
187
|
}
|
|
158
|
-
|
|
159
|
-
engine.load_file(key, bytes);
|
|
160
|
-
}
|
|
188
|
+
return files;
|
|
161
189
|
}
|
|
162
190
|
/**
|
|
163
|
-
*
|
|
164
|
-
*
|
|
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.
|
|
165
193
|
*/
|
|
166
|
-
|
|
194
|
+
_profilesFor(srcs) {
|
|
167
195
|
const profiles = new Map(this._profiles);
|
|
168
196
|
for (const [key, src] of srcs) {
|
|
169
197
|
if (profiles.has(key))
|
|
@@ -172,12 +200,10 @@ class PdfEngine {
|
|
|
172
200
|
profiles.set(key, (0, node_fs_1.readFileSync)(src));
|
|
173
201
|
}
|
|
174
202
|
catch (e) {
|
|
175
|
-
throw new LpdfRenderError(`profile '${key}' of <assets>: cannot read its src '${src}': ${e instanceof Error ? e.message : String(e)}`);
|
|
203
|
+
throw new render_error_1.LpdfRenderError(`profile '${key}' of <assets>: cannot read its src '${src}': ${e instanceof Error ? e.message : String(e)}`);
|
|
176
204
|
}
|
|
177
205
|
}
|
|
178
|
-
|
|
179
|
-
engine.load_profile(key, bytes);
|
|
180
|
-
}
|
|
206
|
+
return profiles;
|
|
181
207
|
}
|
|
182
208
|
_throwIfDisposed() {
|
|
183
209
|
if (this._disposed)
|
|
@@ -185,91 +211,115 @@ class PdfEngine {
|
|
|
185
211
|
}
|
|
186
212
|
async render(input, callOptions = {}) {
|
|
187
213
|
this._throwIfDisposed();
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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));
|
|
207
269
|
}
|
|
270
|
+
catch { /* not found; Rust falls back to Helvetica */ }
|
|
208
271
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
this._loadMarkdownStyles(engine, markdownStylesSrcs(xml));
|
|
216
|
-
this._loadFiles(engine, extractAssetSrcs(xml, 'file'));
|
|
217
|
-
this._loadProfiles(engine, extractAssetSrcs(xml, 'profile'));
|
|
218
|
-
// Auto-load images declared via <image src="…"> that weren't pre-loaded.
|
|
219
|
-
for (const [key, src] of extractAssetSrcs(xml, 'image')) {
|
|
220
|
-
if (!this._images.has(key)) {
|
|
221
|
-
try {
|
|
222
|
-
engine.load_image(key, (0, node_fs_1.readFileSync)(src));
|
|
223
|
-
}
|
|
224
|
-
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));
|
|
225
277
|
}
|
|
278
|
+
catch { /* skip unresolvable image */ }
|
|
226
279
|
}
|
|
227
|
-
const dataJson = callOptions.data != null ? JSON.stringify(callOptions.data) : null;
|
|
228
|
-
pdf = engine.render_pdf(xml, dataJson, ...(0, _shared_1.encryptionArguments)(callOptions, secureRandomBytes));
|
|
229
280
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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));
|
|
241
293
|
}
|
|
294
|
+
catch { /* not found; Rust falls back to Helvetica */ }
|
|
242
295
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
if (!allImages.has(key)) {
|
|
249
|
-
try {
|
|
250
|
-
allImages.set(key, (0, node_fs_1.readFileSync)(src));
|
|
251
|
-
}
|
|
252
|
-
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));
|
|
253
301
|
}
|
|
302
|
+
catch { /* skip unresolvable image */ }
|
|
254
303
|
}
|
|
255
|
-
for (const [name, bytes] of allImages) {
|
|
256
|
-
engine.load_image(name, bytes);
|
|
257
|
-
}
|
|
258
|
-
this._loadMarkdownStyles(engine, markdownStylesSrcsOfDocument(input));
|
|
259
|
-
this._loadFiles(engine, fileSrcsOfDocument(input));
|
|
260
|
-
this._loadProfiles(engine, assetSrcsOfDocument(input, 'profiles'));
|
|
261
|
-
pdf = engine.render_tree_pdf(json, ...(0, _shared_1.encryptionArguments)(callOptions, secureRandomBytes));
|
|
262
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) };
|
|
263
309
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
+
};
|
|
273
323
|
}
|
|
274
324
|
}
|
|
275
325
|
exports.PdfEngine = PdfEngine;
|
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, ProfileAttr, 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, ProfileAttr } 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 `-`,
|
|
@@ -68,7 +73,10 @@ export interface DocumentMeta {
|
|
|
68
73
|
subject?: string;
|
|
69
74
|
keywords?: string;
|
|
70
75
|
creator?: string;
|
|
71
|
-
/**
|
|
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
|
+
*/
|
|
72
80
|
lang?: string;
|
|
73
81
|
}
|
|
74
82
|
/**
|
|
@@ -86,8 +94,14 @@ export interface DocumentOutput {
|
|
|
86
94
|
allow?: string;
|
|
87
95
|
/**
|
|
88
96
|
* Writes the PDF as PDF/A, the standard for files kept for the long term: `2b`, or `2u` when its text must also
|
|
89
|
-
* be searchable
|
|
90
|
-
*
|
|
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.
|
|
91
105
|
*/
|
|
92
106
|
pdfa?: string;
|
|
93
107
|
/**
|
|
@@ -113,6 +127,14 @@ export interface DocumentOutput {
|
|
|
113
127
|
* and linearized last. Any change to the file afterwards, such as a stamp, ends the linearization.
|
|
114
128
|
*/
|
|
115
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;
|
|
116
138
|
}
|
|
117
139
|
export interface SectionAttr {
|
|
118
140
|
size?: string;
|
|
@@ -137,6 +159,16 @@ export interface SectionAttr {
|
|
|
137
159
|
pageFormat?: string;
|
|
138
160
|
/** Text before the section's page numbers, such as `A-`. Without it the section keeps the prefix before; `''` takes it away. */
|
|
139
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;
|
|
140
172
|
}
|
|
141
173
|
export interface DocumentAttr {
|
|
142
174
|
size?: string;
|
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
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LpdfRenderError = exports.ENGINE_CRASHED = void 0;
|
|
4
|
+
/** What a render that crashes the engine rejects with, the same in every SDK of Lpdf. */
|
|
5
|
+
exports.ENGINE_CRASHED = 'The engine crashed during the render. This is a bug in Lpdf, or the render ran out of memory before its cap ' +
|
|
6
|
+
'stopped it: please report it with the document at https://github.com/lpdfio/lpdf/issues.';
|
|
7
|
+
/**
|
|
8
|
+
* Thrown when the lpdf engine returns a layout or parse error, a render runs past its timeout or its memory cap, or
|
|
9
|
+
* the engine crashes.
|
|
10
|
+
*/
|
|
11
|
+
class LpdfRenderError extends Error {
|
|
12
|
+
constructor(message, cause) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = 'LpdfRenderError';
|
|
15
|
+
if (cause !== undefined)
|
|
16
|
+
this.cause = cause;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
exports.LpdfRenderError = LpdfRenderError;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/** The engine's render calls, as wasm-bindgen exposes them in both builds. */
|
|
2
|
+
export interface WasmRenderEngine {
|
|
3
|
+
render_pdf(xml: string, json_data: string | null, user_password: string | null, owner_password: string | null, skip_encryption: boolean, entropy: Uint8Array): Uint8Array;
|
|
4
|
+
render_tree_pdf(json: string, user_password: string | null, owner_password: string | null, skip_encryption: boolean, entropy: Uint8Array): Uint8Array;
|
|
5
|
+
stamp(pdf: Uint8Array, stamps: string): Uint8Array;
|
|
6
|
+
load_font(name: string, bytes: Uint8Array): void;
|
|
7
|
+
load_image(name: string, bytes: Uint8Array): void;
|
|
8
|
+
load_file(name: string, bytes: Uint8Array): void;
|
|
9
|
+
load_profile(name: string, bytes: Uint8Array): void;
|
|
10
|
+
load_markdown_styles(src: string, xml: string): void;
|
|
11
|
+
set_created_on(iso: string): void;
|
|
12
|
+
set_now(unix: bigint): void;
|
|
13
|
+
set_max_memory_mb(mb: number): void;
|
|
14
|
+
free(): void;
|
|
15
|
+
}
|
|
16
|
+
/** The user password, the owner password, `skipEncryption` and the entropy, in the order the render calls take them. */
|
|
17
|
+
export type EncryptionArguments = [string | null, string | null, boolean, Uint8Array];
|
|
18
|
+
/**
|
|
19
|
+
* One render: the document and every file it needs, already read, since a worker reads nothing itself. A stamp runs as
|
|
20
|
+
* a render does, with the PDF and the JSON of its stamps as its document and their PNGs as its images.
|
|
21
|
+
*/
|
|
22
|
+
export interface RenderJob {
|
|
23
|
+
licenseKey: string;
|
|
24
|
+
/** The host's clock in Unix seconds, for the key's expiry. */
|
|
25
|
+
now: bigint;
|
|
26
|
+
/** The megabytes the render may allocate. */
|
|
27
|
+
maxMemoryMb: number;
|
|
28
|
+
createdOn: string | undefined;
|
|
29
|
+
fonts: Map<string, Uint8Array>;
|
|
30
|
+
images: Map<string, Uint8Array>;
|
|
31
|
+
files: Map<string, Uint8Array>;
|
|
32
|
+
profiles: Map<string, Uint8Array>;
|
|
33
|
+
markdownStyles: Map<string, string>;
|
|
34
|
+
/** An XML document with its data as JSON, a document tree as JSON, or a PDF to stamp. */
|
|
35
|
+
document: {
|
|
36
|
+
xml: string;
|
|
37
|
+
data: string | null;
|
|
38
|
+
} | {
|
|
39
|
+
tree: string;
|
|
40
|
+
} | {
|
|
41
|
+
pdf: Uint8Array;
|
|
42
|
+
stamps: string;
|
|
43
|
+
};
|
|
44
|
+
encryption: EncryptionArguments;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* What the worker sends back: the PDF, or the engine's error. `broken` says that the engine trapped, after which its
|
|
48
|
+
* memory can't be trusted, so the worker is replaced before the next render. A crash of the engine has the error every
|
|
49
|
+
* SDK gives for one, and the engine's own as its `cause`.
|
|
50
|
+
*/
|
|
51
|
+
export type RenderReply = {
|
|
52
|
+
pdf: Uint8Array;
|
|
53
|
+
} | {
|
|
54
|
+
error: string;
|
|
55
|
+
broken: boolean;
|
|
56
|
+
cause?: string;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Runs a render on an engine of its own, which is freed after it, and returns the PDF or the error. It never throws: a
|
|
60
|
+
* failure is the reply.
|
|
61
|
+
*/
|
|
62
|
+
export declare function runRenderJob(createEngine: (licenseKey: string) => WasmRenderEngine, job: RenderJob): RenderReply;
|
|
63
|
+
/**
|
|
64
|
+
* The buffers a reply moves to the other thread instead of copying: the PDF's, when the PDF has a buffer of its own.
|
|
65
|
+
* A view into a larger buffer, such as the engine's memory, is copied.
|
|
66
|
+
*/
|
|
67
|
+
export declare function transferList(reply: RenderReply): ArrayBuffer[];
|