pretext-pdf 0.5.2 → 0.8.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/CHANGELOG.md +462 -351
- package/README.md +749 -568
- package/dist/assets.d.ts +5 -0
- package/dist/assets.d.ts.map +1 -1
- package/dist/assets.js +248 -43
- package/dist/assets.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/fonts.d.ts.map +1 -1
- package/dist/fonts.js +67 -8
- package/dist/fonts.js.map +1 -1
- package/dist/index.d.ts +29 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -2
- package/dist/index.js.map +1 -1
- package/dist/markdown.d.ts +28 -0
- package/dist/markdown.d.ts.map +1 -0
- package/dist/markdown.js +222 -0
- package/dist/markdown.js.map +1 -0
- package/dist/measure-blocks.d.ts.map +1 -1
- package/dist/measure-blocks.js +347 -62
- package/dist/measure-blocks.js.map +1 -1
- package/dist/measure-text.d.ts.map +1 -1
- package/dist/measure-text.js +1 -8
- package/dist/measure-text.js.map +1 -1
- package/dist/measure.d.ts.map +1 -1
- package/dist/measure.js +13 -21
- package/dist/measure.js.map +1 -1
- package/dist/render-blocks.d.ts +4 -1
- package/dist/render-blocks.d.ts.map +1 -1
- package/dist/render-blocks.js +227 -105
- package/dist/render-blocks.js.map +1 -1
- package/dist/render-extras.d.ts.map +1 -1
- package/dist/render-extras.js +72 -71
- package/dist/render-extras.js.map +1 -1
- package/dist/render-utils.d.ts +9 -2
- package/dist/render-utils.d.ts.map +1 -1
- package/dist/render-utils.js +24 -13
- package/dist/render-utils.js.map +1 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +27 -3
- package/dist/render.js.map +1 -1
- package/dist/rich-text.d.ts +0 -4
- package/dist/rich-text.d.ts.map +1 -1
- package/dist/rich-text.js +15 -9
- package/dist/rich-text.js.map +1 -1
- package/dist/templates.d.ts +79 -0
- package/dist/templates.d.ts.map +1 -0
- package/dist/templates.js +201 -0
- package/dist/templates.js.map +1 -0
- package/dist/types.d.ts +139 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +241 -28
- package/dist/validate.js.map +1 -1
- package/package.json +175 -130
package/README.md
CHANGED
|
@@ -1,568 +1,749 @@
|
|
|
1
|
-
# pretext-pdf
|
|
2
|
-
|
|
3
|
-
> **Declarative JSON → PDF generation with professional typography.**
|
|
4
|
-
>
|
|
5
|
-
> Build sophisticated, multi-page documents with precise text layout, international support, and zero browser overhead.
|
|
6
|
-
|
|
7
|
-
[](https://www.npmjs.com/package/pretext-pdf)
|
|
8
|
-
[](https://www.npmjs.com/package/pretext-pdf)
|
|
9
|
-
[](https://github.com/Himaan1998Y/pretext-pdf/actions)
|
|
10
|
-
[](https://www.typescriptlang.org/)
|
|
11
|
-
[](LICENSE)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
- **
|
|
26
|
-
|
|
27
|
-
**
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
|
61
|
-
|
|
62
|
-
|
|
|
63
|
-
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
-
|
|
175
|
-
|
|
176
|
-
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
-
|
|
212
|
-
|
|
213
|
-
-
|
|
214
|
-
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
---
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
```
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
npm
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
---
|
|
454
|
-
|
|
455
|
-
##
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
```
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
```
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
1
|
+
# pretext-pdf
|
|
2
|
+
|
|
3
|
+
> **Declarative JSON → PDF generation with professional typography.**
|
|
4
|
+
>
|
|
5
|
+
> Build sophisticated, multi-page documents with precise text layout, international support, and zero browser overhead.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/pretext-pdf)
|
|
8
|
+
[](https://www.npmjs.com/package/pretext-pdf)
|
|
9
|
+
[](https://github.com/Himaan1998Y/pretext-pdf/actions)
|
|
10
|
+
[](https://www.typescriptlang.org/)
|
|
11
|
+
[](#type-safety-v046)
|
|
12
|
+
[](#test-coverage)
|
|
13
|
+
[](LICENSE)
|
|
14
|
+
|
|
15
|
+
**[Try the Live Demo](https://stackblitz.com/github/Himaan1998Y/pretext-pdf/tree/master/demo/stackblitz?file=public%2Findex.html)** — edit JSON, generate PDFs instantly. No install required.
|
|
16
|
+
|
|
17
|
+
**Coming from pdfmake?** See the [Migration Guide](docs/MIGRATION_FROM_PDFMAKE.md) — maps every pdfmake pattern to its pretext-pdf equivalent.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## v0.8.0 — QR Codes, Barcodes, Charts, Markdown, Templates
|
|
22
|
+
|
|
23
|
+
Five new capabilities added, all via optional peer dependencies (zero extra weight if unused):
|
|
24
|
+
|
|
25
|
+
- **`qr-code` element** — embed scannable QR codes (UPI payments, URLs, vCards). Requires `qrcode`.
|
|
26
|
+
- **`barcode` element** — 100+ symbologies (EAN-13, Code128, PDF417, QR, DataMatrix…). Requires `bwip-js`.
|
|
27
|
+
- **`chart` element** — embed Vega-Lite data visualisations as crisp vector SVG. Requires `vega` + `vega-lite`.
|
|
28
|
+
- **`pretext-pdf/markdown`** entry point — convert any Markdown string to `ContentElement[]` in one call. Requires `marked`.
|
|
29
|
+
- **`pretext-pdf/templates`** entry point — zero-dep helper functions: `createInvoice`, `createGstInvoice` (India GST / IGST / CGST+SGST), `createReport`.
|
|
30
|
+
|
|
31
|
+
Install only what you need:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install pretext-pdf@^0.8.0
|
|
35
|
+
npm install qrcode # for qr-code element
|
|
36
|
+
npm install bwip-js # for barcode element
|
|
37
|
+
npm install vega vega-lite # for chart element
|
|
38
|
+
npm install marked # for pretext-pdf/markdown
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
> **ESM only** — pretext-pdf is a pure ESM package (`"type": "module"`). Use `import`, not `require`.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## v0.4.6 — Security & Quality Hardening
|
|
46
|
+
|
|
47
|
+
All 41 issues from comprehensive April 2026 security audit resolved:
|
|
48
|
+
|
|
49
|
+
- **Phase 1**: Security hardening (path traversal protection, async file I/O, explicit error handling)
|
|
50
|
+
- **Phase 2**: Type safety (reduced and documented any-casts, proper module typing, strict inference)
|
|
51
|
+
- **Phase 3**: Test coverage (false-positive fixes, boundary case validation)
|
|
52
|
+
- **Phase 4**: Code quality (silent failures → explicit errors, improved decoupling)
|
|
53
|
+
|
|
54
|
+
**Result**: 188+ comprehensive tests, 100% pass rate, production-ready reliability.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Why pretext-pdf?
|
|
59
|
+
|
|
60
|
+
| | pdfmake | Puppeteer | **pretext-pdf** |
|
|
61
|
+
|---|---|---|---|
|
|
62
|
+
| Easy declarative API | ✅ | ❌ | ✅ |
|
|
63
|
+
| Professional typography | ❌ | ✅ | ✅ |
|
|
64
|
+
| Lightweight (no browser) | ✅ | ❌ | ✅ |
|
|
65
|
+
| International text (RTL/CJK) | ❌ | ✅ | ✅ |
|
|
66
|
+
| Pure Node.js | ✅ | ❌ | ✅ |
|
|
67
|
+
| Hyperlinks + annotations | ❌ | ✅ | ✅ |
|
|
68
|
+
| Document assembly | ❌ | ❌ | ✅ |
|
|
69
|
+
|
|
70
|
+
### Powered by [pretext](https://github.com/chenglou/pretext)
|
|
71
|
+
|
|
72
|
+
Pretext is a precision text layout engine by [Cheng Lou](https://github.com/chenglou) (React core team, Midjourney).
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
JSON descriptor → pretext layout → pdf-lib renderer → PDF bytes
|
|
76
|
+
(kerning, (annotations,
|
|
77
|
+
hyphenation, encryption,
|
|
78
|
+
RTL, CJK) hyperlinks)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Output Samples
|
|
84
|
+
|
|
85
|
+
Real documents generated with pretext-pdf:
|
|
86
|
+
|
|
87
|
+
| Invoice | Market Report | Resume / CV |
|
|
88
|
+
|---------|--------------|-------------|
|
|
89
|
+
| [](examples/showcase-invoice.ts) | [](examples/showcase-report.ts) | [](examples/showcase-resume.ts) |
|
|
90
|
+
| [View source](examples/showcase-invoice.ts) | [View source](examples/showcase-report.ts) | [View source](examples/showcase-resume.ts) |
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Install
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
npm install pretext-pdf@^0.8.0
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
> **ESM only** — use `import`, not `require`.
|
|
101
|
+
|
|
102
|
+
Optional peer dependencies — install only what you need:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
npm install @napi-rs/canvas # SVG elements (qr-code / barcode / chart all require this too)
|
|
106
|
+
npm install qrcode # qr-code element
|
|
107
|
+
npm install bwip-js # barcode element
|
|
108
|
+
npm install vega vega-lite # chart element
|
|
109
|
+
npm install marked # pretext-pdf/markdown entry point
|
|
110
|
+
npm install @signpdf/signpdf # PKCS#7 cryptographic signing
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
> **Encryption is built-in since v0.4.0** — no extra install needed. Just add `encryption` to your document config.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Quick Start
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
import { render } from 'pretext-pdf'
|
|
121
|
+
import { writeFileSync } from 'fs'
|
|
122
|
+
|
|
123
|
+
const pdf = await render({
|
|
124
|
+
pageSize: 'A4',
|
|
125
|
+
margins: { top: 40, bottom: 40, left: 50, right: 50 },
|
|
126
|
+
metadata: { title: 'My Invoice', author: 'Acme Corp' },
|
|
127
|
+
content: [
|
|
128
|
+
{ type: 'heading', level: 1, text: 'Invoice #12345' },
|
|
129
|
+
{ type: 'paragraph', text: 'Thank you for your business.', fontSize: 12 },
|
|
130
|
+
{
|
|
131
|
+
type: 'table',
|
|
132
|
+
columns: [
|
|
133
|
+
{ name: 'Item', width: 200 },
|
|
134
|
+
{ name: 'Qty', width: 50, align: 'right' },
|
|
135
|
+
{ name: 'Price', width: 100, align: 'right' },
|
|
136
|
+
],
|
|
137
|
+
rows: [
|
|
138
|
+
{ Item: 'Professional Services', Qty: '10', Price: '$1,000' },
|
|
139
|
+
{ Item: 'Hosting (annual)', Qty: '1', Price: '$500' },
|
|
140
|
+
],
|
|
141
|
+
},
|
|
142
|
+
{ type: 'paragraph', text: 'Total: $1,500', align: 'right', fontWeight: 700 },
|
|
143
|
+
],
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
writeFileSync('invoice.pdf', pdf)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Builder API
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
import { createPdf } from 'pretext-pdf'
|
|
153
|
+
|
|
154
|
+
const pdf = await createPdf({ pageSize: 'A4' })
|
|
155
|
+
.addHeading('My Report', 1)
|
|
156
|
+
.addText('Fluent chainable API.')
|
|
157
|
+
.addTable({ columns: [{ name: 'Col A' }, { name: 'Col B' }], rows: [{ 'Col A': 'x', 'Col B': 'y' }] })
|
|
158
|
+
.build()
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Agent / AI Integration
|
|
164
|
+
|
|
165
|
+
pretext-pdf works great as a tool for AI agents generating PDFs on demand.
|
|
166
|
+
|
|
167
|
+
### MCP Server (Claude Desktop, Cursor, Windsurf)
|
|
168
|
+
|
|
169
|
+
Use [`pretext-pdf-mcp`](https://www.npmjs.com/package/pretext-pdf-mcp) to call pretext-pdf directly from any AI agent:
|
|
170
|
+
|
|
171
|
+
```json
|
|
172
|
+
{
|
|
173
|
+
"mcpServers": {
|
|
174
|
+
"pretext-pdf": {
|
|
175
|
+
"command": "npx",
|
|
176
|
+
"args": ["-y", "pretext-pdf-mcp"]
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Tools available: `generate_pdf`, `generate_invoice`, `generate_report`, `generate_from_markdown`, `list_element_types`
|
|
183
|
+
|
|
184
|
+
### Quick pattern for LLMs
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
import { render } from 'pretext-pdf'
|
|
188
|
+
|
|
189
|
+
// Every PdfDocument is a plain JSON object — perfect for AI generation
|
|
190
|
+
const pdf = await render({
|
|
191
|
+
metadata: { title: 'AI-Generated Report' },
|
|
192
|
+
content: [
|
|
193
|
+
{ type: 'heading', level: 1, text: 'Summary' },
|
|
194
|
+
{ type: 'paragraph', text: 'Generated content here.' },
|
|
195
|
+
// ... AI fills this array
|
|
196
|
+
]
|
|
197
|
+
})
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Key facts for AI agents
|
|
201
|
+
|
|
202
|
+
- `content` is an array of typed elements — each has a `type` field
|
|
203
|
+
- All fields are optional except `type` and element-specific required fields (e.g. `text`, `level`)
|
|
204
|
+
- Errors are typed: `err.code` tells you exactly what went wrong
|
|
205
|
+
- `render()` is fully async, safe to `await` in any context
|
|
206
|
+
- Works in Node.js 18+ and modern browsers (with `@napi-rs/canvas` for SVG)
|
|
207
|
+
|
|
208
|
+
### Element type reference (quick)
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
paragraph heading(1-4) spacer hr page-break
|
|
212
|
+
table image svg list code
|
|
213
|
+
blockquote rich-paragraph callout comment form-field
|
|
214
|
+
toc qr-code barcode chart
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## India / GST Invoicing
|
|
220
|
+
|
|
221
|
+
pretext-pdf has built-in support for Indian invoice requirements:
|
|
222
|
+
|
|
223
|
+
- **₹ symbol** renders correctly (bundled Inter font includes the Rupee glyph)
|
|
224
|
+
- **Indian number formatting** — helper for 1,00,000 notation (not 100,000)
|
|
225
|
+
- **GST structure** — CGST/SGST (intra-state) and IGST (inter-state) table layouts
|
|
226
|
+
- **Amount in words** — Indian numbering system (Lakh/Crore)
|
|
227
|
+
- **SAC/HSN codes** — column support in line-item tables
|
|
228
|
+
|
|
229
|
+
Use the `createGstInvoice` template for a complete GST-compliant invoice in one function call:
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
import { createGstInvoice } from 'pretext-pdf/templates'
|
|
233
|
+
import { render } from 'pretext-pdf'
|
|
234
|
+
|
|
235
|
+
const content = createGstInvoice({
|
|
236
|
+
supplier: { name: 'Antigravity Systems', address: 'Gurugram, HR', gstin: '06AAACA1234A1ZV', state: 'Haryana' },
|
|
237
|
+
buyer: { name: 'TechStartup Ltd', address: 'Mumbai, MH', gstin: '27AABCB5678B1ZP', state: 'Maharashtra' },
|
|
238
|
+
invoiceNumber: 'INV/2026-27/001',
|
|
239
|
+
invoiceDate: '20 Apr 2026',
|
|
240
|
+
placeOfSupply: 'Maharashtra (27)',
|
|
241
|
+
items: [
|
|
242
|
+
{ description: 'Software Development', hsnSac: '998314', quantity: 80, unit: 'Hrs', rate: 3000, taxRate: 18 },
|
|
243
|
+
],
|
|
244
|
+
isInterState: true, // auto-detected from state fields if omitted
|
|
245
|
+
qrUpiData: 'upi://pay?pa=merchant@hdfc&pn=Antigravity&am=283200',
|
|
246
|
+
bankName: 'HDFC Bank', accountNumber: '501001234567', ifscCode: 'HDFC0001234',
|
|
247
|
+
})
|
|
248
|
+
const pdf = await render({ content })
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
See [`examples/gst-invoice-india.ts`](examples/gst-invoice-india.ts) for the raw element approach.
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## Markdown → PDF (`pretext-pdf/markdown`)
|
|
256
|
+
|
|
257
|
+
Convert any Markdown string to a `pretext-pdf` document in one call. Requires `marked` peer dep.
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
import { markdownToContent } from 'pretext-pdf/markdown'
|
|
261
|
+
import { render } from 'pretext-pdf'
|
|
262
|
+
import { writeFileSync } from 'fs'
|
|
263
|
+
|
|
264
|
+
const md = `
|
|
265
|
+
# Q1 2026 Report
|
|
266
|
+
|
|
267
|
+
Revenue grew **18%** year-over-year, driven by:
|
|
268
|
+
|
|
269
|
+
- Cloud services (+32%)
|
|
270
|
+
- Enterprise licenses (+12%)
|
|
271
|
+
|
|
272
|
+
> All figures are in USD millions.
|
|
273
|
+
`
|
|
274
|
+
|
|
275
|
+
const content = await markdownToContent(md, {
|
|
276
|
+
codeFontFamily: 'Courier New', // enables fenced code block rendering
|
|
277
|
+
})
|
|
278
|
+
const pdf = await render({ content })
|
|
279
|
+
writeFileSync('report.pdf', pdf)
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Supported Markdown: headings h1–h4, bold, italic, strikethrough, inline code, links, ordered/unordered lists (2 levels), fenced code blocks, blockquotes, horizontal rules.
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Invoice & Report Templates (`pretext-pdf/templates`)
|
|
287
|
+
|
|
288
|
+
Pre-built zero-dependency template functions that generate `ContentElement[]` arrays:
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
import { createInvoice, createGstInvoice, createReport } from 'pretext-pdf/templates'
|
|
292
|
+
import { render } from 'pretext-pdf'
|
|
293
|
+
|
|
294
|
+
// Generic invoice (any currency)
|
|
295
|
+
const invoiceContent = createInvoice({
|
|
296
|
+
from: { name: 'Acme Corp', address: '123 Main St', email: 'billing@acme.com' },
|
|
297
|
+
to: { name: 'Client Ltd', address: '456 Oak Ave' },
|
|
298
|
+
invoiceNumber: 'INV-2026-001', date: '2026-04-20',
|
|
299
|
+
items: [{ description: 'Consulting', quantity: 10, unitPrice: 150 }],
|
|
300
|
+
currency: '$', taxRate: 10, taxLabel: 'GST',
|
|
301
|
+
qrData: 'upi://pay?pa=acme@bank&am=1650',
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
// Research report with optional TOC
|
|
305
|
+
const reportContent = createReport({
|
|
306
|
+
title: 'Annual Performance Report',
|
|
307
|
+
author: 'Finance Team', date: 'April 2026',
|
|
308
|
+
abstract: 'Revenue grew 18% YoY across all segments.',
|
|
309
|
+
includeTableOfContents: true,
|
|
310
|
+
sections: [
|
|
311
|
+
{ title: 'Revenue', paragraphs: ['Cloud +32%, Enterprise +12%.'], bullets: ['SaaS: $2.8M', 'Services: $1.1M'] },
|
|
312
|
+
],
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
const pdf = await render({ content: reportContent })
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## Features
|
|
321
|
+
|
|
322
|
+
### Security & Reliability
|
|
323
|
+
|
|
324
|
+
- ✅ **Type-safe architecture** — strict TypeScript inference, documented casts for pdf-lib internals
|
|
325
|
+
- ✅ **Cryptographically signed PDFs** — PKCS#7 signing support (Phase 3)
|
|
326
|
+
- ✅ **Path traversal protection** — Secure file operations with validated paths
|
|
327
|
+
- ✅ **Error sanitization** — No sensitive data in error messages
|
|
328
|
+
- ✅ **Async-safe I/O** — Non-blocking file operations throughout
|
|
329
|
+
- ✅ **Comprehensive test coverage** — 188+ tests with 100% pass rate
|
|
330
|
+
- ✅ **No hardcoded secrets** — Environment-based configuration
|
|
331
|
+
|
|
332
|
+
### Element Types
|
|
333
|
+
|
|
334
|
+
| Element | What it does |
|
|
335
|
+
| --- | --- |
|
|
336
|
+
| `paragraph` | Text block — font, size, color, align, background, letterSpacing, smallCaps, tabularNumbers, multi-column (`columns` + `columnGap`), RTL (`dir`) |
|
|
337
|
+
| `heading` | H1–H4 with bookmarks, URL links, internal anchors, tabularNumbers, RTL (`dir`) |
|
|
338
|
+
| `table` | Fixed/proportional columns, colspan, rowspan, repeating headers across page breaks |
|
|
339
|
+
| `image` | PNG/JPG/WebP with sizing, alignment, float left/right with `floatText` or rich `floatSpans` (mixed-format caption) |
|
|
340
|
+
| `list` | Ordered/unordered, 2-level nesting, `nestedNumberingStyle: 'restart' \| 'continue'` |
|
|
341
|
+
| `code` | Monospace block with background and padding |
|
|
342
|
+
| `blockquote` | Left border + background |
|
|
343
|
+
| `rich-paragraph` | Mixed bold/italic/color/size/super/subscript spans with inline hyperlinks |
|
|
344
|
+
| `svg` | Embedded SVG graphics with auto-sizing from viewBox |
|
|
345
|
+
| `toc` | Auto-generated table of contents with accurate page numbers (two-pass) |
|
|
346
|
+
| `qr-code` | Scannable QR code — UPI payment links, URLs, vCards. `data`, `size`, `errorCorrectionLevel`, `foreground`/`background` color. Requires `qrcode` peer dep. |
|
|
347
|
+
| `barcode` | 100+ symbologies — EAN-13, Code128, PDF417, DataMatrix, and more via `symbology` field. Requires `bwip-js` peer dep. |
|
|
348
|
+
| `chart` | Vega-Lite data visualisation — pass any valid Vega-Lite spec to `spec`. Rendered as vector SVG. Requires `vega` + `vega-lite` peer deps. |
|
|
349
|
+
| `comment` | PDF sticky-note annotation (visible in Acrobat/Preview sidebar) |
|
|
350
|
+
| `hr` | Horizontal rule |
|
|
351
|
+
| `spacer` | Fixed-height gap |
|
|
352
|
+
| `page-break` | Force new page |
|
|
353
|
+
|
|
354
|
+
### Document Features
|
|
355
|
+
|
|
356
|
+
| Feature | Config key | Notes |
|
|
357
|
+
| --- | --- | --- |
|
|
358
|
+
| Watermarks | `doc.watermark` | Text or image, opacity, rotation |
|
|
359
|
+
| Encryption | `doc.encryption` | Password + granular permissions |
|
|
360
|
+
| PDF Bookmarks | `doc.bookmarks` | Auto-generated from headings |
|
|
361
|
+
| Hyphenation | `doc.hyphenation` | Liang's algorithm, `language: 'en-us'` |
|
|
362
|
+
| Headers/Footers | `doc.header` / `doc.footer` | `{{pageNumber}}`, `{{totalPages}}`, `{{date}}`, `{{author}}` tokens |
|
|
363
|
+
| Per-section overrides | `doc.sections` | Different header/footer/margins per page range |
|
|
364
|
+
| Metadata | `doc.metadata` | Title, author, subject, keywords, `language` (PDF /Lang), `producer` |
|
|
365
|
+
|
|
366
|
+
### Phase 8 Features
|
|
367
|
+
|
|
368
|
+
| Feature | API |
|
|
369
|
+
| --- | --- |
|
|
370
|
+
| **Hyperlinks** | `paragraph.url`, `heading.url`, `heading.anchor`, `span.href` |
|
|
371
|
+
| **Inline formatting** | `span.verticalAlign: 'superscript'\|'subscript'`, `paragraph.letterSpacing`, `heading.smallCaps` |
|
|
372
|
+
| **Sticky notes** | `{ type: 'comment', contents: '...' }`, `paragraph.annotation` |
|
|
373
|
+
| **Document assembly** | `merge(pdfs)`, `assemble(parts)` |
|
|
374
|
+
| **Interactive forms** | `{ type: 'form-field', fieldType: 'text'\|'checkbox'\|'radio'\|'dropdown'\|'button' }`, `doc.flattenForms` |
|
|
375
|
+
| **Signature placeholder** | `doc.signature: { signerName, reason, location, x, y, page }` |
|
|
376
|
+
| **Callout boxes** | `{ type: 'callout', content, style: 'info'\|'warning'\|'tip'\|'note', title }` |
|
|
377
|
+
| **Form error handling** | `doc.onFormFieldError: (name, err) => 'skip' \| 'throw'` |
|
|
378
|
+
| **Image error handling** | `doc.onImageLoadError: (src, err) => 'skip' \| 'throw'` |
|
|
379
|
+
|
|
380
|
+
### Type Safety (v0.4.6+)
|
|
381
|
+
|
|
382
|
+
pretext-pdf is built with **strict TypeScript**. Remaining `as any` casts are limited to pdf-lib internal APIs with no public type surface, each documented with a comment explaining why:
|
|
383
|
+
|
|
384
|
+
- **Full type inference** — No need to cast document configs or response types
|
|
385
|
+
- **Element validation** — TypeScript catches invalid element types at compile time
|
|
386
|
+
- **API contract testing** — Every API boundary has comprehensive type tests
|
|
387
|
+
- **Error types** — `PretextPdfError` with typed code field for safe error handling
|
|
388
|
+
- **Module typing** — Complete type definitions for all exports and configurations
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## Security Audit (April 2026)
|
|
393
|
+
|
|
394
|
+
Comprehensive security and quality audit completed. **41 issues identified and fixed across 5 phases:**
|
|
395
|
+
|
|
396
|
+
| Phase | Focus | Issues | Status |
|
|
397
|
+
| --- | --- | --- | --- |
|
|
398
|
+
| 0 | Core rendering | Footnote truncation | ✅ Fixed |
|
|
399
|
+
| 1 | Security hardening | Path validation, async I/O, error handling | ✅ Fixed |
|
|
400
|
+
| 2 | Type safety | Any-cast elimination, module typing | ✅ Fixed |
|
|
401
|
+
| 3 | Test coverage | False-positives, boundary cases, crypto signing | ✅ Fixed |
|
|
402
|
+
| 4 | Code quality | Silent failures → explicit errors, decoupling | ✅ Fixed |
|
|
403
|
+
|
|
404
|
+
**Audit results:**
|
|
405
|
+
|
|
406
|
+
- Zero path traversal vulnerabilities
|
|
407
|
+
- All error messages sanitized (no data leaks)
|
|
408
|
+
- Async file I/O throughout (non-blocking)
|
|
409
|
+
- No hardcoded secrets or credentials
|
|
410
|
+
- 188+ tests, 100% pass rate
|
|
411
|
+
- Production-ready reliability
|
|
412
|
+
|
|
413
|
+
See [SECURITY.md](SECURITY.md) for detailed security policies.
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
## Examples
|
|
418
|
+
|
|
419
|
+
Run working examples from the `examples/` directory:
|
|
420
|
+
|
|
421
|
+
```bash
|
|
422
|
+
# v0.8.0 new element examples (install optional deps first)
|
|
423
|
+
# npm install qrcode bwip-js vega vega-lite marked
|
|
424
|
+
|
|
425
|
+
# QR code in a document:
|
|
426
|
+
# content: [{ type: 'qr-code', data: 'upi://pay?pa=merchant@upi&am=1000', size: 80, align: 'center' }]
|
|
427
|
+
|
|
428
|
+
# Barcode:
|
|
429
|
+
# content: [{ type: 'barcode', symbology: 'ean13', data: '5901234123457', width: 200, height: 80 }]
|
|
430
|
+
|
|
431
|
+
# Vega-Lite chart:
|
|
432
|
+
# content: [{ type: 'chart', spec: { data: { values: [...] }, mark: 'bar', encoding: { x: ..., y: ... } } }]
|
|
433
|
+
|
|
434
|
+
# Phase 7 examples
|
|
435
|
+
npm run example # Basic invoice
|
|
436
|
+
npm run example:watermark # Text/image watermarks
|
|
437
|
+
npm run example:bookmarks # PDF outline/bookmarks
|
|
438
|
+
npm run example:toc # Auto table of contents
|
|
439
|
+
npm run example:rtl # Arabic/Hebrew RTL text
|
|
440
|
+
npm run example:encryption # Password-protected PDF
|
|
441
|
+
|
|
442
|
+
# Phase 8 examples
|
|
443
|
+
npm run example:hyperlinks # External links, email links, internal anchors
|
|
444
|
+
npm run example:annotations # Sticky notes on elements
|
|
445
|
+
npm run example:assembly # Merge and assemble multiple PDFs
|
|
446
|
+
npm run example:inline # Superscript, subscript, letter-spacing, small-caps
|
|
447
|
+
npm run example:forms # Interactive form fields (text, checkbox, radio, dropdown)
|
|
448
|
+
npm run example:callout # Callout boxes (info, warning, tip, note presets)
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
All examples write output to `output/*.pdf`.
|
|
452
|
+
|
|
453
|
+
---
|
|
454
|
+
|
|
455
|
+
## API Reference
|
|
456
|
+
|
|
457
|
+
### `render(doc): Promise<Uint8Array>`
|
|
458
|
+
|
|
459
|
+
```typescript
|
|
460
|
+
import { render } from 'pretext-pdf'
|
|
461
|
+
|
|
462
|
+
const pdf = await render({
|
|
463
|
+
pageSize: 'A4', // 'A4' | 'A3' | 'A5' | 'Letter' | 'Legal' | [w, h]
|
|
464
|
+
margins: { top: 72, bottom: 72, left: 72, right: 72 },
|
|
465
|
+
defaultFont: 'Inter', // Inter 400 bundled; load others via doc.fonts
|
|
466
|
+
defaultFontSize: 12,
|
|
467
|
+
metadata: {
|
|
468
|
+
title: 'Document Title',
|
|
469
|
+
author: 'Author Name',
|
|
470
|
+
subject: 'Description',
|
|
471
|
+
keywords: ['pdf', 'report'],
|
|
472
|
+
},
|
|
473
|
+
watermark: { text: 'DRAFT', opacity: 0.15, rotation: -45 },
|
|
474
|
+
encryption: { userPassword: 'open', ownerPassword: 'admin', permissions: { printing: true, copying: false } },
|
|
475
|
+
bookmarks: { minLevel: 1, maxLevel: 3 },
|
|
476
|
+
hyphenation: { language: 'en-us', minWordLength: 6 }, // ⚠️ Use lowercase: 'en-us' not 'en-US' — matches the npm package name hyphenation.en-us
|
|
477
|
+
header: { text: 'My Document — {{pageNumber}} of {{totalPages}}', align: 'right' },
|
|
478
|
+
footer: { text: 'Confidential', align: 'center', color: '#999999' },
|
|
479
|
+
content: [ /* ContentElement[] */ ],
|
|
480
|
+
})
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### `merge(pdfs): Promise<Uint8Array>`
|
|
484
|
+
|
|
485
|
+
Combine pre-rendered PDFs:
|
|
486
|
+
|
|
487
|
+
```typescript
|
|
488
|
+
import { merge } from 'pretext-pdf'
|
|
489
|
+
|
|
490
|
+
const combined = await merge([coverPdf, bodyPdf, appendixPdf])
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### `assemble(parts): Promise<Uint8Array>`
|
|
494
|
+
|
|
495
|
+
Mix new document configs with existing PDFs:
|
|
496
|
+
|
|
497
|
+
```typescript
|
|
498
|
+
import { assemble } from 'pretext-pdf'
|
|
499
|
+
|
|
500
|
+
const report = await assemble([
|
|
501
|
+
{ pdf: existingCoverPdf },
|
|
502
|
+
{ doc: { content: [...] } }, // rendered fresh
|
|
503
|
+
{ pdf: standardTermsPdf },
|
|
504
|
+
])
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
---
|
|
508
|
+
|
|
509
|
+
## Error Handling
|
|
510
|
+
|
|
511
|
+
Every error throws `PretextPdfError` with a typed code:
|
|
512
|
+
|
|
513
|
+
```typescript
|
|
514
|
+
import { render, PretextPdfError } from 'pretext-pdf'
|
|
515
|
+
|
|
516
|
+
try {
|
|
517
|
+
const pdf = await render(config)
|
|
518
|
+
} catch (err) {
|
|
519
|
+
if (err instanceof PretextPdfError) {
|
|
520
|
+
switch (err.code) {
|
|
521
|
+
case 'VALIDATION_ERROR': // Invalid config
|
|
522
|
+
case 'FONT_LOAD_FAILED': // Font file not found
|
|
523
|
+
case 'IMAGE_TOO_TALL': // Image doesn't fit on page
|
|
524
|
+
case 'ASSEMBLY_EMPTY': // merge/assemble called with empty array
|
|
525
|
+
// ... see CHANGELOG.md for full list
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
---
|
|
532
|
+
|
|
533
|
+
## Troubleshooting
|
|
534
|
+
|
|
535
|
+
### Hyphenation language not found
|
|
536
|
+
|
|
537
|
+
```
|
|
538
|
+
UNSUPPORTED_LANGUAGE: Language 'en-US' not supported
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
Use **lowercase** language codes that match the npm package name:
|
|
542
|
+
|
|
543
|
+
```typescript
|
|
544
|
+
// Wrong — 'en-US' fails on Linux (case-sensitive filesystem)
|
|
545
|
+
hyphenation: { language: 'en-US' }
|
|
546
|
+
|
|
547
|
+
// Correct — matches 'hyphenation.en-us' package name
|
|
548
|
+
hyphenation: { language: 'en-us' }
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
### Encryption
|
|
552
|
+
|
|
553
|
+
Encryption is built-in since v0.4.0. Add `encryption` to your document config:
|
|
554
|
+
|
|
555
|
+
```typescript
|
|
556
|
+
const pdf = await render({
|
|
557
|
+
encryption: {
|
|
558
|
+
userPassword: 'open123',
|
|
559
|
+
ownerPassword: 'admin456',
|
|
560
|
+
permissions: { printing: true, copying: false, modifying: false }
|
|
561
|
+
},
|
|
562
|
+
content: [...]
|
|
563
|
+
})
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
### SVG rendering requires optional dependency
|
|
567
|
+
|
|
568
|
+
Install `@napi-rs/canvas` for SVG support:
|
|
569
|
+
|
|
570
|
+
```bash
|
|
571
|
+
npm install @napi-rs/canvas
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
### PDF is blank or too small
|
|
575
|
+
|
|
576
|
+
Check margins — if left+right margins exceed page width, content width becomes negative:
|
|
577
|
+
|
|
578
|
+
```typescript
|
|
579
|
+
// For narrow pages, reduce margins:
|
|
580
|
+
margins: { top: 36, bottom: 36, left: 36, right: 36 }
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
### Form fields not interactive after flattenForms
|
|
584
|
+
|
|
585
|
+
`flattenForms: true` bakes fields into static content — by design. Remove it to keep interactive.
|
|
586
|
+
|
|
587
|
+
---
|
|
588
|
+
|
|
589
|
+
## Test Coverage
|
|
590
|
+
|
|
591
|
+
598+ tests across all phases with 100% pass rate:
|
|
592
|
+
|
|
593
|
+
```bash
|
|
594
|
+
npm test # Full suite (unit + e2e + all phases including v0.8.0)
|
|
595
|
+
npm run test:unit # Validation, builder, rich-text unit tests
|
|
596
|
+
npm run test:e2e # End-to-end render tests
|
|
597
|
+
npm run test:10a # QR code + barcode tests
|
|
598
|
+
npm run test:10b # Vega-Lite chart tests
|
|
599
|
+
npm run test:10c # Markdown converter tests
|
|
600
|
+
npm run test:10d # Template function tests
|
|
601
|
+
npm run test:phases # All phase tests (7–11, performance, signatures)
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
**Coverage**: Type safety, path validation, error handling, boundary cases, crypto signing, document assembly, all content elements, optional-dep error codes, MCP tool validation.
|
|
605
|
+
|
|
606
|
+
---
|
|
607
|
+
|
|
608
|
+
## Custom Fonts
|
|
609
|
+
|
|
610
|
+
```typescript
|
|
611
|
+
const pdf = await render({
|
|
612
|
+
fonts: [
|
|
613
|
+
{ family: 'Roboto', weight: 400, src: '/path/to/Roboto-Regular.ttf' },
|
|
614
|
+
{ family: 'Roboto', weight: 700, src: '/path/to/Roboto-Bold.ttf' },
|
|
615
|
+
{ family: 'Roboto', style: 'italic', src: '/path/to/Roboto-Italic.ttf' },
|
|
616
|
+
],
|
|
617
|
+
defaultFont: 'Roboto',
|
|
618
|
+
content: [
|
|
619
|
+
{ type: 'paragraph', text: 'Uses Roboto font' },
|
|
620
|
+
{ type: 'paragraph', text: 'Bold text', fontWeight: 700 },
|
|
621
|
+
],
|
|
622
|
+
})
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
---
|
|
626
|
+
|
|
627
|
+
## Rich Text
|
|
628
|
+
|
|
629
|
+
```typescript
|
|
630
|
+
{
|
|
631
|
+
type: 'rich-paragraph',
|
|
632
|
+
fontSize: 13,
|
|
633
|
+
spans: [
|
|
634
|
+
{ text: 'Normal ' },
|
|
635
|
+
{ text: 'bold', fontWeight: 700 },
|
|
636
|
+
{ text: ' and ', fontStyle: 'italic' },
|
|
637
|
+
{ text: 'colored', color: '#e63946' },
|
|
638
|
+
{ text: ' and ' },
|
|
639
|
+
{ text: 'linked', href: 'https://example.com', underline: true, color: '#0070f3' },
|
|
640
|
+
{ text: '. Also: E=mc' },
|
|
641
|
+
{ text: '2', verticalAlign: 'superscript' },
|
|
642
|
+
{ text: ' and H' },
|
|
643
|
+
{ text: '2', verticalAlign: 'subscript' },
|
|
644
|
+
{ text: 'O.' },
|
|
645
|
+
],
|
|
646
|
+
}
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
---
|
|
650
|
+
|
|
651
|
+
## Footnotes
|
|
652
|
+
|
|
653
|
+
Use `createFootnoteSet()` to generate matched reference/definition pairs with guaranteed unique IDs:
|
|
654
|
+
|
|
655
|
+
```typescript
|
|
656
|
+
import { render, createFootnoteSet } from 'pretext-pdf'
|
|
657
|
+
|
|
658
|
+
const notes = createFootnoteSet([
|
|
659
|
+
{ text: 'Smith, J. (2022). Typography in PDFs.' },
|
|
660
|
+
{ text: 'Ibid., p. 42.' },
|
|
661
|
+
])
|
|
662
|
+
|
|
663
|
+
await render({
|
|
664
|
+
content: [
|
|
665
|
+
{
|
|
666
|
+
type: 'rich-paragraph',
|
|
667
|
+
spans: [
|
|
668
|
+
{ text: 'See the original research' },
|
|
669
|
+
{ text: '¹', verticalAlign: 'superscript', footnoteRef: notes[0]!.id },
|
|
670
|
+
{ text: ' for details.' },
|
|
671
|
+
],
|
|
672
|
+
},
|
|
673
|
+
...notes.map(n => n.def), // footnote-def elements go at end of document
|
|
674
|
+
],
|
|
675
|
+
})
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
---
|
|
679
|
+
|
|
680
|
+
## Roadmap
|
|
681
|
+
|
|
682
|
+
| Phase | Feature | Status |
|
|
683
|
+
|-------|---------|--------|
|
|
684
|
+
| 1–4 | Core engine, pagination, typography | ✅ |
|
|
685
|
+
| 5 | Rich text / builder API | ✅ |
|
|
686
|
+
| 6 | Headers/footers, columns, decoration | ✅ |
|
|
687
|
+
| 7A | PDF Bookmarks / Outline | ✅ |
|
|
688
|
+
| 7B | Watermarks | ✅ |
|
|
689
|
+
| 7C | Hyphenation | ✅ |
|
|
690
|
+
| 7D | Table of Contents | ✅ |
|
|
691
|
+
| 7E | SVG support | ✅ |
|
|
692
|
+
| 7F | RTL text (Arabic/Hebrew) | ✅ |
|
|
693
|
+
| 7G | Encryption | ✅ |
|
|
694
|
+
| 8A | Sticky note annotations | ✅ |
|
|
695
|
+
| 8B | Interactive forms (text/checkbox/radio/dropdown/button) | ✅ |
|
|
696
|
+
| 8C | Document assembly (merge + assemble) | ✅ |
|
|
697
|
+
| 8D | Callout boxes (info/warning/tip/note) | ✅ |
|
|
698
|
+
| 8E | Signature placeholder | ✅ |
|
|
699
|
+
| 8F | Document metadata (language, producer) | ✅ |
|
|
700
|
+
| 8G | Hyperlinks | ✅ |
|
|
701
|
+
| 8H | Inline formatting (super/subscript, letterSpacing, smallCaps) | ✅ |
|
|
702
|
+
| 9A | Digital signatures (cryptographic, PKCS#7) | 🔜 |
|
|
703
|
+
| 9B | Image floats (text flowing around images) | 🔜 |
|
|
704
|
+
| 9C | Font subsetting pre-computation | 🔜 |
|
|
705
|
+
|
|
706
|
+
---
|
|
707
|
+
|
|
708
|
+
## Performance
|
|
709
|
+
|
|
710
|
+
Benchmarked on Windows 11 / Node 22 / Intel i7-12th Gen. Numbers are averages over 10 runs, excluding the first cold JIT run.
|
|
711
|
+
|
|
712
|
+
| Document | Render time | PDF size |
|
|
713
|
+
| --- | --- | --- |
|
|
714
|
+
| 1 page (heading + paragraph + list) | ~220 ms | ~45 KB |
|
|
715
|
+
| 10 pages (40 sections, mixed elements) | ~1,100 ms | ~180 KB |
|
|
716
|
+
| Mixed (heading + paragraph + 20-row table + list + hr) | ~290 ms | ~60 KB |
|
|
717
|
+
|
|
718
|
+
**Font subsetting** is automatic for TTF/OTF fonts. Only the glyphs used in the document are embedded, typically reducing PDF size by 40–60% compared to full font embedding. A typical single-font invoice renders under 65 KB. WOFF2 fonts are embedded without subsetting due to an upstream library limitation.
|
|
719
|
+
|
|
720
|
+
For large documents (10,000+ elements), set `NODE_OPTIONS=--max-old-space-size=4096` to prevent GC pressure.
|
|
721
|
+
|
|
722
|
+
---
|
|
723
|
+
|
|
724
|
+
## Migration from pdfmake
|
|
725
|
+
|
|
726
|
+
Coming from pdfmake? See the **[Migration Guide](docs/MIGRATION_FROM_PDFMAKE.md)** for a complete cheat sheet covering every common pdfmake pattern and its pretext-pdf equivalent.
|
|
727
|
+
|
|
728
|
+
---
|
|
729
|
+
|
|
730
|
+
## Contributing
|
|
731
|
+
|
|
732
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). TDD approach — write tests first.
|
|
733
|
+
|
|
734
|
+
---
|
|
735
|
+
|
|
736
|
+
## License
|
|
737
|
+
|
|
738
|
+
[MIT](LICENSE)
|
|
739
|
+
|
|
740
|
+
---
|
|
741
|
+
|
|
742
|
+
## Credits
|
|
743
|
+
|
|
744
|
+
Built by [Himanshu Jain](https://github.com/Himaan1998Y) on top of:
|
|
745
|
+
- **[pretext](https://github.com/chenglou/pretext)** — Text layout engine (Cheng Lou)
|
|
746
|
+
- **[pdf-lib](https://github.com/Hopding/pdf-lib)** — PDF manipulation
|
|
747
|
+
- **[@napi-rs/canvas](https://github.com/napi-rs/canvas)** — Server-side Canvas API for Node.js
|
|
748
|
+
|
|
749
|
+
Questions? [Open an issue](https://github.com/Himaan1998Y/pretext-pdf/issues)
|