pretext-pdf 0.2.0 → 0.3.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 +42 -5
- package/README.md +303 -250
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/fonts.d.ts.map +1 -1
- package/dist/fonts.js +22 -6
- package/dist/fonts.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/measure.d.ts.map +1 -1
- package/dist/measure.js +72 -0
- package/dist/measure.js.map +1 -1
- package/dist/paginate.d.ts.map +1 -1
- package/dist/paginate.js +8 -6
- package/dist/paginate.js.map +1 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +294 -0
- package/dist/render.js.map +1 -1
- package/dist/types.d.ts +139 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +86 -1
- package/dist/validate.js.map +1 -1
- package/package.json +103 -79
package/README.md
CHANGED
|
@@ -1,92 +1,81 @@
|
|
|
1
1
|
# pretext-pdf
|
|
2
2
|
|
|
3
3
|
> **Declarative JSON → PDF generation with professional typography.**
|
|
4
|
-
>
|
|
4
|
+
>
|
|
5
5
|
> Build sophisticated, multi-page documents with precise text layout, international support, and zero browser overhead.
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/pretext-pdf)
|
|
8
8
|
[](https://www.npmjs.com/package/pretext-pdf)
|
|
9
|
+
[](https://github.com/Himaan1998Y/pretext-pdf/actions)
|
|
9
10
|
[](https://www.typescriptlang.org/)
|
|
10
|
-
[](#test-coverage)
|
|
11
12
|
[](LICENSE)
|
|
12
13
|
|
|
13
14
|
---
|
|
14
15
|
|
|
15
16
|
## Why pretext-pdf?
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
| | pdfmake | Puppeteer | **pretext-pdf** |
|
|
19
|
+
|---|---|---|---|
|
|
20
|
+
| Easy declarative API | ✅ | ❌ | ✅ |
|
|
21
|
+
| Professional typography | ❌ | ✅ | ✅ |
|
|
22
|
+
| Lightweight (no browser) | ✅ | ❌ | ✅ |
|
|
23
|
+
| International text (RTL/CJK) | ❌ | ✅ | ✅ |
|
|
24
|
+
| Pure Node.js | ✅ | ❌ | ✅ |
|
|
25
|
+
| Hyperlinks + annotations | ❌ | ✅ | ✅ |
|
|
26
|
+
| Document assembly | ❌ | ❌ | ✅ |
|
|
21
27
|
|
|
22
|
-
###
|
|
23
|
-
|
|
28
|
+
### Powered by [pretext](https://github.com/chenglou/pretext)
|
|
29
|
+
|
|
30
|
+
Pretext is a precision text layout engine by [Cheng Lou](https://github.com/chenglou) (React core team, Midjourney).
|
|
24
31
|
|
|
25
32
|
```
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
JSON descriptor → pretext layout → pdf-lib renderer → PDF bytes
|
|
34
|
+
(kerning, (annotations,
|
|
35
|
+
hyphenation, encryption,
|
|
36
|
+
RTL, CJK) hyperlinks)
|
|
30
37
|
```
|
|
31
38
|
|
|
32
|
-
### Powered by [pretext](https://github.com/chenglou/pretext)
|
|
33
|
-
Pretext is a precision text layout engine built by [Cheng Lou](https://github.com/chenglou) (React core team, Midjourney).
|
|
34
|
-
It handles:
|
|
35
|
-
- **Proper line breaking** for justified text and optimal paragraph layout
|
|
36
|
-
- **International text**: CJK, Arabic, Hebrew, Thai, and mixed LTR/RTL content
|
|
37
|
-
- **Fast measurement**: 300-600x faster than DOM reflow
|
|
38
|
-
- **Zero dependencies**: 15KB library, pure TypeScript
|
|
39
|
-
|
|
40
39
|
---
|
|
41
40
|
|
|
42
|
-
##
|
|
41
|
+
## Output Samples
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
- **13 element types**: paragraph, heading, table, image, list, code, blockquote, hr, spacer, page-break, rich-paragraph, SVG, table of contents
|
|
46
|
-
- **Professional typography**: hyphenation, justified text, orphan/widow control, multi-column layout
|
|
47
|
-
- **International support**: RTL text (Arabic/Hebrew), CJK line breaking, per-element direction control
|
|
48
|
-
- **Custom fonts**: Embed TTF fonts with subsetting, bundled Inter font included
|
|
49
|
-
- **Document metadata**: Title, author, subject, keywords, creation date
|
|
50
|
-
- **Headers/footers**: Dynamic `{{pageNumber}}` and `{{totalPages}}` tokens
|
|
51
|
-
- **PDF outlines**: Auto-generated bookmarks from heading structure
|
|
52
|
-
- **Watermarks**: Text or image watermarks on every page
|
|
53
|
-
- **Encryption**: Password-protect PDFs with granular permission control
|
|
54
|
-
- **Hyperlinks**: External URLs, email links, internal page anchors
|
|
55
|
-
- **Comments**: Sticky-note annotations on any element
|
|
56
|
-
- **Forms**: Interactive text fields, checkboxes, radio buttons, dropdowns
|
|
57
|
-
- **SVG support**: Embedded SVG graphics with auto-sizing
|
|
58
|
-
- **Document assembly**: Merge multiple PDFs, attach files
|
|
59
|
-
- **Digital signatures**: Visual signature fields, optional PKCS#7 signing
|
|
43
|
+
Real documents generated with pretext-pdf:
|
|
60
44
|
|
|
61
|
-
|
|
45
|
+
| Invoice | Market Report | Resume / CV |
|
|
46
|
+
|---------|--------------|-------------|
|
|
47
|
+
| [](examples/showcase-invoice.ts) | [](examples/showcase-report.ts) | [](examples/showcase-resume.ts) |
|
|
48
|
+
| [View source](examples/showcase-invoice.ts) | [View source](examples/showcase-report.ts) | [View source](examples/showcase-resume.ts) |
|
|
62
49
|
|
|
63
|
-
|
|
50
|
+
---
|
|
64
51
|
|
|
65
|
-
|
|
52
|
+
## Install
|
|
66
53
|
|
|
67
54
|
```bash
|
|
68
55
|
npm install pretext-pdf
|
|
69
56
|
```
|
|
70
57
|
|
|
71
|
-
|
|
58
|
+
Optional peer dependencies:
|
|
59
|
+
```bash
|
|
60
|
+
npm install @cantoo/pdf-lib # Required for encryption
|
|
61
|
+
npm install @napi-rs/canvas # Required for SVG support (auto-installed in most setups)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Quick Start
|
|
72
67
|
|
|
73
68
|
```typescript
|
|
74
69
|
import { render } from 'pretext-pdf'
|
|
70
|
+
import { writeFileSync } from 'fs'
|
|
75
71
|
|
|
76
72
|
const pdf = await render({
|
|
77
73
|
pageSize: 'A4',
|
|
78
|
-
margins: { top: 40, bottom: 40, left:
|
|
74
|
+
margins: { top: 40, bottom: 40, left: 50, right: 50 },
|
|
75
|
+
metadata: { title: 'My Invoice', author: 'Acme Corp' },
|
|
79
76
|
content: [
|
|
80
|
-
{
|
|
81
|
-
|
|
82
|
-
level: 1,
|
|
83
|
-
text: 'Invoice #12345',
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
type: 'paragraph',
|
|
87
|
-
text: 'Thank you for your business.',
|
|
88
|
-
fontSize: 12,
|
|
89
|
-
},
|
|
77
|
+
{ type: 'heading', level: 1, text: 'Invoice #12345' },
|
|
78
|
+
{ type: 'paragraph', text: 'Thank you for your business.', fontSize: 12 },
|
|
90
79
|
{
|
|
91
80
|
type: 'table',
|
|
92
81
|
columns: [
|
|
@@ -96,193 +85,198 @@ const pdf = await render({
|
|
|
96
85
|
],
|
|
97
86
|
rows: [
|
|
98
87
|
{ Item: 'Professional Services', Qty: '10', Price: '$1,000' },
|
|
99
|
-
{ Item: 'Hosting', Qty: '1', Price: '$500' },
|
|
88
|
+
{ Item: 'Hosting (annual)', Qty: '1', Price: '$500' },
|
|
100
89
|
],
|
|
101
90
|
},
|
|
91
|
+
{ type: 'paragraph', text: 'Total: $1,500', align: 'right', fontWeight: 700 },
|
|
102
92
|
],
|
|
103
93
|
})
|
|
104
94
|
|
|
105
|
-
|
|
106
|
-
import fs from 'fs'
|
|
107
|
-
fs.writeFileSync('invoice.pdf', pdf)
|
|
95
|
+
writeFileSync('invoice.pdf', pdf)
|
|
108
96
|
```
|
|
109
97
|
|
|
110
|
-
###
|
|
98
|
+
### Builder API
|
|
111
99
|
|
|
112
100
|
```typescript
|
|
113
101
|
import { createPdf } from 'pretext-pdf'
|
|
114
102
|
|
|
115
103
|
const pdf = await createPdf({ pageSize: 'A4' })
|
|
116
104
|
.addHeading('My Report', 1)
|
|
117
|
-
.addText('
|
|
118
|
-
.addTable({
|
|
119
|
-
columns: [{ name: 'Col A' }, { name: 'Col B' }],
|
|
120
|
-
rows: [{ 'Col A': 'Value', 'Col B': 'Data' }],
|
|
121
|
-
})
|
|
105
|
+
.addText('Fluent chainable API.')
|
|
106
|
+
.addTable({ columns: [{ name: 'Col A' }, { name: 'Col B' }], rows: [{ 'Col A': 'x', 'Col B': 'y' }] })
|
|
122
107
|
.build()
|
|
123
108
|
```
|
|
124
109
|
|
|
125
110
|
---
|
|
126
111
|
|
|
127
|
-
##
|
|
112
|
+
## Agent / AI Integration
|
|
128
113
|
|
|
129
|
-
|
|
114
|
+
pretext-pdf works great as a tool for AI agents generating PDFs on demand.
|
|
130
115
|
|
|
131
|
-
|
|
132
|
-
|---------|-------------|
|
|
133
|
-
| **paragraph** | Text block with customizable font, size, color, alignment, background |
|
|
134
|
-
| **heading** | H1-H4 with auto-sizing, bold by default, optional bookmark/anchor |
|
|
135
|
-
| **table** | Fixed/proportional columns, colspan support, header repetition on page breaks |
|
|
136
|
-
| **image** | PNG/JPG with auto-detection, sizing, alignment, optional float |
|
|
137
|
-
| **list** | Ordered/unordered, nested, custom markers |
|
|
138
|
-
| **code** | Monospace with background, padding, syntax highlighting |
|
|
139
|
-
| **blockquote** | Left border + background, italic support |
|
|
140
|
-
| **rich-paragraph** | Mixed bold/italic/color/size spans, hyperlinks, annotations |
|
|
141
|
-
| **svg** | Embedded SVG graphics, auto-sizing, multi-page support |
|
|
142
|
-
| **toc** | Auto-generated table of contents with accurate page numbers |
|
|
143
|
-
| **hr** | Horizontal rule with customizable thickness/color |
|
|
144
|
-
| **spacer** | Fixed-height gap |
|
|
145
|
-
| **page-break** | Force new page |
|
|
146
|
-
| **comment** | Sticky-note annotation |
|
|
147
|
-
| **form-field** | Interactive text input, checkbox, radio, dropdown, button |
|
|
148
|
-
| **callout** | Side box / margin note |
|
|
149
|
-
|
|
150
|
-
### Document Config
|
|
116
|
+
### Quick pattern for LLMs
|
|
151
117
|
|
|
152
118
|
```typescript
|
|
153
|
-
|
|
154
|
-
// Page layout
|
|
155
|
-
pageSize?: 'A4' | 'Letter' | 'A3' | 'Legal' | 'A5' | 'Tabloid' | [width, height]
|
|
156
|
-
margins?: { top: number; bottom: number; left: number; right: number }
|
|
157
|
-
|
|
158
|
-
// Typography
|
|
159
|
-
defaultFont?: string // Default font family (default: 'Inter')
|
|
160
|
-
defaultFontSize?: number // Default size in pt (default: 12)
|
|
161
|
-
lineHeight?: number // Line height multiplier (default: 1.5)
|
|
162
|
-
hyphenation?: { language: 'en-US' | 'de-DE' | ... }
|
|
163
|
-
|
|
164
|
-
// Document metadata
|
|
165
|
-
title?: string
|
|
166
|
-
author?: string
|
|
167
|
-
subject?: string
|
|
168
|
-
keywords?: string[]
|
|
169
|
-
creator?: string
|
|
170
|
-
producer?: string
|
|
171
|
-
language?: string // BCP 47 tag (e.g., 'en-US', 'ar')
|
|
172
|
-
|
|
173
|
-
// Features
|
|
174
|
-
watermark?: WatermarkSpec // Text or image watermark on every page
|
|
175
|
-
bookmarks?: { minLevel: 1; maxLevel: 3 } // Auto-generate outline from headings
|
|
176
|
-
encryption?: EncryptionSpec // Password protection with permission control
|
|
177
|
-
signature?: SignatureSpec // Digital signature field (+ optional PKCS#7 signing)
|
|
178
|
-
|
|
179
|
-
// Content
|
|
180
|
-
content: ContentElement[]
|
|
181
|
-
header?: HeaderFooterSpec // Repeated header on every page
|
|
182
|
-
footer?: HeaderFooterSpec // Repeated footer on every page
|
|
183
|
-
}
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
### Feature Matrix
|
|
187
|
-
|
|
188
|
-
| Feature | Phase | Status |
|
|
189
|
-
|---------|-------|--------|
|
|
190
|
-
| Core rendering | 1-4 | ✅ Complete |
|
|
191
|
-
| Rich text / Builder API | 5 | ✅ Complete |
|
|
192
|
-
| Advanced features | 6 | ✅ Complete |
|
|
193
|
-
| Bookmarks / Outline | 7A | ✅ Complete |
|
|
194
|
-
| Watermarks | 7B | ✅ Complete |
|
|
195
|
-
| Hyphenation | 7C | ✅ Complete |
|
|
196
|
-
| Table of contents | 7D | ✅ Complete |
|
|
197
|
-
| SVG support | 7E | ✅ Complete |
|
|
198
|
-
| RTL text support | 7F | ✅ Complete |
|
|
199
|
-
| Encryption | 7G | ✅ Complete |
|
|
200
|
-
| Hyperlinks | 8G | ✅ Complete |
|
|
201
|
-
| Comments/Annotations | 8A | ✅ Complete |
|
|
202
|
-
| Forms | 8B | ✅ Complete |
|
|
203
|
-
| Multi-file assembly | 8C | ✅ Complete |
|
|
204
|
-
| Font subsetting | 8F | ✅ Complete |
|
|
205
|
-
| Inline formatting | 8H | ✅ Complete |
|
|
206
|
-
| Digital signatures | 8E | ✅ Complete |
|
|
207
|
-
| Advanced layout | 8D | ✅ Complete |
|
|
208
|
-
|
|
209
|
-
---
|
|
210
|
-
|
|
211
|
-
## API Reference
|
|
212
|
-
|
|
213
|
-
### `render(doc: DocConfig): Promise<Uint8Array>`
|
|
214
|
-
Render a document configuration to PDF bytes.
|
|
119
|
+
import { render } from 'pretext-pdf'
|
|
215
120
|
|
|
216
|
-
|
|
121
|
+
// Every PdfDocument is a plain JSON object — perfect for AI generation
|
|
217
122
|
const pdf = await render({
|
|
218
|
-
|
|
219
|
-
content: [
|
|
123
|
+
metadata: { title: 'AI-Generated Report' },
|
|
124
|
+
content: [
|
|
125
|
+
{ type: 'heading', level: 1, text: 'Summary' },
|
|
126
|
+
{ type: 'paragraph', text: 'Generated content here.' },
|
|
127
|
+
// ... AI fills this array
|
|
128
|
+
]
|
|
220
129
|
})
|
|
221
|
-
// pdf is a Uint8Array — write to file or send to client
|
|
222
130
|
```
|
|
223
131
|
|
|
224
|
-
###
|
|
132
|
+
### Key facts for AI agents
|
|
225
133
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
.addImage(imageBytes, { width: 200 })
|
|
232
|
-
.addPageBreak()
|
|
233
|
-
.build()
|
|
234
|
-
```
|
|
134
|
+
- `content` is an array of typed elements — each has a `type` field
|
|
135
|
+
- All fields are optional except `type` and element-specific required fields (e.g. `text`, `level`)
|
|
136
|
+
- Errors are typed: `err.code` tells you exactly what went wrong
|
|
137
|
+
- `render()` is fully async, safe to `await` in any context
|
|
138
|
+
- Works in Node.js 18+ and modern browsers (with `@napi-rs/canvas` for SVG)
|
|
235
139
|
|
|
236
|
-
###
|
|
237
|
-
Merge multiple PDFs into a single document.
|
|
140
|
+
### Element type reference (quick)
|
|
238
141
|
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
])
|
|
142
|
+
```
|
|
143
|
+
paragraph heading(1-4) spacer hr page-break
|
|
144
|
+
table image svg list code
|
|
145
|
+
blockquote rich-paragraph callout comment form-field
|
|
146
|
+
toc
|
|
245
147
|
```
|
|
246
148
|
|
|
247
|
-
|
|
248
|
-
Convenience function to merge pre-rendered PDFs.
|
|
149
|
+
---
|
|
249
150
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
151
|
+
## Features
|
|
152
|
+
|
|
153
|
+
### Element Types
|
|
154
|
+
|
|
155
|
+
| Element | What it does |
|
|
156
|
+
|---------|-------------|
|
|
157
|
+
| `paragraph` | Text block — font, size, color, align, background, letterSpacing, smallCaps |
|
|
158
|
+
| `heading` | H1–H4 with bookmarks, URL links, internal anchors |
|
|
159
|
+
| `table` | Fixed/proportional columns, colspan, repeating headers across page breaks |
|
|
160
|
+
| `image` | PNG/JPG/WebP with sizing, alignment, auto-format detection |
|
|
161
|
+
| `list` | Ordered/unordered, nested, custom markers |
|
|
162
|
+
| `code` | Monospace block with background and padding |
|
|
163
|
+
| `blockquote` | Left border + background |
|
|
164
|
+
| `rich-paragraph` | Mixed bold/italic/color/size/super/subscript spans with inline hyperlinks |
|
|
165
|
+
| `svg` | Embedded SVG graphics with auto-sizing from viewBox |
|
|
166
|
+
| `toc` | Auto-generated table of contents with accurate page numbers (two-pass) |
|
|
167
|
+
| `comment` | PDF sticky-note annotation (visible in Acrobat/Preview sidebar) |
|
|
168
|
+
| `hr` | Horizontal rule |
|
|
169
|
+
| `spacer` | Fixed-height gap |
|
|
170
|
+
| `page-break` | Force new page |
|
|
171
|
+
|
|
172
|
+
### Document Features
|
|
173
|
+
|
|
174
|
+
| Feature | Config key | Notes |
|
|
175
|
+
|---------|-----------|-------|
|
|
176
|
+
| Watermarks | `doc.watermark` | Text or image, opacity, rotation |
|
|
177
|
+
| Encryption | `doc.encryption` | Password + granular permissions |
|
|
178
|
+
| PDF Bookmarks | `doc.bookmarks` | Auto-generated from headings |
|
|
179
|
+
| Hyphenation | `doc.hyphenation` | Liang's algorithm, `language: 'en-us'` |
|
|
180
|
+
| Headers/Footers | `doc.header` / `doc.footer` | `{{pageNumber}}` / `{{totalPages}}` tokens |
|
|
181
|
+
| Metadata | `doc.metadata` | Title, author, subject, keywords, `language` (PDF /Lang), `producer` |
|
|
182
|
+
|
|
183
|
+
### Phase 8 Features
|
|
184
|
+
|
|
185
|
+
| Feature | API |
|
|
186
|
+
|---------|-----|
|
|
187
|
+
| **Hyperlinks** | `paragraph.url`, `heading.url`, `heading.anchor`, `span.href` |
|
|
188
|
+
| **Inline formatting** | `span.verticalAlign: 'superscript'\|'subscript'`, `paragraph.letterSpacing`, `heading.smallCaps` |
|
|
189
|
+
| **Sticky notes** | `{ type: 'comment', contents: '...' }`, `paragraph.annotation` |
|
|
190
|
+
| **Document assembly** | `merge(pdfs)`, `assemble(parts)` |
|
|
191
|
+
| **Interactive forms** | `{ type: 'form-field', fieldType: 'text'\|'checkbox'\|'radio'\|'dropdown'\|'button' }`, `doc.flattenForms` |
|
|
192
|
+
| **Signature placeholder** | `doc.signature: { signerName, reason, location, x, y, page }` |
|
|
193
|
+
| **Callout boxes** | `{ type: 'callout', content, style: 'info'\|'warning'\|'tip'\|'note', title }` |
|
|
253
194
|
|
|
254
195
|
---
|
|
255
196
|
|
|
256
197
|
## Examples
|
|
257
198
|
|
|
258
|
-
|
|
199
|
+
Run working examples from the `examples/` directory:
|
|
259
200
|
|
|
260
201
|
```bash
|
|
261
|
-
|
|
262
|
-
npm run example
|
|
263
|
-
npm run example:
|
|
264
|
-
npm run example:
|
|
202
|
+
# Phase 7 examples
|
|
203
|
+
npm run example # Basic invoice
|
|
204
|
+
npm run example:watermark # Text/image watermarks
|
|
205
|
+
npm run example:bookmarks # PDF outline/bookmarks
|
|
206
|
+
npm run example:toc # Auto table of contents
|
|
207
|
+
npm run example:rtl # Arabic/Hebrew RTL text
|
|
265
208
|
npm run example:encryption # Password-protected PDF
|
|
209
|
+
|
|
210
|
+
# Phase 8 examples
|
|
211
|
+
npm run example:hyperlinks # External links, email links, internal anchors
|
|
212
|
+
npm run example:annotations # Sticky notes on elements
|
|
213
|
+
npm run example:assembly # Merge and assemble multiple PDFs
|
|
214
|
+
npm run example:inline # Superscript, subscript, letter-spacing, small-caps
|
|
215
|
+
npm run example:forms # Interactive form fields (text, checkbox, radio, dropdown)
|
|
216
|
+
npm run example:callout # Callout boxes (info, warning, tip, note presets)
|
|
266
217
|
```
|
|
267
218
|
|
|
268
|
-
|
|
219
|
+
All examples write output to `output/*.pdf`.
|
|
269
220
|
|
|
270
221
|
---
|
|
271
222
|
|
|
272
|
-
##
|
|
223
|
+
## API Reference
|
|
224
|
+
|
|
225
|
+
### `render(doc): Promise<Uint8Array>`
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
import { render } from 'pretext-pdf'
|
|
273
229
|
|
|
274
|
-
|
|
230
|
+
const pdf = await render({
|
|
231
|
+
pageSize: 'A4', // 'A4' | 'A3' | 'A5' | 'Letter' | 'Legal' | [w, h]
|
|
232
|
+
margins: { top: 72, bottom: 72, left: 72, right: 72 },
|
|
233
|
+
defaultFont: 'Inter', // Inter 400 bundled; load others via doc.fonts
|
|
234
|
+
defaultFontSize: 12,
|
|
235
|
+
metadata: {
|
|
236
|
+
title: 'Document Title',
|
|
237
|
+
author: 'Author Name',
|
|
238
|
+
subject: 'Description',
|
|
239
|
+
keywords: ['pdf', 'report'],
|
|
240
|
+
},
|
|
241
|
+
watermark: { text: 'DRAFT', opacity: 0.15, rotation: -45 },
|
|
242
|
+
encryption: { userPassword: 'open', ownerPassword: 'admin', permissions: { printing: true, copying: false } },
|
|
243
|
+
bookmarks: { minLevel: 1, maxLevel: 3 },
|
|
244
|
+
hyphenation: { language: 'en-us', minWordLength: 6 }, // ⚠️ Use lowercase: 'en-us' not 'en-US' — matches the npm package name hyphenation.en-us
|
|
245
|
+
header: { text: 'My Document — {{pageNumber}} of {{totalPages}}', align: 'right' },
|
|
246
|
+
footer: { text: 'Confidential', align: 'center', color: '#999999' },
|
|
247
|
+
content: [ /* ContentElement[] */ ],
|
|
248
|
+
})
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### `merge(pdfs): Promise<Uint8Array>`
|
|
275
252
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
253
|
+
Combine pre-rendered PDFs:
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
import { merge } from 'pretext-pdf'
|
|
257
|
+
|
|
258
|
+
const combined = await merge([coverPdf, bodyPdf, appendixPdf])
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### `assemble(parts): Promise<Uint8Array>`
|
|
262
|
+
|
|
263
|
+
Mix new document configs with existing PDFs:
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
import { assemble } from 'pretext-pdf'
|
|
267
|
+
|
|
268
|
+
const report = await assemble([
|
|
269
|
+
{ pdf: existingCoverPdf },
|
|
270
|
+
{ doc: { content: [...] } }, // rendered fresh
|
|
271
|
+
{ pdf: standardTermsPdf },
|
|
272
|
+
])
|
|
273
|
+
```
|
|
280
274
|
|
|
281
275
|
---
|
|
282
276
|
|
|
283
277
|
## Error Handling
|
|
284
278
|
|
|
285
|
-
|
|
279
|
+
Every error throws `PretextPdfError` with a typed code:
|
|
286
280
|
|
|
287
281
|
```typescript
|
|
288
282
|
import { render, PretextPdfError } from 'pretext-pdf'
|
|
@@ -291,112 +285,171 @@ try {
|
|
|
291
285
|
const pdf = await render(config)
|
|
292
286
|
} catch (err) {
|
|
293
287
|
if (err instanceof PretextPdfError) {
|
|
294
|
-
|
|
295
|
-
|
|
288
|
+
switch (err.code) {
|
|
289
|
+
case 'VALIDATION_ERROR': // Invalid config
|
|
290
|
+
case 'FONT_LOAD_FAILED': // Font file not found
|
|
291
|
+
case 'IMAGE_TOO_TALL': // Image doesn't fit on page
|
|
292
|
+
case 'ENCRYPTION_NOT_AVAILABLE': // @cantoo/pdf-lib not installed
|
|
293
|
+
case 'ASSEMBLY_EMPTY': // merge/assemble called with empty array
|
|
294
|
+
// ... see CHANGELOG.md for full list
|
|
295
|
+
}
|
|
296
296
|
}
|
|
297
297
|
}
|
|
298
298
|
```
|
|
299
299
|
|
|
300
|
-
See [CHANGELOG.md](CHANGELOG.md) for all error codes.
|
|
301
|
-
|
|
302
300
|
---
|
|
303
301
|
|
|
304
|
-
##
|
|
302
|
+
## Troubleshooting
|
|
305
303
|
|
|
306
|
-
###
|
|
307
|
-
- ✅ Better typography (kerning, ligatures, proper line breaking)
|
|
308
|
-
- ✅ International support (CJK, Arabic, Hebrew)
|
|
309
|
-
- ✅ Smaller bundle (~15KB vs ~400KB)
|
|
310
|
-
- ❌ Fewer built-in features (pdfmake has table styling, QR codes)
|
|
304
|
+
### Hyphenation language not found
|
|
311
305
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
- ✅ No browser installation required
|
|
316
|
-
- ❌ Can't render arbitrary HTML/CSS (pretext-pdf is declarative)
|
|
306
|
+
```
|
|
307
|
+
UNSUPPORTED_LANGUAGE: Language 'en-US' not supported
|
|
308
|
+
```
|
|
317
309
|
|
|
318
|
-
|
|
319
|
-
- ✅ JavaScript ecosystem (can use npm packages)
|
|
320
|
-
- ✅ Faster compilation
|
|
321
|
-
- ❌ Typst has more advanced layout features (floats, complex positioning)
|
|
310
|
+
Use **lowercase** language codes that match the npm package name:
|
|
322
311
|
|
|
323
|
-
|
|
312
|
+
```typescript
|
|
313
|
+
// Wrong — 'en-US' fails on Linux (case-sensitive filesystem)
|
|
314
|
+
hyphenation: { language: 'en-US' }
|
|
315
|
+
|
|
316
|
+
// Correct — matches 'hyphenation.en-us' package name
|
|
317
|
+
hyphenation: { language: 'en-us' }
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Encryption requires optional dependency
|
|
321
|
+
|
|
322
|
+
Install `@cantoo/pdf-lib` separately before using `doc.encryption`:
|
|
323
|
+
|
|
324
|
+
```bash
|
|
325
|
+
npm install @cantoo/pdf-lib
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### SVG rendering requires optional dependency
|
|
329
|
+
|
|
330
|
+
Install `@napi-rs/canvas` for SVG support:
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
npm install @napi-rs/canvas
|
|
334
|
+
```
|
|
324
335
|
|
|
325
|
-
|
|
336
|
+
### PDF is blank or too small
|
|
326
337
|
|
|
327
|
-
|
|
328
|
-
The library automatically installs `@napi-rs/canvas` (included) for server-side rendering.
|
|
338
|
+
Check margins — if left+right margins exceed page width, content width becomes negative:
|
|
329
339
|
|
|
330
|
-
|
|
340
|
+
```typescript
|
|
341
|
+
// For narrow pages, reduce margins:
|
|
342
|
+
margins: { top: 36, bottom: 36, left: 36, right: 36 }
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Form fields not interactive after flattenForms
|
|
346
|
+
|
|
347
|
+
`flattenForms: true` bakes fields into static content — by design. Remove it to keep interactive.
|
|
331
348
|
|
|
332
349
|
---
|
|
333
350
|
|
|
334
351
|
## Test Coverage
|
|
335
352
|
|
|
336
|
-
|
|
353
|
+
146 tests across all phases:
|
|
337
354
|
|
|
338
355
|
```bash
|
|
339
|
-
npm test
|
|
340
|
-
npm run test:unit
|
|
341
|
-
npm run test:
|
|
356
|
+
npm test # All 146 tests
|
|
357
|
+
npm run test:unit # Validation, builder, rich-text unit tests
|
|
358
|
+
npm run test:e2e # End-to-end render tests
|
|
359
|
+
npm run test:phase-7 # Phase 7A-7G feature tests
|
|
360
|
+
npm run test:phase-8 # Phase 8A-8H feature tests
|
|
342
361
|
```
|
|
343
362
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## Custom Fonts
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
const pdf = await render({
|
|
369
|
+
fonts: [
|
|
370
|
+
{ family: 'Roboto', weight: 400, src: '/path/to/Roboto-Regular.ttf' },
|
|
371
|
+
{ family: 'Roboto', weight: 700, src: '/path/to/Roboto-Bold.ttf' },
|
|
372
|
+
{ family: 'Roboto', style: 'italic', src: '/path/to/Roboto-Italic.ttf' },
|
|
373
|
+
],
|
|
374
|
+
defaultFont: 'Roboto',
|
|
375
|
+
content: [
|
|
376
|
+
{ type: 'paragraph', text: 'Uses Roboto font' },
|
|
377
|
+
{ type: 'paragraph', text: 'Bold text', fontWeight: 700 },
|
|
378
|
+
],
|
|
379
|
+
})
|
|
380
|
+
```
|
|
349
381
|
|
|
350
382
|
---
|
|
351
383
|
|
|
352
|
-
##
|
|
384
|
+
## Rich Text
|
|
353
385
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
386
|
+
```typescript
|
|
387
|
+
{
|
|
388
|
+
type: 'rich-paragraph',
|
|
389
|
+
fontSize: 13,
|
|
390
|
+
spans: [
|
|
391
|
+
{ text: 'Normal ' },
|
|
392
|
+
{ text: 'bold', fontWeight: 700 },
|
|
393
|
+
{ text: ' and ', fontStyle: 'italic' },
|
|
394
|
+
{ text: 'colored', color: '#e63946' },
|
|
395
|
+
{ text: ' and ' },
|
|
396
|
+
{ text: 'linked', href: 'https://example.com', underline: true, color: '#0070f3' },
|
|
397
|
+
{ text: '. Also: E=mc' },
|
|
398
|
+
{ text: '2', verticalAlign: 'superscript' },
|
|
399
|
+
{ text: ' and H' },
|
|
400
|
+
{ text: '2', verticalAlign: 'subscript' },
|
|
401
|
+
{ text: 'O.' },
|
|
402
|
+
],
|
|
403
|
+
}
|
|
404
|
+
```
|
|
359
405
|
|
|
360
406
|
---
|
|
361
407
|
|
|
362
408
|
## Roadmap
|
|
363
409
|
|
|
364
|
-
|
|
365
|
-
|
|
410
|
+
| Phase | Feature | Status |
|
|
411
|
+
|-------|---------|--------|
|
|
412
|
+
| 1–4 | Core engine, pagination, typography | ✅ |
|
|
413
|
+
| 5 | Rich text / builder API | ✅ |
|
|
414
|
+
| 6 | Headers/footers, columns, decoration | ✅ |
|
|
415
|
+
| 7A | PDF Bookmarks / Outline | ✅ |
|
|
416
|
+
| 7B | Watermarks | ✅ |
|
|
417
|
+
| 7C | Hyphenation | ✅ |
|
|
418
|
+
| 7D | Table of Contents | ✅ |
|
|
419
|
+
| 7E | SVG support | ✅ |
|
|
420
|
+
| 7F | RTL text (Arabic/Hebrew) | ✅ |
|
|
421
|
+
| 7G | Encryption | ✅ |
|
|
422
|
+
| 8A | Sticky note annotations | ✅ |
|
|
423
|
+
| 8B | Interactive forms (text/checkbox/radio/dropdown/button) | ✅ |
|
|
424
|
+
| 8C | Document assembly (merge + assemble) | ✅ |
|
|
425
|
+
| 8D | Callout boxes (info/warning/tip/note) | ✅ |
|
|
426
|
+
| 8E | Signature placeholder | ✅ |
|
|
427
|
+
| 8F | Document metadata (language, producer) | ✅ |
|
|
428
|
+
| 8G | Hyperlinks | ✅ |
|
|
429
|
+
| 8H | Inline formatting (super/subscript, letterSpacing, smallCaps) | ✅ |
|
|
430
|
+
| 9A | Digital signatures (cryptographic, PKCS#7) | 🔜 |
|
|
431
|
+
| 9B | Image floats (text flowing around images) | 🔜 |
|
|
432
|
+
| 9C | Font subsetting pre-computation | 🔜 |
|
|
433
|
+
|
|
434
|
+
---
|
|
435
|
+
|
|
436
|
+
## Contributing
|
|
366
437
|
|
|
367
|
-
|
|
368
|
-
- Justified text alignment (currently left/right/center only)
|
|
369
|
-
- Enhanced text decorations (underline color, underline style)
|
|
370
|
-
- Font subsetting optimization (reduce file size for limited character sets)
|
|
371
|
-
- Browser compatibility (via WASM)
|
|
372
|
-
- PDF/A compliance (archival format)
|
|
373
|
-
- Accessibility tags (tagged PDF for screen readers)
|
|
438
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). TDD approach — write tests first.
|
|
374
439
|
|
|
375
440
|
---
|
|
376
441
|
|
|
377
442
|
## License
|
|
378
443
|
|
|
379
|
-
[MIT](LICENSE)
|
|
444
|
+
[MIT](LICENSE)
|
|
380
445
|
|
|
381
446
|
---
|
|
382
447
|
|
|
383
448
|
## Credits
|
|
384
449
|
|
|
385
|
-
Built by [Himanshu Jain](https://github.com/
|
|
450
|
+
Built by [Himanshu Jain](https://github.com/Himaan1998Y) on top of:
|
|
386
451
|
- **[pretext](https://github.com/chenglou/pretext)** — Text layout engine (Cheng Lou)
|
|
387
452
|
- **[pdf-lib](https://github.com/Hopding/pdf-lib)** — PDF manipulation
|
|
388
|
-
- **[
|
|
389
|
-
- **[@napi-rs/canvas](https://github.com/napi-rs/canvas)** — Server-side Canvas for Node.js
|
|
390
|
-
|
|
391
|
-
---
|
|
392
|
-
|
|
393
|
-
## Questions?
|
|
394
|
-
|
|
395
|
-
- 📖 Read the [CHANGELOG.md](CHANGELOG.md) for all features and error codes
|
|
396
|
-
- 🔍 Check the `examples/` directory for working code samples
|
|
397
|
-
- 🐛 Report issues on [GitHub](https://github.com/Himanshu-Jain-32/pretext-pdf/issues)
|
|
398
|
-
- 💬 Discussions & feature requests welcome
|
|
399
|
-
|
|
400
|
-
---
|
|
453
|
+
- **[@napi-rs/canvas](https://github.com/napi-rs/canvas)** — Server-side Canvas API for Node.js
|
|
401
454
|
|
|
402
|
-
|
|
455
|
+
Questions? [Open an issue](https://github.com/Himaan1998Y/pretext-pdf/issues)
|