pretext-pdf 0.5.3 → 0.8.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/README.md +366 -276
  3. package/dist/assets.d.ts +5 -0
  4. package/dist/assets.d.ts.map +1 -1
  5. package/dist/assets.js +248 -43
  6. package/dist/assets.js.map +1 -1
  7. package/dist/errors.d.ts +1 -1
  8. package/dist/errors.d.ts.map +1 -1
  9. package/dist/errors.js.map +1 -1
  10. package/dist/fonts.d.ts.map +1 -1
  11. package/dist/fonts.js +88 -16
  12. package/dist/fonts.js.map +1 -1
  13. package/dist/index.d.ts +29 -2
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +35 -2
  16. package/dist/index.js.map +1 -1
  17. package/dist/markdown.d.ts +28 -0
  18. package/dist/markdown.d.ts.map +1 -0
  19. package/dist/markdown.js +222 -0
  20. package/dist/markdown.js.map +1 -0
  21. package/dist/measure-blocks.d.ts.map +1 -1
  22. package/dist/measure-blocks.js +347 -62
  23. package/dist/measure-blocks.js.map +1 -1
  24. package/dist/measure-text.d.ts.map +1 -1
  25. package/dist/measure-text.js +1 -8
  26. package/dist/measure-text.js.map +1 -1
  27. package/dist/measure.d.ts.map +1 -1
  28. package/dist/measure.js +13 -21
  29. package/dist/measure.js.map +1 -1
  30. package/dist/render-blocks.d.ts +4 -1
  31. package/dist/render-blocks.d.ts.map +1 -1
  32. package/dist/render-blocks.js +227 -105
  33. package/dist/render-blocks.js.map +1 -1
  34. package/dist/render-extras.d.ts.map +1 -1
  35. package/dist/render-extras.js +72 -71
  36. package/dist/render-extras.js.map +1 -1
  37. package/dist/render-utils.d.ts +9 -2
  38. package/dist/render-utils.d.ts.map +1 -1
  39. package/dist/render-utils.js +24 -13
  40. package/dist/render-utils.js.map +1 -1
  41. package/dist/render.d.ts.map +1 -1
  42. package/dist/render.js +27 -3
  43. package/dist/render.js.map +1 -1
  44. package/dist/rich-text.d.ts +0 -4
  45. package/dist/rich-text.d.ts.map +1 -1
  46. package/dist/rich-text.js +15 -9
  47. package/dist/rich-text.js.map +1 -1
  48. package/dist/templates.d.ts +79 -0
  49. package/dist/templates.d.ts.map +1 -0
  50. package/dist/templates.js +201 -0
  51. package/dist/templates.js.map +1 -0
  52. package/dist/types.d.ts +139 -5
  53. package/dist/types.d.ts.map +1 -1
  54. package/dist/validate.d.ts.map +1 -1
  55. package/dist/validate.js +241 -28
  56. package/dist/validate.js.map +1 -1
  57. package/package.json +57 -12
package/README.md CHANGED
@@ -1,91 +1,109 @@
1
1
  # pretext-pdf
2
2
 
3
- > **Declarative JSON PDF generation with professional typography.**
3
+ > **The PDF library AI agents speak natively.**
4
4
  >
5
- > Build sophisticated, multi-page documents with precise text layout, international support, and zero browser overhead.
5
+ > A `PdfDocument` is plain JSON. LLMs emit it in one shot — no codegen, no headless browser, no `eval`. Humans get a strict-typed, declarative API for invoices, reports, and templates.
6
6
 
