pretext-pdf 2.0.14 → 2.1.1
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 +2280 -2232
- package/README.md +56 -2
- package/UPSTREAM.md +2 -2
- package/dist/cli.js +19 -19
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/plugin-types.d.ts +2 -4
- package/dist/plugin-types.d.ts.map +1 -1
- package/dist/render.d.ts +4 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +4 -1
- package/dist/render.js.map +1 -1
- package/dist/validate/document.d.ts.map +1 -1
- package/dist/validate/document.js +7 -2
- package/dist/validate/document.js.map +1 -1
- package/dist/validate/elements/list.d.ts.map +1 -1
- package/dist/validate/elements/list.js +14 -2
- package/dist/validate/elements/list.js.map +1 -1
- package/dist/validate/elements/table.d.ts.map +1 -1
- package/dist/validate/elements/table.js +8 -3
- package/dist/validate/elements/table.js.map +1 -1
- package/dist/validate/elements/text.d.ts +1 -1
- package/dist/validate/elements/text.d.ts.map +1 -1
- package/dist/validate/elements/text.js +8 -1
- package/dist/validate/elements/text.js.map +1 -1
- package/dist/validate/helpers.d.ts +6 -0
- package/dist/validate/helpers.d.ts.map +1 -1
- package/dist/validate/helpers.js +80 -0
- package/dist/validate/helpers.js.map +1 -1
- package/dist/version-check.d.ts +4 -4
- package/dist/version-check.js +4 -4
- package/package.json +239 -239
- package/dist/post-process.d.ts +0 -16
- package/dist/post-process.d.ts.map +0 -1
- package/dist/post-process.js +0 -117
- package/dist/post-process.js.map +0 -1
- package/dist/types-public.d.ts +0 -9
- package/dist/types-public.d.ts.map +0 -1
- package/dist/types-public.js +0 -2
- package/dist/types-public.js.map +0 -1
- package/dist/validate/elements/structural.d.ts +0 -12
- package/dist/validate/elements/structural.d.ts.map +0 -1
- package/dist/validate/elements/structural.js +0 -12
- package/dist/validate/elements/structural.js.map +0 -1
- package/dist/validate.d.ts +0 -10
- package/dist/validate.d.ts.map +0 -1
- package/dist/validate.js +0 -10
- package/dist/validate.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,2232 +1,2280 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
<!-- markdownlint-disable MD024 -->
|
|
4
|
-
|
|
5
|
-
All notable changes to pretext-pdf are documented here.
|
|
6
|
-
Format: [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/)
|
|
7
|
-
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
## [2.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
### Fixed (
|
|
585
|
-
|
|
586
|
-
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
- **`
|
|
597
|
-
|
|
598
|
-
###
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
- **SVG
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
- **
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
-
|
|
705
|
-
- **
|
|
706
|
-
-
|
|
707
|
-
- **
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
- **
|
|
712
|
-
- **
|
|
713
|
-
- **
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
-
|
|
725
|
-
|
|
726
|
-
-
|
|
727
|
-
|
|
728
|
-
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
### Fixed
|
|
737
|
-
|
|
738
|
-
- **`
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
- **
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
- **`src/
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
-
|
|
793
|
-
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
###
|
|
809
|
-
|
|
810
|
-
- **
|
|
811
|
-
- **
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
- **`
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
###
|
|
839
|
-
|
|
840
|
-
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
`
|
|
883
|
-
`
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
###
|
|
928
|
-
|
|
929
|
-
- **
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
###
|
|
976
|
-
|
|
977
|
-
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
- **`
|
|
1040
|
-
`
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
`
|
|
1046
|
-
`
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
`
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
`
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
`
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
`
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
- **
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
###
|
|
1272
|
-
|
|
1273
|
-
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
-
|
|
1482
|
-
(
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
`
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
- **
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
###
|
|
1534
|
-
|
|
1535
|
-
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
- `
|
|
1591
|
-
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
-
|
|
1639
|
-
|
|
1640
|
-
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
- **`
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
- **`
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
-
|
|
1687
|
-
|
|
1688
|
-
- `
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
-
|
|
1704
|
-
|
|
1705
|
-
- **`
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
- `
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
-
|
|
1795
|
-
- **
|
|
1796
|
-
- **
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
-
|
|
1803
|
-
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
###
|
|
1847
|
-
|
|
1848
|
-
- **
|
|
1849
|
-
- **
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
-
|
|
1884
|
-
|
|
1885
|
-
### Notes
|
|
1886
|
-
|
|
1887
|
-
-
|
|
1888
|
-
-
|
|
1889
|
-
|
|
1890
|
-
---
|
|
1891
|
-
|
|
1892
|
-
## [0.8.
|
|
1893
|
-
|
|
1894
|
-
###
|
|
1895
|
-
|
|
1896
|
-
-
|
|
1897
|
-
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
-
|
|
1918
|
-
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
-
|
|
1931
|
-
- `
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
- **
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
###
|
|
1959
|
-
|
|
1960
|
-
- **`
|
|
1961
|
-
- **
|
|
1962
|
-
- **
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
-
|
|
1967
|
-
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
- **
|
|
2002
|
-
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
- `
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
-
|
|
2050
|
-
- `
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
-
|
|
2059
|
-
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
- `
|
|
2065
|
-
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
-
|
|
2086
|
-
-
|
|
2087
|
-
-
|
|
2088
|
-
-
|
|
2089
|
-
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
- `
|
|
2096
|
-
-
|
|
2097
|
-
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
-
|
|
2124
|
-
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
-
|
|
2129
|
-
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
- `
|
|
2136
|
-
- `
|
|
2137
|
-
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
- `
|
|
2162
|
-
-
|
|
2163
|
-
-
|
|
2164
|
-
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
-
|
|
2172
|
-
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
-
|
|
2184
|
-
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
-
|
|
2210
|
-
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
-
|
|
2215
|
-
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
-
|
|
2228
|
-
-
|
|
2229
|
-
-
|
|
2230
|
-
-
|
|
2231
|
-
-
|
|
2232
|
-
-
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
<!-- markdownlint-disable MD024 -->
|
|
4
|
+
|
|
5
|
+
All notable changes to pretext-pdf are documented here.
|
|
6
|
+
Format: [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## [2.1.1] — 2026-07-20
|
|
11
|
+
|
|
12
|
+
Dependency security fix.
|
|
13
|
+
|
|
14
|
+
### Security
|
|
15
|
+
|
|
16
|
+
- **`undici` bumped from `^6.25.0` to `^6.27.0`** — resolves 4 HIGH-severity advisories in this direct production dependency: HTTP header injection via Set-Cookie percent-decoding, WebSocket client DoS via fragment count bypass, HTTP response queue poisoning via keep-alive socket reuse, and Set-Cookie SameSite downgrade via permissive substring matching.
|
|
17
|
+
- Dev-only transitive advisories (`markdown-it`/`linkify-it` via `typedoc`) resolved via `npm audit fix`. `esbuild` (via `tsx`, dev-only, Windows dev-server file read) remains at low severity — below the CI `--audit-level=high` gate, tracked in ROADMAP.
|
|
18
|
+
|
|
19
|
+
### Testing
|
|
20
|
+
|
|
21
|
+
- Full suite re-verified after the bump: 340/340 unit tests, 61/61 security tests (SSRF, IPv4-bypass, scheme blocklist) passing.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## [2.1.0] — 2026-06-01
|
|
26
|
+
|
|
27
|
+
Developer experience improvements: auto-coercion for intuitive-but-incorrect patterns, better error messages with structure hints, and deprecation pathway for v3.0 breaking changes.
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- **Auto-coercion: List items** — strings now auto-convert to `{text: 'string'}` objects, allowing intuitive `items: ['a', 'b']` syntax alongside `items: [{text: 'a'}, {text: 'b'}]`
|
|
32
|
+
- **Auto-coercion: Fonts** — single font object auto-converts to array, allowing `fonts: {family: 'Inter'}` alongside `fonts: [{family: 'Inter'}]`
|
|
33
|
+
- **Auto-coercion: Table structure** — pdfmake-style `{headers, rows}` tables auto-adapt to pretext `{columns, rows[{cells}]}` schema for smoother migration from other PDF libraries
|
|
34
|
+
- **Better error messages** — validation errors now include expected structure hints: list items show `{text: 'string'}` format, tables show `{columns: [...], rows: [{cells: [...]}]}` format, helping agents and humans understand the correct schema
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- **Deprecation: Callout `content` field** — `content` will be renamed to `text` in v3.0 for consistency with paragraph/heading/blockquote. v2.1 emits a console warning when `content` is used.
|
|
39
|
+
- **Table error messages enhanced** — columns and rows validation now include structure hints in error output
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- **Headers-only table validation** — tables with headers but no data rows now properly trigger VALIDATION_ERROR instead of being silently adapted
|
|
44
|
+
- **Mixed row format corruption prevention** — table adapter now validates row shape (array vs object) before attempting pdfmake-to-pretext conversion, preventing silent data corruption when rows use inconsistent formats
|
|
45
|
+
- **Strict-mode false positives in table adapter** — removed pdfmake-style field properties cleanly with `delete` instead of setting to undefined, eliminating spurious unknown-property warnings in strict validation mode
|
|
46
|
+
- **Callout deprecation warning condition** — fixed logic inversion so warning fires only when `content` field is used without `text` field
|
|
47
|
+
|
|
48
|
+
### Testing
|
|
49
|
+
|
|
50
|
+
- Added 49 new corpus tests covering list auto-coercion, font coercion, and table adapter edge cases
|
|
51
|
+
- Added 4 new corpus JSON files for agent edge-case testing (list-string-coercion, table-pdfmake, callout-old-field, fonts-object)
|
|
52
|
+
- Added deprecation warning routing tests to verify logger.warn receives v3.0 migration warnings
|
|
53
|
+
- All validation changes verified with pixel-perfect rendering equivalence tests
|
|
54
|
+
- Total test count: 340/340 passing
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## [2.0.14] — 2026-05-30
|
|
59
|
+
|
|
60
|
+
Vendor snapshot updated to pretext v0.0.7-patched.1 (upstream v0.0.7 + 11 unique patches).
|
|
61
|
+
|
|
62
|
+
### Changed
|
|
63
|
+
|
|
64
|
+
- **Vendored pretext layout engine upgraded from v0.0.6-patched.2 to v0.0.7-patched.1.**
|
|
65
|
+
Incorporates upstream v0.0.7 improvements: keep-all mixed-script grouping, punctuation chain wrapping, opening punctuation line breaks, soft-hyphen break preservation, terminal letter spacing, numeric affix stickiness, rich inline boundary overflow prevention, browser-like dash/symbol runs, and line streaming performance optimizations.
|
|
66
|
+
Plus fork-specific patches: German low-opening-quote fix, bidi surrogate handling, trailing collapsible space reconstruction, stream-friendly chunk layout optimization, and no-op merge pass skipping.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## [2.0.13] — 2026-05-29
|
|
71
|
+
|
|
72
|
+
Security hardening and coverage improvements from final review pass.
|
|
73
|
+
|
|
74
|
+
### Security
|
|
75
|
+
|
|
76
|
+
- **Symlink escape closed in `assertPathAllowed`** — `path-allowlist.ts` now calls `realpathSync` before the prefix comparison, so a symlink placed inside an allowed directory cannot dereference to an arbitrary path outside the allowlist (F-1).
|
|
77
|
+
- **Chart `spec` scanned for prototype-pollution keys** — `validateChart` in `media.ts` now rejects any `spec` object containing `__proto__`, `constructor`, or `prototype` keys at any nesting depth before passing it to the Vega renderer (F-9).
|
|
78
|
+
- **`http://` URLs rejected at loader entry points** — `images.ts` and `resolve-content.ts` now throw immediately on `http://` src values instead of entering the fetch path and relying on the inner SSRF guard (F-4).
|
|
79
|
+
|
|
80
|
+
### Fixed
|
|
81
|
+
|
|
82
|
+
- **`stageInit` metadata fallback logs message only** — `pipeline.ts` no longer serializes the full error object into `console.warn`; only `err.message` is included, preventing internal API names and stack frames from appearing in library stderr (F-3).
|
|
83
|
+
|
|
84
|
+
### Tests Added
|
|
85
|
+
|
|
86
|
+
- `test/compat-normalize.test.ts` — 62 tests covering all exported functions in `src/compat/normalize.ts` directly via tsx (previously only reachable through the dist/ barrel).
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## [2.0.12] — 2026-05-29
|
|
91
|
+
|
|
92
|
+
Multi-perspective review fixes: security, correctness, silent failures, dead code, and test coverage.
|
|
93
|
+
|
|
94
|
+
### Security
|
|
95
|
+
|
|
96
|
+
- **`allowedFileDirs` array now copied in `fromPdfmake()`** — Previously assigned by reference, allowing the caller to mutate the allowlist after translation and silently expand path-traversal permissions mid-render. Now copied with `[...arr]` (SEC-1).
|
|
97
|
+
|
|
98
|
+
### Fixed
|
|
99
|
+
|
|
100
|
+
- **`content` field is now `readonly ContentElement[]` on `PdfDocument`** — The last mutable public field. Prevents callers from `.push()`-ing into a document after passing it to `render()`, which could corrupt multi-stage async pipeline results. `builder.toDocument()` now snapshots the content array at call time (H-1).
|
|
101
|
+
- **`buildFootnoteNumbering` and `runFootnoteTwoPass` parameter widened to `readonly ContentElement[]`** — Required by the `content` readonly change; no semantic change.
|
|
102
|
+
- **Footnote sentinel `?? 0` replaced with `PretextPdfError`** — A missing footnote number now throws `PAGINATION_FAILED` with a clear message instead of silently rendering footnote `0` (M-3).
|
|
103
|
+
- **`onUnsupported` default now logs a one-time warning** — The previous default was a silent no-op, contradicting its own JSDoc. Default is now `console.warn('[pretext-pdf/compat] Unsupported pdfmake feature skipped: <feature>')` with per-call deduplication (M-1).
|
|
104
|
+
- **`stageInit` metadata catch block now logs the error** — The fallback path for `getInfoDict()` unavailability no longer silently swallows the root cause (M-2).
|
|
105
|
+
- **`FONT_EMBED_FAILED` error message is user-facing** — Removed "this is a bug" from the message; replaced with actionable guidance (M-5).
|
|
106
|
+
- **Internal exports removed from `translate.ts`** — `translateNodeInner`, `applyStyleToParagraph`, `collectSpans` are no longer exported from the sub-module (M-4).
|
|
107
|
+
- **`_require` unexported from `bundled-paths.ts`** — Was exported with a misleading `_` prefix but only used internally (L-1).
|
|
108
|
+
- **`fonts.ts` header comment includes `collect-text.ts`** — Fourth sub-module was omitted (L-2).
|
|
109
|
+
- **`translate.ts` uses named `ListItem` import** — Replaced inline `import('../types.js').ListItem` expression (L-3).
|
|
110
|
+
|
|
111
|
+
### Tests Added
|
|
112
|
+
|
|
113
|
+
- `test/compat/normalize.test.ts` — 28 direct unit tests for `mergeStyles`, `normalizePageSize`, `normalizeMargins`, `normalizeHeaderFooter`, `normalizeStyleNames`
|
|
114
|
+
- `test/fonts/collect-text.test.ts` — 9 direct unit tests for `collectTextByFont` across paragraph, heading, rich-paragraph, header/footer, and defaultFont override
|
|
115
|
+
- `test/compat.test.ts` — 3 isolation tests for `allowedFileDirs` array copy (SEC-1)
|
|
116
|
+
- `test/builder.test.ts` — 2 snapshot isolation tests for `toDocument()` content copy (H-1)
|
|
117
|
+
|
|
118
|
+
### Changed
|
|
119
|
+
|
|
120
|
+
- **`pdfmake-types.ts`: `allowedFileDirs` is now `readonly string[]`** — Type made symmetric with `PdfDocument.allowedFileDirs`
|
|
121
|
+
|
|
122
|
+
## [2.0.11] — 2026-05-29
|
|
123
|
+
|
|
124
|
+
Phase 6a: `readonly` on all scalar public interface fields (non-breaking).
|
|
125
|
+
|
|
126
|
+
### Changed
|
|
127
|
+
|
|
128
|
+
- **All scalar fields on public interfaces marked `readonly`** — `PdfDocument`, `DocumentMetadata`,
|
|
129
|
+
`Margins`, `FontSpec`, `HeaderFooterSpec`, `WatermarkBase`/`WatermarkSpec`, `EncryptionSpec`,
|
|
130
|
+
`SignatureSpec`, `BookmarkConfig`, `HyphenationConfig`, `AnnotationSpec`.
|
|
131
|
+
|
|
132
|
+
Array fields (`content`, `fonts`, `keywords`, `sections`, `allowedFileDirs`) are intentionally
|
|
133
|
+
left mutable — marking those `readonly` is a breaking change deferred to v3.
|
|
134
|
+
|
|
135
|
+
This is non-breaking for all callers: TypeScript allows assigning to `readonly` fields in
|
|
136
|
+
object literal initializers, and the runtime behavior is identical.
|
|
137
|
+
|
|
138
|
+
- **`compat.ts`**: Internal construction variables use `Mutable<T>` to allow incremental property
|
|
139
|
+
assignment, cast to the full interface at return. Externally the API is unchanged.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## [2.0.10] — 2026-05-29
|
|
144
|
+
|
|
145
|
+
Sprint 5B: Split `compat.ts` (613L) and `fonts.ts` (530L) into focused sub-directories.
|
|
146
|
+
|
|
147
|
+
### Changed
|
|
148
|
+
|
|
149
|
+
- **`src/compat.ts` split into `src/compat/` (3 files)**
|
|
150
|
+
- `compat/pdfmake-types.ts` (76L) — `PdfmakeDocument`, `PdfmakeObjectNode`, `PdfmakeStyle`, `CompatOptions`, `TranslateCtx`, `DEFAULT_HEADING_MAP`
|
|
151
|
+
- `compat/translate.ts` (155L) — `translateNode`, `translateNodeInner`, `translateTextNode`, `applyStyleToParagraph`, `collectSpans`, `pdfmakeNodeToListItem`
|
|
152
|
+
- `compat/normalize.ts` (115L) — `extractFlatText`, `translateTable`, `mergeStyles`, `normalizeStyleNames`, `pdfmakeAlignToPretext`, `normalizePageSize`, `normalizeMargins`, `normalizeHeaderFooter`
|
|
153
|
+
- `compat.ts` (85L) — `fromPdfmake` (the only public export) + type re-exports
|
|
154
|
+
Circular-dependency between translate↔normalize avoided by moving `extractFlatText` to `normalize.ts`.
|
|
155
|
+
|
|
156
|
+
- **`src/fonts.ts` split into `src/fonts/` (4 files)**
|
|
157
|
+
- `fonts/bundled-paths.ts` (50L) — `IS_NODE`, `resolveInterFile`, all four `BUNDLED_INTER_*_PATHS` arrays
|
|
158
|
+
- `fonts/load-bytes.ts` (80L) — `loadFontBytes` (file / Uint8Array / bundled-Inter async loader)
|
|
159
|
+
- `fonts/collect-needed.ts` (75L) — `collectNeededFonts` (document font-variant scanner)
|
|
160
|
+
- `fonts/collect-text.ts` (120L) — `collectTextByFont` (glyph subsetting text collector)
|
|
161
|
+
- `fonts.ts` (70L) — `loadFonts` + `collectTextByFont` re-export (both public exports intact)
|
|
162
|
+
|
|
163
|
+
The public API is unchanged. All existing test and source imports continue to work.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## [2.0.9] — 2026-05-29
|
|
168
|
+
|
|
169
|
+
Sprint 5A: dead-export removal, ts-prune scan.
|
|
170
|
+
|
|
171
|
+
### Changed
|
|
172
|
+
|
|
173
|
+
- **`addGoToAnnotation` removed from `src/render-utils.ts`** — The function was exported but
|
|
174
|
+
never called anywhere in the codebase. GoTo annotation support (internal document links)
|
|
175
|
+
was prepared prematurely; the implementation is preserved in git history and can be
|
|
176
|
+
restored when anchor-link rendering is actually wired up.
|
|
177
|
+
|
|
178
|
+
- **`RTL_REGEX` removed from `src/validate/helpers.ts`** — Exported constant never imported
|
|
179
|
+
anywhere. The regex pattern is preserved in git history.
|
|
180
|
+
|
|
181
|
+
- **`PDFRef` import removed from `render-utils.ts`** — Only needed for the now-deleted
|
|
182
|
+
`addGoToAnnotation` signature.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## [2.0.7] — 2026-05-28
|
|
187
|
+
|
|
188
|
+
Sprint 4: Split `schema.ts` (907 LOC) into focused `src/schema/` modules.
|
|
189
|
+
|
|
190
|
+
### Changed
|
|
191
|
+
|
|
192
|
+
- **`src/schema.ts` split into `src/schema/` (6 focused files)** — The 907-line monolith is
|
|
193
|
+
replaced by a 19-line barrel re-export and six cohesive sub-files, each under 300 lines:
|
|
194
|
+
- `schema/shared.ts` (30L) — atomic sub-schemas: align, color, fontWeight, dir, space, inlineSpan
|
|
195
|
+
- `schema/elements-text.ts` (270L) — paragraph, heading, blockquote, code, callout, richParagraph, list, toc, footnoteDef
|
|
196
|
+
- `schema/elements-media.ts` (87L) — image, svg, qrCode, barcode, chart
|
|
197
|
+
- `schema/elements-block.ts` (164L) — spacer, hr, pageBreak, comment, formField, floatGroup
|
|
198
|
+
- `schema/elements-table.ts` (60L) — table
|
|
199
|
+
- `schema/document.ts` (293L) — top-level pdfDocumentSchema (assembles all element schemas)
|
|
200
|
+
The public API (`import { pdfDocumentSchema } from 'pretext-pdf/schema'`) is unchanged.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## [2.0.6] — 2026-05-28
|
|
205
|
+
|
|
206
|
+
Post-sprint audit fixes: /Lang encoding, BCP47 regex, test correctness.
|
|
207
|
+
|
|
208
|
+
### Fixed
|
|
209
|
+
|
|
210
|
+
- **`/Lang` catalog entry now uses `PDFString.of()` (correct literal string)** — Previously
|
|
211
|
+
used `PDFHexString.of()` which stored raw ASCII bytes as invalid hex. `/Lang` is a
|
|
212
|
+
PDF text string per spec §14.9.2; `PDFString` (literal string) is correct and matches
|
|
213
|
+
pdf-lib's own `setLanguage()`. BCP47 tags are ASCII-only (validated by regex) so
|
|
214
|
+
literal-string injection risk is zero.
|
|
215
|
+
|
|
216
|
+
- **BCP47 regex widened to accept `i-` and `x-` prefixed tags** — `/^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/`
|
|
217
|
+
now allows the single-char leading subtag required by grandfathered (`i-klingon`) and
|
|
218
|
+
private-use (`x-custom-app`) tags per RFC 5646 §2.2.7. Previously `{2,8}` incorrectly
|
|
219
|
+
rejected valid language codes.
|
|
220
|
+
|
|
221
|
+
### Tests
|
|
222
|
+
|
|
223
|
+
- **H6 (signerName truncation)** strengthened — replaced the vacuous 5% byte-size ratio with
|
|
224
|
+
a `deepEqual(pdf100, pdf101)` identity check: a 100-char and 101-char signerName must produce
|
|
225
|
+
byte-identical PDFs, directly testing that `slice(0, 100)` is applied.
|
|
226
|
+
|
|
227
|
+
- **M5 (/TU encoding)** — added `FEFF` BOM assertion to the radio and dropdown loop,
|
|
228
|
+
making it consistent with the checkbox/button loop.
|
|
229
|
+
|
|
230
|
+
- **M6 (bookmark Title)** — replaced the `FEFF0043` prefix check (too broad) with the full
|
|
231
|
+
6-character "Chapte" UTF-16BE hex prefix for an unambiguous unique match.
|
|
232
|
+
|
|
233
|
+
- **M7 (signing field escaping)** — clarified test scope: the visual placeholder path uses
|
|
234
|
+
`drawText()` (CIDFont glyph encoding — no injection risk). The `escapePdfLit` contract is
|
|
235
|
+
tested in `signatures-validation.test.ts` which covers the crypto signing code path.
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## [2.0.5] — 2026-05-28
|
|
240
|
+
|
|
241
|
+
Sprint 3 audit fixes: annotation array safety, test coverage gaps closed.
|
|
242
|
+
|
|
243
|
+
### Fixed
|
|
244
|
+
|
|
245
|
+
- **`addStickyNoteAnnotation` now has `instanceof PDFArray` guard** — the last remaining
|
|
246
|
+
`as any` annotation-push in `render-utils.ts`. All three annotation functions
|
|
247
|
+
(`addLinkAnnotation`, `addGoToAnnotation`, `addStickyNoteAnnotation`) now safely fall back
|
|
248
|
+
to creating a new array if the existing `Annots` entry is not a direct `PDFArray`.
|
|
249
|
+
|
|
250
|
+
### Tests
|
|
251
|
+
|
|
252
|
+
- **H6** (`test/signatures-visual.test.ts`): `signerName` longer than 100 chars renders to
|
|
253
|
+
the same byte-size PDF as a 100-char name — confirms the `slice(0, 100)` truncation is applied.
|
|
254
|
+
- **M5** (`test/forms.test.ts`): `/TU` encoding verified for checkbox, radio, dropdown, and
|
|
255
|
+
button field types — not just `text`.
|
|
256
|
+
- **M6** (`test/bookmarks.test.ts`): Bookmark `Title` with special chars (parentheses) appears
|
|
257
|
+
as UTF-16BE hex in the PDF bytes — raw literal form `(Chapter (1): ...)` does not appear.
|
|
258
|
+
- **M7** (`test/signatures-visual.test.ts`): Backslash and parentheses in `signerName`,
|
|
259
|
+
`reason`, and `location` render without throwing (escaping confirmed).
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## [2.0.4] — 2026-05-28
|
|
264
|
+
|
|
265
|
+
Sprint 2 audit fixes: type-safety improvements in render-utils.ts and vendor declarations.
|
|
266
|
+
|
|
267
|
+
### Changed
|
|
268
|
+
|
|
269
|
+
- **`pdf-lib-augment.d.ts` expanded** — Declares `PdfFontEmbedder` interface covering the
|
|
270
|
+
private `embedder.font` (underlinePosition, underlineThickness, xHeight) and `embedder.scale`
|
|
271
|
+
fields accessed for text decoration metrics. The `(pdfFont as any).embedder` cast in
|
|
272
|
+
`render-utils.ts` is now `(pdfFont as unknown as { embedder?: PdfFontEmbedder }).embedder`,
|
|
273
|
+
eliminating untyped `any` propagation into font-metric arithmetic.
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## [2.0.3] — 2026-05-28
|
|
278
|
+
|
|
279
|
+
Sprint 1 audit fixes: GoTo annotation guard, BCP47 metadata validation, dependency security.
|
|
280
|
+
|
|
281
|
+
### Fixed
|
|
282
|
+
|
|
283
|
+
- **`addGoToAnnotation` now has `instanceof PDFArray` guard** — the same fix applied to
|
|
284
|
+
`addLinkAnnotation` and `addStickyNoteAnnotation` in v2.0.2 was missed for GoTo annotations.
|
|
285
|
+
Existing `Annots` entries that are not a direct `PDFArray` now fall back to creating a new
|
|
286
|
+
array instead of silently calling `.push()` on an `any`-cast value.
|
|
287
|
+
|
|
288
|
+
- **`metadata.language` validated against `LANGUAGE_TAG_REGEX`** — previously only checked for
|
|
289
|
+
non-empty string and max length. Now enforces the same BCP47 regex (`/^[a-zA-Z]{2,8}(-[a-zA-Z0-9]{2,8})*$/`)
|
|
290
|
+
used for `hyphenation.language`, closing an inconsistency in the validation layer.
|
|
291
|
+
|
|
292
|
+
- **Dependency audit** — `npm audit fix` resolved `fast-uri <=3.1.1` CVE (GHSA-v39h-62p7-jpjc,
|
|
293
|
+
GHSA-q3j6-qgpj-74h6) in the dev dependency chain.
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## [2.0.2] — 2026-05-28
|
|
298
|
+
|
|
299
|
+
Second post-release audit patch: PDF injection hardening (metadata, AcroForm names), PDFHexString encoding correctness, signing field escaping, annotation array safety.
|
|
300
|
+
|
|
301
|
+
### Fixed
|
|
302
|
+
|
|
303
|
+
- **Metadata Info dict fields now use `PDFHexString.fromText()` (correct UTF-16BE encoding)** —
|
|
304
|
+
The v2.0.1 fix used `PDFHexString.of()` which stored raw bytes between `<>` delimiters (not
|
|
305
|
+
valid hex). `fromText` correctly produces `<FEFF...>` UTF-16BE hex, the same encoding used by
|
|
306
|
+
pdf-lib's built-in `setTitle()` etc. Injection protection is now both correct AND effective.
|
|
307
|
+
|
|
308
|
+
- **Form field `name` and option `value` restricted to AcroForm-safe characters** — Field names
|
|
309
|
+
and radio/dropdown option export values are written as PDF literal strings by pdf-lib. Characters
|
|
310
|
+
like `)`, `\`, and null bytes that could corrupt the AcroForm `/T` dictionary are now rejected
|
|
311
|
+
at validation time with a clear error message. Allowed: `[a-zA-Z0-9_.@-]+` (`HIGH-2`).
|
|
312
|
+
|
|
313
|
+
- **Signing placeholder fields escaped for PDF literal strings** — `sig.reason`, `sig.location`,
|
|
314
|
+
`sig.contactInfo`, and `sig.signerName` are passed to `@signpdf/placeholder-pdf-lib` which
|
|
315
|
+
writes them as PDF literal strings. Backslashes and parentheses are now escaped with `\` before
|
|
316
|
+
passing, so values like "New York (USA)" are preserved correctly without breaking the dict.
|
|
317
|
+
|
|
318
|
+
- **Annotation array push guarded with `instanceof PDFArray`** — The previous `as any` cast on
|
|
319
|
+
`pdfDoc.context.lookup(existingAnnots)` allowed silent no-ops if the value wasn't a PDFArray.
|
|
320
|
+
All three annotation functions now check `instanceof PDFArray` and fall back to creating a new
|
|
321
|
+
array rather than silently dropping the annotation.
|
|
322
|
+
|
|
323
|
+
- **Bookmark `Title` and AcroForm `/TU` use `PDFHexString.fromText()`** — Same `of()` → `fromText()`
|
|
324
|
+
correction applied to bookmark headings and all five AcroForm `/TU` (accessibility tooltip) writes.
|
|
325
|
+
|
|
326
|
+
- **Sticky note `Contents` and author `T` use `PDFHexString.fromText()`** — Human-readable text in
|
|
327
|
+
sticky note annotations now uses proper UTF-16BE encoding.
|
|
328
|
+
|
|
329
|
+
- **Signature placeholder `signerName` truncated to 100 chars** — Prevents glyph overflow outside
|
|
330
|
+
the visual signature box for very long signer names.
|
|
331
|
+
|
|
332
|
+
- **Schema `content.items` changed from `anyOf` to `oneOf`** — JSON Schema validators and AI agent
|
|
333
|
+
code generators now get exclusive-match semantics, preventing multi-schema ambiguity.
|
|
334
|
+
|
|
335
|
+
### Tests
|
|
336
|
+
|
|
337
|
+
- Added T6: `/TU` accessibilityLabel byte-level injection guard in `test/forms.test.ts`.
|
|
338
|
+
- Added T7: metadata title/author/accessibility UTF-16BE hex-encoding byte checks in `test/metadata.test.ts`.
|
|
339
|
+
- Added field name and option value AcroForm-safety tests in `test/forms.test.ts`.
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## [2.0.1] — 2026-05-28
|
|
344
|
+
|
|
345
|
+
Post-release patch: audit-driven hardening of every change introduced in v2.0.0.
|
|
346
|
+
|
|
347
|
+
### Fixed
|
|
348
|
+
|
|
349
|
+
- **SVG sanitizer: `on*` handlers with whitespace in attribute name now stripped** — Attackers
|
|
350
|
+
could inject `on\nload=` or `on\tclick=` to bypass the previous `\w+` name regex. The pattern
|
|
351
|
+
now uses `[\w\r\n\t ]+` for the attribute name portion (`H3`).
|
|
352
|
+
|
|
353
|
+
- **SVG sanitizer: `expression()` strips arguments with nested parens** — `expression(alert(1))`
|
|
354
|
+
and `expression(eval(x))` are now stripped in a single pass. The inner-parens pattern
|
|
355
|
+
`(?:[^()]*|\([^()]*\))*` handles one level of argument nesting; multi-pass unwinds deeper
|
|
356
|
+
nesting (`M6`).
|
|
357
|
+
|
|
358
|
+
- **SVG sanitizer: size/element-count guards now throw `PretextPdfError('SVG_LOAD_FAILED')`** —
|
|
359
|
+
Previously both guards returned the raw unstripped SVG on overflow, silently bypassing
|
|
360
|
+
script/event stripping. Now both throw, so callers always receive a typed error (`H1`).
|
|
361
|
+
|
|
362
|
+
- **Form-field strict mode no longer flags cross-variant props as unknown** — `ALLOWED_PROPS`
|
|
363
|
+
entry for `form-field` now covers the union of all variant-specific keys so the first strict
|
|
364
|
+
check never fires false positives. Per-variant narrowing still runs inside `validateFormField`
|
|
365
|
+
to catch cross-contamination (`B1`).
|
|
366
|
+
|
|
367
|
+
- **Bookmark `Title` and AcroForm `/TU` now use `PDFHexString`** — Previously `PDFString` was
|
|
368
|
+
used, which is vulnerable to unbalanced-parenthesis injection in user-controlled strings. All
|
|
369
|
+
five `/TU` writes and the outline `Title` write now use the hex-encoded form (`B3`).
|
|
370
|
+
|
|
371
|
+
- **Link URI annotation and sticky-note `Contents`/`T` use `PDFHexString`** — Same injection
|
|
372
|
+
guard extended to `addLinkAnnotation` and `addStickyNoteAnnotation` (`B3`).
|
|
373
|
+
|
|
374
|
+
- **`getInfoDict()` private-API call is now try/caught** — Accessibility and semantic metadata
|
|
375
|
+
are silently omitted (rather than throwing) if the `@cantoo/pdf-lib` internal API is removed
|
|
376
|
+
in a future library version (`M7`).
|
|
377
|
+
|
|
378
|
+
- **`SignPdf`/`P12Signer` dynamic imports typed via local interfaces** — Removes `any` casts
|
|
379
|
+
on the signpdf module destructuring, catching future API drift at compile time (`M4`).
|
|
380
|
+
|
|
381
|
+
- **Signing error message scrubbed of certificate details** — `SIGNATURE_FAILED` no longer
|
|
382
|
+
leaks P12 structural details or ASN.1 internals; only the first 120 chars of the underlying
|
|
383
|
+
error message are included (`F9`).
|
|
384
|
+
|
|
385
|
+
- **`FORM_FIELD_VARIANT_PROPS` type narrowed** — The export type is now
|
|
386
|
+
`Record<'text'|'checkbox'|'radio'|'dropdown'|'button', ReadonlySet<string>>` so callers that
|
|
387
|
+
iterate the map get exhaustive narrowing rather than a plain string index (`M1`).
|
|
388
|
+
|
|
389
|
+
- **`fieldType` validation is now derived from `FORM_FIELD_VARIANT_PROPS`** — The allowed-values
|
|
390
|
+
list and the dispatch map are now the same object, removing the risk of one going stale (`M2`).
|
|
391
|
+
|
|
392
|
+
- **Form-field options arrays are structurally validated** — Each item in `options` for `radio`
|
|
393
|
+
and `dropdown` is now checked to be a `{value: string, label: string}` object. Invalid items
|
|
394
|
+
throw `VALIDATION_ERROR` with an indexed path like `options[1].value` (`H2`).
|
|
395
|
+
|
|
396
|
+
- **`accessibilityLabel` validated as non-empty string** — Empty or non-string values now
|
|
397
|
+
throw `VALIDATION_ERROR` rather than embedding an empty `/TU` annotation (`H2`).
|
|
398
|
+
|
|
399
|
+
- **`signing/placeholder.ts` uses `buildFontKey()` instead of hardcoded literal** — Keeps the
|
|
400
|
+
font lookup in sync with the canonical key format if the naming convention ever changes (`L1`).
|
|
401
|
+
|
|
402
|
+
- **`PluginMeasureResult.spaceBefore/spaceAfter` marked readonly** — Plugins cannot mutate the
|
|
403
|
+
pipeline's spacing side-table; callers can still construct the object with literal values (`L2`).
|
|
404
|
+
|
|
405
|
+
- **`package.json` `mcpCompat` range updated** — Was `>=1.4.0 <2.0.0` (excluded v2.0.0 itself);
|
|
406
|
+
now `>=1.5.0 <3.0.0` (`B2`).
|
|
407
|
+
|
|
408
|
+
- **`pdfDocumentSchema`: `form-field` now a `oneOf` discriminated union** — The flat single-object
|
|
409
|
+
schema was replaced with five variant schemas keyed by `fieldType: { const: '...' }`, with
|
|
410
|
+
correct per-variant `required` arrays (radio/dropdown require `options`) (`M3`).
|
|
411
|
+
|
|
412
|
+
### Tests
|
|
413
|
+
|
|
414
|
+
- Added T1: `on\nload` and `on\tclick` attribute-name injection regression guard in
|
|
415
|
+
`test/svg-sanitizer.test.ts`.
|
|
416
|
+
- Added T2: Per-variant strict-mode unknown-props tests for form-field in
|
|
417
|
+
`test/validate-strict.test.ts` (Group 8).
|
|
418
|
+
- Added T3: `./signing` entry point added to `test/public-api-surface.test.ts` tripwire.
|
|
419
|
+
- Added T4: `MAX_SVG_ELEMENTS` boundary tests (at-limit sanitizes, over-limit throws) in
|
|
420
|
+
`test/svg-sanitizer.test.ts`.
|
|
421
|
+
- Added T5: `fieldType: 'textarea'` (unknown value) test triggering `VALIDATION_ERROR`.
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## [2.0.0] — 2026-05-28
|
|
426
|
+
|
|
427
|
+
Major release. Three breaking changes, six improvements, and two security fixes.
|
|
428
|
+
|
|
429
|
+
### Breaking Changes
|
|
430
|
+
|
|
431
|
+
- **`FormFieldElement` is now a discriminated union** — Replace the single flat interface with
|
|
432
|
+
five per-variant types (`TextFormField | CheckboxFormField | RadioFormField | DropdownFormField |
|
|
433
|
+
ButtonFormField`) that narrow available properties by `fieldType`. The `BaseFormField` interface
|
|
434
|
+
holds all shared fields and is also exported. Callers that assign into `FormFieldElement` without
|
|
435
|
+
narrowing will need to specify the concrete variant. **Migration:** narrow on `fieldType` or
|
|
436
|
+
import the specific variant type.
|
|
437
|
+
|
|
438
|
+
- **`HorizontalRuleElement.spaceAbove` / `spaceBelow` removed** — Use `spaceBefore` / `spaceAfter`
|
|
439
|
+
(stable since v0.9). The deprecated aliases were present in v1.x with `@deprecated` JSDoc.
|
|
440
|
+
**Migration:** replace `spaceAbove` → `spaceBefore`, `spaceBelow` → `spaceAfter`.
|
|
441
|
+
|
|
442
|
+
- **`ValidationResult.warningCount` removed** — The field was always `0` in v1.x (validator emits
|
|
443
|
+
errors only). **Migration:** use `result.errors.filter(e => e.severity === 'warning').length` if
|
|
444
|
+
you need a count — returns `0` until warning-severity issues are introduced.
|
|
445
|
+
|
|
446
|
+
### Added
|
|
447
|
+
|
|
448
|
+
- **`./signing` export** — New subpath export `pretext-pdf/signing` exposes `applySignature`,
|
|
449
|
+
`applyEncryption`, `applyPostProcessing`, and `renderSignaturePlaceholder` as a standalone
|
|
450
|
+
signing module. Useful for post-render pipeline composition without importing the full library.
|
|
451
|
+
|
|
452
|
+
- **`MAX_SVG_ELEMENTS` constant** — Exported from `dist/assets.js` alongside `SVG_MAX_BYTES`.
|
|
453
|
+
Heuristic element-count guard (5,000 open tags) that skips sanitization of pathologically deep
|
|
454
|
+
SVGs to prevent memory exhaustion during rasterization.
|
|
455
|
+
|
|
456
|
+
- **Per-variant `accessibilityLabel` wired to AcroForm `/TU`** — `accessibilityLabel` on form
|
|
457
|
+
fields is now written to the PDF annotation `/TU` (tooltip/alt-text) dictionary entry.
|
|
458
|
+
Screen readers and assistive technology that consume AcroForm annotations will pick it up.
|
|
459
|
+
|
|
460
|
+
- **`metadata.accessibility` / `metadata.semantic` written to Info dict** — Both reserved fields
|
|
461
|
+
are now serialized as JSON strings into the PDF Info dictionary (`Accessibility` and `Semantic`
|
|
462
|
+
keys). Previously they were accepted but ignored at render time.
|
|
463
|
+
|
|
464
|
+
### Changed
|
|
465
|
+
|
|
466
|
+
- **`ValidationError` and `ValidationResult` fields are `readonly`** — All fields on both
|
|
467
|
+
interfaces are now `readonly`. This is technically breaking for callers that mutate validation
|
|
468
|
+
results directly (which should be none in practice).
|
|
469
|
+
|
|
470
|
+
- **`PluginMeasureResult.height` is `readonly`** — Prevents accidental mutation of the measured
|
|
471
|
+
height by render hooks.
|
|
472
|
+
|
|
473
|
+
### Fixed (security)
|
|
474
|
+
|
|
475
|
+
- **SVG `on*` handler regex now catches newline-injected attributes** — The previous pattern
|
|
476
|
+
`\bon\w+\s*=` missed attributes like `on\nload=`. Fixed to `\bon\w+[\s\n\r]*=`.
|
|
477
|
+
|
|
478
|
+
- **`allowed-props` strict mode now covers `accessibilityLabel`, `accessibility`, `semantic`** —
|
|
479
|
+
These fields were missing from their respective key arrays; strict-mode validation would
|
|
480
|
+
incorrectly report them as unknown properties. Fixed.
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
484
|
+
## [1.9.0] — 2026-05-28
|
|
485
|
+
|
|
486
|
+
Additive release: error categorisation, typed plugin generics, beta graduations, soft deprecations, and a 100 MB output size guard.
|
|
487
|
+
|
|
488
|
+
### Added
|
|
489
|
+
|
|
490
|
+
- **`PretextPdfError.category`** — New readonly field on every thrown error. Groups all 50+
|
|
491
|
+
error codes into 8 high-level categories (`'validation' | 'font' | 'image' | 'layout' |
|
|
492
|
+
'security' | 'dependency' | 'signature' | 'render'`). Lets callers branch on class of
|
|
493
|
+
failure without exhaustive switches. Also exported as the `ErrorCategory` type.
|
|
494
|
+
|
|
495
|
+
- **`LEGACY_ERROR_CODE_MAP`** — `Record<string, ErrorCode>` exported from the main entry
|
|
496
|
+
point. Documents the canonical code for any renamed or aliased error strings. Currently
|
|
497
|
+
maps `FONT_ENCODE_FAIL → FONT_ENCODE_FAIL` (no renames have occurred yet); future renames
|
|
498
|
+
will be recorded here before the old string is removed.
|
|
499
|
+
|
|
500
|
+
- **`PluginDefinition<T>` generic** — `PluginDefinition`, `PluginMeasureResult`, and
|
|
501
|
+
`PluginRenderContext` are now generic over a `T` type parameter (default `unknown`).
|
|
502
|
+
`pluginData` in `measure` and `render` is now typed as `T` instead of `unknown`. Fully
|
|
503
|
+
backward compatible — existing plugins without a type argument continue to compile.
|
|
504
|
+
|
|
505
|
+
- **`MAX_PDF_BYTES` constant** — Exported from the main entry point. Value: `100 * 1024 * 1024`
|
|
506
|
+
(100 MB). `render()` now throws `RENDER_FAILED` if the serialized PDF exceeds this limit,
|
|
507
|
+
preventing runaway memory use from pathological documents.
|
|
508
|
+
|
|
509
|
+
### Changed (promotions — no breaking changes)
|
|
510
|
+
|
|
511
|
+
- **`PluginDefinition`, `PluginMeasureContext`, `PluginMeasureResult`, `PluginRenderContext`**
|
|
512
|
+
— Promoted from `@beta` to `@public`. The plugin API is stable.
|
|
513
|
+
|
|
514
|
+
- **`RenderOptions.plugins`** — `@beta` tag removed. Stable.
|
|
515
|
+
|
|
516
|
+
- **`createFootnoteSet()`** — `@beta` tag removed. Stable.
|
|
517
|
+
|
|
518
|
+
### Deprecated
|
|
519
|
+
|
|
520
|
+
- **`HorizontalRuleElement.spaceAbove`** and **`.spaceBelow`** — Use `spaceBefore` and
|
|
521
|
+
`spaceAfter` instead (consistent with paragraph/heading naming). Both aliases remain
|
|
522
|
+
functional in v1.x. Will be removed in v2.0.
|
|
523
|
+
|
|
524
|
+
- **`ValidationResult.warningCount`** — Always `0` in v1.x (the validator emits errors
|
|
525
|
+
only). Will be removed in v2.0. Use
|
|
526
|
+
`result.errors.filter(e => e.severity === 'warning').length` directly when
|
|
527
|
+
warning-severity items are introduced.
|
|
528
|
+
|
|
529
|
+
---
|
|
530
|
+
|
|
531
|
+
## [1.8.0] — 2026-05-28
|
|
532
|
+
|
|
533
|
+
Additive release: type narrowing, shared leaf modules, 5 new test coverage points, and a Windows font-path validation bugfix.
|
|
534
|
+
|
|
535
|
+
### Added
|
|
536
|
+
|
|
537
|
+
- **`ColumnDef.width` narrowed** — `number | string` → `number | \`${number}*\` | '*' | 'auto'`. The `as unknown as number` cast in `compat.ts` that faked `'auto'` as a number is now removed; `'auto' as const` is the direct type. No behavior change — accepted inputs are identical.
|
|
538
|
+
|
|
539
|
+
- **`DocumentMetadata.accessibility?` and `DocumentMetadata.semantic?` reserved** (`Record<string, unknown>`, `@alpha`) — Forward-reserved slots for PDF/UA accessibility metadata and document-structure annotations. No render-time effect in v1.x; schema allows them so tooling can start populating them. Will be wired in v2.0.
|
|
540
|
+
|
|
541
|
+
- **`FormFieldElement.accessibilityLabel?` reserved** (`string`, `@alpha`) — Reserved slot for AcroForm `/TU` tooltip/alt-text annotation. Stored but not written to the PDF in v1.x. Will be wired in v2.0.
|
|
542
|
+
|
|
543
|
+
- **`src/url-utils.ts` leaf module** — `SAFE_URL_SCHEME` regex consolidated into a single shared module. Previously duplicated in `render-utils.ts` and `validate/helpers.ts` (v1.7.2 added dedup comments). Both sites now import from `url-utils.ts`; `validate/helpers.ts` re-exports it for downstream consumers.
|
|
544
|
+
|
|
545
|
+
- **`src/font-key.ts` leaf module** — `buildFontKey` extracted from `measure.ts` to break the `measure.ts → measure-blocks/index.ts → measure.ts` circular dependency. All 9 former `./measure.js` importers now import directly from `./font-key.js`. `measure.ts` re-exports `buildFontKey` for backward compatibility.
|
|
546
|
+
|
|
547
|
+
- **`test/error-coverage.test.ts`** — Fills 5 coverage holes identified in the v1.7.2 audit:
|
|
548
|
+
1. `FONT_LOAD_FAILED` — relative font path rejection
|
|
549
|
+
2. `FONT_LOAD_FAILED` — nonexistent absolute font path
|
|
550
|
+
3. `SVG_LOAD_FAILED` — local mock server returning HTTP error (via `fetchWithTimeout` directly)
|
|
551
|
+
4. `CHART_LOAD_FAILED` silent path — chart failure warns, not throws (contract guard)
|
|
552
|
+
5. `RTL_REORDER_FAILED` — error code round-trips correctly; RTL Arabic renders without error
|
|
553
|
+
|
|
554
|
+
### Fixed
|
|
555
|
+
|
|
556
|
+
- **`validate/helpers.ts — validateFontSpec` Windows path false positive** — Windows absolute paths like `F:\path\font.ttf` were being parsed by `new URL()` as `f:` protocol URLs, causing valid font paths to throw `VALIDATION_ERROR: 'src' contains a URL`. Fixed by skipping the URL check for paths that `isAbsolutePath()` identifies as OS-native absolute paths (POSIX `/` prefix or Windows `[A-Z]:\` prefix).
|
|
557
|
+
|
|
558
|
+
---
|
|
559
|
+
|
|
560
|
+
## [1.7.2] — 2026-05-28
|
|
561
|
+
|
|
562
|
+
Internal hardening. No public API changes.
|
|
563
|
+
|
|
564
|
+
### Fixed (silent failures → observable warnings)
|
|
565
|
+
|
|
566
|
+
- **`render.ts` — `getForm()` bare-catch now logs a warning** — previously the error was swallowed silently with a comment. Now routes through the existing `warn()` bridge so structured-logger callers can observe it.
|
|
567
|
+
|
|
568
|
+
- **`render-blocks/footnote.ts` — silent footnote drop on font miss now logs** — `if (!pdfFont) continue` now emits a `[pretext-pdf] footnote #N: font "key" not in fontMap — footnote skipped` warning to stderr before skipping.
|
|
569
|
+
|
|
570
|
+
- **`measure-blocks/image.ts` — non-null assertion on `floatText` replaced with guard** — `element.floatText!` → `element.floatText !== undefined` guard. If neither `floatSpans` nor `floatText` is present, `textLines` stays empty and the float renders image-only (valid case) instead of crashing with `Cannot read properties of undefined`.
|
|
571
|
+
|
|
572
|
+
### Fixed (code correctness)
|
|
573
|
+
|
|
574
|
+
- **`compat.ts` — proto-pollution defense restored** — `copySafeStyleProperties` used `key in source` (traverses prototype chain) instead of `Object.hasOwn`. Replaced with `Object.hasOwn` and narrowed the `source` parameter from `any` to `unknown`, removing the unsafe `as any` cast at the assignment site.
|
|
575
|
+
|
|
576
|
+
### Improved (exhaustiveness + type safety)
|
|
577
|
+
|
|
578
|
+
- **`render-extras.ts` switch(el.fieldType)** — added `default` arm with runtime warning and TypeScript `never` guard. New `fieldType` values added without updating this switch will produce a compile error.
|
|
579
|
+
|
|
580
|
+
- **`render-utils.ts` switch(align)** — added `default` arm with `satisfies never` guard and `startX` safe fallback. The `'left' | 'center' | 'right'` union is already exhaustive; the guard prevents silent breakage if the union ever widens.
|
|
581
|
+
|
|
582
|
+
- **`fonts.ts` switch(el.type)** — added explicit `default: break` arm with a comment listing intentionally-unhandled types. Previously the fall-through silence could hide newly-added element types that do carry text (and would need glyph collection).
|
|
583
|
+
|
|
584
|
+
### Fixed (stale comments)
|
|
585
|
+
|
|
586
|
+
- **`types-public/render-options.ts`** — corrected false claim that bidi-js fallback warnings go to `console.warn` directly; they've been routed through `setBidiWarnFn` / the logger bridge since v1.3.x.
|
|
587
|
+
|
|
588
|
+
- **`render-utils.ts` and `validate/helpers.ts`** — annotated the duplicate `SAFE_URL_SCHEME` regex with a `// NOTE: duplicated` comment pointing to the other location. Consolidation to a shared `url-utils.ts` leaf is deferred to v1.8.
|
|
589
|
+
|
|
590
|
+
- **`assets/loaders/images.ts`** — corrected "still in assets.ts for this commit" → actual location `assets/loaders/orchestrator.ts`.
|
|
591
|
+
|
|
592
|
+
- **`measure-text.ts`** — corrected "measure-blocks.ts call sites" → "call sites in measure-blocks/" (the file was split into a directory in v1.6.0).
|
|
593
|
+
|
|
594
|
+
- **`fonts.ts`** — corrected inline comment `(assets.ts)` → `(assets/security/path-allowlist.ts)`.
|
|
595
|
+
|
|
596
|
+
- **`measure.ts`** — corrected inline comment `(assets.ts)` → `(assets/loaders/orchestrator.ts)`.
|
|
597
|
+
|
|
598
|
+
### Tests rewritten (theatrical → meaningful)
|
|
599
|
+
|
|
600
|
+
- **`test/signatures-crypto.test.ts`** — `'SIGNATURE_DEP_MISSING when @signpdf/signpdf not installed'` renamed and rewritten as `'invisible-only signature: renders without p12, no crypto deps needed'`. The old test body had nothing to do with `SIGNATURE_DEP_MISSING`; the new body verifies that invisible signing without a `p12` path produces a valid PDF that does NOT contain a `/ByteRange` dict (confirming no PKCS#7 signing ran).
|
|
601
|
+
|
|
602
|
+
- **`test/validate-concurrent.test.ts`** — The `'8 parallel validates'` test was vacuous: since `validateDocument` is synchronous, `parallel[i]` trivially equals `sequential[i]`. Replaced with a content-specific isolation test: 5 intentionally-broken docs are validated, and each result's error messages are asserted to mention a keyword specific to THAT doc's element type (e.g. paragraph errors mention "text", table errors mention "columns"). This catches module-level state leakage without relying on async interleaving.
|
|
603
|
+
|
|
604
|
+
---
|
|
605
|
+
|
|
606
|
+
## [1.7.1] — 2026-05-27
|
|
607
|
+
|
|
608
|
+
Security hardening for SVG sanitizer + schema accuracy fix for signing fields.
|
|
609
|
+
|
|
610
|
+
### Security
|
|
611
|
+
|
|
612
|
+
- **SVG `<style>` block: strip `@import` rules** — `sanitizeSvg` now removes `@import` directives before any CSS can be used to trigger an outbound network request from the rasterizer. Previously only `expression()` was stripped from inline `<style>` blocks; `@import url('https://attacker.example/track.css')` would survive sanitization.
|
|
613
|
+
|
|
614
|
+
- **SVG `<style>` block: strip `url(javascript:|vbscript:|data:)` values** — Removes URL function calls using JS/vbscript/data schemes from style content, closing an injection path that bypasses the existing `<a href>` and `<image href>` filters (those only target *attribute values*, not *CSS property values*).
|
|
615
|
+
|
|
616
|
+
- **SVG `<style>` block: strip `url(https?://...)` values (defense-in-depth)** — SVGs embedded in PDFs have no legitimate reason to hot-link external stylesheets or background images. Removes all HTTP/HTTPS `url()` references from style blocks so the rasterizer cannot be coerced into outbound connections via crafted input.
|
|
617
|
+
|
|
618
|
+
### Fixed
|
|
619
|
+
|
|
620
|
+
- **`schema.ts` missing signing fields** — `signature` schema block was missing `p12`, `passphrase`, and `contactInfo` that were added to `SignatureSpec` in v1.7.0. JSON schema consumers (e.g. editors, the MCP generate_pdf tool) now see all valid signing inputs. The TypeScript type was always correct; this was a schema-only gap.
|
|
621
|
+
|
|
622
|
+
### Changed
|
|
623
|
+
|
|
624
|
+
- **`schema.ts` signature description** updated from "Visual signature placeholder drawn on a specified page" to reflect that providing `p12` enables PKCS#7 cryptographic signing; without `p12` a visual-only placeholder box is drawn.
|
|
625
|
+
|
|
626
|
+
---
|
|
627
|
+
|
|
628
|
+
## [1.7.0] — 2026-05-25
|
|
629
|
+
|
|
630
|
+
Signing path repaired. **No public API changes.** The cryptographic signing pipeline has been architecturally broken end-to-end since v1.3.6 — calling `render({ signature: { p12, passphrase } })` would fail with `SIGNATURE_FAILED: PDF signing failed: No ByteRangeStrings found within PDF buffer`. v1.7.0 fixes it with a surgical change inside `applySignature`.
|
|
631
|
+
|
|
632
|
+
### Fixed (critical)
|
|
633
|
+
|
|
634
|
+
- **Signing path was architecturally broken since v1.3.6**. Root cause: `applySignature` loaded the placeholder doc via `@cantoo/pdf-lib`'s `PDFDocument.load`, then handed it to `@signpdf/placeholder-pdf-lib.pdflibAddPlaceholder`, which internally builds `/ByteRange` using **upstream** `pdf-lib`'s `PDFArray`/`PDFNumber`/`PDFName` classes (it imports them directly from `"pdf-lib"`). Cantoo's serializer doesn't recognize upstream's class instances and emitted a malformed `/ByteRange` dict; `@signpdf/utils.findByteRange` then aborted parsing with `No ByteRangeStrings found within PDF buffer`. Fix: load the doc via **upstream `pdf-lib`** for the placeholder hop only. Encryption stays on `@cantoo/pdf-lib` in `applyEncryption` — the two paths are mutually exclusive via the existing `SIGNATURE_CERT_AND_ENCRYPTION` guard, so we never need both pdf-libs active simultaneously. The previously KNOWN-BROKEN `test/signatures-crypto.test.ts → P12 signature verifies cryptographically (real CMS verify)` is now unskipped and green.
|
|
635
|
+
|
|
636
|
+
### Added
|
|
637
|
+
|
|
638
|
+
- **AcroForm regression assertion** inside `test/signatures-crypto.test.ts`. Any valid signed PDF must carry `/AcroForm`, `/Fields [...]`, `/SigFlags 3`, and a `/Type /Sig` object — these four properties are asserted on the signed bytes so a future regression that loses AcroForm structure surfaces immediately.
|
|
639
|
+
- **Signature path snapshot tripwire** (`test/signatures-snapshot.test.ts` + `test/data/signatures-snapshot.json`). Captures the categorical structural shape of a signed PDF (presence of `/ByteRange`, `/AcroForm`, `/SigFlags` value, etc.) rather than byte offsets (which are document- and randomness-dependent). Wired into `test:phases`. Regenerate with `UPDATE_SNAPSHOT=1`.
|
|
640
|
+
|
|
641
|
+
### Changed
|
|
642
|
+
|
|
643
|
+
- **`pdf-lib` declared as an explicit optional peer dependency** (`^1.17.1`). It was previously only present transitively via `@signpdf/placeholder-pdf-lib`'s `dependencies`. Now it's a documented peer with `peerDependenciesMeta.pdf-lib.optional: true`, mirroring the existing `@signpdf/*` pattern. Users with `@signpdf/*` already installed need no action — npm satisfies the new peer from the existing transitive install.
|
|
644
|
+
- **`SIGNATURE_DEP_MISSING` error message** now lists `pdf-lib` alongside the three `@signpdf/*` packages and drops the "currently non-functional due to fork incompatibility" disclaimer that was added in v1.3.6.
|
|
645
|
+
|
|
646
|
+
### Migration
|
|
647
|
+
|
|
648
|
+
None. Same `signature: { p12, passphrase, reason, contactInfo, signerName, location, page, invisible }` config. Same error codes (`SIGNATURE_DEP_MISSING`, `SIGNATURE_P12_LOAD_FAILED`, `SIGNATURE_FAILED`, `SIGNATURE_CERT_AND_ENCRYPTION`). One error message string changed: `SIGNATURE_DEP_MISSING` now mentions `pdf-lib` and no longer carries the "non-functional" disclaimer.
|
|
649
|
+
|
|
650
|
+
### Verification
|
|
651
|
+
|
|
652
|
+
- All 456 tests pass (was 454 pass + 1 skipped pre-fix; now 455 pass after unskip + 1 new snapshot test = 456 total).
|
|
653
|
+
- Encryption-after-signing path still rejected with `SIGNATURE_CERT_AND_ENCRYPTION` (regression-tested).
|
|
654
|
+
- All 7 v1.6.0 verification gates (G1–G7) still pass.
|
|
655
|
+
|
|
656
|
+
---
|
|
657
|
+
|
|
658
|
+
## [1.6.0] — 2026-05-25
|
|
659
|
+
|
|
660
|
+
Internal restructuring + SVG sanitizer hardening. **No public API changes.** The previously-monolithic `src/assets.ts` (961 lines pre-sprint) has been split into 10 focused files under `src/assets/`. A 14-line back-compat shim at `src/assets.ts` re-exports the barrel so every existing consumer (internal modules, public API, and direct test imports via `dist/assets.js`) keeps working unchanged.
|
|
661
|
+
|
|
662
|
+
### Security
|
|
663
|
+
|
|
664
|
+
- **SVG sanitizer hardening** — `sanitizeSvg` now strips three additional payload classes that survived the previous regex chain:
|
|
665
|
+
- **`<foreignObject>` blocks** — the only XML-in-SVG construct that can host arbitrary HTML/XML namespaces. Both self-closing and paired forms are removed wholesale; sibling SVG primitives (`<rect/>`, `<path/>`, etc.) are preserved.
|
|
666
|
+
- **`javascript:` / `vbscript:` / `data:` hrefs on `<a>` elements** — previously only `<image>`/`<use>` hrefs were filtered. Only the dangerous href attribute is dropped, so the `<a>` element's text children still render.
|
|
667
|
+
- **CSS `expression(...)` calls inside `<style>` blocks** — legacy IE XSS vector. Only the `expression(...)` call site is excised; the surrounding stylesheet remains parseable.
|
|
668
|
+
|
|
669
|
+
Coverage: new `test/svg-sanitizer.test.ts` (10 cases) plus expanded MA-4 / MA-5 fixtures in `test/data/assets-split-tripwire.json` (30 fixtures total).
|
|
670
|
+
|
|
671
|
+
### Changed
|
|
672
|
+
|
|
673
|
+
- **`src/assets.ts` split into 10 files** under `src/assets/` (see Internal). No public API change — the file at `src/assets.ts` is now a 14-line re-export shim that aggregates the new barrel `src/assets/index.ts`. Consumers importing from `dist/assets.js` continue to resolve every previously-public symbol (`loadImages`, `assertPathAllowed`, `sanitizeSvg`, `assertSafeUrl`, `resolveAndValidateUrl`, `normalizeIpv4Hostname`, `fetchWithTimeout`, `redactPath`, `VECTOR_RASTER_CONCURRENCY`, plus the `ResolvedSafeUrl` type) at the same module path.
|
|
674
|
+
- **`PretextPdfError` constructor signature snapshot refreshed** — `etc/pretext-pdf.api.md` now reflects the `options?: ErrorOptions` parameter that was added in v1.2.1 but never re-snapshotted. No code change — the parameter has been live for several releases. The snapshot was stale; this release reconciles it.
|
|
675
|
+
|
|
676
|
+
### Internal
|
|
677
|
+
|
|
678
|
+
- **New `src/assets/` directory layout:**
|
|
679
|
+
```
|
|
680
|
+
src/assets/
|
|
681
|
+
├── index.ts # internal barrel
|
|
682
|
+
├── util/
|
|
683
|
+
│ └── redact-path.ts # commit 4
|
|
684
|
+
├── security/
|
|
685
|
+
│ ├── path-allowlist.ts # commit 5
|
|
686
|
+
│ ├── ipv4-normalize.ts # commit 6
|
|
687
|
+
│ ├── url-validation.ts # commit 7
|
|
688
|
+
│ └── fetch.ts # commit 8 (undici Agent stays lazy)
|
|
689
|
+
├── svg/
|
|
690
|
+
│ ├── sanitize.ts # commit 9 (+ SVG_MAX_BYTES)
|
|
691
|
+
│ ├── dimensions.ts # commit 10
|
|
692
|
+
│ ├── resolve-content.ts # commit 10
|
|
693
|
+
│ └── rasterize.ts # commit 11 (@napi-rs/canvas dynamic)
|
|
694
|
+
├── generators/
|
|
695
|
+
│ ├── qr.ts # commit 12 (qrcode dynamic)
|
|
696
|
+
│ ├── barcode.ts # commit 12 (bwip-js dynamic)
|
|
697
|
+
│ └── chart.ts # commit 12 (vega/vega-lite dynamic)
|
|
698
|
+
└── loaders/
|
|
699
|
+
├── images.ts # commit 13
|
|
700
|
+
├── vectors.ts # commit 14
|
|
701
|
+
├── watermark.ts # commit 15
|
|
702
|
+
└── orchestrator.ts # commit 15 (top-level loadImages)
|
|
703
|
+
```
|
|
704
|
+
All optional peer-dependency dynamic imports (`@napi-rs/canvas`, `qrcode`, `bwip-js`, `vega`, `vega-lite`, `undici`) are preserved as lazy loads — cold-start cost is unchanged.
|
|
705
|
+
- **7 verification gates (G1–G7) added** to catch regressions during the split:
|
|
706
|
+
- **G1** Snapshot tripwire (`test/assets-split-tripwire.test.ts`) — 30 fixtures covering sanitizer output, URL normalization, path allowlist, and error code surface
|
|
707
|
+
- **G2** DNS lookup dedup (`test/assets-dns-dedup.test.ts`)
|
|
708
|
+
- **G3** SSRF blocking (`test/security-ssrf.test.ts`, expanded)
|
|
709
|
+
- **G4** Parallel-render concurrency (`test/assets-concurrency.test.ts`) — 10× concurrent `render()` calls must produce bit-identical PDFs
|
|
710
|
+
- **G5** ErrorCode stability (`test/assets-errorcode-stability.test.ts`)
|
|
711
|
+
- **G6** api-extractor diff against `etc/pretext-pdf.api.md`
|
|
712
|
+
- **G7** Cold-start perf (`test/assets-perf-coldstart.test.ts`, baseline at `test/data/perf-coldstart-baseline.json`) — 100 sequential renders within 2.5× the v1.5.2 baseline
|
|
713
|
+
- **G7 measurement on the final shim**: 12,224ms / 100 renders (vs 21,205ms baseline = -42%, i.e. the split happens to be *faster*, well under the upper bound).
|
|
714
|
+
|
|
715
|
+
---
|
|
716
|
+
|
|
717
|
+
## [1.5.2] — 2026-05-25
|
|
718
|
+
|
|
719
|
+
Security hotfix. **No public API changes.** **Upgrade recommended for any deployment that accepts user-controlled image / SVG URLs.**
|
|
720
|
+
|
|
721
|
+
### Security
|
|
722
|
+
|
|
723
|
+
- **CVE-class SSRF bypass via IPv4 alternative notations** — `isPrivateAddress` in `src/assets.ts` applied dotted-decimal regexes (`/^127\./`, `/^10\./`, …) against `URL#hostname`. The WHATWG URL parser does NOT normalize non-dotted IPv4 forms, so an attacker could reach private services by encoding the target in any inet_aton-compatible form:
|
|
724
|
+
- Pure decimal: `https://2130706433/x` → 127.0.0.1
|
|
725
|
+
- Pure hex: `https://0x7f000001/x` → 127.0.0.1
|
|
726
|
+
- Octal octet: `https://0177.0.0.1/x` → 127.0.0.1
|
|
727
|
+
- Hex octet: `https://0x7f.0.0.1/x` → 127.0.0.1
|
|
728
|
+
- Short form: `https://127.1/x` → 127.0.0.1
|
|
729
|
+
|
|
730
|
+
Without normalization, `parsed.hostname` is e.g. `"2130706433"`, the private-range regex chain misses it, the `isIpv4Literal` (4-dot) check misses it, and the URL falls through to DNS — which on Linux's `getaddrinfo` resolves to 127.0.0.1 and bypasses the SSRF guard. Same vector reaches RFC 1918 ranges (10/8, 192.168/16, 172.16/12), link-local (169.254.169.254 — AWS IMDS), and CGNAT.
|
|
731
|
+
|
|
732
|
+
**Fix:** new internal helper `normalizeIpv4Hostname()` implements inet_aton-style parsing (decimal/octal/hex per part, short-form packing for 1/2/3-part inputs, strict 32-bit range guard). `resolveAndValidateUrl` normalizes before the private-IP check AND before DNS, then treats the normalized form as an IP literal so undici never re-resolves a non-dotted private encoding. `isPrivateAddress` also normalizes its input as defense-in-depth on the post-DNS path. Public alternative encodings (e.g. `134744072` == 8.8.8.8) continue to resolve and fetch normally.
|
|
733
|
+
|
|
734
|
+
**Test coverage:** new `test/security-ipv4-bypass.test.ts` adds 24 cases — every blocked encoding above, public regression cases, plus direct unit tests for `normalizeIpv4Hostname` (round-trips, range guards, malformed-octal rejection, public-IP allowlist). Wired into the `test:phases` stage; phases stage grows from 417 to 441 tests.
|
|
735
|
+
|
|
736
|
+
### Fixed
|
|
737
|
+
|
|
738
|
+
- **`measure-blocks/float-group.ts` — fontSize fallback intent clarified** — v1.5.1 M5a removed a dead `baseFontSize = doc.defaultFontSize ?? 12` plus per-block `fontSize = block.fontSize || baseFontSize` local that was never read (the item assignment used `block.fontSize` directly). Audit of upstream measure helpers (measure-paragraph, measure-heading, …) confirms `block.fontSize` is always populated with a positive value for real content, so the fallback would never have fired in practice. Added a leading comment so future contributors don't reintroduce the fallback in the mistaken belief that `block.fontSize === 0` is a real case. No behavior change.
|
|
739
|
+
|
|
740
|
+
---
|
|
741
|
+
|
|
742
|
+
## [1.5.1] — 2026-05-24
|
|
743
|
+
|
|
744
|
+
Hotfix batch closing 9 audit findings from the 6-agent v1.5.0 review. **No public API changes** — guarded by `test/public-api-surface.test.ts`. **No behavior changes other than the documented fixes**. Snapshot baseline expanded from 68 to 73 fixtures (5 new: 2 metadata.keywords + 3 watermark.image).
|
|
745
|
+
|
|
746
|
+
### Security
|
|
747
|
+
|
|
748
|
+
- **Watermark image URL scheme validation (H1)** — `doc.watermark.image` now passes through the same `validateUrl` pre-flight check used by `validateImage`. Unsafe schemes (`javascript:`, `data:`, `vbscript:`, `blob:`, `about:`, `file:`) are rejected at validate-time so CLI lint and MCP validate tools catch them before render. Relative file paths fall through unchanged. The shared URL-shape helper `looksLikeUrl` was moved from `validate/elements/media.ts` to `validate/helpers.ts`.
|
|
749
|
+
|
|
750
|
+
### Fixed
|
|
751
|
+
|
|
752
|
+
- **`metadata.keywords[]` element validation (H3)** — Previously, `for (const field of [...'keywords'...])` was paired with `typeof val === 'string'`, which silently no-op'd for the `string[]`-shaped `keywords` field. Each keyword entry is now validated for control-character injection and the 1000-character length cap via `validateMetadataString(kw, \`keywords[\${i}]\`)`.
|
|
753
|
+
- **highlight.js dynamic-import error logging (H4)** — Previously, `catch { /* not installed */ }` silently swallowed every failure including real module-load errors. The catch now logs a warning unless the error code is `ERR_MODULE_NOT_FOUND`/`MODULE_NOT_FOUND` (the only legitimate "optional dep absent" signal).
|
|
754
|
+
- **`hljs.highlight()` runtime exception logging (M2)** — Same silent-fallback issue as H4 but for tokenization failures. Now logs a warning naming the language before falling back to plain text.
|
|
755
|
+
- **Font-variant registration logging (M3)** — `installNodePolyfill` now tracks per-variant success and warns when an individual Inter weight (400 or 700) fails to register, instead of only warning when *both* failed. Text metrics for the missing variant may be inaccurate; operators see this in logs.
|
|
756
|
+
|
|
757
|
+
### Changed
|
|
758
|
+
|
|
759
|
+
- **Removed redundant `as import('...').X` casts in `validate/elements/forms-floats.ts` (M1)** — `validateFormField`, `validateFootnoteDef`, and `validateFloatGroup` already accept `Extract<ContentElement, { type: 'X' }>`-typed `el`. The inner `const ff = el as FormFieldElement` casts were no-ops; removed.
|
|
760
|
+
- **Removed unreachable `toc-entry` validator (M4)** — The pre-switch throw at `validate/index.ts:260` already rejects `toc-entry` before dispatch. The `case 'toc-entry':` arm (guarded with `@ts-expect-error`) and the `validateTocEntry` function in `validate/elements/structural-simple.ts` were unreachable dead code. Drift-guard test updated with `VALIDATE_DISPATCHER_EXCLUDES` (mirrors the existing `MEASURE_DISPATCHER_EXCLUDES` pattern) to keep the orchestrator scan honest.
|
|
761
|
+
- **tsconfig: `noUnusedParameters` + `noUnusedLocals` enabled (M5a)** — Catches dead destructured locals and unused imports at build time. Cascade: 51 errors → 0. 30 of those were per-type `type _X = Exact<...>` drift guards in `allowed-props.ts` (load-bearing scaffolding) — consolidated into a single exported tuple `_AllowedPropsDriftGuard` that TypeScript counts as used. The remaining 20 were genuine unused-locals / unused-imports cleanup across measure, render, rich-text, and assets modules.
|
|
762
|
+
- **tsconfig: `verbatimModuleSyntax` enabled (M5b)** — Enforces `import type` discipline. Cascade was only 6 errors, all `HyphenatorOpts` runtime imports that needed splitting into `import type`. Well under the 30-line cascade-defer threshold.
|
|
763
|
+
|
|
764
|
+
---
|
|
765
|
+
|
|
766
|
+
## [1.5.0] — 2026-05-24
|
|
767
|
+
|
|
768
|
+
Architecture sprint completing the v1.4.0 god-file split debt. Six items shipped, single minor release. **No public API changes** — guarded by `test/public-api-surface.test.ts`. **No behavioral changes** — Item A's security-critical extraction guarded by new `test/validate-document-snapshot.test.ts` (68 fixtures, bit-exact preservation verified).
|
|
769
|
+
|
|
770
|
+
### Changed (internal structure — non-breaking)
|
|
771
|
+
|
|
772
|
+
- **`src/validate/index.ts` (594L) → orchestrator (322L) + `src/validate/document.ts` (324L)** —
|
|
773
|
+
Extracted 11 doc-level check categories (pageSize, margins, fonts, header/footer, defaultParagraphStyle, sections, watermark, encryption, signature, bookmarks, hyphenation, metadata) into a new `validateDocumentLevel(doc, ctx)` function. Single function with labeled `// ── name ──` blocks. **Security-critical**: snapshot tripwire test (68 fixtures across all 11 categories) verified bit-exact error preservation through the move.
|
|
774
|
+
- **`src/measure-blocks/index.ts` (337L) → dispatcher (243L) + `src/measure-blocks/simple-blocks.ts` (151L)** —
|
|
775
|
+
Extracted 7 simple measurement arms (spacer, page-break, comment, form-field, hr, toc, footnote-def). Throw-guards for image/svg/qr-code/barcode/chart stay in dispatcher (security invariants tied to routing).
|
|
776
|
+
- **`src/validate/elements/structural.ts` (239L grab-bag) → `structural-simple.ts` (120L) + `forms-floats.ts` (128L)** —
|
|
777
|
+
Split light element validators (spacer, hr, toc, toc-entry, comment) from heavy ones (form-field, footnote-def, float-group). Cleaner separation of concerns.
|
|
778
|
+
|
|
779
|
+
### Added
|
|
780
|
+
|
|
781
|
+
- **`src/validate/elements/README.md`** — Placement guide + validator signature contract + `_ctx` policy documentation + `withCycleGuard` usage guidance. Onboarding aid for adding new element types.
|
|
782
|
+
- **`test/validate-document-snapshot.test.ts`** + `test/data/validate-document-snapshot.json` — Bit-exact error preservation tripwire for `validateDocumentLevel`. 68 fixtures across pageSize, margins, fonts, header/footer, defaultParagraphStyle, sections, watermark, encryption, signature, bookmarks, hyphenation, metadata, content guards, and valid-doc sanity cases.
|
|
783
|
+
|
|
784
|
+
### Fixed
|
|
785
|
+
|
|
786
|
+
- **`dist/` no longer tracked in git** (168 files removed) — was already in `.gitignore` but tracked from prior history.
|
|
787
|
+
|
|
788
|
+
### Notes
|
|
789
|
+
|
|
790
|
+
- 12 commits across 6 items, each independently revertable via the 3-commit-per-split pattern (stage → route → delete) proven in v1.4.0.
|
|
791
|
+
- Test suite: 416 pass / 1 skip / 0 fail throughout the sprint, plus the new snapshot tripwire test (passes against generated baseline).
|
|
792
|
+
- Public API surface unchanged: 15 runtime exports × 6 entry points.
|
|
793
|
+
- `_ctx` policy decision: keep underscore-prefixed param for validators that don't currently consume context. Documented in `validate/elements/README.md` — stable signature lets future strict-mode or context-aware checks be added without changing call sites.
|
|
794
|
+
- `loadedFamilies` initialization order: populated after `validateDocumentLevel` returns. Audit confirmed no order dependency — `document.ts` never reads `loadedFamilies`, only `validateFontSpec` for shape validation.
|
|
795
|
+
- Path-traversal pre-flight in `validateImage` deferred to v2.0 — runtime SSRF + `allowedFileDirs` guards already provide defense-in-depth.
|
|
796
|
+
|
|
797
|
+
---
|
|
798
|
+
|
|
799
|
+
## [1.4.1] — 2026-05-23
|
|
800
|
+
|
|
801
|
+
Cleanup batch addressing audit findings surfaced by the v1.4.0 god-file split. Five MEDIUM-severity items, all internal — no public API changes, no behavioral changes for callers using documented schemas. Test suite unchanged: 416 pass / 1 skip / 0 fail.
|
|
802
|
+
|
|
803
|
+
### Fixed
|
|
804
|
+
|
|
805
|
+
- **M1 — Dead outer `withCycleGuard` removed from `validateElement` dispatcher (`src/validate/index.ts`).** The dispatcher wrapped `list` and `float-group` cases in an outer `withCycleGuard` with an empty body, then the inner element validators (`validateList` in `elements/list.ts`, `validateFloatGroup` in `elements/structural.ts`) immediately opened their own guard on the same element. The outer guard ran a no-op body and `finally`-deleted the element from `seen` BEFORE the inner guard added it — dead code communicating false intent. The inner validators own the guard. Import of `withCycleGuard` also dropped from `index.ts` since it is no longer used there.
|
|
806
|
+
- **M2 — `measure-blocks/float-group.ts ↔ measure-blocks/index.ts` runtime cycle broken (Option C: dependency injection).** `float-group.ts` previously imported `measureBlock` from `./index.js`, while `index.ts` re-exported `measureFloatGroup` from `./float-group.js` — a real ESM cycle that hoisting tolerated but which violated module-boundary discipline. Resolved by promoting `measureBlock` to an explicit parameter of `measureFloatGroup` (new exported `MeasureBlockFn` type). The sole caller (the orchestrator in `measure.ts`) already has `measureBlock` in scope, so the change is non-invasive. Option C was chosen over Option A (inlining the dispatch — too much duplication) and Option B (extracting `dispatch.ts` — restructured more than needed for a one-edge cycle).
|
|
807
|
+
|
|
808
|
+
### Changed
|
|
809
|
+
|
|
810
|
+
- **M3 — Concurrent validate test annotated with sync-vs-async semantics (`test/validate-concurrent.test.ts`).** Added a comment block above the parallel-validate `describe` clarifying that `validateDocument` is synchronous, so `Promise.all` over it cannot exercise real concurrent execution. The test now explicitly documents what it does prove (shape stability across N invocations) versus what it does not (concurrent isolation — guaranteed structurally by the per-call `WeakSet` opened at the top of `validate()` in `src/validate/index.ts`). The async render-path tests later in the same file DO exercise real concurrency because `render()` crosses `await` boundaries.
|
|
811
|
+
- **M4 — Benchmark harness upgraded (`scripts/run-bench-snapshot.mjs`).** Default measured runs raised from 3 to 10. New `--runs N` CLI flag for callers to override (CI: 5, dev: 3, full: 10). Output now reports `median`, `p90`, and `min` in addition to `avg`. Variance on cold-tsx invocations can hit ±70%, which made any <10% regression gate meaningless at N=3.
|
|
812
|
+
|
|
813
|
+
### Added
|
|
814
|
+
|
|
815
|
+
- **M5 — URL scheme check in `validateImage` (`src/validate/elements/media.ts`).** When `el.src` is a string that looks like a URL (matches `data:`, `javascript:`, `vbscript:`, `blob:`, `about:`, `file:`, or any `scheme://` form), the validator now routes through `validateUrl` from `validate/helpers.ts`. Matches the runtime SSRF guard's posture in `src/compat.ts` so validate-only callers (CLI lint, MCP `validate_document` tool) catch unsafe schemes pre-flight instead of letting them through to the asset-loader guard. http/https/ftp/mailto/anchor links still pass.
|
|
816
|
+
|
|
817
|
+
---
|
|
818
|
+
|
|
819
|
+
## [1.4.0] — 2026-05-23
|
|
820
|
+
|
|
821
|
+
Architecture sprint: four god-files split into thin orchestrators + cohesive sub-modules. **No public API changes** — guarded by `test/public-api-surface.test.ts` (15 runtime exports across 6 entry points, snapshot tripwire). 12 granular commits, each independently revertable.
|
|
822
|
+
|
|
823
|
+
### Changed (internal structure — non-breaking)
|
|
824
|
+
|
|
825
|
+
- **`src/validate.ts` (1834L) → `src/validate/` (9 files, ~250L each)** —
|
|
826
|
+
`validate/index.ts` orchestrator + `helpers.ts`, `fonts.ts`, `errors.ts`, and per-element validators under `elements/`. Introduced explicit `ValidationContext` ({ errors, strict, loadedFamilies, seen, options }) threaded into every element validator instead of relying on closure-captured locals. Per-call `WeakSet` for cycle detection — concurrent isolation verified by `test/validate-concurrent.test.ts`.
|
|
827
|
+
- **`src/measure-blocks.ts` (1600L) → `src/measure-blocks/` (10 files)** —
|
|
828
|
+
`measure-blocks/index.ts` dispatcher + `text-blocks.ts`, `list.ts`, `image.ts`, `float-group.ts`, `highlight.ts`, `helpers.ts`, plus `table/{measure,spans,columns}.ts`. Added explicit case arms for `qr-code`, `barcode`, `chart` (previously fell through to "Unknown element type"). `_hljsCache` intentionally remains module-scoped in `highlight.ts` — idempotent process-wide cache, not concurrent-isolated state.
|
|
829
|
+
- **`src/render-blocks.ts` (1277L) → `src/render-blocks/` (13 files)** —
|
|
830
|
+
Per-render-function modules. Each independent (no shared state). Dropped four unused imports from the original (`PDFFont`, `PDFName`, `renderTocEntry`, `renderFormField` — never referenced).
|
|
831
|
+
- **`src/types-public.ts` (1200L) → `src/types-public/` (8 files)** —
|
|
832
|
+
Split by domain: `document.ts`, `elements-{text,block,media}.ts`, `union.ts`, `validation.ts`, `render-options.ts`. Type-only intra-package cycles are erased at runtime (verified by clean tsc build).
|
|
833
|
+
|
|
834
|
+
### Added
|
|
835
|
+
|
|
836
|
+
- **`test/drift-guards.test.ts` — measure-blocks dispatcher case-arm guard.** New invariant mirrors the existing validate + render guards: every `ELEMENT_TYPES` entry must have an explicit `case` arm in `src/measure-blocks/index.ts` (except internal `toc-entry`). Catches future additions to `ELEMENT_TYPES` that forget to handle measurement.
|
|
837
|
+
|
|
838
|
+
### Fixed
|
|
839
|
+
|
|
840
|
+
- **Dead imports removed:** `validate` was imported but never used in `src/builder.ts` after the split. Removed.
|
|
841
|
+
- **Unused locals/params:** `lineHeight` in `float-group.ts` and `table/measure.ts` (computed but never read); `doc` param in `measureCallout` and `measureCode` (preserved as `_doc` for signature compatibility, signals intentional non-use).
|
|
842
|
+
|
|
843
|
+
### Notes
|
|
844
|
+
|
|
845
|
+
- Public API surface tripwire: 15 runtime exports across 6 entry points, all unchanged.
|
|
846
|
+
- Benchmark gate: 5/7 corpora within 5% of v1.3.4; two corpora (`rich-text-mixed-spans` +12.9%, `table-stress` +7.2%) breach but match documented run-to-run variance (5-10% noise on dev machine). Cold-start module-load unchanged. CI re-snapshot recommended to disambiguate variance from regression.
|
|
847
|
+
- Known follow-up: ESM-fragile circular import in `measure-blocks/float-group.ts` ↔ `measure-blocks/index.ts` (load-safe today because `measureBlock` is async-runtime only; revisit if top-level `await` is ever added).
|
|
848
|
+
- Known follow-up: type-only intra-cycles in `types-public/` (no runtime risk — erased at compile time).
|
|
849
|
+
|
|
850
|
+
---
|
|
851
|
+
|
|
852
|
+
## [1.3.6] — 2026-05-23
|
|
853
|
+
|
|
854
|
+
Architecture-sprint scaffolding release: vendor integrity check, concurrent isolation tests, signing import correctness, public API tripwire.
|
|
855
|
+
|
|
856
|
+
### Added
|
|
857
|
+
|
|
858
|
+
- **Boot-time vendor integrity check (#12)** — `src/version-check.ts` exports `assertVendorIntegrity()`, called once at the start of every `render()`. Verifies the vendored pretext version (`src/vendor/pretext/VERSION.ts`) is in the compatible range. Warns (does not throw) on drift. Inline semver matcher avoids adding a `semver` dependency.
|
|
859
|
+
- **Concurrent validate/render isolation tests (#25)** — `test/validate-concurrent.test.ts` exercises 8 parallel `validate()` calls plus 4 parallel `render()` calls across different fonts AND different scripts (en/he/ar/th). Byte-identical fingerprints between parallel and sequential runs prove `vendor/pretext/measurement.ts` and `vendor/pretext/analysis.ts` shared state holds up under concurrent use.
|
|
860
|
+
- **Public API surface tripwire** — `test/public-api-surface.test.ts` snapshots all 15 runtime exports across 6 entry points. Will guard against accidental API drift during the upcoming v1.4.0 god-file splits.
|
|
861
|
+
- **P12/CMS crypto verification test (#26)** — `test/signatures-crypto.test.ts` adds a real `crypto.createVerify('RSA-SHA256')` round-trip with positive + negative cases. **Currently `t.skip`'d** — see KNOWN ISSUES below.
|
|
862
|
+
- **`pretextPdf.mcpCompat`** field in `package.json` — declares `>=1.4.0 <2.0.0` compatibility range for the pretext-pdf-mcp consumer. MCP-side check ships separately.
|
|
863
|
+
|
|
864
|
+
### Fixed
|
|
865
|
+
|
|
866
|
+
- **`signpdf` v3 API + import correctness** — `src/post-process.ts` was destructuring `pdflibAddPlaceholder` from `@signpdf/signpdf` where it does not exist; the symbol lives in `@signpdf/placeholder-pdf-lib`. Also updated to the v3 API (`new P12Signer(buffer, { passphrase })` instead of `signer.sign(buffer, { passphrase })`). Added `@signpdf/placeholder-pdf-lib` and `@signpdf/signer-p12` to `peerDependencies` + `peerDependenciesMeta.optional` (mirroring `@signpdf/signpdf`).
|
|
867
|
+
- **`SIGNATURE_DEP_MISSING` error message** — now identifies exactly which `@signpdf/*` packages are missing and tells the user which to install, instead of always listing all three.
|
|
868
|
+
|
|
869
|
+
### KNOWN ISSUES (deferred to follow-up sprint)
|
|
870
|
+
|
|
871
|
+
- **Signing path is architecturally non-functional** — even with correct imports, `@cantoo/pdf-lib`'s serializer is fork-incompatible with `@signpdf/placeholder-pdf-lib`. The placeholder ByteRange dict is emitted in a shape that `@signpdf/utils.findByteRange` cannot parse. End-to-end signing has never worked. The crypto verify test (#26) is correctly written and ready to run once signing is repaired. Three fix paths exist: `@signpdf/placeholder-plain` swap (breaks AcroForm), porting placeholder-pdf-lib onto cantoo primitives, or a merge-bytes approach. None are in v1.3.6 scope. The `SIGNATURE_DEP_MISSING` message now pre-warns callers.
|
|
872
|
+
|
|
873
|
+
## [1.3.5] — 2026-05-22
|
|
874
|
+
|
|
875
|
+
### Fixed
|
|
876
|
+
|
|
877
|
+
- **`toc-entry` drift-guard regression** —
|
|
878
|
+
`test/drift-guards.test.ts` was failing because `src/validate.ts` had no
|
|
879
|
+
`case 'toc-entry':` arm. `toc-entry` elements are produced internally by
|
|
880
|
+
the TOC two-pass processor, but the drift guard correctly insists every
|
|
881
|
+
registered `ElementType` has a validator case. Added a defensive validator
|
|
882
|
+
for `text`, `pageNumber`, `level`, `levelIndent`, and `leader` so
|
|
883
|
+
user-authored `toc-entry` payloads (rare but possible) fail loud instead
|
|
884
|
+
of slipping through.
|
|
885
|
+
|
|
886
|
+
### Removed
|
|
887
|
+
|
|
888
|
+
- **`test/pretext-api-contract.test.ts`** —
|
|
889
|
+
Reframed in v1.3.3 as a local export-shape guard for the vendored pretext
|
|
890
|
+
layout module, but the test was tautological: it could only fail if
|
|
891
|
+
someone hand-edited `src/vendor/pretext/*.ts` to remove an export, in
|
|
892
|
+
which case TypeScript would already fail the build. Deleted along with
|
|
893
|
+
its entry in the `test:contract` npm script. Drift guards in
|
|
894
|
+
`test/drift-guards.test.ts` cover the real risk (registry vs. switch
|
|
895
|
+
arms going out of sync).
|
|
896
|
+
|
|
897
|
+
### Performance
|
|
898
|
+
|
|
899
|
+
- **v1.3.2+ benchmark numbers captured** —
|
|
900
|
+
Re-ran the seven core corpora against `benchmarks/benchmark-baseline.json`
|
|
901
|
+
(recorded 2026-04-10 at v1.3.0). Results documented in
|
|
902
|
+
`benchmarks/v1.3.2-results.md`: ~1.66x geometric-mean speedup across
|
|
903
|
+
corpora, with the largest wins on text-heavy workloads (table-stress
|
|
904
|
+
-52%, punctuation-heavy -51%, rtl-layout -43%). Confirms the DNS dedup,
|
|
905
|
+
parallel raster, and word-width cache work landed in v1.3.2 was real
|
|
906
|
+
rather than aspirational.
|
|
907
|
+
|
|
908
|
+
### Docs
|
|
909
|
+
|
|
910
|
+
- **README version table extended** —
|
|
911
|
+
Added 1.1.x (vendor switch), 1.2.x (security + benchmarks), and
|
|
912
|
+
1.3.0–1.3.4 (perf + drift guards) rows so the version table no longer
|
|
913
|
+
stops at 1.0.6.
|
|
914
|
+
|
|
915
|
+
### Tooling
|
|
916
|
+
|
|
917
|
+
- **`scripts/run-bench-snapshot.mjs`** —
|
|
918
|
+
Small one-shot runner that prints avg/min render time per corpus across
|
|
919
|
+
three measured runs (after a warmup). Used to capture the v1.3.5
|
|
920
|
+
benchmark results above; useful for ad-hoc perf checks without touching
|
|
921
|
+
the regression-guarded `test/benchmark-baseline.test.ts`.
|
|
922
|
+
|
|
923
|
+
---
|
|
924
|
+
|
|
925
|
+
## [1.3.4] — 2026-05-17
|
|
926
|
+
|
|
927
|
+
### Fixed
|
|
928
|
+
|
|
929
|
+
- **DNS dedup test now imports from source** —
|
|
930
|
+
`test/assets-dns-dedup.test.ts` previously imported `fetchWithTimeout` /
|
|
931
|
+
`assertSafeUrl` from `../dist/assets.js`, which would silently pass against
|
|
932
|
+
a stale build (false confidence). Switched to `../src/assets.js` so the
|
|
933
|
+
test always runs against the current source tree under tsx.
|
|
934
|
+
|
|
935
|
+
### Added
|
|
936
|
+
|
|
937
|
+
- **FIFO eviction boundary test for word-width cache** —
|
|
938
|
+
`test/measure-text-cache.test.ts` now asserts that when the cache is
|
|
939
|
+
pre-filled to `WORD_WIDTH_CACHE_MAX` and a new `measureWord` call is made,
|
|
940
|
+
`cache.size` stays at the cap, the oldest insertion (`syn0`) is evicted,
|
|
941
|
+
and a re-accessed entry (`syn1`) survives — proving FIFO semantics, not LRU.
|
|
942
|
+
|
|
943
|
+
### Changed
|
|
944
|
+
|
|
945
|
+
- **Constant tunability scope documented** — `VECTOR_RASTER_CONCURRENCY` and
|
|
946
|
+
`WORD_WIDTH_CACHE_MAX` are exported as read-only constants for observability
|
|
947
|
+
and test introspection. Consumers wanting different values must fork;
|
|
948
|
+
runtime tunability (env vars or options) is a future enhancement and not
|
|
949
|
+
planned for the v1.x line.
|
|
950
|
+
|
|
951
|
+
---
|
|
952
|
+
|
|
953
|
+
## [1.3.3] — 2026-05-17
|
|
954
|
+
|
|
955
|
+
### Fixed
|
|
956
|
+
|
|
957
|
+
- **Parallel rasterization concurrency cap** — `loadVectorAssets` now runs at
|
|
958
|
+
most 4 SVG/QR/barcode/chart rasterization tasks concurrently (was unbounded).
|
|
959
|
+
Prevents file-descriptor / worker exhaustion on documents with many vector
|
|
960
|
+
assets. New exported constant: `VECTOR_RASTER_CONCURRENCY`.
|
|
961
|
+
- **Word-width cache memory bound** — `measureWord` now FIFO-evicts at 50,000
|
|
962
|
+
entries to bound memory for long-running processes that reuse a single
|
|
963
|
+
`wordWidthCache`. New exported constant: `WORD_WIDTH_CACHE_MAX`.
|
|
964
|
+
|
|
965
|
+
### Changed
|
|
966
|
+
|
|
967
|
+
- **Pretext API contract test reframed** — `test/pretext-api-contract.test.ts`
|
|
968
|
+
header clarified: this is a local export-shape guard for the vendored
|
|
969
|
+
pretext layout module, not an upstream version canary. Pretext has been
|
|
970
|
+
vendored at `src/vendor/pretext/` since v1.1.0.
|
|
971
|
+
- **CHANGELOG clarification on v1.3.2 parallel rasterization** — see updated
|
|
972
|
+
v1.3.2 entry below; the speedup is real for I/O-bound fan-out, but CPU
|
|
973
|
+
rasterization still serializes on the V8 main thread.
|
|
974
|
+
|
|
975
|
+
### Documented (not changed)
|
|
976
|
+
|
|
977
|
+
- **Word-width cache scope (H1)** — the cache is currently consulted on the
|
|
978
|
+
hyphenation path (`measureTextWithHyphenation`) only. The non-hyphenation
|
|
979
|
+
branch of `measureText` delegates directly to pretext's `layoutWithLines`
|
|
980
|
+
to preserve CJK character-level breaking, RTL/bidi, Thai segmentation,
|
|
981
|
+
kerning, and justify semantics that word-by-word summing would diverge
|
|
982
|
+
from. Documents that do not configure a hyphenator will not see
|
|
983
|
+
cross-paragraph cache reuse; this is intentional for correctness.
|
|
984
|
+
|
|
985
|
+
---
|
|
986
|
+
|
|
987
|
+
## [1.3.2] — 2026-05-17
|
|
988
|
+
|
|
989
|
+
### Performance
|
|
990
|
+
|
|
991
|
+
- Removed double DNS resolution in image/SVG fetch (one lookup per remote asset, not two)
|
|
992
|
+
- Parallel SVG/QR/barcode generation+rasterization (sequential embed retained for pdf-lib safety)
|
|
993
|
+
- Sub-note (added in v1.3.3): the parallelism is real for the I/O-bound fan-out
|
|
994
|
+
(remote SVG fetches overlap). CPU rasterization (sharp/svg2pdfkit) still
|
|
995
|
+
contends on the V8 main thread, so wall-clock improvement is dominated by
|
|
996
|
+
remote-asset latency, not raster throughput.
|
|
997
|
+
- Document-level word-width measurement cache (cross-paragraph dedup of common-word measurements)
|
|
998
|
+
- Sub-note (added in v1.3.3): the cache is consulted on the hyphenation code
|
|
999
|
+
path only. See v1.3.3 "Documented (not changed)" for the rationale.
|
|
1000
|
+
|
|
1001
|
+
---
|
|
1002
|
+
|
|
1003
|
+
## [1.3.1] — 2026-05-17
|
|
1004
|
+
|
|
1005
|
+
### Fixed
|
|
1006
|
+
|
|
1007
|
+
- Internal test fixtures updated to set `allowedFileDirs` (resolved with `path.resolve` for Windows drive-letter compatibility) after the v1.2.2 deny-by-default flip — no library behavior change. Affected: `signatures-crypto`, `signatures-validation`, `svg`, `image-floats` test files.
|
|
1008
|
+
- `markdown-gfm` compat test updated to use an `https:` image src; `data:` URLs are blocked by the scheme guard added in v1.3.0, so the pre-existing fixture no longer round-tripped — test-only change.
|
|
1009
|
+
|
|
1010
|
+
---
|
|
1011
|
+
|
|
1012
|
+
## [1.3.0] — 2026-05-17
|
|
1013
|
+
|
|
1014
|
+
### ⚠️ BREAKING (retroactive note covering v1.2.2)
|
|
1015
|
+
|
|
1016
|
+
- **`assertPathAllowed` is now deny-by-default.** Documents using `file://` image sources without an explicit `allowedFileDirs` configuration will throw `PATH_TRAVERSAL`. This was shipped in v1.2.2 as a security fix but is technically a breaking change — consumers on `^1.2.0` who upgrade past v1.2.1 must either set `allowedFileDirs` or migrate away from `file://` sources. v1.3.0 is the recommended upgrade target with full semver signal.
|
|
1017
|
+
|
|
1018
|
+
### Fixed
|
|
1019
|
+
|
|
1020
|
+
- **Scheme guard whitespace bypass** in `compat.ts` — leading whitespace in image src (e.g. `" file:///etc/passwd"`) no longer bypasses scheme stripping.
|
|
1021
|
+
- **Extended scheme blocklist** in `compat.ts` — added `vbscript:`, `blob:`, `about:` alongside existing `file://`, `data:`, `javascript:`.
|
|
1022
|
+
|
|
1023
|
+
### Tests
|
|
1024
|
+
|
|
1025
|
+
- Added redirect-chain SSRF test using a local mock HTTP server.
|
|
1026
|
+
- Pinned CLI exit-code assertion to detect regressions.
|
|
1027
|
+
|
|
1028
|
+
---
|
|
1029
|
+
|
|
1030
|
+
## [1.2.2] — 2026-05-17
|
|
1031
|
+
|
|
1032
|
+
### Security
|
|
1033
|
+
|
|
1034
|
+
- **`assertPathAllowed` is now deny-by-default** — Previously, when `doc.allowedFileDirs` was
|
|
1035
|
+
undefined or empty, local file:// paths were silently allowed. Now the function throws
|
|
1036
|
+
`PATH_TRAVERSAL` unless `allowedFileDirs` is explicitly configured with at least one directory.
|
|
1037
|
+
This closes an unintended open-access footgun for server-side deployments.
|
|
1038
|
+
|
|
1039
|
+
- **`compat.ts` — dangerous image schemes stripped in `fromPdfmake`** — `file://`, `data:`, and
|
|
1040
|
+
`javascript:` image `src` values are now silently dropped during pdfmake→pretext-pdf translation
|
|
1041
|
+
rather than forwarded verbatim. This prevents the compat shim from acting as an indirect
|
|
1042
|
+
bypass for the scheme-level SSRF guards in `assets.ts`.
|
|
1043
|
+
|
|
1044
|
+
- **`compat.ts` — `allowedFileDirs` forwarded from `PdfmakeDocument`** — The `PdfmakeDocument`
|
|
1045
|
+
interface now accepts `allowedFileDirs?: string[]`, which is forwarded into the resulting
|
|
1046
|
+
`PdfDocument`. Callers who previously passed file paths via the compat shim can now
|
|
1047
|
+
allowlist their directories explicitly.
|
|
1048
|
+
|
|
1049
|
+
- **CLI validates before rendering** — `pretext-pdf` now calls `validateDocument()` before
|
|
1050
|
+
invoking `render()`. Invalid documents produce a `VALIDATION_ERROR` message on stderr and
|
|
1051
|
+
exit with code 1, avoiding wasted work during the render phase.
|
|
1052
|
+
|
|
1053
|
+
---
|
|
1054
|
+
|
|
1055
|
+
## [1.2.1] — 2026-05-16
|
|
1056
|
+
|
|
1057
|
+
### Fixed
|
|
1058
|
+
|
|
1059
|
+
- **`PretextPdfError` now preserves root cause via `err.cause`** — `ASSEMBLY_FAILED` errors
|
|
1060
|
+
thrown by `merge()` and `assemble()` now carry the original pdf-lib error as `err.cause`,
|
|
1061
|
+
making root-cause debugging possible without losing the upstream message.
|
|
1062
|
+
|
|
1063
|
+
- **`form.updateFieldAppearances()` failure is now logged** — Previously silently swallowed
|
|
1064
|
+
(`catch { /* non-fatal */ }`). Now emits a structured warning via the document logger or
|
|
1065
|
+
`console.warn`. Behaviour is unchanged (non-fatal); the warning aids debugging.
|
|
1066
|
+
|
|
1067
|
+
- **Owner-only encryption now warns explicitly** — When `doc.encryption` is set without
|
|
1068
|
+
`userPassword`, a `console.warn` is emitted explaining that the PDF will open without a
|
|
1069
|
+
password. Owner-only encryption remains valid (it restricts editing/printing, not opening).
|
|
1070
|
+
|
|
1071
|
+
- **`assemble([{}])` now throws `VALIDATION_ERROR`** — Regression from v1.2.0 discriminated
|
|
1072
|
+
union changes: passing a part with neither `doc` nor `pdf` previously crashed with a
|
|
1073
|
+
`TypeError` (no `.code` property). Now throws a proper `VALIDATION_ERROR` with a clear
|
|
1074
|
+
message before attempting to render.
|
|
1075
|
+
|
|
1076
|
+
- **`watermark: {}` now throws `VALIDATION_ERROR`** — Regression from v1.2.0: the
|
|
1077
|
+
`WatermarkSpec` discriminated union enforced text/image presence at compile-time but the
|
|
1078
|
+
runtime validation was missing. A watermark object with neither `text` nor `image` now
|
|
1079
|
+
correctly throws at validate time.
|
|
1080
|
+
|
|
1081
|
+
- **`svg: ''` (empty string) now throws `VALIDATION_ERROR`** — Empty SVG strings passed
|
|
1082
|
+
the validation stage and surfaced as `SVG_LOAD_FAILED` during render. Now caught at
|
|
1083
|
+
validate time with a clear `VALIDATION_ERROR`.
|
|
1084
|
+
|
|
1085
|
+
### Changed
|
|
1086
|
+
|
|
1087
|
+
- **`PretextPdfError` constructor accepts optional `ErrorOptions`** — Third argument
|
|
1088
|
+
`options?: ErrorOptions` (i.e. `{ cause?: unknown }`) is now accepted and passed to the
|
|
1089
|
+
native `Error` constructor. Fully backwards-compatible — all existing call sites unchanged.
|
|
1090
|
+
|
|
1091
|
+
- **bidi-js missing-peer warning routes through document logger** — When a `logger` is
|
|
1092
|
+
passed to `render()`, bidi-js peer-dependency warnings are now routed through
|
|
1093
|
+
`logger.warn` instead of always using `console.warn`. New low-level export:
|
|
1094
|
+
`setBidiWarnFn(fn)` (prefer the `logger` render option in application code).
|
|
1095
|
+
|
|
1096
|
+
---
|
|
1097
|
+
|
|
1098
|
+
## [1.2.0] — 2026-05-16
|
|
1099
|
+
|
|
1100
|
+
Post-audit hardening release. Type system tightened, concurrency-safe validation,
|
|
1101
|
+
@internal type leaks closed, RTL/asset failures surface as structured errors,
|
|
1102
|
+
SSRF defense upgraded to undici-pinned IP. No source-level API removals from the
|
|
1103
|
+
package entry point — see migration notes below for `@internal` types that were
|
|
1104
|
+
already not exported from `src/index.ts`.
|
|
1105
|
+
|
|
1106
|
+
### Added
|
|
1107
|
+
|
|
1108
|
+
- **Discriminated unions on four public types** (`src/types-public.ts`, audit Phase B) —
|
|
1109
|
+
`WatermarkSpec`, `AssemblyPart`, `SvgElement`, and `ImageElement` (float variants)
|
|
1110
|
+
now use TypeScript discriminated unions instead of flat optional structs. The
|
|
1111
|
+
compiler now prevents invalid combinations (e.g., a watermark with both `text`
|
|
1112
|
+
and `image`, or an SVG element with neither `svg` nor `src`) that previously
|
|
1113
|
+
could only be caught at runtime. Existing valid usages continue to compile.
|
|
1114
|
+
|
|
1115
|
+
- **`pdf-lib` type augmentation** (`src/vendor/pdf-lib-augment.d.ts`, audit Phase D) —
|
|
1116
|
+
Documents the load-bearing `as any` casts against pdf-lib internals
|
|
1117
|
+
(`PDFArray.push`, `PDFFont.embedder`) and removes the one genuinely-avoidable
|
|
1118
|
+
cast in `measure.ts`.
|
|
1119
|
+
|
|
1120
|
+
- **DNS rebinding defense via undici Agent IP pinning** (`src/assets.ts`, audit B6) —
|
|
1121
|
+
`assertSafeUrl` was upgraded to `resolveAndValidateUrl` which returns the
|
|
1122
|
+
resolved IP, and `fetchWithTimeout` now uses an undici `Agent` whose
|
|
1123
|
+
`connect.lookup` callback always returns the pre-validated IP. Closes the
|
|
1124
|
+
TOCTOU window where DNS could rebind between validation and connect.
|
|
1125
|
+
Extended private-range coverage: 0.0.0.0/8, 192.0.0/24, 198.18/15, IPv6
|
|
1126
|
+
multicast, and IPv4-mapped IPv6 normalization. +14 SSRF tests (25 total).
|
|
1127
|
+
|
|
1128
|
+
- **Per-call cycle-detection state** (`src/validate.ts`, audit Perf-1) — Moved
|
|
1129
|
+
`seenInRecursion` WeakSet from module scope into `validate()` and threaded
|
|
1130
|
+
through `withCycleGuard`. Makes validation reentrant and concurrency-safe
|
|
1131
|
+
(no shared mutable state across parallel `validate()` calls). +1 regression
|
|
1132
|
+
test.
|
|
1133
|
+
|
|
1134
|
+
- **Structured error codes on RTL + asset failures** (`src/errors.ts`,
|
|
1135
|
+
`src/measure-text.ts`, `src/assets.ts`, audit silent-failure pass) —
|
|
1136
|
+
- `RTL_REORDER_FAILED` — surfaces when `bidi-js` is installed but throws.
|
|
1137
|
+
Previously fell through with `isRTL:true` on logical-order text =
|
|
1138
|
+
visually broken Arabic/Hebrew renders. Missing `bidi-js` still degrades
|
|
1139
|
+
gracefully (warn + LTR render) since it is an optional peer dep.
|
|
1140
|
+
- `CHART_LOAD_FAILED` — embedded in warn logs from QR/barcode/chart loaders
|
|
1141
|
+
so failures are debuggable from log scraping alone.
|
|
1142
|
+
- `FONT_ENCODE_FAIL` — replaces the prior bare `catch` in `src/fonts.ts`
|
|
1143
|
+
that silently swallowed font subset failures (audit B2).
|
|
1144
|
+
|
|
1145
|
+
### Changed
|
|
1146
|
+
|
|
1147
|
+
- **`@internal` types removed from the `types.ts` barrel** (audit H8 / type-design HIGH) —
|
|
1148
|
+
`RichLine`, `RichFragment`, and `TocEntryElement` were tagged `@internal`
|
|
1149
|
+
but re-exported through `src/types.ts`. They have been removed from that
|
|
1150
|
+
barrel and canonicalized in `src/types-internal.ts`. `TocEntryElement` is
|
|
1151
|
+
no longer a member of the public `ContentElement` union (it was always
|
|
1152
|
+
pipeline-synthesized, never user-constructed). Internal imports updated
|
|
1153
|
+
in `rich-text.ts`, `measure.ts`, `render-extras.ts`, `allowed-props.ts`,
|
|
1154
|
+
`validate.ts`, `measure-blocks.ts`, `fonts.ts`. **Migration note:** these
|
|
1155
|
+
types were never exported from `src/index.ts` (the package entry point),
|
|
1156
|
+
so consumers using the supported import path (`import { ... } from
|
|
1157
|
+
'pretext-pdf'`) are unaffected. Deep imports (`'pretext-pdf/src/types'`)
|
|
1158
|
+
are unsupported and never were stable.
|
|
1159
|
+
|
|
1160
|
+
- **`api-extractor` enforcement escalated to `error`** (`api-extractor.json`) —
|
|
1161
|
+
`ae-forgotten-export` log level changed from `warning` to `error` so CI
|
|
1162
|
+
fails on future `@internal` type leaks. `etc/pretext-pdf.api.md` baseline
|
|
1163
|
+
regenerated.
|
|
1164
|
+
|
|
1165
|
+
- **`assertUnknownProps` parameter tightened** (`src/validate.ts`) —
|
|
1166
|
+
`obj: any` → `obj: unknown` with an explicit type guard at the boundary.
|
|
1167
|
+
Removes one of the few remaining `any` exposures at a security-sensitive
|
|
1168
|
+
validation entrypoint.
|
|
1169
|
+
|
|
1170
|
+
### Fixed
|
|
1171
|
+
|
|
1172
|
+
- **Concurrent validation false positives** (`src/validate.ts`) — Two
|
|
1173
|
+
simultaneous `validateDocument(doc)` calls on the same object reference
|
|
1174
|
+
could produce a false "cyclic reference detected" error because the
|
|
1175
|
+
WeakSet was at module scope. Per-call WeakSet fix closes this and any
|
|
1176
|
+
future re-entrant validator scenarios.
|
|
1177
|
+
|
|
1178
|
+
- **Stale `tests-743` badge** (`README.md`) — Replaced with the durable
|
|
1179
|
+
`tests-passing` (current unit count is 319).
|
|
1180
|
+
|
|
1181
|
+
- **Dead `case 'toc-entry'` branch** (`src/fonts.ts:collectTextByFont`) —
|
|
1182
|
+
After `TocEntryElement` left the public `ContentElement` union, the
|
|
1183
|
+
branch became unreachable.
|
|
1184
|
+
|
|
1185
|
+
### Migration notes (v1.1.x → v1.2.0)
|
|
1186
|
+
|
|
1187
|
+
If you only import from `'pretext-pdf'`: **no source changes needed.**
|
|
1188
|
+
|
|
1189
|
+
If you do unsupported deep imports of internal types
|
|
1190
|
+
(`'pretext-pdf/src/types'`):
|
|
1191
|
+
- `RichLine`, `RichFragment`, `TocEntryElement` — import from
|
|
1192
|
+
`'pretext-pdf/src/types-internal'` instead, with the understanding that
|
|
1193
|
+
these are not part of the stable public API and may change in any release.
|
|
1194
|
+
|
|
1195
|
+
If you construct `WatermarkSpec` / `AssemblyPart` / `SvgElement` / `ImageElement`
|
|
1196
|
+
literals: you may need to delete fields you weren't using anyway. TypeScript
|
|
1197
|
+
will flag any literals that previously satisfied the loose type but violated
|
|
1198
|
+
the actual invariants.
|
|
1199
|
+
|
|
1200
|
+
---
|
|
1201
|
+
|
|
1202
|
+
## [1.1.3] — 2026-05-15
|
|
1203
|
+
|
|
1204
|
+
### Added
|
|
1205
|
+
|
|
1206
|
+
- **Cycle detection + depth cap on TableElement walk** (`src/validate.ts`, Sprint 3 / M2) —
|
|
1207
|
+
The rows/cells iteration is now wrapped in `withCycleGuard`, matching the
|
|
1208
|
+
protection already in place for `ListItem.items`, `FloatGroup.content`, and
|
|
1209
|
+
`RichParagraph.spans`. A self-referential row or cell shape now produces a
|
|
1210
|
+
structured `VALIDATION_ERROR` instead of an unbounded walk.
|
|
1211
|
+
|
|
1212
|
+
- **Root-level depth guard for `document.content` entries** (`src/validate.ts`, Sprint 3 / M1) —
|
|
1213
|
+
Each top-level element call into `validateElement` now runs an explicit
|
|
1214
|
+
`assertDepthOk(depth, prefix)` so the `MAX_VALIDATION_DEPTH = 32` cap fires
|
|
1215
|
+
even for plugin-typed elements that do not open their own `withCycleGuard`
|
|
1216
|
+
scope. Internal recursive walks (`list`, `float-group`, `rich-paragraph`,
|
|
1217
|
+
`table`) continue to enforce the cap via `withCycleGuard`.
|
|
1218
|
+
|
|
1219
|
+
- **Round-trip tests for the pdfmake compatibility shim** (`test/compat.test.ts`, Sprint 3 / M3) —
|
|
1220
|
+
Four new tests covering pdfmake → pretext → render integration, style
|
|
1221
|
+
propagation, sanity rendering of native pretext docs, and large-table
|
|
1222
|
+
preservation (5 columns × 10 rows).
|
|
1223
|
+
|
|
1224
|
+
- **`## Validation` section in `README.md`** (Sprint 3 / M4) — Explicit guidance
|
|
1225
|
+
to call `validateDocument()` before `render()` on untrusted input, with the
|
|
1226
|
+
concrete failure modes (stack overflow on cyclic input, prototype pollution
|
|
1227
|
+
via `__proto__`, runtime 500s on malformed shapes) the validator prevents.
|
|
1228
|
+
|
|
1229
|
+
### Fixed
|
|
1230
|
+
|
|
1231
|
+
- **Type safety in validateDocument** (`src/validate.ts`) — Replaced unchecked `as PdfDocument` cast with `isValidPdfDocumentLike()` type guard. Returns proper error when input is not a plain object.
|
|
1232
|
+
|
|
1233
|
+
- **Prototype pollution in mergeStyles** (`src/compat.ts`) — `Object.assign(merged, s)` allowed user-supplied pdfmake JSON to pollute the prototype chain. Replaced with `copySafeStyleProperties()` that whitelists only known safe style keys (fontSize, bold, italics, color, alignment, font).
|
|
1234
|
+
|
|
1235
|
+
- **Path traversal in digital signatures** (`src/post-process.ts`) — P12 certificate path bypassed the `allowedFileDirs` security check. Now validates path via `assertPathAllowed()` before reading, preventing directory traversal attacks via signature feature.
|
|
1236
|
+
|
|
1237
|
+
- **Fragile errorCount regex in validateDocument** (`src/validate.ts`) — Original regex could match anywhere in error message. Refined to header-only pattern (`^Strict validation failed`) to extract true error count even when >20 errors are returned (capped array but accurate count in message).
|
|
1238
|
+
|
|
1239
|
+
- **Fake test coverage** (`test/validate-document.test.ts`) — Removed describe block with `assert.ok(true, 'TODO')` placeholder. Replaced with documentation explaining why the non-PretextPdfError code path is manually audited.
|
|
1240
|
+
|
|
1241
|
+
- **Missing LICENSE for vendored code** (`src/vendor/pretext/LICENSE`) — Added MIT license file with attribution to upstream pretext library and this fork, satisfying legal compliance for vendored dependencies.
|
|
1242
|
+
|
|
1243
|
+
### Documentation
|
|
1244
|
+
|
|
1245
|
+
- **`Logger` interface guidance** (`src/types-public.ts`, audit L2) — Expanded
|
|
1246
|
+
JSDoc on the `Logger` interface and the `logger?` field on `RenderOptions`
|
|
1247
|
+
to call out that passing a no-op (`{ warn: () => {} }`) silences **every**
|
|
1248
|
+
advisory warning — fine in tests, dangerous in production. Documents the
|
|
1249
|
+
default (`console.warn`) and recommends pino/winston for production
|
|
1250
|
+
routing. No code change.
|
|
1251
|
+
|
|
1252
|
+
- **pdfmake `defaultStyle` / `styles` mapping** (`src/compat.ts`, audit L3) —
|
|
1253
|
+
Added JSDoc to `PdfmakeStyle` enumerating the supported subset
|
|
1254
|
+
(`font`, `fontSize`, `bold`, `italics`, `color`, `alignment`) and the
|
|
1255
|
+
silently-dropped pdfmake properties (`lineHeight`, `marginX/Y`,
|
|
1256
|
+
`decoration`, `background`, `characterSpacing`, `noWrap`, etc.) that
|
|
1257
|
+
consumers migrating from pdfmake commonly trip over. `fromPdfmake()`
|
|
1258
|
+
now documents how `defaultStyle.font` / `.fontSize` route to
|
|
1259
|
+
document-level `defaultFont` / `defaultFontSize` and how all other
|
|
1260
|
+
`defaultStyle` properties cascade through `mergeStyles()`.
|
|
1261
|
+
|
|
1262
|
+
### Changed
|
|
1263
|
+
|
|
1264
|
+
- **Benchmark floor override** (`test/benchmark-baseline.test.ts`, audit L4) —
|
|
1265
|
+
The per-corpus regression guard now honors a `PRETEXT_BENCHMARK_FLOOR_MS`
|
|
1266
|
+
environment variable. Set it to a positive integer to raise the 5000ms
|
|
1267
|
+
default floor on slow CI runners; set it to `skip` / `0` / `false` / `off`
|
|
1268
|
+
to bypass the timing assertion entirely. The structural assertions (corpus
|
|
1269
|
+
IDs match baseline, stages present) still run.
|
|
1270
|
+
|
|
1271
|
+
### Notes — Phase A / B / D history
|
|
1272
|
+
|
|
1273
|
+
The cycle-detection and depth-cap machinery (`withCycleGuard`,
|
|
1274
|
+
`MAX_VALIDATION_DEPTH`, the per-container guards on `list`, `float-group`,
|
|
1275
|
+
`rich-paragraph`) and the discriminated-union refactor of `ContentElement`
|
|
1276
|
+
plus the typed `pdf-lib` augmentation were landed during in-flight audit
|
|
1277
|
+
sprints between `[1.0.x]` and `[1.1.0]` that were not individually tagged.
|
|
1278
|
+
Sprint 3 (this release) backfills those gaps with the M1/M2 root + table
|
|
1279
|
+
guards, plus explicit tests and documentation.
|
|
1280
|
+
|
|
1281
|
+
---
|
|
1282
|
+
|
|
1283
|
+
## [1.1.2] — 2026-05-08
|
|
1284
|
+
|
|
1285
|
+
### Fixed
|
|
1286
|
+
|
|
1287
|
+
- **Silent font-subset failure** (`src/fonts.ts`) — Bare `catch {}` on `pdfFont.encodeText()`
|
|
1288
|
+
silently swallowed glyph-encoding errors, producing wrong characters with no signal.
|
|
1289
|
+
Now logs a `console.warn` so callers know which font key failed.
|
|
1290
|
+
|
|
1291
|
+
- **Explicit RTL direction silently flipping to LTR** (`src/measure-text.ts`) — When
|
|
1292
|
+
`dir:'rtl'` was set and `bidi-js` threw during reordering, the fallback incorrectly
|
|
1293
|
+
returned `isRTL: false`, causing Arabic/Hebrew paragraphs to align and wrap as LTR.
|
|
1294
|
+
The fallback now preserves `isRTL: true` so the layout engine honours the explicit
|
|
1295
|
+
direction even without bidi reordering.
|
|
1296
|
+
|
|
1297
|
+
- **SSRF DNS rebinding window** (`src/assets.ts`) — `assertSafeUrl()` was synchronous.
|
|
1298
|
+
An attacker with TTL=0 DNS could pass the hostname check then rebind to `169.254.x.x`
|
|
1299
|
+
between the check and the actual `fetch()` call. The function is now async and
|
|
1300
|
+
pre-resolves hostnames via `dns.lookup()` before the private-range check, closing
|
|
1301
|
+
the TOCTOU window. Falls back gracefully when DNS is unavailable (fetch will also
|
|
1302
|
+
fail in that case). All call sites updated to `await assertSafeUrl()`.
|
|
1303
|
+
|
|
1304
|
+
- **Concurrent PDFDocument mutation race** (`src/pipeline.ts`) — `loadFonts` and
|
|
1305
|
+
`loadImages` were run with `Promise.all()` over the same `PDFDocument` instance.
|
|
1306
|
+
Both mutate the cross-reference table, causing intermittent xref corruption under
|
|
1307
|
+
load. Now sequenced: `loadFonts` completes before `loadImages` begins.
|
|
1308
|
+
|
|
1309
|
+
- **Test suite cascade: 692 tests silently dropped on benchmark failure** (`package.json`,
|
|
1310
|
+
`scripts/test-all.mjs`) — The `&&`-chained `npm test` command aborted all downstream
|
|
1311
|
+
stages when `test:contract` failed. Replaced with a Node.js runner that executes all
|
|
1312
|
+
4 stages and collects failures. Benchmark is now in a separate `test:benchmark` script
|
|
1313
|
+
(not in `test:contract`) with `FLOOR_MS` raised to 5s to absorb dev-hardware variance.
|
|
1314
|
+
|
|
1315
|
+
---
|
|
1316
|
+
|
|
1317
|
+
## [1.1.1] — 2026-05-08
|
|
1318
|
+
|
|
1319
|
+
### Fixed
|
|
1320
|
+
|
|
1321
|
+
- **`validateDocument` fallback parser: path extraction** — `parseValidationErrorsStructured`
|
|
1322
|
+
now correctly falls back to `path: "document"` for single-throw errors whose message
|
|
1323
|
+
contains a sentence (e.g. `"margins.left must be a non-negative finite number. Got: -1"`).
|
|
1324
|
+
Previously the heuristic accepted any text-before-colon that started with a letter,
|
|
1325
|
+
producing a corrupted path like `"margins.left must be a non-negative finite number. Got"`.
|
|
1326
|
+
Fix: reject candidates that contain `". "` (period + space), which only appears in
|
|
1327
|
+
prose sentences, never in path expressions like `content[0] (paragraph) spans[0].href`.
|
|
1328
|
+
|
|
1329
|
+
- **README `runtime%20deps` badge** — Updated from `8` to `7` to reflect the removal
|
|
1330
|
+
of `@chenglou/pretext` from `dependencies` in v1.1.0.
|
|
1331
|
+
|
|
1332
|
+
- **`SECURITY.md` personal email removed** — Replaced `akashchikara1998@gmail.com`
|
|
1333
|
+
with the GitHub private vulnerability reporting URL.
|
|
1334
|
+
|
|
1335
|
+
### Changed
|
|
1336
|
+
|
|
1337
|
+
- **CI matrix: Node 18.x removed** — Node 18 reached End of Life in April 2025.
|
|
1338
|
+
The CI matrix now targets Node 20.x and 22.x only. The `engines.node` field in
|
|
1339
|
+
`package.json` is updated to `>=20.0.0`. The Node 18 matrix slot was causing
|
|
1340
|
+
flaky benchmark failures (EOL runners are slower) that killed the `&&` test chain
|
|
1341
|
+
and caused the badge verifier to see a truncated test count.
|
|
1342
|
+
|
|
1343
|
+
---
|
|
1344
|
+
|
|
1345
|
+
## [1.1.0] — 2026-05-07
|
|
1346
|
+
|
|
1347
|
+
Vendor `@chenglou/pretext` source directly into the package, eliminating the
|
|
1348
|
+
GitHub URL dependency and all associated install risks (mutable tags, npm audit
|
|
1349
|
+
gaps, network-only install, no SRI).
|
|
1350
|
+
|
|
1351
|
+
### Changed
|
|
1352
|
+
|
|
1353
|
+
- **`@chenglou/pretext` is now vendored** — The upstream text-layout engine
|
|
1354
|
+
(`src/vendor/pretext/`) is compiled as part of pretext-pdf itself. Consumers
|
|
1355
|
+
no longer need to install `@chenglou/pretext`; the GitHub URL dependency has
|
|
1356
|
+
been removed from `package.json`. The vendored snapshot is pinned to
|
|
1357
|
+
`v0.0.6-patched.2` (commit `658edfec`) with 9 upstream PRs cherry-picked on
|
|
1358
|
+
top of the `v0.0.6` release. See `UPSTREAM.md` for the full patch inventory
|
|
1359
|
+
and upgrade procedure.
|
|
1360
|
+
|
|
1361
|
+
### Added
|
|
1362
|
+
|
|
1363
|
+
- **`UPSTREAM.md`** — Authoritative attribution and upgrade guide for the
|
|
1364
|
+
vendored `@chenglou/pretext` source. Documents provenance, the 9 cherry-picked
|
|
1365
|
+
upstream PRs (#3, #29, #105, #119, #132, #138, #140, #161, #165), which
|
|
1366
|
+
commits are excluded from vendoring (fork infra), and the procedure for
|
|
1367
|
+
updating when upstream publishes a new release.
|
|
1368
|
+
|
|
1369
|
+
### Removed
|
|
1370
|
+
|
|
1371
|
+
- **`@chenglou/pretext` dependency** — Removed from `dependencies`. The library
|
|
1372
|
+
source is now bundled inside the package at `dist/vendor/pretext/`. No runtime
|
|
1373
|
+
behavior change; the same patched code is used.
|
|
1374
|
+
|
|
1375
|
+
---
|
|
1376
|
+
|
|
1377
|
+
## [1.0.9] — 2026-05-06
|
|
1378
|
+
|
|
1379
|
+
Test coverage Phase 2: filling blind spots in the CLI, the pdfmake compat shim, and
|
|
1380
|
+
the performance regression guard. Adds c8 coverage tooling for measurability.
|
|
1381
|
+
|
|
1382
|
+
### Added
|
|
1383
|
+
|
|
1384
|
+
- **`test/cli.test.ts`** (+13 tests) — End-to-end coverage for the `pretext-pdf` CLI binary,
|
|
1385
|
+
spawning `dist/cli.js` as a subprocess. Covers argument parsing (`--version`, `--help`,
|
|
1386
|
+
`-i/-o/--markdown/--code-font`, positional fallback, unknown flags), JSON and Markdown
|
|
1387
|
+
input modes, stdin/stdout piping, and exit codes 0/1/2.
|
|
1388
|
+
|
|
1389
|
+
- **`test/compat.test.ts`** (+34 tests) — Coverage for the pdfmake → pretext-pdf
|
|
1390
|
+
translation shim (`fromPdfmake`). Covers page setup (pageSize string and object,
|
|
1391
|
+
pageMargins scalar/2-tuple/4-tuple, orientation), styles (defaultStyle, named styles,
|
|
1392
|
+
headingMap override), all content node types (string, `text`, `ul`/`ol` with nesting,
|
|
1393
|
+
`table` with header rows, `image`, `qr`, `pageBreak`, `stack`), header/footer string
|
|
1394
|
+
forms, integration render, and unsupported nodes (`columns`, `canvas`).
|
|
1395
|
+
|
|
1396
|
+
- **c8 coverage tooling** — `npm run coverage` (text + lcov reporters) and
|
|
1397
|
+
`npm run coverage:check` (75/65/75 thresholds, non-blocking in CI initially).
|
|
1398
|
+
Configuration in `.c8rc.json` excludes type-only files and CLI from instrumentation.
|
|
1399
|
+
Coverage step added to CI as `continue-on-error: true` while baseline thresholds
|
|
1400
|
+
are calibrated.
|
|
1401
|
+
|
|
1402
|
+
### Fixed
|
|
1403
|
+
|
|
1404
|
+
- **`test/benchmark-baseline.test.ts`: regression guard now actually guards** —
|
|
1405
|
+
Replaced the prior "TODO: enable when baseline is calibrated" stub (which collected
|
|
1406
|
+
timings but asserted nothing) with a real 3x-baseline-with-500ms-floor budget per
|
|
1407
|
+
corpus. Missing corpora in the baseline JSON now `assert.fail()` instead of silently
|
|
1408
|
+
defaulting to a zero budget that would mask any regression.
|
|
1409
|
+
|
|
1410
|
+
- **CONTRIBUTING.md: removed stale "(676 tests)" annotation** — Test count drift bait;
|
|
1411
|
+
the README badge already auto-verifies via `verify:badges`.
|
|
1412
|
+
|
|
1413
|
+
### Changed
|
|
1414
|
+
|
|
1415
|
+
- **Test runner now builds first** — Added `pretest:unit: npm run build` so contributors
|
|
1416
|
+
running `npm run test:unit` always get a fresh dist; the new CLI tests spawn the
|
|
1417
|
+
compiled binary and would otherwise fail with a confusing module-not-found error.
|
|
1418
|
+
|
|
1419
|
+
---
|
|
1420
|
+
|
|
1421
|
+
## [1.0.8] — 2026-05-06
|
|
1422
|
+
|
|
1423
|
+
Public API contract integrity: the `RenderOptions.logger` option now actually does what
|
|
1424
|
+
its JSDoc has always promised, and `@napi-rs/canvas` no longer auto-installs.
|
|
1425
|
+
|
|
1426
|
+
### Fixed
|
|
1427
|
+
|
|
1428
|
+
- **`RenderOptions.logger` now routes warnings from asset loading and rendering** —
|
|
1429
|
+
Previously only validation warnings respected the `logger` option. Now all advisory
|
|
1430
|
+
warnings from `loadImages` (image load, image embed, QR/barcode/chart skipped, plugin
|
|
1431
|
+
loadAsset failed, watermark image skipped — 7 call sites) and `renderDocument` (form
|
|
1432
|
+
field render failure) flow through `logger.warn` when one is provided. Bidi-js fallback
|
|
1433
|
+
warnings from RTL reordering remain on `console.warn`; the JSDoc on `RenderOptions.logger`
|
|
1434
|
+
has been updated to document the actual scope honestly.
|
|
1435
|
+
|
|
1436
|
+
- **Missing `[pretext-pdf]` log prefix on bidi-js error path** — One `console.warn` in
|
|
1437
|
+
`measure-text.ts` was logging without the canonical `[pretext-pdf]` prefix, making it
|
|
1438
|
+
hard to identify the library as the source in consumer logs. Now consistent.
|
|
1439
|
+
|
|
1440
|
+
### Changed
|
|
1441
|
+
|
|
1442
|
+
- **`@napi-rs/canvas` removed from `optionalDependencies`** — Was double-listed in both
|
|
1443
|
+
`peerDependencies` (with `optional: true`) and `optionalDependencies`. The latter caused
|
|
1444
|
+
npm to attempt installing the native canvas binary on every install, including in
|
|
1445
|
+
edge/serverless environments where the platform may not be supported and the dep is
|
|
1446
|
+
not needed. Now only listed under `peerDependencies` — install it explicitly when you
|
|
1447
|
+
need SVG/QR/barcode/chart rasterization in Node.
|
|
1448
|
+
|
|
1449
|
+
### Documentation
|
|
1450
|
+
|
|
1451
|
+
- **README — security callout for `allowedFileDirs`** — Added a prominent callout in the
|
|
1452
|
+
Quick Start section. The default behavior allows `image.src` and `svg.src` to read any
|
|
1453
|
+
absolute file path, which is a path-traversal vector when document JSON originates from
|
|
1454
|
+
user input or an LLM. The callout now appears immediately after the first `render()`
|
|
1455
|
+
example.
|
|
1456
|
+
|
|
1457
|
+
---
|
|
1458
|
+
|
|
1459
|
+
## [1.0.7] — 2026-05-05
|
|
1460
|
+
|
|
1461
|
+
Picks up pretext fork v0.0.6-patched.2: 8 additional upstream PRs (11 total).
|
|
1462
|
+
|
|
1463
|
+
### Fixed
|
|
1464
|
+
|
|
1465
|
+
- **German low opening quote `„` no longer breaks at line-start on hyphenation path** —
|
|
1466
|
+
`KINSOKU_START_FORBIDDEN` in `src/measure-text.ts` now includes U+201E (`„`), matching
|
|
1467
|
+
pretext PR #165 which fixed the non-hyphenation path. Previously `„` could appear
|
|
1468
|
+
at the start of a wrapped line when hyphenation was active.
|
|
1469
|
+
|
|
1470
|
+
- **Currency symbols stay glued to adjacent numbers** — Upstream PR #105 (cherry-picked in
|
|
1471
|
+
`v0.0.6-patched.2`) prevents `$`, `€`, `£`, `₹` etc. from line-breaking away from
|
|
1472
|
+
the number they annotate.
|
|
1473
|
+
|
|
1474
|
+
- **Trailing collapsible-space reconstruction fixed** — Upstream PR #29 fix (extended in
|
|
1475
|
+
v0.0.6-patched.2): a word followed by a space that exactly fills `maxWidth` no longer
|
|
1476
|
+
drops the space from line boundary cursors, preventing Arabic/mixed-script text from
|
|
1477
|
+
losing inter-word spaces during reconstruction.
|
|
1478
|
+
|
|
1479
|
+
### Changed
|
|
1480
|
+
|
|
1481
|
+
- **`@chenglou/pretext` dependency** — Bumped from `v0.0.6-patched` to `v0.0.6-patched.2`
|
|
1482
|
+
(GitHub fork, 11 upstream PRs total). Adds: CJK overflow prevention (PR #132),
|
|
1483
|
+
fit-advance cache fix (PR #161), rich inline stats unification (PR #138),
|
|
1484
|
+
chunk layout side table O(1) lookup (PR #140), bidi surrogate handling (PR #3),
|
|
1485
|
+
skip no-op merge passes (PR #119), currency stickiness (PR #105),
|
|
1486
|
+
German quote fix (PR #165), and trailing-space reconstruction (PR #29).
|
|
1487
|
+
|
|
1488
|
+
---
|
|
1489
|
+
|
|
1490
|
+
## [1.0.6] — 2026-05-04
|
|
1491
|
+
|
|
1492
|
+
Audit bug fixes: validator correctness, internal export hygiene, schema gaps, README accuracy.
|
|
1493
|
+
|
|
1494
|
+
### Fixed
|
|
1495
|
+
|
|
1496
|
+
- **lineHeight upper-bound cap removed** — `validate()` no longer rejects `lineHeight > 20`. The
|
|
1497
|
+
field is in points (pt), not a multiplier; 36pt is valid for a large heading. The `> 20` cap in
|
|
1498
|
+
`paragraph`, `heading`, and `defaultParagraphStyle` validators has been removed. The lower-bound
|
|
1499
|
+
check (lineHeight >= fontSize) is preserved.
|
|
1500
|
+
|
|
1501
|
+
- **form-field error messages use `${prefix}` format** — Error messages from the `form-field` case
|
|
1502
|
+
now follow the `content[N] (form-field): ...` format used by all other element types, instead of
|
|
1503
|
+
the old `[N] form-field.` prefix.
|
|
1504
|
+
|
|
1505
|
+
- **`assertUnknownProps` hint punctuation fixed** — The "unknown property" message previously
|
|
1506
|
+
produced `unknown property. did you mean "color"` (period before hint). Fixed to
|
|
1507
|
+
`unknown property; did you mean "color"` — no period, semicolon separator.
|
|
1508
|
+
|
|
1509
|
+
- **British "colour" → "color" in JSDoc** — Two `QrCodeElement` field comments
|
|
1510
|
+
(`foreground`, `background`) and the `ValidationError.path` JSDoc example corrected.
|
|
1511
|
+
|
|
1512
|
+
- **`TocEntryElement`, `RichLine`, `RichFragment` removed from public exports** — These types are
|
|
1513
|
+
marked `@internal` in `types-public.ts` and should not be part of the npm API surface. Removed
|
|
1514
|
+
from `src/index.ts`.
|
|
1515
|
+
|
|
1516
|
+
- **Signature error includes original cause** — `SIGNATURE_FAILED` now preserves the underlying
|
|
1517
|
+
error message: `PDF signing failed: <original message>` instead of a static string.
|
|
1518
|
+
|
|
1519
|
+
- **Header-only table now valid** — `validate()` previously rejected tables where all rows are
|
|
1520
|
+
headers (`headerRowCount === rows.length`). Changed `>=` to `>`: tables where every row is a
|
|
1521
|
+
header are valid (useful for column-label-only tables).
|
|
1522
|
+
|
|
1523
|
+
- **Dead sub-condition removed in `float-group` floatWidth guard** — `fg.floatWidth <= 0` was a
|
|
1524
|
+
dead branch (any value `<= 0` is already `< 30`). Removed to clarify intent.
|
|
1525
|
+
|
|
1526
|
+
- **`warningCount` JSDoc updated** — Documents that the validator currently only emits errors, so
|
|
1527
|
+
`warningCount` is always 0 (reserved for future use).
|
|
1528
|
+
|
|
1529
|
+
- **`validateDocument` no longer re-throws unexpected errors** — Non-`PretextPdfError` exceptions
|
|
1530
|
+
(e.g. circular JSON, unexpected runtime errors) are now caught and returned as a structured
|
|
1531
|
+
`ValidationResult` instead of propagating. `validateDocument` now always returns, never throws.
|
|
1532
|
+
|
|
1533
|
+
### Changed (Schema additions — `pretext-pdf/schema`)
|
|
1534
|
+
|
|
1535
|
+
- `qrCodeSchema`: added `margin` field.
|
|
1536
|
+
- `imageSchema`: added `floatFontSize`, `floatFontFamily`, `floatColor` fields.
|
|
1537
|
+
- `codeSchema`: added `dir` and `highlightTheme` fields.
|
|
1538
|
+
- `tableSchema`: added `dir`, `headerRows`, and cell-level `dir`, `fontFamily`, `fontSize`,
|
|
1539
|
+
`tabularNumbers` fields.
|
|
1540
|
+
|
|
1541
|
+
### Docs
|
|
1542
|
+
|
|
1543
|
+
- README: `highlight.js` added to optional peer dependencies table.
|
|
1544
|
+
- README: `validate_document` added to MCP server tool list.
|
|
1545
|
+
|
|
1546
|
+
---
|
|
1547
|
+
|
|
1548
|
+
## [1.0.5] — 2026-05-04
|
|
1549
|
+
|
|
1550
|
+
Schema coverage completion, `ValidationResult.warningCount`, and README API docs.
|
|
1551
|
+
|
|
1552
|
+
### Added
|
|
1553
|
+
|
|
1554
|
+
- **`ValidationResult.warningCount`** — `validateDocument()` now returns `warningCount` alongside
|
|
1555
|
+
`errorCount`. Computed by filtering `errors[]` by `severity === 'warning'`. MCP consumers no
|
|
1556
|
+
longer need to derive it client-side.
|
|
1557
|
+
|
|
1558
|
+
- **JSON Schema: remaining field coverage** — `src/schema.ts` now covers all previously missing
|
|
1559
|
+
fields across 9 element types:
|
|
1560
|
+
- `inlineSpanSchema`: `dir`
|
|
1561
|
+
- `paragraphSchema`: `columns`, `columnGap`, `tabularNumbers`, `hyphenate`
|
|
1562
|
+
- `headingSchema`: `tabularNumbers`, `hyphenate`
|
|
1563
|
+
- `blockquoteSchema`: `lineHeight`, `padding`, `paddingH`, `paddingV`, `underline`, `strikethrough`
|
|
1564
|
+
- `calloutSchema`: `titleColor`, `fontWeight`, `lineHeight`, `padding`, `paddingH`, `paddingV`
|
|
1565
|
+
- `listSchema`: `lineHeight`, `markerWidth`, `itemSpaceAfter`, `nestedNumberingStyle`; nested
|
|
1566
|
+
items now carry `dir` and have a typed inner schema
|
|
1567
|
+
- `tocSchema`: `titleFontSize`, `levelIndent`, `leader`, `entrySpacing`
|
|
1568
|
+
- `formFieldSchema`: `borderColor`, `backgroundColor`, `keepTogether`, `defaultSelected`
|
|
1569
|
+
- `richParagraphSchema`: `columns`, `columnGap`, `tabularNumbers`
|
|
1570
|
+
|
|
1571
|
+
- **README: `validateDocument` and `pretext-pdf/schema` documented** — both entry points now have
|
|
1572
|
+
`### API reference` sections with code examples.
|
|
1573
|
+
|
|
1574
|
+
---
|
|
1575
|
+
|
|
1576
|
+
## [1.0.4] — 2026-05-04
|
|
1577
|
+
|
|
1578
|
+
Schema export hardening: post-release audit fixes addressing coverage gaps and a
|
|
1579
|
+
malformed dialect URI.
|
|
1580
|
+
|
|
1581
|
+
### Fixed
|
|
1582
|
+
|
|
1583
|
+
- **`pretext-pdf/schema`: `$schema` dialect URI corrected** — was
|
|
1584
|
+
`https://json-schema.org/draft/2020-12` (not a registered URI), now
|
|
1585
|
+
`https://json-schema.org/draft/2020-12/schema`. Strict JSON Schema validators
|
|
1586
|
+
(AJV, Smithery, VS Code) will now correctly identify the dialect.
|
|
1587
|
+
- **`pretext-pdf/schema`: `hr` element spacing fields** — `spaceAbove` and
|
|
1588
|
+
`spaceBelow` (the primary documented fields, default 12) were missing.
|
|
1589
|
+
`spaceBefore` and `spaceAfter` are now correctly marked as aliases.
|
|
1590
|
+
- **`pretext-pdf/schema`: `float-group` and `chart` element types** — both
|
|
1591
|
+
first-class public element types were missing from the `content.items.anyOf`
|
|
1592
|
+
list. Schema-driven tooling will now know they exist.
|
|
1593
|
+
|
|
1594
|
+
### Added (schema coverage)
|
|
1595
|
+
|
|
1596
|
+
- `pdfDocumentSchema.sections` — page-range header/footer overrides
|
|
1597
|
+
- `headingSchema.annotation` — annotation field (was already on paragraph)
|
|
1598
|
+
- `tableSchema.cellPaddingH` / `cellPaddingV` — primary table density controls
|
|
1599
|
+
- `imageSchema.floatWidth` / `floatGap` / `floatSpans` — column-layout controls
|
|
1600
|
+
for floated images
|
|
1601
|
+
|
|
1602
|
+
---
|
|
1603
|
+
|
|
1604
|
+
## [1.0.3] — 2026-05-03
|
|
1605
|
+
|
|
1606
|
+
Enhancements: JSON Schema export, simplified marked peer dep range, and internal API polish.
|
|
1607
|
+
|
|
1608
|
+
### Added
|
|
1609
|
+
|
|
1610
|
+
- **`pretext-pdf/schema` entry point** — exports `pdfDocumentSchema`, a machine-readable JSON Schema
|
|
1611
|
+
object describing the full `PdfDocument` type. Covers all 22 element types and 18 top-level
|
|
1612
|
+
document properties. Intended for editor tooling, MCP clients, and Smithery UI form generation.
|
|
1613
|
+
|
|
1614
|
+
```typescript
|
|
1615
|
+
import { pdfDocumentSchema } from 'pretext-pdf/schema'
|
|
1616
|
+
```
|
|
1617
|
+
|
|
1618
|
+
### Changed
|
|
1619
|
+
|
|
1620
|
+
- **`marked` peer dependency simplified** — `^9.0.0 || ^10.0.0 || ... || ^18.0.0` condensed to
|
|
1621
|
+
`>=9.0.0`. Semantically identical, cleaner npm output.
|
|
1622
|
+
|
|
1623
|
+
- **`validateDocument` logger option** — `options.logger` now passed to the underlying `validate()`
|
|
1624
|
+
call via conditional spread, respecting `exactOptionalPropertyTypes: true` constraints.
|
|
1625
|
+
|
|
1626
|
+
### Fixed
|
|
1627
|
+
|
|
1628
|
+
- **`fonts.ts` unsafe cast removed** — `(spec as { style?: string }).style` replaced with direct
|
|
1629
|
+
property access on the widened parameter type.
|
|
1630
|
+
|
|
1631
|
+
---
|
|
1632
|
+
|
|
1633
|
+
## [1.0.2] — 2026-05-03
|
|
1634
|
+
|
|
1635
|
+
### Added
|
|
1636
|
+
|
|
1637
|
+
- `validateDocument(doc, options?)` — non-throwing validation API that returns a structured `ValidationResult` with typed `ValidationError[]` instead of throwing. Each error includes `path`, `message`, `code`, `severity`, and `suggestion` fields.
|
|
1638
|
+
- `ValidationError` and `ValidationResult` exported from the public API surface.
|
|
1639
|
+
- `Logger` interface and `logger?: Logger` in `RenderOptions` — route diagnostic warnings through a custom logger instead of `console.warn`.
|
|
1640
|
+
- Inter italic font support (Inter-400-italic, Inter-700-italic) via bundled `@fontsource/inter` — italic markdown and `fontStyle: 'italic'` now work without manual font setup.
|
|
1641
|
+
|
|
1642
|
+
---
|
|
1643
|
+
|
|
1644
|
+
## [1.0.1] — 2026-05-02
|
|
1645
|
+
|
|
1646
|
+
Patch: strict mode correctness fixes. No API changes.
|
|
1647
|
+
|
|
1648
|
+
### Fixed
|
|
1649
|
+
|
|
1650
|
+
- **`levenshteinDist` early-exit bug** — per-cell `if (curr[j]! > 2) return 999` inside
|
|
1651
|
+
the inner DP loop fired on intermediate cells, causing d=1 pairs like `hrefs→href` and
|
|
1652
|
+
`spaceafter→spaceAfter` to incorrectly return 999 instead of 1. Fix: removed the per-cell
|
|
1653
|
+
guard; final check only (`prev[n]! > 2 ? 999 : prev[n]!`).
|
|
1654
|
+
- **Seven path-prefix annotations** — strict-mode error paths had `(type)` suffixes
|
|
1655
|
+
(e.g. `doc(table).rows[0]`) that no other validator used and that tests didn't expect.
|
|
1656
|
+
All seven removed so paths are plain dot-notation.
|
|
1657
|
+
- **`encryption` block not strict-checked** — unknown props inside `doc.encryption`
|
|
1658
|
+
were silently accepted in strict mode. Now validated against `ALLOWED_PROPS_SUB['encryption']`.
|
|
1659
|
+
- **Root path was `'document'` not `'doc'`** — top-level `assertUnknownProps` was called
|
|
1660
|
+
with `'document'` as the path prefix, producing paths like `document.content[0]` instead
|
|
1661
|
+
of `doc.content[0]`. Corrected to `'doc'`.
|
|
1662
|
+
- **Suggestion format mismatched** — `Did you mean 'x'?` → `did you mean "x"` (lowercase,
|
|
1663
|
+
double-quotes) to match the format tests asserted.
|
|
1664
|
+
- **`formatErrors` missing header** — multi-error output now begins with
|
|
1665
|
+
`Strict validation failed (N issues):\n` so callers can detect strict vs. regular errors.
|
|
1666
|
+
|
|
1667
|
+
### Tests
|
|
1668
|
+
|
|
1669
|
+
- Added `test/validate-strict.test.ts` (35 tests) to `test:unit` script — these tests were
|
|
1670
|
+
written but not wired into CI in v1.0.0.
|
|
1671
|
+
|
|
1672
|
+
---
|
|
1673
|
+
|
|
1674
|
+
## [1.0.0] — 2026-05-02
|
|
1675
|
+
|
|
1676
|
+
First stable release. Completes the plugin extension API, closes all v1.0 gate requirements,
|
|
1677
|
+
and ships a fully verified public surface with zero breaking changes from 0.9.x.
|
|
1678
|
+
|
|
1679
|
+
### Added
|
|
1680
|
+
|
|
1681
|
+
- **Plugin extension API** — Register custom element types via `RenderOptions.plugins`.
|
|
1682
|
+
Each `PluginDefinition` participates in all four pipeline stages: `validate`, `loadAsset`,
|
|
1683
|
+
`measure`, and `render`. Plugins are fully typed and tree-shaken from documents
|
|
1684
|
+
that don't use them. See README § Custom element types (plugins) and
|
|
1685
|
+
`examples/plugin-custom-element.ts` for a runnable example.
|
|
1686
|
+
- **`PluginDefinition`, `PluginMeasureContext`, `PluginMeasureResult`, `PluginRenderContext`**
|
|
1687
|
+
exported from `pretext-pdf` public surface (previously internal).
|
|
1688
|
+
- **`PdfBuilder` and `PdfBuilderOptions`** exported from `pretext-pdf` (enables type-safe
|
|
1689
|
+
builder construction in downstream code without re-declaring the interface).
|
|
1690
|
+
- **`TocEntryElement`** exported from `pretext-pdf` public surface (was in the `ContentElement`
|
|
1691
|
+
union but not individually importable).
|
|
1692
|
+
- **`plugins` option on `createPdf()`** — `PdfBuilderOptions.plugins` threads plugins through
|
|
1693
|
+
the builder's `build()` call automatically.
|
|
1694
|
+
- **`Intl.Segmenter` pre-flight guard** in `render()` — throws `RENDER_FAILED` with a clear
|
|
1695
|
+
message on Node.js < 16 or runtimes without full-ICU data, instead of silently producing
|
|
1696
|
+
incorrect line breaks.
|
|
1697
|
+
- **`PluginRenderContext.pageWidth/pageHeight/margins`** — render hooks now receive full page
|
|
1698
|
+
geometry for layout calculations (page-relative positioning, bleed boxes, etc.).
|
|
1699
|
+
- **`render` context Y-coordinate docs** — expanded JSDoc with multi-line text example showing
|
|
1700
|
+
how to position text baselines relative to `context.y`.
|
|
1701
|
+
- **Benchmark corpora manifest** and **smoke staging** tests wired into `npm test`
|
|
1702
|
+
(previously orphaned).
|
|
1703
|
+
- **`test/table-determinism.test.ts`** — asserts that table pagination produces identical
|
|
1704
|
+
layout traces across repeated invocations of `prepareLayoutState`.
|
|
1705
|
+
- **`test/validate-strict.test.ts`** (35 tests) — comprehensive contract for `strict: true`
|
|
1706
|
+
validation covering all element types, nested structures, Levenshtein suggestions, error
|
|
1707
|
+
message format, doc-level and sub-structure prop checks. Total test count: 676.
|
|
1708
|
+
- `examples/plugin-custom-element.ts` — runnable plugin example (`npm run example:plugin`).
|
|
1709
|
+
|
|
1710
|
+
### Fixed
|
|
1711
|
+
|
|
1712
|
+
- **`SIGNATURE_CERT_AND_ENCRYPTION` error code** — was declared in the `ErrorCode` union
|
|
1713
|
+
but never thrown; validate.ts now uses it correctly when a document specifies both
|
|
1714
|
+
signatures and encryption (previously threw a generic `VALIDATION_ERROR`).
|
|
1715
|
+
- **Build break under `exactOptionalPropertyTypes: true`** — `PdfBuilder.build()` no longer
|
|
1716
|
+
passes `{ plugins: undefined }` to `runPipeline` when no plugins are configured.
|
|
1717
|
+
- **Plugin `validate` hook empty-string normalization** — `plugin.validate()` returning `''`
|
|
1718
|
+
now correctly accepts the element (was previously treated as a rejection message).
|
|
1719
|
+
- **`toc` element reaching render default arm** — `render.ts` now has an explicit
|
|
1720
|
+
`case 'toc': return` guard before the default arm; TOC elements are pre-processed
|
|
1721
|
+
during pagination and should never reach the renderer.
|
|
1722
|
+
- **`RichLine` and `RichFragment`** demoted from `@public` to `@internal`; these are
|
|
1723
|
+
implementation details of the rich-text pipeline, not intended for external use.
|
|
1724
|
+
- **Sentinel value documentation** — `MeasuredBlock` comment now explicitly states that
|
|
1725
|
+
`lines: []`, `fontSize: 0`, `lineHeight: 0`, `fontKey: ''` applies to spacers, tables,
|
|
1726
|
+
images, hr, *and plugin blocks* — not a bug but a documented convention.
|
|
1727
|
+
|
|
1728
|
+
### Internal
|
|
1729
|
+
|
|
1730
|
+
- `src/plugin-registry.ts` (new): Pure orchestration helpers for the four plugin injection
|
|
1731
|
+
points (`findPlugin`, `runPluginValidate`, `runPluginLoadAsset`, `runPluginMeasure`,
|
|
1732
|
+
`runPluginRender`).
|
|
1733
|
+
- `src/plugin-types.ts` (new): `PluginDefinition` interface and context/result types.
|
|
1734
|
+
- `src/layout-state.ts`: `prepareLayoutState` now accepts `options?: RenderOptions` and
|
|
1735
|
+
threads plugins to `stageValidate`, `stageLoadAssets`, and `stageMeasure`.
|
|
1736
|
+
- `docs/V1.0-RUNBOOK.md`: Full release runbook with first-principles audit, anti-hallucination
|
|
1737
|
+
protocol, verified-facts table, and phase-by-phase plan.
|
|
1738
|
+
|
|
1739
|
+
---
|
|
1740
|
+
|
|
1741
|
+
## [0.9.4] — 2026-05-02
|
|
1742
|
+
|
|
1743
|
+
> **Note:** This release was never published to npm as a standalone tag. All changes listed
|
|
1744
|
+
> here shipped as part of [1.0.0] on the same date.
|
|
1745
|
+
|
|
1746
|
+
Architecture hardening + API surface snapshot. No public API changes; internal
|
|
1747
|
+
restructuring to eliminate circular dependencies and add drift guards before v1.0 freeze.
|
|
1748
|
+
|
|
1749
|
+
### Added
|
|
1750
|
+
|
|
1751
|
+
- **API surface snapshot** (`etc/pretext-pdf.api.md`) checked into source control as
|
|
1752
|
+
the v1.0 baseline. The `api:check` CI step will fail on unintentional public-API drift.
|
|
1753
|
+
- **`src/layout-state.ts`** — `prepareLayoutState()` and `summarizeLayoutState()` extracted
|
|
1754
|
+
from the pipeline for testability; `layout-contract` and `hard-text-contract` tests
|
|
1755
|
+
wired into `test:unit`.
|
|
1756
|
+
- **`src/benchmarks/corpora.ts`** — benchmark corpus manifest (`getBenchmarkCorpora()`)
|
|
1757
|
+
restored from git history; `benchmark-baseline.test.ts` wired into `test:contract`.
|
|
1758
|
+
- **Drift guards** (`test/drift-guards.test.ts`) — asserts that `ELEMENT_TYPES`,
|
|
1759
|
+
`ALLOWED_PROPS`, `validate.ts` cases, and `render.ts` cases all agree at test time.
|
|
1760
|
+
Catches any future element-type addition that isn't plumbed through all four places.
|
|
1761
|
+
- **`render.ts` default arm** — unknown element types now throw immediately instead of
|
|
1762
|
+
silently producing a blank block.
|
|
1763
|
+
|
|
1764
|
+
### Refactored
|
|
1765
|
+
|
|
1766
|
+
- **Circular dependency broken**: `src/post-process.ts` extracted so `builder.ts` and
|
|
1767
|
+
`index.ts` no longer form a cycle through each other.
|
|
1768
|
+
- **`ELEMENT_TYPES` extracted** to `src/element-types.ts` as single source of truth;
|
|
1769
|
+
re-exported from `index.ts`, imported by `validate.ts` — eliminates the previous
|
|
1770
|
+
per-file string-literal duplication.
|
|
1771
|
+
|
|
1772
|
+
### Fixed
|
|
1773
|
+
|
|
1774
|
+
- `post-process.ts`: drop raw signing library error message from `SIGNATURE_FAILED`
|
|
1775
|
+
to avoid leaking certificate or passphrase details in error output.
|
|
1776
|
+
- `layout-state.ts`: polyfill install wrapped in try/catch; throws `CANVAS_UNAVAILABLE`
|
|
1777
|
+
on failure instead of an untyped exception.
|
|
1778
|
+
|
|
1779
|
+
---
|
|
1780
|
+
|
|
1781
|
+
## [0.9.3] — 2026-04-23
|
|
1782
|
+
|
|
1783
|
+
Strict validation release. Opt-in property validation to catch unknown properties on elements and sub-structures via typo detection and precise JSONPath error reporting.
|
|
1784
|
+
|
|
1785
|
+
### Added
|
|
1786
|
+
|
|
1787
|
+
- **Strict validation mode**: Pass `{ strict: true }` to `render(doc, options)` to reject unknown properties. Non-strict mode (default) remains permissive for backwards compatibility.
|
|
1788
|
+
- **`render()` options parameter**: Updated signature to `render(doc: PdfDocument, options?: RenderOptions)` where `RenderOptions = { strict?: boolean }`.
|
|
1789
|
+
- **`validate()` public export**: `validate()` is now exported from `pretext-pdf` for standalone validation and testing.
|
|
1790
|
+
- **Validation error details**:
|
|
1791
|
+
- Unknown properties reported with Levenshtein edit-distance suggestions (distance ≤2) for typo correction.
|
|
1792
|
+
- Errors include JSONPath-like paths (`content[3].table.rows[0].cells[1].align`) for precise location reporting.
|
|
1793
|
+
- Error accumulation: all violations collected before throwing a single VALIDATION_ERROR with formatted multi-line message.
|
|
1794
|
+
- First 20 errors shown; overflow indicator present.
|
|
1795
|
+
- **Compile-time drift guards**: `src/allowed-props.ts` uses `Exact<T, Keys>` TypeScript type assertions to catch property definition drift at type-check time. If element types change, `tsc --noEmit` will error if allowed-props lists don't match.
|
|
1796
|
+
- **Property allowlists**:
|
|
1797
|
+
- `ALLOWED_PROPS`: 22 element types (paragraph, heading, table, image, code, list, etc.)
|
|
1798
|
+
- `ALLOWED_PROPS_SUB`: 8 sub-structures (document, metadata, table-row, table-cell, list-item, inline-span, column-def, annotation)
|
|
1799
|
+
|
|
1800
|
+
### Internal
|
|
1801
|
+
|
|
1802
|
+
- `src/allowed-props.ts` (new): Central configuration for allowed properties with compile-time assertions.
|
|
1803
|
+
- `src/validate.ts` (enhanced): Added `levenshteinDist()`, `closestMatch()`, `assertUnknownProps()`, and `formatErrors()` helpers; threading strict flag through `validateElement()` for nested structure validation (tables, lists, rich-paragraphs, float-groups, annotations).
|
|
1804
|
+
|
|
1805
|
+
---
|
|
1806
|
+
|
|
1807
|
+
## [0.9.2] — 2026-04-22
|
|
1808
|
+
|
|
1809
|
+
Maintenance release. Engine refresh + repo-hygiene automation. No runtime behavior changes beyond the `@chenglou/pretext` bump.
|
|
1810
|
+
|
|
1811
|
+
### Changed
|
|
1812
|
+
|
|
1813
|
+
- **Bumped `@chenglou/pretext` to 0.0.6** (from 0.0.5). Brings two upstream improvements: (a) CJK text followed by opening-bracket annotations now wraps like browsers instead of leaving the opening bracket on the previous line (upstream PR #148), (b) native numeric `letterSpacing` support on `prepare()` and `prepareWithSegments()` (upstream PRs #108/#156). Our manual letterSpacing compensation in `src/measure-blocks.ts` and `src/rich-text.ts` continues to work unchanged — delegating to pretext's native path is tracked as Tier 1 follow-up in `docs/ROADMAP.md`. All 624 tests green, all 5 visual regression baselines green.
|
|
1814
|
+
|
|
1815
|
+
### Fixed
|
|
1816
|
+
|
|
1817
|
+
- **README badges matched to reality**: `runtime-deps-7` → `runtime-deps-8` (there are 8 direct `dependencies`, not 7), `tests-600+` → `tests-624` (the full `npm test` chain runs 624 tests across 5 subsuites). Drift guarded by a new CI step; see below.
|
|
1818
|
+
|
|
1819
|
+
### Added
|
|
1820
|
+
|
|
1821
|
+
- `scripts/verify-badges.js` + CI step — compares README shields.io badge values against `package.json` dep count and `npm test` total. Fails CI on drift. Fast path via `SKIP_TEST_RUN=1` for pre-commit use.
|
|
1822
|
+
- `release` job in `ci.yml` — on `v*` tag push, auto-extracts the matching `## [X.Y.Z]` section from this file and creates the GitHub release (requires publish to succeed first). Closes the "tag exists but no release page" gap that affected v0.9.1. (Note: originally shipped as `.github/workflows/release-on-tag.yml`; merged into `ci.yml` for dependency ordering in Tier 0.5.)
|
|
1823
|
+
- `renovate.json` — watches dependencies, auto-merges devDependency bumps that pass CI, opens PRs (without auto-merge) for runtime, peer, and `@chenglou/pretext` engine bumps. Closes the gap that left us one release behind upstream.
|
|
1824
|
+
|
|
1825
|
+
### Removed
|
|
1826
|
+
|
|
1827
|
+
- `test/smoke-staging.test.ts` — exercised a non-existent `{ type: 'paragraph', footnote: {...} }` shape that the permissive validator silently accepted. False coverage. A strict validator rollout (rejecting unknown element properties) is the root fix and is tracked as a Tier 1 item in the rewritten `docs/ROADMAP.md`.
|
|
1828
|
+
- `src/brain/` — inert auto-logger artifact (34 blank-body entries, no active writer). Never published to npm.
|
|
1829
|
+
|
|
1830
|
+
### Docs
|
|
1831
|
+
|
|
1832
|
+
- `docs/ROADMAP.md` — complete rewrite as a living document (Now / Next / Under consideration / Shipped / History + Update discipline). The previous "master remediation plan" with phase-numbered sections was dropped: phases 0–5 all shipped by v0.9.1, and the document had rotted to the point of contradicting `package.json` on dependency pinning and `CHANGELOG.md` on what was live. History section preserves the prior plan's origin date and scope for reference.
|
|
1833
|
+
|
|
1834
|
+
---
|
|
1835
|
+
|
|
1836
|
+
## [0.9.1] — 2026-04-21
|
|
1837
|
+
|
|
1838
|
+
Bug-fix + hardening release. Ships the callout + rich-text rendering fixes from PR #2 together with PR #3's producer-validator contract around measured blocks.
|
|
1839
|
+
|
|
1840
|
+
### Fixed
|
|
1841
|
+
|
|
1842
|
+
- **Rich-paragraph: leading-space tokens stripped after hard break** ([src/rich-text.ts](src/rich-text.ts)). A pre-overflow guard (`isLeadingSpace: currentX === 0 && token.text.trim() === ''`) fired whenever `currentX` was zero — both at block start *and* after a `\n` hard break reset the cursor. Continuation spans beginning with whitespace (e.g. `' · text'`) had their first token silently dropped, causing separator glyphs and indented text to appear mis-positioned. Guard removed; the overflow-wrap skip path that correctly skips trailing spaces after soft wraps is unaffected.
|
|
1843
|
+
- **Callout: `spaceAfter` double-applied by paginator** ([src/measure-blocks.ts](src/measure-blocks.ts)). `callout` block measurement included `el.spaceAfter ?? 12` inside `totalHeight` *and* returned the same value as `block.spaceAfter`. `paginate.ts` added `block.spaceAfter` on top of `block.height`, counting it twice and pushing callout content ~12 pt below its intended position. Fixed by removing `spaceAfter` from the `totalHeight` formula; the value is still returned in `block.spaceAfter` for the paginator.
|
|
1844
|
+
- **Callout with title: background rect clips title row when split across pages** ([src/paginate.ts](src/paginate.ts)). `splitBlock` did not subtract `calloutData.titleHeight` from `availableForLines` for the first chunk, allowing `floor((titleH + lh) / lh)` extra lines to be placed, leaving no room for the title row. `getCurrentY` also omitted `titleHeight` from `blockBottom`, producing incorrect Y tracking after a split callout. Both fixed: `titleH` is now subtracted from available space on the first chunk only, and added to `blockBottom` when computing the cursor position after the first chunk renders.
|
|
1845
|
+
|
|
1846
|
+
### Added / hardened
|
|
1847
|
+
|
|
1848
|
+
- **Producer-validator contract for measured blocks** ([src/paginate.ts](src/paginate.ts)). `validateMeasuredBlocks()` runs at `paginate()` entry in O(n) and throws `PretextPdfError('PAGINATION_FAILED')` if a callout `MeasuredBlock` is missing `calloutData` or any of `titleHeight` / `paddingV` / `paddingH` is non-finite — same for blockquote padding/border fields. Surfaces producer bugs directly instead of as downstream NaN arithmetic or `PAGE_LIMIT_EXCEEDED`.
|
|
1849
|
+
- **Narrowed internal types** `MeasuredCalloutBlock` / `MeasuredBlockquoteBlock` (intersection types in [src/types.ts](src/types.ts)) consumed by `calloutTitleHeight` + `verticalPadding` helpers in `paginate.ts`. No defensive runtime checks downstream.
|
|
1850
|
+
- **Extracted `CalloutData` interface** from the previously-inline shape on `MeasuredBlock.calloutData`. Measurer constructs it as a typed literal, so TypeScript enforces the full contract at the producer site.
|
|
1851
|
+
- **Zero-width non-whitespace tokens preserved**: the rich-text post-soft-wrap guard only skips tokens where `text.trim() === ''`. ZWJ (U+200D), combining marks, and other zero-width non-whitespace characters pass through so emoji / CJK shaping stays intact — pinned by a regression test.
|
|
1852
|
+
- **Extracted `LINK_COLOR_DEFAULT`** constant in `src/rich-text.ts`.
|
|
1853
|
+
|
|
1854
|
+
### Tests
|
|
1855
|
+
|
|
1856
|
+
- `test/rich-text.test.ts` 20 → 23 (+3): block-start leading whitespace preserved; leading whitespace after hard break preserved; ZWJ preservation.
|
|
1857
|
+
- `test/phase-8d-callout.test.ts` 12 → 19 (+7): callout `spaceAfter` double-count regression, titled split line count, untitled split, continuation chunk `yFromTop === 0`, mid-page split entry, validator rejection on missing `calloutData`, validator rejection on partial `calloutData` (non-finite fields), validator rejection on partial blockquote padding, non-callout-document early-return.
|
|
1858
|
+
- Full suite: 624 tests, 100% pass.
|
|
1859
|
+
|
|
1860
|
+
### Chore / docs
|
|
1861
|
+
|
|
1862
|
+
- Removed `brain/learnings/*.md`, `docs/PLAN-v0.6-v1.0.md`, `test/paginate.test.ts.archive` — internal dev artifacts not for the public repo.
|
|
1863
|
+
- Stripped `Phase N:` nomenclature from `src/` comments (pure rename — no logic delta).
|
|
1864
|
+
- Added `demo/stackblitz/.stackblitzrc`, `docs/articles/pretext-pdf-vs-pdfmake-2026.md` (draft).
|
|
1865
|
+
- Added `examples/visual-pr2-bug1-separator.ts` + `examples/visual-pr2-bug3-callout-split.ts` plus 4 reference PNGs under `docs/visuals/pr2/` for bug-reproduction demonstrations.
|
|
1866
|
+
- README test badge corrected `650+ → 600+` (verified: 624 tests total).
|
|
1867
|
+
|
|
1868
|
+
---
|
|
1869
|
+
|
|
1870
|
+
## [0.9.0] — 2026-04-20
|
|
1871
|
+
|
|
1872
|
+
Three additive enhancements that broaden the package's surface without growing its mandatory dependency footprint.
|
|
1873
|
+
|
|
1874
|
+
### Added
|
|
1875
|
+
|
|
1876
|
+
- **CLI binary** — `pretext-pdf` is now a `bin` entry. `pretext-pdf doc.json out.pdf`, `cat doc.json | pretext-pdf > out.pdf`, `echo '{...}' | pretext-pdf -o out.pdf`. Supports stdin/stdout and file arguments. `--markdown` flag converts Markdown input to PDF in one step (requires the `marked` peer dep). See [src/cli.ts](src/cli.ts).
|
|
1877
|
+
- **`pretext-pdf/compat` entry point** — `fromPdfmake(pdfmakeDoc)` translates pdfmake document descriptors into `PdfDocument` so existing pdfmake codebases can switch with a one-line change at the entry point. Covers strings, `text` nodes (with `style`/`bold`/`italics`/`color`/`fontSize`/`alignment`/`font`), `ul`/`ol`, `table` (with `widths` + `headerRows`), `image`, `qr`, `pageBreak` (`before`/`after`), `stack`, `pageSize`/`pageOrientation`/`pageMargins`, `defaultStyle`/`styles`, `info` → metadata, and string-form `header`/`footer`. Default style-name → heading mapping is configurable via `headingMap` option.
|
|
1878
|
+
- **Markdown: GFM tables** ([src/markdown.ts](src/markdown.ts)) — `markdownToContent()` now recognises GFM tables and translates them to `TableElement`, including column alignment from `:---:` / `---:` markers. Ragged rows are padded with empty cells.
|
|
1879
|
+
- **Markdown: GFM task lists** — `- [x] done` and `- [ ] todo` render with ☑ / ☐ Unicode markers prepended to the item text.
|
|
1880
|
+
|
|
1881
|
+
### Tests
|
|
1882
|
+
|
|
1883
|
+
- New `test/v0.9.0-features.test.ts` (21 tests): markdown table + task list, full CLI exec coverage (stdin, file, `--markdown`, error paths), and pdfmake compat (strings, headings, rich-paragraphs, lists, tables, images, QR, `pageBreak`, `stack`, `pageSize`/`pageMargins`, end-to-end render of a translated document).
|
|
1884
|
+
|
|
1885
|
+
### Notes
|
|
1886
|
+
|
|
1887
|
+
- Zero new mandatory dependencies. The CLI uses only Node built-ins. The compat shim is pure TypeScript. Markdown additions ride on the existing optional `marked` peer.
|
|
1888
|
+
- `dist/cli.js` is wired through `package.json#bin.pretext-pdf` — `npm install -g pretext-pdf` makes the CLI globally available; `npx pretext-pdf` works without install.
|
|
1889
|
+
|
|
1890
|
+
---
|
|
1891
|
+
|
|
1892
|
+
## [0.8.3] — 2026-04-20
|
|
1893
|
+
|
|
1894
|
+
### Security
|
|
1895
|
+
|
|
1896
|
+
- **SSRF — IPv4-mapped IPv6 bypass** ([src/assets.ts](src/assets.ts) `assertSafeUrl`). Pre-0.8.3 the private-IP guard checked the parsed hostname against dotted-decimal regexes only. WHATWG `URL` normalizes `[::ffff:127.0.0.1]` to `[::ffff:7f00:1]` (hex IPv4-in-IPv6), so attacker-supplied URLs of the form `https://[::ffff:127.0.0.1]/admin` slipped past every `^127\.`/`^10\.`/etc. check and reached localhost or RFC 1918 ranges. Patched by detecting both the dotted (`::ffff:127.0.0.1`) and hex-compressed (`::ffff:7f00:1`) IPv4-mapped forms and decoding the embedded IPv4 before regex matching. Also explicitly blocks the IPv6 unspecified address `::`.
|
|
1897
|
+
- **SSRF — redirect-following bypass** ([src/assets.ts](src/assets.ts) `fetchWithTimeout`). The previous implementation used the default `redirect: 'follow'`, so a public URL could `302` to `http://127.0.0.1:8080/internal` and the library would happily fetch the private target despite the upfront `assertSafeUrl` check on the *initial* URL. Patched to use `redirect: 'manual'` and re-validate every `Location` hop with `assertSafeUrl`, capped at 3 redirects. Browser opaqueredirect responses are rejected with a clear error.
|
|
1898
|
+
|
|
1899
|
+
### Fixed
|
|
1900
|
+
|
|
1901
|
+
- **`createGstInvoice` amount-in-words double space for sub-rupee totals** ([src/templates.ts](src/templates.ts)). An invoice whose total was less than ₹1 (e.g. ₹0.50) produced `"Rupees and Fifty Paise Only"` (two spaces after "Rupees") because the rupee-words branch resolved to an empty string. Now uses an explicit `"Zero"` when there are no rupees: `"Rupees Zero and Fifty Paise Only"`.
|
|
1902
|
+
- **Markdown deeper-than-2-level lists silently dropped** ([src/markdown.ts](src/markdown.ts) `convertListItem`). Pre-0.8.3 the converter only created text-only leaves for nested lists, so `- A\n - B\n - C` lost C entirely. Now recursive — preserves arbitrary nesting depth in the resulting `ListItem` tree.
|
|
1903
|
+
- **Markdown list items with paragraph-typed content** ([src/markdown.ts](src/markdown.ts)). When list items were separated by blank lines, marked emits `paragraph` tokens (not `text` tokens) for the item content. The converter only handled `text`, silently dropping the item text. Now also handles `paragraph` tokens.
|
|
1904
|
+
|
|
1905
|
+
### Tests
|
|
1906
|
+
|
|
1907
|
+
- New `test/v0.8.3-ssrf.test.ts` covers 11 IPv4-mapped IPv6 bypass cases, IPv6 unspecified/loopback regressions, and HTTP rejection.
|
|
1908
|
+
- Extended `test/phase-10c-markdown.test.ts` with regressions for 3-level nesting and paragraph-typed list items.
|
|
1909
|
+
- Extended `test/phase-10d-templates.test.ts` with the sub-rupee amount-in-words case.
|
|
1910
|
+
|
|
1911
|
+
---
|
|
1912
|
+
|
|
1913
|
+
## [0.8.2] — 2026-04-20
|
|
1914
|
+
|
|
1915
|
+
### Fixed
|
|
1916
|
+
|
|
1917
|
+
- **Rich-paragraph whitespace collapse** — multi-span `rich-paragraph` content rendered with adjacent words overlapping (e.g. `"Founder & CEO" + " — Antigravity Systems"` displayed as `"Founder& CEO—AntigravitySystems"`). Root cause: pretext's `layoutWithLines` follows CSS-like behavior and excludes trailing whitespace from line widths, so tokens like `"Hello "` or `" "` measured to width 0 and downstream fragments overlapped the previous one. `measureTokenWidth` in [src/rich-text.ts](src/rich-text.ts) now uses a sentinel-character technique (append non-whitespace `\u2588`, measure combined string, subtract sentinel width) to recover the true rendered width whenever a token has trailing whitespace. Sentinel width is cached per font config.
|
|
1918
|
+
- The fast path (no trailing whitespace) is unchanged — single pretext call. Slow path adds two pretext calls per affected token, with one cached.
|
|
1919
|
+
|
|
1920
|
+
### Tests
|
|
1921
|
+
|
|
1922
|
+
- Added 3 regression tests in `test/rich-text.test.ts` under `whitespace preservation (v0.8.2 fix)` covering trailing whitespace inside spans, whitespace-only separator spans, and the exact `"Founder & CEO" → "Antigravity Systems"` resume-preset scenario.
|
|
1923
|
+
|
|
1924
|
+
---
|
|
1925
|
+
|
|
1926
|
+
## [0.8.1] — 2026-04-20
|
|
1927
|
+
|
|
1928
|
+
### Fixed
|
|
1929
|
+
|
|
1930
|
+
- **Browser support** — `pretext-pdf` now imports cleanly in browsers. Module-init in `src/fonts.ts` previously called `fileURLToPath(import.meta.url)` and `createRequire(import.meta.url)` eagerly, which threw `"The URL must be of scheme file"` whenever the module was loaded from a non-`file://` URL (esm.sh, jsdelivr, Vite dev server). Both calls are now gated on a runtime `IS_NODE` check, and the bundled-Inter `BUNDLED_INTER_PATHS` arrays are constructed only in Node.
|
|
1931
|
+
- **Browser font-loading errors** — `loadFontBytes` now throws clear `FONT_LOAD_FAILED` messages when bundled Inter or string font paths are requested in a browser, pointing the consumer at the correct workaround (supply `Uint8Array` bytes via `doc.fonts`).
|
|
1932
|
+
|
|
1933
|
+
### Notes for browser users
|
|
1934
|
+
|
|
1935
|
+
- Always supply Inter (or your default font) explicitly via `doc.fonts: [{ family: 'Inter', weight: 400, src: <Uint8Array> }, { family: 'Inter', weight: 700, src: <Uint8Array> }]`. The library cannot read local font files in the browser.
|
|
1936
|
+
- SVG / chart / qr-code / barcode elements still depend on `@napi-rs/canvas` at runtime; in the browser, the native `OffscreenCanvas` is used instead and the polyfill is skipped automatically.
|
|
1937
|
+
|
|
1938
|
+
---
|
|
1939
|
+
|
|
1940
|
+
## [0.8.0] — 2026-04-19
|
|
1941
|
+
|
|
1942
|
+
### Added
|
|
1943
|
+
|
|
1944
|
+
- **`qr-code` element** — generate QR codes as inline PDF content using the `qrcode` optional peer dependency. Supports `data`, `size`, `errorCorrectionLevel` (L/M/Q/H), `foreground`/`background` hex colours, `margin`, `align`, `spaceBefore`/`spaceAfter`. Fully serverless — pure JS, no canvas required.
|
|
1945
|
+
- **`barcode` element** — generate 100+ barcode symbologies (EAN-13, Code128, PDF417, QR, DataMatrix, etc.) via the `bwip-js` optional peer dependency. Supports `symbology`, `data`, `width`, `height`, `includeText`, `align`, `spaceBefore`/`spaceAfter`. Pure JS, Lambda/Edge safe.
|
|
1946
|
+
- **`chart` element** — embed Vega-Lite charts as vector SVG using `vega` + `vega-lite` optional peer deps. Accepts any Vega-Lite `spec`, `width`, `height`, `caption`, `align`. Rendered with `renderer: 'none'` — zero canvas/puppeteer dependency.
|
|
1947
|
+
- **`pretext-pdf/markdown` entry point** — `markdownToContent(md, options?)` converts a Markdown string to `ContentElement[]`. Requires optional `marked` peer dep. Supports headings, bold/italic/links (→ rich-paragraph), lists (2 levels), blockquotes, code blocks, and HR.
|
|
1948
|
+
- **`pretext-pdf/templates` entry point** — three typed template functions with zero extra dependencies: `createInvoice(data)` (generic invoice with currency, tax, discount, QR payment), `createGstInvoice(data)` (GST-compliant Indian tax invoice with IGST/CGST+SGST, UPI QR, bank details, amount in words), `createReport(data)` (structured business report with optional TOC).
|
|
1949
|
+
- **New error codes** — `QR_DEP_MISSING`, `QR_GENERATE_FAILED`, `BARCODE_DEP_MISSING`, `BARCODE_GENERATE_FAILED`, `BARCODE_SYMBOLOGY_INVALID`, `CHART_DEP_MISSING`, `CHART_SPEC_INVALID`, `CHART_RENDER_FAILED`, `MARKDOWN_DEP_MISSING`.
|
|
1950
|
+
|
|
1951
|
+
---
|
|
1952
|
+
|
|
1953
|
+
## [0.7.2] — 2026-04-20
|
|
1954
|
+
|
|
1955
|
+
Phase 11 cross-cutting enhancements. Retroactively attributed to 0.7.2; these features were
|
|
1956
|
+
originally left as `[Unreleased]` and published out of chronological order after 0.7.1.
|
|
1957
|
+
|
|
1958
|
+
### Added
|
|
1959
|
+
|
|
1960
|
+
- **`floatSpans` on image elements** — rich-text alternative to plain `floatText`. Accepts `InlineSpan[]` for mixed bold/italic/color/link captions beside float images. Mutually exclusive with `floatText` (validated).
|
|
1961
|
+
- **2-level list nesting** — `ListItem.items` now supports one further level of nesting (depth 0 → 1 → 2). Unordered marker: `▪`. Ordered: inherits parent counter or restarts via `nestedNumberingStyle: 'restart'`.
|
|
1962
|
+
- **Table `rowspan`** — `TableCell.rowspan` spans a cell across multiple rows. Works alongside `colspan`. Origin cell draws background over full span height; continuation rows automatically receive placeholder cells.
|
|
1963
|
+
- **`onFormFieldError` callback** — `doc.onFormFieldError: (name, err) => 'skip' | 'throw'` mirrors `onImageLoadError`. Controls render behaviour when a form field fails.
|
|
1964
|
+
- **`createFootnoteSet(defs)`** — helper exported from `pretext-pdf` that generates footnote definition/reference pairs with globally unique IDs. Returns `Array<{ id, def }>`.
|
|
1965
|
+
- **`renderDate` field** — `doc.renderDate: Date | string` overrides the PDF creation date. Useful for reproducible builds and testing.
|
|
1966
|
+
- **`{{date}}` and `{{author}}` tokens** in header/footer text — join existing `{{pageNumber}}` / `{{totalPages}}`. `{{date}}` resolves from `renderDate`; `{{author}}` resolves from `doc.metadata.author`.
|
|
1967
|
+
- **`tabularNumbers`** on `rich-paragraph` — digits rendered at uniform slot width (widest digit in font), so columns of numbers align without OpenType TNUM feature.
|
|
1968
|
+
- **`smallCaps` + `letterSpacing` per span** — `InlineSpan.smallCaps` and `InlineSpan.letterSpacing` now respected in `rich-paragraph` rendering.
|
|
1969
|
+
- **Per-span `fontSize`** — `InlineSpan.fontSize` overrides the element-level font size for that span. Enables mixed-size text in a single paragraph.
|
|
1970
|
+
|
|
1971
|
+
### Fixed
|
|
1972
|
+
|
|
1973
|
+
- `resolveTokens()` used `.replace()` (replaces first occurrence only) — changed to `.replaceAll()` for all four tokens.
|
|
1974
|
+
- Table span grid: continuation-row cursor was advancing by 1 instead of `colspan` when skipping a spanned column — now advances by full span width.
|
|
1975
|
+
- Font family names now validated for safe characters (`/^[a-zA-Z0-9 _-]+$/`) in `requireFamily()` — rejects null bytes and control characters.
|
|
1976
|
+
- Annotation `color` and `author` fields now validated in `validateElement()` for both `paragraph` and `heading` annotations.
|
|
1977
|
+
- `buildOutlineTree` memoizes `parentIdxOf()` into a pre-computed array — eliminates O(n²) scan for documents with large heading counts.
|
|
1978
|
+
- Table grid-line renderer pre-computes active boundary set — eliminates O(rows × cols) inner loop for large tables.
|
|
1979
|
+
- `addLinkAnnotation()` re-validates URL scheme at render time (defense-in-depth; `validate.ts` is the primary gate).
|
|
1980
|
+
|
|
1981
|
+
---
|
|
1982
|
+
|
|
1983
|
+
## [0.7.1] — 2026-04-19
|
|
1984
|
+
|
|
1985
|
+
### Changed
|
|
1986
|
+
|
|
1987
|
+
- **Upstream pretext pinned to `f2014338487a`** — picks up unreleased CJK opening-bracket annotation fix, Hangul jamo line-walker alignment fix, and two internal line-object churn reductions. No public API changes.
|
|
1988
|
+
|
|
1989
|
+
### Fixed
|
|
1990
|
+
|
|
1991
|
+
- **List nesting depth enforced at validation** — `ListItem.items` (2nd-level items) now correctly rejects any further `.items` property, matching the documented 2-level maximum. Previously the validation silently passed 3-level data which could cause undefined render behaviour.
|
|
1992
|
+
- **3 phase-11 list tests corrected** — test data incorrectly contained 3-level nesting while named "2-level"; data trimmed to match documented contract.
|
|
1993
|
+
|
|
1994
|
+
---
|
|
1995
|
+
|
|
1996
|
+
## [0.7.0] — 2026-04-17
|
|
1997
|
+
|
|
1998
|
+
### Added
|
|
1999
|
+
|
|
2000
|
+
- **6 production templates** (`templates/`) — GST invoice, international invoice, resume, multi-section report, NDA, and meeting minutes. Each is a self-contained `.ts` file outputting a valid PDF. Smoke-tested in Phase 2F Block D.
|
|
2001
|
+
- **StackBlitz live demo** (`demo/stackblitz/`) — 4-tab UI (Invoice, Report, Resume, Custom) backed by a Node.js render server. Edit JSON and generate PDFs instantly, no install required. Accessible at the StackBlitz link in the README.
|
|
2002
|
+
- **`## Performance` section in README** — measured render times and PDF sizes for 1-page, 10-page, and mixed-element documents. Font subsetting behaviour documented.
|
|
2003
|
+
- **Stress tests and benchmarks** (`test/phase-2f-stress.test.ts`) — 32 tests across 4 blocks: large document stress (400-element, 200-row table), edge case stress (CJK, RTL, empty arrays, extreme sizes), timing benchmarks (1-page < 500 ms, 10-page < 5,000 ms), and template smoke tests.
|
|
2004
|
+
- **Error code coverage** — new tests for `COLUMN_WIDTH_TOO_NARROW`, `IMAGE_LOAD_FAILED`, `SVG_LOAD_FAILED`, and `ASSEMBLY_FAILED`. 16 of 19 error codes now have direct test coverage.
|
|
2005
|
+
|
|
2006
|
+
### Changed
|
|
2007
|
+
|
|
2008
|
+
- **`as any` audit** — eliminated 10 casts in `validate.ts` by introducing a typed `FormFieldElement` local binding. The remaining 8 instances (pdf-lib interop, dynamic import, internal back-references) are now documented with one-line comments explaining the constraint.
|
|
2009
|
+
- **Comparison article** (`docs/articles/pretext-pdf-vs-pdfmake-2026.md`) — 2,200-word draft covering feature matrix, typography quality, API design, performance, and migration quick-start. Marked `published: false` pending live demo.
|
|
2010
|
+
- **Migration guide** (`docs/MIGRATION_FROM_PDFMAKE.md`) — 30+ pdfmake → pretext-pdf mappings, complete before/after invoice example, and a quick-start checklist. Linked from README.
|
|
2011
|
+
|
|
2012
|
+
### Fixed
|
|
2013
|
+
|
|
2014
|
+
- **Phase 2F test types** — `fontWeight: 700 as 700` cast in pre-constructed rows array; removed non-existent `creationDate` from `DocumentMetadata`; replaced `allowCopying: false` with correct `encryption: { permissions: { copying: false } }`.
|
|
2015
|
+
- **StackBlitz integration** — added `.stackblitzrc` so WebContainer auto-runs `npm start` and opens the browser preview on port 3000.
|
|
2016
|
+
|
|
2017
|
+
---
|
|
2018
|
+
|
|
2019
|
+
## [0.5.3] — 2026-04-16
|
|
2020
|
+
|
|
2021
|
+
### Changed
|
|
2022
|
+
|
|
2023
|
+
- **Upgraded `@chenglou/pretext` from 0.0.3 to 0.0.5** — picks up improved text analysis accuracy (~35% larger analysis module), better measurement precision, extracted bidi-data module for cleaner tree-shaking, and new `rich-inline` export (not yet used by pretext-pdf). No breaking changes — `prepareWithSegments()` and `layoutWithLines()` APIs are unchanged. All 223 tests pass, 3 example PDFs visually verified (RTL, TOC, hyperlinks).
|
|
2024
|
+
|
|
2025
|
+
---
|
|
2026
|
+
|
|
2027
|
+
## [0.5.2] — 2026-04-13
|
|
2028
|
+
|
|
2029
|
+
### Added
|
|
2030
|
+
|
|
2031
|
+
- **`onImageLoadError` callback on `PdfDocument`** — gives callers control over image load failures. Return `'skip'` to silently omit the image (preserves existing default behavior). Return `'throw'` to abort rendering with the original error. Previously, all image failures were silently downgraded to `console.warn` with no way to detect them programmatically.
|
|
2032
|
+
|
|
2033
|
+
```typescript
|
|
2034
|
+
await render({
|
|
2035
|
+
content: [...],
|
|
2036
|
+
onImageLoadError: (src, error) => {
|
|
2037
|
+
myLogger.warn('Image skipped', { src, error })
|
|
2038
|
+
return 'skip' // or 'throw' to abort
|
|
2039
|
+
}
|
|
2040
|
+
})
|
|
2041
|
+
```
|
|
2042
|
+
|
|
2043
|
+
---
|
|
2044
|
+
|
|
2045
|
+
## [0.4.0] — 2026-04-08
|
|
2046
|
+
|
|
2047
|
+
### Breaking Changes
|
|
2048
|
+
|
|
2049
|
+
- **Migrated from `pdf-lib` to `@cantoo/pdf-lib`** — `@cantoo/pdf-lib` is now a direct `dependency` (always installed). Previously it was an optional peer dependency required only for encryption. This removes the `ENCRYPTION_NOT_AVAILABLE` error code and the separate `npm install @cantoo/pdf-lib` installation step. Encryption now works out of the box.
|
|
2050
|
+
- **`ENCRYPTION_NOT_AVAILABLE` error code removed** — encryption is now always available. Update any `switch` statements that handled this code.
|
|
2051
|
+
|
|
2052
|
+
### Why this change
|
|
2053
|
+
|
|
2054
|
+
`pdf-lib` (the original) has not received a meaningful commit since November 2021. `@cantoo/pdf-lib` is the actively maintained fork (v2.6.5, 107+ releases, MIT license). pretext-pdf was already using `@cantoo/pdf-lib` for encryption — this commit makes it the single source of truth for all PDF operations.
|
|
2055
|
+
|
|
2056
|
+
### Added
|
|
2057
|
+
|
|
2058
|
+
- `test/pretext-api-contract.test.ts` — canary test that asserts `@chenglou/pretext` exports the exact functions pretext-pdf depends on. Breaks loudly if pretext changes its API.
|
|
2059
|
+
- `docs/ROADMAP.md` — public multi-phase development plan
|
|
2060
|
+
|
|
2061
|
+
### Changed
|
|
2062
|
+
|
|
2063
|
+
- `@chenglou/pretext` version pinned to exact `0.0.3` (no caret) — prevents surprise breaking changes from upstream auto-updates
|
|
2064
|
+
- `test:contract` script added — runs the pretext API contract test before the full test suite
|
|
2065
|
+
- All internal comments updated from `pdf-lib` to `@cantoo/pdf-lib`
|
|
2066
|
+
|
|
2067
|
+
---
|
|
2068
|
+
|
|
2069
|
+
## [0.3.1] — 2026-04-08
|
|
2070
|
+
|
|
2071
|
+
### Fixed
|
|
2072
|
+
|
|
2073
|
+
- **Critical: Font resolution when installed as npm package** — `@fontsource/inter` is now resolved via `createRequire(import.meta.url)` instead of a hardcoded relative path. Previously, `path.join(__dirname, '..', 'node_modules', '@fontsource', 'inter', ...)` failed when npm hoisted the dependency to the consumer's top-level `node_modules`, causing `FONT_LOAD_FAILED` on every install. Now resolves correctly regardless of npm hoisting behavior.
|
|
2074
|
+
|
|
2075
|
+
---
|
|
2076
|
+
|
|
2077
|
+
## [0.3.0] — 2026-04-08
|
|
2078
|
+
|
|
2079
|
+
### Added (Phase 8B — Interactive Forms)
|
|
2080
|
+
|
|
2081
|
+
- New `form-field` element type — creates interactive AcroForm fields in PDFs
|
|
2082
|
+
- `fieldType: 'text' | 'checkbox' | 'radio' | 'dropdown' | 'button'`
|
|
2083
|
+
- `label` renders above the field as static text
|
|
2084
|
+
- Text fields: `defaultValue`, `multiline`, `placeholder`, `maxLength`
|
|
2085
|
+
- Checkboxes: `checked` initial state
|
|
2086
|
+
- Radio groups and dropdowns: `options` array, `defaultSelected`
|
|
2087
|
+
- `doc.flattenForms: true` — bakes all fields into static content
|
|
2088
|
+
- Custom `borderColor`, `backgroundColor`, `width`, `height`, `fontSize` per field
|
|
2089
|
+
- New error codes: `FORM_FIELD_NAME_DUPLICATE` (duplicate `name` across fields), `FORM_FLATTEN_FAILED`
|
|
2090
|
+
- Post-render `form.updateFieldAppearances()` ensures proper display in all PDF readers
|
|
2091
|
+
- 10 comprehensive tests covering all form field types
|
|
2092
|
+
|
|
2093
|
+
### Added (Phase 8E — Signature Placeholder)
|
|
2094
|
+
|
|
2095
|
+
- `doc.signature` — visual signature box drawn on a specified page
|
|
2096
|
+
- Fields: `signerName`, `reason`, `location`, `x`, `y`, `width`, `height`, `page`, `borderColor`, `fontSize`
|
|
2097
|
+
- Draws signature line, date line, and optional text inside a bordered rectangle
|
|
2098
|
+
- `page` is 0-indexed, defaults to last page, clamps gracefully if out of range
|
|
2099
|
+
- 6 comprehensive tests
|
|
2100
|
+
|
|
2101
|
+
### Added (Phase 8D — Callout Boxes)
|
|
2102
|
+
|
|
2103
|
+
- New `callout` element type — styled highlight box with optional title
|
|
2104
|
+
- Preset styles: `style: 'info'` (blue), `'warning'` (amber), `'tip'` (green), `'note'` (gray)
|
|
2105
|
+
- Optional `title` rendered bold above content with left border accent
|
|
2106
|
+
- Fully customizable: `backgroundColor`, `borderColor`, `color`, `titleColor`, `padding`
|
|
2107
|
+
- Paginates correctly across pages (reuses blockquote pagination logic)
|
|
2108
|
+
- 8 comprehensive tests
|
|
2109
|
+
|
|
2110
|
+
### Added (Phase 8F — Document Metadata Extensions)
|
|
2111
|
+
|
|
2112
|
+
- `doc.metadata.language` — sets PDF `/Lang` catalog entry (BCP47 tag e.g. `'en-US'`, `'hi'`)
|
|
2113
|
+
- `doc.metadata.producer` — sets PDF producer field (e.g. `'MyApp v2.1'`)
|
|
2114
|
+
- Both fields validate as non-empty strings
|
|
2115
|
+
- 5 comprehensive tests
|
|
2116
|
+
|
|
2117
|
+
---
|
|
2118
|
+
|
|
2119
|
+
## [0.2.0] — 2026-04-08
|
|
2120
|
+
|
|
2121
|
+
### Added (Phase 8H — Inline Formatting)
|
|
2122
|
+
|
|
2123
|
+
- `verticalAlign: 'superscript' | 'subscript'` on `InlineSpan` in rich-paragraphs
|
|
2124
|
+
- Superscript renders at 65% font size, baseline shifted up by 40% of font size
|
|
2125
|
+
- Subscript renders at 65% font size, baseline shifted down by 20% of font size
|
|
2126
|
+
- `letterSpacing?: number` on `ParagraphElement`, `HeadingElement`, `RichParagraphElement` — extra pt between characters
|
|
2127
|
+
- `smallCaps?: boolean` on those same three element types — simulated via uppercase + 80% fontSize
|
|
2128
|
+
- Character-by-character rendering for letterSpacing (pdf-lib has no native spacing param)
|
|
2129
|
+
- 8 comprehensive tests covering all inline formatting functionality
|
|
2130
|
+
|
|
2131
|
+
### Added (Phase 8A — Annotations/Comments)
|
|
2132
|
+
|
|
2133
|
+
- New `comment` element type — sticky note annotation at position in document
|
|
2134
|
+
- `annotation?: AnnotationSpec` on `ParagraphElement` and `HeadingElement` — attach note to element
|
|
2135
|
+
- Supports: `contents`, `author`, `color` (hex), `open` (popup default state)
|
|
2136
|
+
- Uses PDF `Subtype: 'Text'` annotation (sticky note icon in PDF viewers)
|
|
2137
|
+
- 8 comprehensive tests covering all annotation functionality
|
|
2138
|
+
|
|
2139
|
+
### Added (Phase 8C — Document Assembly)
|
|
2140
|
+
|
|
2141
|
+
- New `merge(pdfs: Uint8Array[])` exported function — combine pre-rendered PDFs
|
|
2142
|
+
- New `assemble(parts: AssemblyPart[])` exported function — mix rendered docs + existing PDFs
|
|
2143
|
+
- `AssemblyPart` interface: `{ doc?: PdfDocument, pdf?: Uint8Array }`
|
|
2144
|
+
- New error codes: `ASSEMBLY_EMPTY`, `ASSEMBLY_FAILED`
|
|
2145
|
+
- 8 comprehensive tests covering all assembly functionality
|
|
2146
|
+
|
|
2147
|
+
### Fixed
|
|
2148
|
+
|
|
2149
|
+
- **CI case-sensitivity bug**: `test/phase-7-integration.test.ts` used `'en-US'` (uppercase) for hyphenation language. On Linux CI (case-sensitive filesystem) this failed with `UNSUPPORTED_LANGUAGE`. Changed to `'en-us'` to match package name `hyphenation.en-us`.
|
|
2150
|
+
|
|
2151
|
+
---
|
|
2152
|
+
|
|
2153
|
+
## [0.1.1] — 2026-04-08
|
|
2154
|
+
|
|
2155
|
+
### Added
|
|
2156
|
+
|
|
2157
|
+
- **Phase 8G: Hyperlinks** — Complete link annotation support:
|
|
2158
|
+
- `paragraph.url` for external URI links on paragraphs
|
|
2159
|
+
- `heading.url` for external URI links on headings
|
|
2160
|
+
- `heading.anchor` for named PDF destinations (internal cross-references)
|
|
2161
|
+
- `InlineSpan.href` for external and internal `#anchorId` links in rich-paragraphs
|
|
2162
|
+
- `mailto:` scheme support for email links
|
|
2163
|
+
- GoTo annotations for internal anchor references
|
|
2164
|
+
- 9 comprehensive tests covering all hyperlink functionality
|
|
2165
|
+
|
|
2166
|
+
### Fixed
|
|
2167
|
+
|
|
2168
|
+
- **Memory leak in test suite**: Removed module-level `_hypherCache` in `src/measure.ts` that accumulated ~188KB per language across 255+ test runs. Changed from cached Hypher instances to fresh instances per call (negligible performance impact, massive memory savings).
|
|
2169
|
+
- **Node.js version compatibility**: Replaced `--experimental-strip-types` with `tsx` runner to support Node.js 18.x, 20.x, and 22.x in CI
|
|
2170
|
+
- **Broken CI examples**: Removed references to non-existent Phase 8 example scripts from GitHub Actions workflow
|
|
2171
|
+
- **README examples mismatch**: Updated Examples section to only list 5 existing Phase 7 examples (watermark, bookmarks, toc, rtl, encryption)
|
|
2172
|
+
- **Test suite OOM issues**: Split large test files (paginate.test.ts) into paginate-basic.test.ts to work around Node.js `--experimental-strip-types` heap exhaustion bug on files >17KB
|
|
2173
|
+
|
|
2174
|
+
### Changed
|
|
2175
|
+
|
|
2176
|
+
- `test:unit` now runs only `test/paginate-basic.test.ts` (fast, no canvas overhead)
|
|
2177
|
+
- Reorganized test scripts: `test:unit`, `test:validate`, `test:e2e`, `test:phases` for better memory management
|
|
2178
|
+
- Moved internal planning documentation to archive (preserved, not published)
|
|
2179
|
+
- `devDependencies`: Added `@napi-rs/canvas` explicitly (was missing, causing CI failures)
|
|
2180
|
+
|
|
2181
|
+
### Added
|
|
2182
|
+
|
|
2183
|
+
- `CONTRIBUTING.md`: Development setup, TDD workflow, PR process guide
|
|
2184
|
+
- `CHENG_LOU_EMAIL_DRAFT.md`: Template for requesting endorsement from pretext creator
|
|
2185
|
+
- `examples/comparison-pdfmake.ts`: pdfmake version of invoice for typography comparison
|
|
2186
|
+
|
|
2187
|
+
---
|
|
2188
|
+
|
|
2189
|
+
## [0.1.0] — 2026-04-07
|
|
2190
|
+
|
|
2191
|
+
### Added (Phase 7G — Encryption)
|
|
2192
|
+
|
|
2193
|
+
- `doc.encryption` configuration for password-protecting PDFs
|
|
2194
|
+
- User password and owner password support
|
|
2195
|
+
- Granular permission restrictions: printing, copying, modifying, annotating
|
|
2196
|
+
- Lazy-loads `@cantoo/pdf-lib` (optional peer dependency) — zero cost when not used
|
|
2197
|
+
- Error code: `ENCRYPTION_NOT_AVAILABLE` when encryption is requested but dependency not installed
|
|
2198
|
+
|
|
2199
|
+
### Added (Phase 7F — RTL Text Support)
|
|
2200
|
+
|
|
2201
|
+
- Right-to-left text support for Arabic, Hebrew, and other RTL languages
|
|
2202
|
+
- Unicode bidirectional text algorithm via `bidi-js`
|
|
2203
|
+
- `dir` attribute on text elements: `'ltr'` | `'rtl'` | `'auto'` for per-element control
|
|
2204
|
+
- RTL text works with headings, paragraphs, lists, tables, and all text elements
|
|
2205
|
+
- Automatic detection of mixed LTR/RTL content
|
|
2206
|
+
|
|
2207
|
+
### Added (Phase 7E — SVG Support)
|
|
2208
|
+
|
|
2209
|
+
- `{ type: 'svg', svg: '<...' }` element for embedding SVG graphics
|
|
2210
|
+
- SVG rasterization via `@napi-rs/canvas`
|
|
2211
|
+
- ViewBox auto-sizing: automatic height calculation from viewBox aspect ratio
|
|
2212
|
+
- Explicit sizing: `width` and `height` parameters for precise control
|
|
2213
|
+
- Alignment options: `align: 'left' | 'center' | 'right'`
|
|
2214
|
+
- Multi-page support: SVGs paginate correctly across page breaks
|
|
2215
|
+
- Error code: `SVG_RENDER_FAILED` for SVG rasterization errors
|
|
2216
|
+
|
|
2217
|
+
### Added (Phase 7D — Table of Contents)
|
|
2218
|
+
|
|
2219
|
+
- `{ type: 'toc' }` element for automatic TOC generation
|
|
2220
|
+
- Two-pass rendering pipeline ensures accurate page numbers
|
|
2221
|
+
- Configurable: `title`, `showTitle`, `minLevel`/`maxLevel`, dot leaders, level indentation
|
|
2222
|
+
- Auto-indexed from heading structure (H1, H2, H3, etc.)
|
|
2223
|
+
- Supports custom formatting via `fontSize`, `color`, `spaceAfter` parameters
|
|
2224
|
+
|
|
2225
|
+
### Added (Phase 7C — Hyphenation)
|
|
2226
|
+
|
|
2227
|
+
- Automatic word hyphenation for better justified text layout
|
|
2228
|
+
- `doc.hyphenation: { language: 'en-US' }` for document-level config
|
|
2229
|
+
- Liang's algorithm via `hypher` package for accurate break points
|
|
2230
|
+
- Configurable: `minWordLength`, `leftMin`, `rightMin`, per-element `hyphenate: false` opt-out
|
|
2231
|
+
- Language support: includes `hyphenation.en-us` (additional languages via npm packages)
|
|
2232
|
+
- Error code: `UNSUPPORTED_LANGUAGE` when language not available
|
|
2233
|
+
|
|
2234
|
+
### Added (Phase 7B — Watermarks)
|
|
2235
|
+
|
|
2236
|
+
- `doc.watermark` for text or image watermarks on every page
|
|
2237
|
+
- Text watermarks: `text`, `fontSize`, `fontWeight`, `color`, `opacity`, `rotation`
|
|
2238
|
+
- Image watermarks: `image` (Uint8Array), `opacity`, `rotation`, `color` (tint)
|
|
2239
|
+
- Watermarks render behind content (lower z-index)
|
|
2240
|
+
- Rotation bounds: -360 ≤ rotation ≤ 360 degrees
|
|
2241
|
+
- Validation: must provide either text or image, never both required
|
|
2242
|
+
|
|
2243
|
+
### Added (Phase 7A — Bookmarks / PDF Outline)
|
|
2244
|
+
|
|
2245
|
+
- PDF sidebar bookmarks auto-generated from heading structure
|
|
2246
|
+
- Enabled by default: `bookmarks: true` or `bookmarks: { minLevel: 1, maxLevel: 3 }`
|
|
2247
|
+
- Level filtering: include/exclude heading levels from outline
|
|
2248
|
+
- Per-heading opt-out: `bookmark: false` on heading elements
|
|
2249
|
+
- Keyboard navigation: Cmd/Ctrl+Opt/Alt+O in PDF readers to toggle bookmark sidebar
|
|
2250
|
+
|
|
2251
|
+
### Added (Phase 6 — Advanced Features)
|
|
2252
|
+
|
|
2253
|
+
- Header and footer support with {{pageNumber}} and {{totalPages}} tokens
|
|
2254
|
+
- Text decoration: strikethrough, underline
|
|
2255
|
+
- Text alignment: left, center, right, justify
|
|
2256
|
+
- Line height control: custom line-height multipliers
|
|
2257
|
+
- Column layout with multi-column content flow
|
|
2258
|
+
- Tables with colspan/rowspan support
|
|
2259
|
+
|
|
2260
|
+
### Added (Phase 5 — Rich Text / Builder API)
|
|
2261
|
+
|
|
2262
|
+
- Fluent builder API for programmatic document construction
|
|
2263
|
+
- Rich text element with nested formatting (bold, italic, links)
|
|
2264
|
+
- Inline code and code blocks with syntax highlighting
|
|
2265
|
+
- Block quotes with custom styling
|
|
2266
|
+
- Horizontal rules (hr element)
|
|
2267
|
+
- Numbered and bulleted lists with nesting
|
|
2268
|
+
|
|
2269
|
+
### Added (Phases 1–4 — Core Engine)
|
|
2270
|
+
|
|
2271
|
+
- Core PDF generation via `pdf-lib`
|
|
2272
|
+
- Element types: paragraph, heading, table, image, list, code, blockquote
|
|
2273
|
+
- Font support: Inter bundled, custom TTF embedding
|
|
2274
|
+
- Document metadata: title, author, subject, keywords, created date
|
|
2275
|
+
- Page sizing: A4, A3, A5, Letter, Legal, or custom dimensions
|
|
2276
|
+
- Margins: top, bottom, left, right per page
|
|
2277
|
+
- Multi-page pagination with orphan/widow control
|
|
2278
|
+
- Image formats: PNG, JPG, WebP
|
|
2279
|
+
- Table features: custom column widths, cell padding, borders, header styling
|
|
2280
|
+
- Colors: hex color codes throughout (text, backgrounds, borders)
|