7
7
  [![npm version](https://img.shields.io/npm/v/pretext-pdf)](https://www.npmjs.com/package/pretext-pdf)
8
8
  [![npm downloads](https://img.shields.io/npm/dw/pretext-pdf)](https://www.npmjs.com/package/pretext-pdf)
9
9
  [![CI](https://github.com/Himaan1998Y/pretext-pdf/actions/workflows/ci.yml/badge.svg)](https://github.com/Himaan1998Y/pretext-pdf/actions)
10
10
  [![TypeScript](https://img.shields.io/badge/typescript-strict-blue)](https://www.typescriptlang.org/)
11
- [![Type Safety](https://img.shields.io/badge/type--safety-0%20any%20casts-blueviolet)](#type-safety)
12
- [![Tests](https://img.shields.io/badge/tests-188%2B-brightgreen)](#test-coverage)
11
+ [![Tests](https://img.shields.io/badge/tests-598-brightgreen)](#test-coverage)
13
12
  [![License](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
14
13
 
14
+ **[Live demo](https://himaan1998y.github.io/pretext-pdf/)** — edit JSON, render PDFs instantly. No install.
15
+ **[`pretext-pdf-mcp`](https://www.npmjs.com/package/pretext-pdf-mcp)** — drop-in MCP server for Claude / Cursor / Windsurf.
16
+ **[Migrating from pdfmake?](docs/MIGRATION_FROM_PDFMAKE.md)** — every pattern mapped.
17
+
18
+ *Layout powered by [`@chenglou/pretext`](https://github.com/chenglou/pretext) — the precision text-layout engine by [Cheng Lou](https://github.com/chenglou) (React core team, Midjourney).*
19
+
15
20
  ---
16
21
 
17
- ## v0.4.6 — Security & Quality Hardening
22
+ ## Why pretext-pdf
18
23
 
19
- All 41 issues from comprehensive April 2026 security audit resolved:
24
+ There are three established camps in JS PDF generation, and one gap. pretext-pdf lives in the gap.
20
25
 
21
- - **Phase 0**: Footnote text truncation fixed
22
- - **Phase 1**: Security hardening (path traversal protection, async file I/O, explicit error handling)
23
- - **Phase 2**: Type safety (eliminated any-casts, proper module typing, strict inference)
24
- - **Phase 3**: Test coverage (false-positive fixes, boundary case validation)
25
- - **Phase 4**: Code quality (silent failures explicit errors, improved decoupling)
26
+ | | pdfmake / jsPDF / pdfkit | Puppeteer / Playwright | LaTeX / WeasyPrint | **pretext-pdf** |
27
+ |---|---|---|---|---|
28
+ | Lightweight (no Chromium) | | ~300 MB | ❌ native binaries | ✅ |
29
+ | Pure ESM, runs in serverless | | ⚠️ painful in Lambda | ❌ | ✅ |
30
+ | Professional typography (kerning, hyphenation, RTL/CJK) | | | ✅ | ✅ |
31
+ | Declarative — describe the document, don't draw it | ⚠️ partial | ❌ | ❌ | ✅ |
32
+ | **LLM emits a working document in one shot** | ❌ requires a code-execution loop | ❌ requires HTML+CSS knowledge | ❌ requires LaTeX knowledge | ✅ pure JSON |
33
+ | MCP server available out of the box | ❌ | ❌ | ❌ | ✅ |
26
34
 
27
- **Result**: 188+ comprehensive tests, 100% pass rate, production-ready reliability.
35
+ **The headline:** every other JS PDF library asks an LLM to *write code*. pretext-pdf asks it for a JSON object. That difference is what makes agent-generated PDFs reliable.
28
36
 
29
37
  ---
30
38
 
31
- ## Why pretext-pdf?
32
-
33
- | | pdfmake | Puppeteer | **pretext-pdf** |
34
- |---|---|---|---|
35
- | Easy declarative API | ✅ | ❌ | ✅ |
36
- | Professional typography | ❌ | ✅ | ✅ |
37
- | Lightweight (no browser) | ✅ | ❌ | ✅ |
38
- | International text (RTL/CJK) | ❌ | ✅ | ✅ |
39
- | Pure Node.js | ✅ | ❌ | ✅ |
40
- | Hyperlinks + annotations | ❌ | ✅ | ✅ |
41
- | Document assembly | ❌ | ❌ | ✅ |
39
+ ## Built for AI agents
42
40
 
43
- ### Powered by [pretext](https://github.com/chenglou/pretext)
41
+ A `PdfDocument` is a plain JSON object. No functions are required. No classes to instantiate. Every field is optional except `type` and a few element-specific essentials. That shape is exactly what an LLM can produce reliably with no tool-use loop.
44
42
 
45
- Pretext is a precision text layout engine by [Cheng Lou](https://github.com/chenglou) (React core team, Midjourney).
43
+ ### Drop into Claude / Cursor / Windsurf via MCP
46
44
 
47
- ```
48
- JSON descriptor → pretext layout → pdf-lib renderer → PDF bytes
49
- (kerning, (annotations,
50
- hyphenation, encryption,
51
- RTL, CJK) hyperlinks)
45
+ ```json
46
+ {
47
+ "mcpServers": {
48
+ "pretext-pdf": {
49
+ "command": "npx",
50
+ "args": ["-y", "pretext-pdf-mcp"]
51
+ }
52
+ }
53
+ }
52
54
  ```
53
55
 
54
- ---
56
+ Tools exposed: `generate_pdf`, `generate_invoice`, `generate_report`, `generate_from_markdown`, `list_element_types`. Built on the live [`pretext-pdf-mcp`](https://www.npmjs.com/package/pretext-pdf-mcp) package — versioned alongside this library.
55
57
 
56
- ## Output Samples
58
+ ### Or call from any agent framework
57
59
 
58
- Real documents generated with pretext-pdf:
60
+ ```typescript
61
+ import { render } from 'pretext-pdf'
59
62
 
60
- | Invoice | Market Report | Resume / CV |
61
- |---------|--------------|-------------|
62
- | [![Invoice](docs/screenshots/showcase-invoice.png)](examples/showcase-invoice.ts) | [![Report](docs/screenshots/showcase-report.png)](examples/showcase-report.ts) | [![Resume](docs/screenshots/showcase-resume.png)](examples/showcase-resume.ts) |
63
- | [View source](examples/showcase-invoice.ts) | [View source](examples/showcase-report.ts) | [View source](examples/showcase-resume.ts) |
63
+ // Whatever produced this JSON Claude, GPT, a workflow node, a form submission — works the same
64
+ const pdf = await render({
65
+ metadata: { title: 'AI-generated quarterly report' },
66
+ content: [
67
+ { type: 'heading', level: 1, text: 'Q1 2026 Summary' },
68
+ { type: 'paragraph', text: 'Revenue grew 18% YoY.' },
69
+ { type: 'table', columns: [...], rows: [...] },
70
+ ],
71
+ })
72
+ ```
73
+
74
+ ### Why JSON-first matters for agents
75
+
76
+ - **No code execution loop.** The model returns JSON; you call `render()`. No sandbox, no `vm`, no Vercel Sandbox roundtrip.
77
+ - **Schema-validatable.** Strict TypeScript types double as the contract. Pair with [Anthropic tool use](https://docs.anthropic.com/en/docs/build-with-claude/tool-use) or [Vercel AI SDK structured output](https://sdk.vercel.ai/docs/ai-sdk-core/generating-structured-data) for guaranteed-shape results.
78
+ - **Self-correcting errors.** Every failure throws `PretextPdfError` with a typed `code`. Feed it back to the model and it fixes itself.
79
+ - **Progressive disclosure.** Optional peer deps mean an agent can ask for QR codes, charts, or markdown only when needed — token-efficient prompts.
64
80
 
65
81
  ---
66
82
 
67
83
  ## Install
68
84
 
69
85
  ```bash
70
- npm install pretext-pdf@^0.4.6
86
+ npm install pretext-pdf
71
87
  ```
72
88
 
73
- Optional peer dependency (for SVG support):
74
- ```bash
75
- npm install @napi-rs/canvas # Required for SVG elements (auto-installed in most setups)
76
- ```
89
+ > **ESM only** use `import`, not `require`. Requires Node.js ≥ 18.
90
+
91
+ Optional peer dependencies install only what you need:
77
92
 
78
- Optional peer dependency (for cryptographic signing):
79
93
  ```bash
80
- npm install @signpdf/signpdf # Required for PKCS#7 document signing
94
+ npm install @napi-rs/canvas # SVG / qr-code / barcode / chart elements
95
+ npm install qrcode # qr-code element
96
+ npm install bwip-js # barcode element
97
+ npm install vega vega-lite # chart element (Vega-Lite specs → vector SVG)
98
+ npm install marked # pretext-pdf/markdown entry point
99
+ npm install @signpdf/signpdf # PKCS#7 cryptographic signing
81
100
  ```
82
101
 
83
- > **Encryption is built-in since v0.4.0** — no extra install needed. Just add `encryption` to your document config.
84
- > **Type safety certified v0.4.6** — 100% strict TypeScript, zero any-casts in critical paths.
102
+ > **Encryption is built-in** since v0.4.0 — no extra install needed. Just add `encryption` to your document config.
85
103
 
86
104
  ---
87
105
 
88
- ## Quick Start
106
+ ## Quick start
89
107
 
90
108
  ```typescript
91
109
  import { render } from 'pretext-pdf'
@@ -117,7 +135,7 @@ const pdf = await render({
117
135
  writeFileSync('invoice.pdf', pdf)
118
136
  ```
119
137
 
120
- ### Builder API
138
+ ### Builder API (fluent style)
121
139
 
122
140
  ```typescript
123
141
  import { createPdf } from 'pretext-pdf'
@@ -131,108 +149,162 @@ const pdf = await createPdf({ pageSize: 'A4' })
131
149
 
132
150
  ---
133
151
 
134
- ## Agent / AI Integration
152
+ ## Output samples
135
153
 
136
- pretext-pdf works great as a tool for AI agents generating PDFs on demand.
154
+ Real documents generated with pretext-pdf:
137
155
 
138
- ### MCP Server (Claude Desktop, Cursor, Windsurf)
156
+ | Invoice | Market Report | Resume / CV |
157
+ |---------|--------------|-------------|
158
+ | [![Invoice](docs/screenshots/showcase-invoice.png)](examples/showcase-invoice.ts) | [![Report](docs/screenshots/showcase-report.png)](examples/showcase-report.ts) | [![Resume](docs/screenshots/showcase-resume.png)](examples/showcase-resume.ts) |
159
+ | [View source](examples/showcase-invoice.ts) | [View source](examples/showcase-report.ts) | [View source](examples/showcase-resume.ts) |
139
160
 
140
- Use [`pretext-pdf-mcp`](https://www.npmjs.com/package/pretext-pdf-mcp) to call pretext-pdf directly from any AI agent:
161
+ ---
141
162
 
142
- ```json
143
- {
144
- "mcpServers": {
145
- "pretext-pdf": {
146
- "command": "npx",
147
- "args": ["-y", "pretext-pdf-mcp"]
148
- }
149
- }
150
- }
151
- ```
163
+ ## What's in v0.8.0
164
+
165
+ Five new capabilities, all behind optional peer dependencies (zero extra weight if unused):
152
166
 
153
- Tools available: `generate_pdf`, `generate_invoice`, `generate_report`, `list_element_types`
167
+ - **`qr-code`** scannable QR codes for UPI payments, URLs, vCards. Requires `qrcode`.
168
+ - **`barcode`** — 100+ symbologies (EAN-13, Code128, PDF417, DataMatrix…). Requires `bwip-js`.
169
+ - **`chart`** — embed Vega-Lite specs as crisp vector SVG. Requires `vega` + `vega-lite`.
170
+ - **`pretext-pdf/markdown`** — convert any Markdown string to `ContentElement[]` in one call. Requires `marked`.
171
+ - **`pretext-pdf/templates`** — zero-dep template helpers: `createInvoice`, `createGstInvoice` (India GST / IGST / CGST+SGST), `createReport`.
154
172
 
155
- ### Quick pattern for LLMs
173
+ See [CHANGELOG.md](CHANGELOG.md) for the full history.
174
+
175
+ ---
176
+
177
+ ## India / GST invoicing
178
+
179
+ pretext-pdf has built-in support for Indian invoice requirements:
180
+
181
+ - **₹ symbol** renders correctly (bundled Inter font includes the Rupee glyph)
182
+ - **Indian number formatting** — helper for 1,00,000 notation (not 100,000)
183
+ - **GST structure** — CGST/SGST (intra-state) and IGST (inter-state) table layouts
184
+ - **Amount in words** — Indian numbering system (Lakh/Crore)
185
+ - **SAC/HSN codes** — column support in line-item tables
186
+
187
+ Use the `createGstInvoice` template for a complete GST-compliant invoice in one call:
156
188
 
157
189
  ```typescript
190
+ import { createGstInvoice } from 'pretext-pdf/templates'
158
191
  import { render } from 'pretext-pdf'
159
192
 
160
- // Every PdfDocument is a plain JSON object — perfect for AI generation
161
- const pdf = await render({
162
- metadata: { title: 'AI-Generated Report' },
163
- content: [
164
- { type: 'heading', level: 1, text: 'Summary' },
165
- { type: 'paragraph', text: 'Generated content here.' },
166
- // ... AI fills this array
167
- ]
193
+ const content = createGstInvoice({
194
+ supplier: { name: 'Antigravity Systems', address: 'Gurugram, HR', gstin: '06AAACA1234A1ZV', state: 'Haryana' },
195
+ buyer: { name: 'TechStartup Ltd', address: 'Mumbai, MH', gstin: '27AABCB5678B1ZP', state: 'Maharashtra' },
196
+ invoiceNumber: 'INV/2026-27/001',
197
+ invoiceDate: '20 Apr 2026',
198
+ placeOfSupply: 'Maharashtra (27)',
199
+ items: [
200
+ { description: 'Software Development', hsnSac: '998314', quantity: 80, unit: 'Hrs', rate: 3000, taxRate: 18 },
201
+ ],
202
+ isInterState: true, // auto-detected from state fields if omitted
203
+ qrUpiData: 'upi://pay?pa=merchant@hdfc&pn=Antigravity&am=283200',
204
+ bankName: 'HDFC Bank', accountNumber: '501001234567', ifscCode: 'HDFC0001234',
168
205
  })
206
+ const pdf = await render({ content })
169
207
  ```
170
208
 
171
- ### Key facts for AI agents
209
+ See [`examples/gst-invoice-india.ts`](examples/gst-invoice-india.ts) for the raw element approach.
172
210
 
173
- - `content` is an array of typed elements — each has a `type` field
174
- - All fields are optional except `type` and element-specific required fields (e.g. `text`, `level`)
175
- - Errors are typed: `err.code` tells you exactly what went wrong
176
- - `render()` is fully async, safe to `await` in any context
177
- - Works in Node.js 18+ and modern browsers (with `@napi-rs/canvas` for SVG)
211
+ ---
178
212
 
179
- ### Element type reference (quick)
213
+ ## Markdown PDF (`pretext-pdf/markdown`)
180
214
 
181
- ```
182
- paragraph heading(1-4) spacer hr page-break
183
- table image svg list code
184
- blockquote rich-paragraph callout comment form-field
185
- toc
215
+ Convert any Markdown string to a `pretext-pdf` document in one call. Requires `marked` peer dep.
216
+
217
+ ```typescript
218
+ import { markdownToContent } from 'pretext-pdf/markdown'
219
+ import { render } from 'pretext-pdf'
220
+ import { writeFileSync } from 'fs'
221
+
222
+ const md = `
223
+ # Q1 2026 Report
224
+
225
+ Revenue grew **18%** year-over-year, driven by:
226
+
227
+ - Cloud services (+32%)
228
+ - Enterprise licenses (+12%)
229
+
230
+ > All figures are in USD millions.
231
+ `
232
+
233
+ const content = await markdownToContent(md, {
234
+ codeFontFamily: 'Courier New', // enables fenced code block rendering
235
+ })
236
+ const pdf = await render({ content })
237
+ writeFileSync('report.pdf', pdf)
186
238
  ```
187
239
 
240
+ Supported Markdown: headings h1–h4, bold, italic, strikethrough, inline code, links, ordered/unordered lists (2 levels), fenced code blocks, blockquotes, horizontal rules.
241
+
188
242
  ---
189
243
 
190
- ## India / GST Invoicing
244
+ ## Invoice & report templates (`pretext-pdf/templates`)
191
245
 
192
- pretext-pdf has built-in support for Indian invoice requirements:
246
+ Pre-built zero-dependency template functions that generate `ContentElement[]` arrays:
193
247
 
194
- - **₹ symbol** renders correctly (bundled Inter font includes the Rupee glyph)
195
- - **Indian number formatting** helper for 1,00,000 notation (not 100,000)
196
- - **GST structure** CGST/SGST (intra-state) and IGST (inter-state) table layouts
197
- - **Amount in words** — Indian numbering system (Lakh/Crore)
198
- - **SAC/HSN codes** — column support in line-item tables
248
+ ```typescript
249
+ import { createInvoice, createGstInvoice, createReport } from 'pretext-pdf/templates'
250
+ import { render } from 'pretext-pdf'
199
251
 
200
- See [`examples/gst-invoice-india.ts`](examples/gst-invoice-india.ts) for a complete GST-compliant invoice template.
252
+ // Generic invoice (any currency)
253
+ const invoiceContent = createInvoice({
254
+ from: { name: 'Acme Corp', address: '123 Main St', email: 'billing@acme.com' },
255
+ to: { name: 'Client Ltd', address: '456 Oak Ave' },
256
+ invoiceNumber: 'INV-2026-001', date: '2026-04-20',
257
+ items: [{ description: 'Consulting', quantity: 10, unitPrice: 150 }],
258
+ currency: '$', taxRate: 10, taxLabel: 'GST',
259
+ qrData: 'upi://pay?pa=acme@bank&am=1650',
260
+ })
201
261
 
202
- ---
262
+ // Research report with optional TOC
263
+ const reportContent = createReport({
264
+ title: 'Annual Performance Report',
265
+ author: 'Finance Team', date: 'April 2026',
266
+ abstract: 'Revenue grew 18% YoY across all segments.',
267
+ includeTableOfContents: true,
268
+ sections: [
269
+ { title: 'Revenue', paragraphs: ['Cloud +32%, Enterprise +12%.'], bullets: ['SaaS: $2.8M', 'Services: $1.1M'] },
270
+ ],
271
+ })
203
272
 
204
- ## Features
273
+ const pdf = await render({ content: reportContent })
274
+ ```
205
275
 
206
- ### Security & Reliability
276
+ ---
207
277
 
208
- - **Type-safe architecture** — 0 any-casts in critical path, strict TypeScript inference
209
- - ✅ **Cryptographically signed PDFs** — PKCS#7 signing support (Phase 3)
210
- - ✅ **Path traversal protection** — Secure file operations with validated paths
211
- - ✅ **Error sanitization** — No sensitive data in error messages
212
- - ✅ **Async-safe I/O** — Non-blocking file operations throughout
213
- - ✅ **Comprehensive test coverage** — 188+ tests with 100% pass rate
214
- - ✅ **No hardcoded secrets** — Environment-based configuration
278
+ ## Element type reference
215
279
 
216
- ### Element Types
280
+ ```
281
+ paragraph heading(1-4) spacer hr page-break
282
+ table image svg list code
283
+ blockquote rich-paragraph callout comment form-field
284
+ toc qr-code barcode chart footnote-def
285
+ ```
217
286
 
218
287
  | Element | What it does |
219
288
  | --- | --- |
220
- | `paragraph` | Text block — font, size, color, align, background, letterSpacing, smallCaps |
221
- | `heading` | H1–H4 with bookmarks, URL links, internal anchors |
222
- | `table` | Fixed/proportional columns, colspan, repeating headers across page breaks |
223
- | `image` | PNG/JPG/WebP with sizing, alignment, auto-format detection |
224
- | `list` | Ordered/unordered, nested, custom markers |
289
+ | `paragraph` | Text block — font, size, color, align, background, letterSpacing, smallCaps, tabularNumbers, multi-column (`columns` + `columnGap`), RTL (`dir`) |
290
+ | `heading` | H1–H4 with bookmarks, URL links, internal anchors, tabularNumbers, RTL (`dir`) |
291
+ | `table` | Fixed/proportional columns, colspan, rowspan, repeating headers across page breaks |
292
+ | `image` | PNG/JPG/WebP with sizing, alignment, float left/right with `floatText` or rich `floatSpans` (mixed-format caption) |
293
+ | `list` | Ordered/unordered, 2-level nesting, `nestedNumberingStyle: 'restart' \| 'continue'` |
225
294
  | `code` | Monospace block with background and padding |
226
295
  | `blockquote` | Left border + background |
227
296
  | `rich-paragraph` | Mixed bold/italic/color/size/super/subscript spans with inline hyperlinks |
228
297
  | `svg` | Embedded SVG graphics with auto-sizing from viewBox |
229
298
  | `toc` | Auto-generated table of contents with accurate page numbers (two-pass) |
299
+ | `qr-code` | Scannable QR code — UPI payment links, URLs, vCards. `data`, `size`, `errorCorrectionLevel`, `foreground`/`background` color. Requires `qrcode` peer dep. |
300
+ | `barcode` | 100+ symbologies — EAN-13, Code128, PDF417, DataMatrix, and more via `symbology` field. Requires `bwip-js` peer dep. |
301
+ | `chart` | Vega-Lite data visualisation — pass any valid Vega-Lite spec to `spec`. Rendered as vector SVG. Requires `vega` + `vega-lite` peer deps. |
230
302
  | `comment` | PDF sticky-note annotation (visible in Acrobat/Preview sidebar) |
231
303
  | `hr` | Horizontal rule |
232
304
  | `spacer` | Fixed-height gap |
233
305
  | `page-break` | Force new page |
234
306
 
235
- ### Document Features
307
+ ### Document-level features
236
308
 
237
309
  | Feature | Config key | Notes |
238
310
  | --- | --- | --- |
@@ -240,85 +312,21 @@ See [`examples/gst-invoice-india.ts`](examples/gst-invoice-india.ts) for a compl
240
312
  | Encryption | `doc.encryption` | Password + granular permissions |
241
313
  | PDF Bookmarks | `doc.bookmarks` | Auto-generated from headings |
242
314
  | Hyphenation | `doc.hyphenation` | Liang's algorithm, `language: 'en-us'` |
243
- | Headers/Footers | `doc.header` / `doc.footer` | `{{pageNumber}}` / `{{totalPages}}` tokens |
315
+ | Headers/Footers | `doc.header` / `doc.footer` | `{{pageNumber}}`, `{{totalPages}}`, `{{date}}`, `{{author}}` tokens |
316
+ | Per-section overrides | `doc.sections` | Different header/footer/margins per page range |
244
317
  | Metadata | `doc.metadata` | Title, author, subject, keywords, `language` (PDF /Lang), `producer` |
245
-
246
- ### Phase 8 Features
247
-
248
- | Feature | API |
249
- | --- | --- |
250
- | **Hyperlinks** | `paragraph.url`, `heading.url`, `heading.anchor`, `span.href` |
251
- | **Inline formatting** | `span.verticalAlign: 'superscript'\|'subscript'`, `paragraph.letterSpacing`, `heading.smallCaps` |
252
- | **Sticky notes** | `{ type: 'comment', contents: '...' }`, `paragraph.annotation` |
253
- | **Document assembly** | `merge(pdfs)`, `assemble(parts)` |
254
- | **Interactive forms** | `{ type: 'form-field', fieldType: 'text'\|'checkbox'\|'radio'\|'dropdown'\|'button' }`, `doc.flattenForms` |
255
- | **Signature placeholder** | `doc.signature: { signerName, reason, location, x, y, page }` |
256
- | **Callout boxes** | `{ type: 'callout', content, style: 'info'\|'warning'\|'tip'\|'note', title }` |
257
-
258
- ### Type Safety (v0.4.6+)
259
-
260
- pretext-pdf is built with **strict TypeScript** and **zero any-casts** in critical paths:
261
-
262
- - **Full type inference** — No need to cast document configs or response types
263
- - **Element validation** — TypeScript catches invalid element types at compile time
264
- - **API contract testing** — Every API boundary has comprehensive type tests
265
- - **Error types** — `PretextPdfError` with typed code field for safe error handling
266
- - **Module typing** — Complete type definitions for all exports and configurations
318
+ | Hyperlinks | `paragraph.url`, `heading.url`, `heading.anchor`, `span.href` | External, mailto, internal anchors |
319
+ | Inline formatting | `span.verticalAlign: 'superscript'\|'subscript'`, `paragraph.letterSpacing`, `heading.smallCaps` | |
320
+ | Sticky notes | `{ type: 'comment', contents: '...' }`, `paragraph.annotation` | |
321
+ | Document assembly | `merge(pdfs)`, `assemble(parts)` | Combine pre-rendered + freshly rendered |
322
+ | Interactive forms | `{ type: 'form-field', fieldType: 'text'\|'checkbox'\|'radio'\|'dropdown'\|'button' }`, `doc.flattenForms` | |
323
+ | Cryptographic signing | `doc.signature: { p12, passphrase, signerName, reason, location }` | PKCS#7 via optional `@signpdf/signpdf` |
324
+ | Visual signature placeholder | `doc.signature: { signerName, reason, location, x, y, page }` | |
325
+ | Callout boxes | `{ type: 'callout', content, style: 'info'\|'warning'\|'tip'\|'note', title }` | |
267
326
 
268
327
  ---
269
328
 
270
- ## Security Audit (April 2026)
271
-
272
- Comprehensive security and quality audit completed. **41 issues identified and fixed across 5 phases:**
273
-
274
- | Phase | Focus | Issues | Status |
275
- | --- | --- | --- | --- |
276
- | 0 | Core rendering | Footnote truncation | ✅ Fixed |
277
- | 1 | Security hardening | Path validation, async I/O, error handling | ✅ Fixed |
278
- | 2 | Type safety | Any-cast elimination, module typing | ✅ Fixed |
279
- | 3 | Test coverage | False-positives, boundary cases, crypto signing | ✅ Fixed |
280
- | 4 | Code quality | Silent failures → explicit errors, decoupling | ✅ Fixed |
281
-
282
- **Audit results:**
283
-
284
- - Zero path traversal vulnerabilities
285
- - All error messages sanitized (no data leaks)
286
- - Async file I/O throughout (non-blocking)
287
- - No hardcoded secrets or credentials
288
- - 188+ tests, 100% pass rate
289
- - Production-ready reliability
290
-
291
- See [SECURITY.md](SECURITY.md) for detailed security policies.
292
-
293
- ---
294
-
295
- ## Examples
296
-
297
- Run working examples from the `examples/` directory:
298
-
299
- ```bash
300
- # Phase 7 examples
301
- npm run example # Basic invoice
302
- npm run example:watermark # Text/image watermarks
303
- npm run example:bookmarks # PDF outline/bookmarks
304
- npm run example:toc # Auto table of contents
305
- npm run example:rtl # Arabic/Hebrew RTL text
306
- npm run example:encryption # Password-protected PDF
307
-
308
- # Phase 8 examples
309
- npm run example:hyperlinks # External links, email links, internal anchors
310
- npm run example:annotations # Sticky notes on elements
311
- npm run example:assembly # Merge and assemble multiple PDFs
312
- npm run example:inline # Superscript, subscript, letter-spacing, small-caps
313
- npm run example:forms # Interactive form fields (text, checkbox, radio, dropdown)
314
- npm run example:callout # Callout boxes (info, warning, tip, note presets)
315
- ```
316
-
317
- All examples write output to `output/*.pdf`.
318
-
319
- ---
320
-
321
- ## API Reference
329
+ ## API reference
322
330
 
323
331
  ### `render(doc): Promise<Uint8Array>`
324
332
 
@@ -372,9 +380,113 @@ const report = await assemble([
372
380
 
373
381
  ---
374
382
 
375
- ## Error Handling
383
+ ## Custom fonts
376
384
 
377
- Every error throws `PretextPdfError` with a typed code:
385
+ ```typescript
386
+ const pdf = await render({
387
+ fonts: [
388
+ { family: 'Roboto', weight: 400, src: '/path/to/Roboto-Regular.ttf' },
389
+ { family: 'Roboto', weight: 700, src: '/path/to/Roboto-Bold.ttf' },
390
+ { family: 'Roboto', style: 'italic', src: '/path/to/Roboto-Italic.ttf' },
391
+ ],
392
+ defaultFont: 'Roboto',
393
+ content: [
394
+ { type: 'paragraph', text: 'Uses Roboto font' },
395
+ { type: 'paragraph', text: 'Bold text', fontWeight: 700 },
396
+ ],
397
+ })
398
+ ```
399
+
400
+ > **Avoid `system-ui`** as a font name on macOS — it triggers a known layout-measurement inaccuracy in Pretext. Always name fonts explicitly.
401
+
402
+ ---
403
+
404
+ ## Rich text
405
+
406
+ ```typescript
407
+ {
408
+ type: 'rich-paragraph',
409
+ fontSize: 13,
410
+ spans: [
411
+ { text: 'Normal ' },
412
+ { text: 'bold', fontWeight: 700 },
413
+ { text: ' and ', fontStyle: 'italic' },
414
+ { text: 'colored', color: '#e63946' },
415
+ { text: ' and ' },
416
+ { text: 'linked', href: 'https://example.com', underline: true, color: '#0070f3' },
417
+ { text: '. Also: E=mc' },
418
+ { text: '2', verticalAlign: 'superscript' },
419
+ { text: ' and H' },
420
+ { text: '2', verticalAlign: 'subscript' },
421
+ { text: 'O.' },
422
+ ],
423
+ }
424
+ ```
425
+
426
+ ---
427
+
428
+ ## Footnotes
429
+
430
+ Use `createFootnoteSet()` to generate matched reference/definition pairs with guaranteed unique IDs:
431
+
432
+ ```typescript
433
+ import { render, createFootnoteSet } from 'pretext-pdf'
434
+
435
+ const notes = createFootnoteSet([
436
+ { text: 'Smith, J. (2022). Typography in PDFs.' },
437
+ { text: 'Ibid., p. 42.' },
438
+ ])
439
+
440
+ await render({
441
+ content: [
442
+ {
443
+ type: 'rich-paragraph',
444
+ spans: [
445
+ { text: 'See the original research' },
446
+ { text: '¹', verticalAlign: 'superscript', footnoteRef: notes[0]!.id },
447
+ { text: ' for details.' },
448
+ ],
449
+ },
450
+ ...notes.map(n => n.def), // footnote-def elements go at end of document
451
+ ],
452
+ })
453
+ ```
454
+
455
+ ---
456
+
457
+ ## Examples
458
+
459
+ Run working examples from the `examples/` directory:
460
+
461
+ ```bash
462
+ # v0.8.0 new element examples (install optional deps first)
463
+ # npm install qrcode bwip-js vega vega-lite marked
464
+
465
+ # Phase 7 examples
466
+ npm run example # Basic invoice
467
+ npm run example:watermark # Text/image watermarks
468
+ npm run example:bookmarks # PDF outline/bookmarks
469
+ npm run example:toc # Auto table of contents
470
+ npm run example:rtl # Arabic/Hebrew RTL text
471
+ npm run example:encryption # Password-protected PDF
472
+
473
+ # Phase 8 examples
474
+ npm run example:hyperlinks # External links, email links, internal anchors
475
+ npm run example:annotations # Sticky notes on elements
476
+ npm run example:assembly # Merge and assemble multiple PDFs
477
+ npm run example:inline # Superscript, subscript, letter-spacing, small-caps
478
+ npm run example:forms # Interactive form fields
479
+ npm run example:callout # Callout boxes (info, warning, tip, note)
480
+ npm run example:gst # India GST-compliant invoice
481
+ ```
482
+
483
+ All examples write output to `output/*.pdf`.
484
+
485
+ ---
486
+
487
+ ## Error handling
488
+
489
+ Every error throws `PretextPdfError` with a typed `code` — designed so an LLM (or a human) can self-correct:
378
490
 
379
491
  ```typescript
380
492
  import { render, PretextPdfError } from 'pretext-pdf'
@@ -414,24 +526,9 @@ hyphenation: { language: 'en-US' }
414
526
  hyphenation: { language: 'en-us' }
415
527
  ```
416
528
 
417
- ### Encryption
418
-
419
- Encryption is built-in since v0.4.0. Add `encryption` to your document config:
420
-
421
- ```typescript
422
- const pdf = await render({
423
- encryption: {
424
- userPassword: 'open123',
425
- ownerPassword: 'admin456',
426
- permissions: { printing: true, copying: false, modifying: false }
427
- },
428
- content: [...]
429
- })
430
- ```
431
-
432
529
  ### SVG rendering requires optional dependency
433
530
 
434
- Install `@napi-rs/canvas` for SVG support:
531
+ Install `@napi-rs/canvas` for SVG / chart / qr-code / barcode support:
435
532
 
436
533
  ```bash
437
534
  npm install @napi-rs/canvas
@@ -446,103 +543,99 @@ Check margins — if left+right margins exceed page width, content width becomes
446
543
  margins: { top: 36, bottom: 36, left: 36, right: 36 }
447
544
  ```
448
545
 
449
- ### Form fields not interactive after flattenForms
546
+ ### Form fields not interactive after `flattenForms`
450
547
 
451
- `flattenForms: true` bakes fields into static content — by design. Remove it to keep interactive.
548
+ `flattenForms: true` bakes fields into static content — by design. Remove it to keep them interactive.
452
549
 
453
550
  ---
454
551
 
455
- ## Test Coverage
552
+ ## Non-goals
456
553
 
457
- 437+ tests across all phases with 100% pass rate:
554
+ What pretext-pdf is **not** trying to be pick a different tool for these:
458
555
 
459
- ```bash
460
- npm test # Full suite (unit + e2e + all phases)
461
- npm run test:unit # Validation, builder, rich-text unit tests
462
- npm run test:e2e # End-to-end render tests
463
- npm run test:phase-7 # Phase 7A-7G feature tests
464
- npm run test:phase-8 # Phase 8A-8H feature tests
465
- npm run test:phases # All phase tests (7–9, performance, signatures)
466
- ```
556
+ - **Editing or parsing existing PDFs** → [`pdf-lib`](https://github.com/Hopding/pdf-lib), [`pdf-parse`](https://www.npmjs.com/package/pdf-parse)
557
+ - **Filling existing PDF form templates** [`pdf-lib`](https://github.com/Hopding/pdf-lib), [`pdftk`](https://www.pdflabs.com/tools/pdftk-server/)
558
+ - **Heavily art-directed pages** with CSS grids, SVG illustrations, floats, background images → headless Chrome (Puppeteer) still wins
559
+ - **PDF/A archival, PDF/UA accessibility tagging** → not yet
560
+ - **Print-shop kerning pairs, OpenType ligatures, variable-font axes beyond weight** → Pretext itself doesn't model these
561
+
562
+ ---
467
563
 
468
- **Coverage**: Type safety, path validation, error handling, boundary cases, crypto signing, document assembly, and all content elements.
564
+ ## Runtime requirements
565
+
566
+ - **Node.js ≥ 18** with `@napi-rs/canvas` peer dep (lazy-loaded — only required when you use SVG/chart/QR/barcode elements)
567
+ - **`Intl.Segmenter`** (built-in on Node 18+ and all modern browsers)
568
+ - **Browser support** — works directly in modern browsers; bring your own font bytes
569
+ - **Cold-start cost** on serverless: `@napi-rs/canvas` adds ~5–10 MB and a few hundred ms on the first request. Subsequent requests in a warm container are sub-second.
570
+ - **Fonts must be fully loaded** before `render()` runs — for browser usage, await `document.fonts.ready` first
469
571
 
470
572
  ---
471
573
 
472
- ## Custom Fonts
574
+ ## Performance
473
575
 
474
- ```typescript
475
- const pdf = await render({
476
- fonts: [
477
- { family: 'Roboto', weight: 400, src: '/path/to/Roboto-Regular.ttf' },
478
- { family: 'Roboto', weight: 700, src: '/path/to/Roboto-Bold.ttf' },
479
- { family: 'Roboto', style: 'italic', src: '/path/to/Roboto-Italic.ttf' },
480
- ],
481
- defaultFont: 'Roboto',
482
- content: [
483
- { type: 'paragraph', text: 'Uses Roboto font' },
484
- { type: 'paragraph', text: 'Bold text', fontWeight: 700 },
485
- ],
486
- })
487
- ```
576
+ Benchmarked on Windows 11 / Node 22 / Intel i7-12th Gen. Numbers are averages over 10 runs, excluding the first cold JIT run.
577
+
578
+ | Document | Render time | PDF size |
579
+ | --- | --- | --- |
580
+ | 1 page (heading + paragraph + list) | ~220 ms | ~45 KB |
581
+ | 10 pages (40 sections, mixed elements) | ~1,100 ms | ~180 KB |
582
+ | Mixed (heading + paragraph + 20-row table + list + hr) | ~290 ms | ~60 KB |
583
+
584
+ **Font subsetting** is automatic for TTF/OTF fonts. Only the glyphs used in the document are embedded, typically reducing PDF size by 40–60% compared to full font embedding. A typical single-font invoice renders under 65 KB. WOFF2 fonts are embedded without subsetting due to an upstream library limitation.
585
+
586
+ For large documents (10,000+ elements), set `NODE_OPTIONS=--max-old-space-size=4096` to prevent GC pressure.
488
587
 
489
588
  ---
490
589
 
491
- ## Rich Text
590
+ ## Test coverage
492
591
 
493
- ```typescript
494
- {
495
- type: 'rich-paragraph',
496
- fontSize: 13,
497
- spans: [
498
- { text: 'Normal ' },
499
- { text: 'bold', fontWeight: 700 },
500
- { text: ' and ', fontStyle: 'italic' },
501
- { text: 'colored', color: '#e63946' },
502
- { text: ' and ' },
503
- { text: 'linked', href: 'https://example.com', underline: true, color: '#0070f3' },
504
- { text: '. Also: E=mc' },
505
- { text: '2', verticalAlign: 'superscript' },
506
- { text: ' and H' },
507
- { text: '2', verticalAlign: 'subscript' },
508
- { text: 'O.' },
509
- ],
510
- }
592
+ 598+ tests across all phases with 100% pass rate:
593
+
594
+ ```bash
595
+ npm test # Full suite (unit + e2e + all phases including v0.8.0)
596
+ npm run test:unit # Validation, builder, rich-text unit tests
597
+ npm run test:e2e # End-to-end render tests
598
+ npm run test:10a # QR code + barcode tests
599
+ npm run test:10b # Vega-Lite chart tests
600
+ npm run test:10c # Markdown converter tests
601
+ npm run test:10d # Template function tests
602
+ npm run test:phases # All phase tests (7–11, performance, signatures)
511
603
  ```
512
604
 
605
+ **Coverage**: type safety, path validation, error handling, boundary cases, crypto signing, document assembly, all content elements, optional-dep error codes, MCP tool validation.
606
+
607
+ ---
608
+
609
+ ## Security
610
+
611
+ Comprehensive April 2026 security audit completed — 41 issues identified and fixed across path-traversal protection, async I/O, error sanitization, type-safety, and explicit failure modes. See [SECURITY.md](SECURITY.md) for the disclosure policy and [CHANGELOG.md](CHANGELOG.md) for audit details.
612
+
613
+ Highlights:
614
+ - Zero known path-traversal vulnerabilities; opt-in `allowedFileDirs` lockdown for user-controlled inputs
615
+ - All error messages sanitized — no filesystem paths or secrets leak through
616
+ - Async file I/O throughout (non-blocking)
617
+ - Strict TypeScript with documented `any`-casts only at pdf-lib internal boundaries
618
+
513
619
  ---
514
620
 
515
621
  ## Roadmap
516
622
 
517
623
  | Phase | Feature | Status |
518
624
  |-------|---------|--------|
519
- | 1–4 | Core engine, pagination, typography | ✅ |
520
- | 5 | Rich text / builder API | ✅ |
521
- | 6 | Headers/footers, columns, decoration | ✅ |
522
- | 7A | PDF Bookmarks / Outline | ✅ |
523
- | 7B | Watermarks | ✅ |
524
- | 7C | Hyphenation | |
525
- | 7D | Table of Contents | ✅ |
526
- | 7E | SVG support | ✅ |
527
- | 7F | RTL text (Arabic/Hebrew) | ✅ |
528
- | 7G | Encryption | ✅ |
529
- | 8A | Sticky note annotations | ✅ |
530
- | 8B | Interactive forms (text/checkbox/radio/dropdown/button) | ✅ |
531
- | 8C | Document assembly (merge + assemble) | ✅ |
532
- | 8D | Callout boxes (info/warning/tip/note) | ✅ |
533
- | 8E | Signature placeholder | ✅ |
534
- | 8F | Document metadata (language, producer) | ✅ |
535
- | 8G | Hyperlinks | ✅ |
536
- | 8H | Inline formatting (super/subscript, letterSpacing, smallCaps) | ✅ |
537
- | 9A | Digital signatures (cryptographic, PKCS#7) | 🔜 |
538
- | 9B | Image floats (text flowing around images) | 🔜 |
539
- | 9C | Font subsetting pre-computation | 🔜 |
625
+ | 1–6 | Core engine, pagination, typography, rich text, builder, columns | ✅ |
626
+ | 7A–G | Bookmarks, watermarks, hyphenation, TOC, SVG, RTL, encryption | ✅ |
627
+ | 8A–H | Annotations, forms, assembly, callouts, signatures, metadata, hyperlinks, inline formatting | ✅ |
628
+ | 9A–C | Cryptographic signatures (PKCS#7), image floats, font subsetting | ✅ |
629
+ | 10A–D | QR codes, barcodes, Vega-Lite charts, Markdown, templates | ✅ |
630
+ | 11+ | Variable fonts, OpenType features, PDF/A, PDF/UA accessibility | 🔜 |
631
+
632
+ See [docs/ROADMAP.md](docs/ROADMAP.md) for the full plan.
540
633
 
541
634
  ---
542
635
 
543
636
  ## Migration from pdfmake
544
637
 
545
- Coming from pdfmake? See the **[Migration Guide](docs/MIGRATION_FROM_PDFMAKE.md)** for a complete cheat sheet covering every common pdfmake pattern and its pretext-pdf equivalent.
638
+ Coming from pdfmake? See the **[Migration Guide](docs/MIGRATION_FROM_PDFMAKE.md)** every common pdfmake pattern mapped to its pretext-pdf equivalent.
546
639
 
547
640
  ---
548
641
 
@@ -560,9 +653,6 @@ See [CONTRIBUTING.md](CONTRIBUTING.md). TDD approach — write tests first.
560
653
 
561
654
  ## Credits
562
655
 
563
- Built by [Himanshu Jain](https://github.com/Himaan1998Y) on top of:
564
- - **[pretext](https://github.com/chenglou/pretext)** — Text layout engine (Cheng Lou)
565
- - **[pdf-lib](https://github.com/Hopding/pdf-lib)** — PDF manipulation
566
- - **[@napi-rs/canvas](https://github.com/napi-rs/canvas)** — Server-side Canvas API for Node.js
656
+ Built by [Himanshu Jain](https://github.com/Himaan1998Y) on the shoulders of [pretext](https://github.com/chenglou/pretext), [pdf-lib](https://github.com/Hopding/pdf-lib), and [@napi-rs/canvas](https://github.com/napi-rs/canvas).
567
657
 
568
- Questions? [Open an issue](https://github.com/Himaan1998Y/pretext-pdf/issues)
658
+ Questions? [Open an issue](https://github.com/Himaan1998Y/pretext-pdf/issues) — or try it live at the [demo](https://himaan1998y.github.io/pretext-pdf/).