pretext-pdf 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -5
- package/README.md +220 -259
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.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 +91 -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,159 @@ 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
|
+
## Features
|
|
128
113
|
|
|
129
114
|
### Element Types
|
|
130
115
|
|
|
131
|
-
| Element |
|
|
116
|
+
| Element | What it does |
|
|
132
117
|
|---------|-------------|
|
|
133
|
-
|
|
|
134
|
-
|
|
|
135
|
-
|
|
|
136
|
-
|
|
|
137
|
-
|
|
|
138
|
-
|
|
|
139
|
-
|
|
|
140
|
-
|
|
|
141
|
-
|
|
|
142
|
-
|
|
|
143
|
-
|
|
|
144
|
-
|
|
|
145
|
-
|
|
|
146
|
-
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
118
|
+
| `paragraph` | Text block — font, size, color, align, background, letterSpacing, smallCaps |
|
|
119
|
+
| `heading` | H1–H4 with bookmarks, URL links, internal anchors |
|
|
120
|
+
| `table` | Fixed/proportional columns, colspan, repeating headers across page breaks |
|
|
121
|
+
| `image` | PNG/JPG/WebP with sizing, alignment, auto-format detection |
|
|
122
|
+
| `list` | Ordered/unordered, nested, custom markers |
|
|
123
|
+
| `code` | Monospace block with background and padding |
|
|
124
|
+
| `blockquote` | Left border + background |
|
|
125
|
+
| `rich-paragraph` | Mixed bold/italic/color/size/super/subscript spans with inline hyperlinks |
|
|
126
|
+
| `svg` | Embedded SVG graphics with auto-sizing from viewBox |
|
|
127
|
+
| `toc` | Auto-generated table of contents with accurate page numbers (two-pass) |
|
|
128
|
+
| `comment` | PDF sticky-note annotation (visible in Acrobat/Preview sidebar) |
|
|
129
|
+
| `hr` | Horizontal rule |
|
|
130
|
+
| `spacer` | Fixed-height gap |
|
|
131
|
+
| `page-break` | Force new page |
|
|
132
|
+
|
|
133
|
+
### Document Features
|
|
134
|
+
|
|
135
|
+
| Feature | Config key | Notes |
|
|
136
|
+
|---------|-----------|-------|
|
|
137
|
+
| Watermarks | `doc.watermark` | Text or image, opacity, rotation |
|
|
138
|
+
| Encryption | `doc.encryption` | Password + granular permissions |
|
|
139
|
+
| PDF Bookmarks | `doc.bookmarks` | Auto-generated from headings |
|
|
140
|
+
| Hyphenation | `doc.hyphenation` | Liang's algorithm, `language: 'en-us'` |
|
|
141
|
+
| Headers/Footers | `doc.header` / `doc.footer` | `{{pageNumber}}` / `{{totalPages}}` tokens |
|
|
142
|
+
| Metadata | `doc.metadata` | Title, author, subject, keywords, `language` (PDF /Lang), `producer` |
|
|
143
|
+
|
|
144
|
+
### Phase 8 Features
|
|
145
|
+
|
|
146
|
+
| Feature | API |
|
|
147
|
+
|---------|-----|
|
|
148
|
+
| **Hyperlinks** | `paragraph.url`, `heading.url`, `heading.anchor`, `span.href` |
|
|
149
|
+
| **Inline formatting** | `span.verticalAlign: 'superscript'\|'subscript'`, `paragraph.letterSpacing`, `heading.smallCaps` |
|
|
150
|
+
| **Sticky notes** | `{ type: 'comment', contents: '...' }`, `paragraph.annotation` |
|
|
151
|
+
| **Document assembly** | `merge(pdfs)`, `assemble(parts)` |
|
|
152
|
+
| **Interactive forms** | `{ type: 'form-field', fieldType: 'text'\|'checkbox'\|'radio'\|'dropdown'\|'button' }`, `doc.flattenForms` |
|
|
153
|
+
| **Signature placeholder** | `doc.signature: { signerName, reason, location, x, y, page }` |
|
|
154
|
+
| **Callout boxes** | `{ type: 'callout', content, style: 'info'\|'warning'\|'tip'\|'note', title }` |
|
|
151
155
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
}
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Examples
|
|
159
|
+
|
|
160
|
+
Run working examples from the `examples/` directory:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
# Phase 7 examples
|
|
164
|
+
npm run example # Basic invoice
|
|
165
|
+
npm run example:watermark # Text/image watermarks
|
|
166
|
+
npm run example:bookmarks # PDF outline/bookmarks
|
|
167
|
+
npm run example:toc # Auto table of contents
|
|
168
|
+
npm run example:rtl # Arabic/Hebrew RTL text
|
|
169
|
+
npm run example:encryption # Password-protected PDF
|
|
170
|
+
|
|
171
|
+
# Phase 8 examples
|
|
172
|
+
npm run example:hyperlinks # External links, email links, internal anchors
|
|
173
|
+
npm run example:annotations # Sticky notes on elements
|
|
174
|
+
npm run example:assembly # Merge and assemble multiple PDFs
|
|
175
|
+
npm run example:inline # Superscript, subscript, letter-spacing, small-caps
|
|
176
|
+
npm run example:forms # Interactive form fields (text, checkbox, radio, dropdown)
|
|
177
|
+
npm run example:callout # Callout boxes (info, warning, tip, note presets)
|
|
184
178
|
```
|
|
185
179
|
|
|
186
|
-
|
|
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 |
|
|
180
|
+
All examples write output to `output/*.pdf`.
|
|
208
181
|
|
|
209
182
|
---
|
|
210
183
|
|
|
211
184
|
## API Reference
|
|
212
185
|
|
|
213
|
-
### `render(doc
|
|
214
|
-
Render a document configuration to PDF bytes.
|
|
186
|
+
### `render(doc): Promise<Uint8Array>`
|
|
215
187
|
|
|
216
188
|
```typescript
|
|
189
|
+
import { render } from 'pretext-pdf'
|
|
190
|
+
|
|
217
191
|
const pdf = await render({
|
|
218
|
-
pageSize: 'A4',
|
|
219
|
-
|
|
192
|
+
pageSize: 'A4', // 'A4' | 'A3' | 'A5' | 'Letter' | 'Legal' | [w, h]
|
|
193
|
+
margins: { top: 72, bottom: 72, left: 72, right: 72 },
|
|
194
|
+
defaultFont: 'Inter', // Inter 400 bundled; load others via doc.fonts
|
|
195
|
+
defaultFontSize: 12,
|
|
196
|
+
metadata: {
|
|
197
|
+
title: 'Document Title',
|
|
198
|
+
author: 'Author Name',
|
|
199
|
+
subject: 'Description',
|
|
200
|
+
keywords: ['pdf', 'report'],
|
|
201
|
+
},
|
|
202
|
+
watermark: { text: 'DRAFT', opacity: 0.15, rotation: -45 },
|
|
203
|
+
encryption: { userPassword: 'open', ownerPassword: 'admin', permissions: { printing: true, copying: false } },
|
|
204
|
+
bookmarks: { minLevel: 1, maxLevel: 3 },
|
|
205
|
+
hyphenation: { language: 'en-us', minWordLength: 6 },
|
|
206
|
+
header: { text: 'My Document — {{pageNumber}} of {{totalPages}}', align: 'right' },
|
|
207
|
+
footer: { text: 'Confidential', align: 'center', color: '#999999' },
|
|
208
|
+
content: [ /* ContentElement[] */ ],
|
|
220
209
|
})
|
|
221
|
-
// pdf is a Uint8Array — write to file or send to client
|
|
222
210
|
```
|
|
223
211
|
|
|
224
|
-
###
|
|
225
|
-
|
|
226
|
-
```typescript
|
|
227
|
-
const pdf = await createPdf({ pageSize: 'A4', defaultFontSize: 12 })
|
|
228
|
-
.addHeading('Title', 1)
|
|
229
|
-
.addText('Paragraph text')
|
|
230
|
-
.addTable({ columns: [...], rows: [...] })
|
|
231
|
-
.addImage(imageBytes, { width: 200 })
|
|
232
|
-
.addPageBreak()
|
|
233
|
-
.build()
|
|
234
|
-
```
|
|
212
|
+
### `merge(pdfs): Promise<Uint8Array>`
|
|
235
213
|
|
|
236
|
-
|
|
237
|
-
Merge multiple PDFs into a single document.
|
|
214
|
+
Combine pre-rendered PDFs:
|
|
238
215
|
|
|
239
216
|
```typescript
|
|
240
|
-
|
|
241
|
-
{ doc: docConfig1 },
|
|
242
|
-
{ pdf: existingPdfBytes },
|
|
243
|
-
{ doc: docConfig2 },
|
|
244
|
-
])
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
### `merge(pdfs): Promise<Uint8Array>`
|
|
248
|
-
Convenience function to merge pre-rendered PDFs.
|
|
217
|
+
import { merge } from 'pretext-pdf'
|
|
249
218
|
|
|
250
|
-
|
|
251
|
-
const combined = await merge([pdf1, pdf2, pdf3])
|
|
219
|
+
const combined = await merge([coverPdf, bodyPdf, appendixPdf])
|
|
252
220
|
```
|
|
253
221
|
|
|
254
|
-
|
|
222
|
+
### `assemble(parts): Promise<Uint8Array>`
|
|
255
223
|
|
|
256
|
-
|
|
224
|
+
Mix new document configs with existing PDFs:
|
|
257
225
|
|
|
258
|
-
|
|
226
|
+
```typescript
|
|
227
|
+
import { assemble } from 'pretext-pdf'
|
|
259
228
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
npm run example:encryption # Password-protected PDF
|
|
229
|
+
const report = await assemble([
|
|
230
|
+
{ pdf: existingCoverPdf },
|
|
231
|
+
{ doc: { content: [...] } }, // rendered fresh
|
|
232
|
+
{ pdf: standardTermsPdf },
|
|
233
|
+
])
|
|
266
234
|
```
|
|
267
235
|
|
|
268
|
-
**Phase 8 examples** (hyperlinks, forms, document assembly, annotations, fonts, inline formatting, digital signatures) coming soon.
|
|
269
|
-
|
|
270
|
-
---
|
|
271
|
-
|
|
272
|
-
## Performance
|
|
273
|
-
|
|
274
|
-
pretext-pdf is **significantly faster** than Puppeteer for high-volume PDF generation:
|
|
275
|
-
|
|
276
|
-
- **Single document**: 50-200ms (depends on content complexity)
|
|
277
|
-
- **Batch (100 documents)**: ~5-20ms per document on modern hardware
|
|
278
|
-
- **Memory**: <10MB per document (Puppeteer: ~50-100MB per instance)
|
|
279
|
-
- **Bundle size**: 15KB engine + pdf-lib dependencies (~200KB gzipped)
|
|
280
|
-
|
|
281
236
|
---
|
|
282
237
|
|
|
283
238
|
## Error Handling
|
|
284
239
|
|
|
285
|
-
|
|
240
|
+
Every error throws `PretextPdfError` with a typed code:
|
|
286
241
|
|
|
287
242
|
```typescript
|
|
288
243
|
import { render, PretextPdfError } from 'pretext-pdf'
|
|
@@ -291,112 +246,118 @@ try {
|
|
|
291
246
|
const pdf = await render(config)
|
|
292
247
|
} catch (err) {
|
|
293
248
|
if (err instanceof PretextPdfError) {
|
|
294
|
-
|
|
295
|
-
|
|
249
|
+
switch (err.code) {
|
|
250
|
+
case 'VALIDATION_ERROR': // Invalid config
|
|
251
|
+
case 'FONT_LOAD_FAILED': // Font file not found
|
|
252
|
+
case 'IMAGE_TOO_TALL': // Image doesn't fit on page
|
|
253
|
+
case 'ENCRYPTION_NOT_AVAILABLE': // @cantoo/pdf-lib not installed
|
|
254
|
+
case 'ASSEMBLY_EMPTY': // merge/assemble called with empty array
|
|
255
|
+
// ... see CHANGELOG.md for full list
|
|
256
|
+
}
|
|
296
257
|
}
|
|
297
258
|
}
|
|
298
259
|
```
|
|
299
260
|
|
|
300
|
-
See [CHANGELOG.md](CHANGELOG.md) for all error codes.
|
|
301
|
-
|
|
302
261
|
---
|
|
303
262
|
|
|
304
|
-
##
|
|
305
|
-
|
|
306
|
-
### vs. pdfmake
|
|
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)
|
|
263
|
+
## Test Coverage
|
|
311
264
|
|
|
312
|
-
|
|
313
|
-
- ✅ 100x faster for bulk PDF generation
|
|
314
|
-
- ✅ 40x smaller memory footprint
|
|
315
|
-
- ✅ No browser installation required
|
|
316
|
-
- ❌ Can't render arbitrary HTML/CSS (pretext-pdf is declarative)
|
|
265
|
+
113 tests across all phases:
|
|
317
266
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
-
|
|
321
|
-
|
|
267
|
+
```bash
|
|
268
|
+
npm test # All 113 tests
|
|
269
|
+
npm run test:unit # Validation, builder, rich-text unit tests
|
|
270
|
+
npm run test:e2e # End-to-end render tests
|
|
271
|
+
npm run test:phases # Phase 7A-7G + Phase 8A/8C/8G/8H feature tests
|
|
272
|
+
```
|
|
322
273
|
|
|
323
274
|
---
|
|
324
275
|
|
|
325
|
-
##
|
|
276
|
+
## Custom Fonts
|
|
326
277
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
278
|
+
```typescript
|
|
279
|
+
const pdf = await render({
|
|
280
|
+
fonts: [
|
|
281
|
+
{ family: 'Roboto', weight: 400, src: '/path/to/Roboto-Regular.ttf' },
|
|
282
|
+
{ family: 'Roboto', weight: 700, src: '/path/to/Roboto-Bold.ttf' },
|
|
283
|
+
{ family: 'Roboto', style: 'italic', src: '/path/to/Roboto-Italic.ttf' },
|
|
284
|
+
],
|
|
285
|
+
defaultFont: 'Roboto',
|
|
286
|
+
content: [
|
|
287
|
+
{ type: 'paragraph', text: 'Uses Roboto font' },
|
|
288
|
+
{ type: 'paragraph', text: 'Bold text', fontWeight: 700 },
|
|
289
|
+
],
|
|
290
|
+
})
|
|
291
|
+
```
|
|
331
292
|
|
|
332
293
|
---
|
|
333
294
|
|
|
334
|
-
##
|
|
335
|
-
|
|
336
|
-
All phases have comprehensive test coverage:
|
|
295
|
+
## Rich Text
|
|
337
296
|
|
|
338
|
-
```
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
297
|
+
```typescript
|
|
298
|
+
{
|
|
299
|
+
type: 'rich-paragraph',
|
|
300
|
+
fontSize: 13,
|
|
301
|
+
spans: [
|
|
302
|
+
{ text: 'Normal ' },
|
|
303
|
+
{ text: 'bold', fontWeight: 700 },
|
|
304
|
+
{ text: ' and ', fontStyle: 'italic' },
|
|
305
|
+
{ text: 'colored', color: '#e63946' },
|
|
306
|
+
{ text: ' and ' },
|
|
307
|
+
{ text: 'linked', href: 'https://example.com', underline: true, color: '#0070f3' },
|
|
308
|
+
{ text: '. Also: E=mc' },
|
|
309
|
+
{ text: '2', verticalAlign: 'superscript' },
|
|
310
|
+
{ text: ' and H' },
|
|
311
|
+
{ text: '2', verticalAlign: 'subscript' },
|
|
312
|
+
{ text: 'O.' },
|
|
313
|
+
],
|
|
314
|
+
}
|
|
342
315
|
```
|
|
343
316
|
|
|
344
|
-
Tests include:
|
|
345
|
-
- Unit tests for validation, pagination, text measurement
|
|
346
|
-
- End-to-end tests for complete document rendering
|
|
347
|
-
- Visual regression tests with pixel-perfect comparison (pixelmatch)
|
|
348
|
-
- Feature-specific tests for each phase (Phase 7A-7G, 8A-8H)
|
|
349
|
-
|
|
350
317
|
---
|
|
351
318
|
|
|
352
|
-
##
|
|
319
|
+
## Roadmap
|
|
353
320
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
321
|
+
| Phase | Feature | Status |
|
|
322
|
+
|-------|---------|--------|
|
|
323
|
+
| 1–4 | Core engine, pagination, typography | ✅ |
|
|
324
|
+
| 5 | Rich text, builder API | ✅ |
|
|
325
|
+
| 6 | Headers/footers, columns, decoration | ✅ |
|
|
326
|
+
| 7A | PDF Bookmarks / Outline | ✅ |
|
|
327
|
+
| 7B | Watermarks | ✅ |
|
|
328
|
+
| 7C | Hyphenation | ✅ |
|
|
329
|
+
| 7D | Table of Contents | ✅ |
|
|
330
|
+
| 7E | SVG support | ✅ |
|
|
331
|
+
| 7F | RTL text (Arabic/Hebrew) | ✅ |
|
|
332
|
+
| 7G | Encryption | ✅ |
|
|
333
|
+
| 8G | Hyperlinks | ✅ |
|
|
334
|
+
| 8H | Inline formatting (super/subscript, letterSpacing, smallCaps) | ✅ |
|
|
335
|
+
| 8A | Sticky note annotations | ✅ |
|
|
336
|
+
| 8C | Document assembly (merge + assemble) | ✅ |
|
|
337
|
+
| 8F | Document metadata (language, producer) | ✅ |
|
|
338
|
+
| 8B | Interactive forms (text/checkbox/radio/dropdown/button) | ✅ |
|
|
339
|
+
| 8E | Signature placeholder | ✅ |
|
|
340
|
+
| 8D | Callout boxes (info/warning/tip/note) | ✅ |
|
|
359
341
|
|
|
360
342
|
---
|
|
361
343
|
|
|
362
|
-
##
|
|
363
|
-
|
|
364
|
-
### Near-term (Phase 8)
|
|
365
|
-
- ✅ All Phase 8 features (hyperlinks, forms, annotations, assembly, signatures)
|
|
344
|
+
## Contributing
|
|
366
345
|
|
|
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)
|
|
346
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). TDD approach — write tests first.
|
|
374
347
|
|
|
375
348
|
---
|
|
376
349
|
|
|
377
350
|
## License
|
|
378
351
|
|
|
379
|
-
[MIT](LICENSE)
|
|
352
|
+
[MIT](LICENSE)
|
|
380
353
|
|
|
381
354
|
---
|
|
382
355
|
|
|
383
356
|
## Credits
|
|
384
357
|
|
|
385
|
-
Built by [Himanshu Jain](https://github.com/
|
|
358
|
+
Built by [Himanshu Jain](https://github.com/Himaan1998Y) on top of:
|
|
386
359
|
- **[pretext](https://github.com/chenglou/pretext)** — Text layout engine (Cheng Lou)
|
|
387
360
|
- **[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
|
-
---
|
|
361
|
+
- **[@napi-rs/canvas](https://github.com/napi-rs/canvas)** — Server-side Canvas API for Node.js
|
|
401
362
|
|
|
402
|
-
|
|
363
|
+
Questions? [Open an issue](https://github.com/Himaan1998Y/pretext-pdf/issues)
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Error codes for PretextPdfError */
|
|
2
|
-
export type ErrorCode = 'VALIDATION_ERROR' | 'FONT_LOAD_FAILED' | 'FONT_EMBED_FAILED' | 'PAGE_TOO_SMALL' | 'CANVAS_UNAVAILABLE' | 'PAGE_LIMIT_EXCEEDED' | 'IMAGE_LOAD_FAILED' | 'IMAGE_FORMAT_MISMATCH' | 'IMAGE_TOO_TALL' | 'TABLE_COLUMN_OVERFLOW' | 'TABLE_COLUMN_TOO_NARROW' | 'MONOSPACE_FONT_REQUIRED' | 'ITALIC_FONT_NOT_LOADED' | 'FONT_NOT_LOADED' | 'COLUMN_WIDTH_TOO_NARROW' | 'COLSPAN_OVERFLOW' | 'ENCRYPTION_NOT_AVAILABLE' | 'UNSUPPORTED_LANGUAGE' | 'SVG_RENDER_FAILED' | 'WATERMARK_ROTATION_OUT_OF_RANGE' | 'SVG_INVALID_MARKUP' | 'ASSEMBLY_EMPTY' | 'ASSEMBLY_FAILED';
|
|
2
|
+
export type ErrorCode = 'VALIDATION_ERROR' | 'FONT_LOAD_FAILED' | 'FONT_EMBED_FAILED' | 'PAGE_TOO_SMALL' | 'CANVAS_UNAVAILABLE' | 'PAGE_LIMIT_EXCEEDED' | 'IMAGE_LOAD_FAILED' | 'IMAGE_FORMAT_MISMATCH' | 'IMAGE_TOO_TALL' | 'TABLE_COLUMN_OVERFLOW' | 'TABLE_COLUMN_TOO_NARROW' | 'MONOSPACE_FONT_REQUIRED' | 'ITALIC_FONT_NOT_LOADED' | 'FONT_NOT_LOADED' | 'COLUMN_WIDTH_TOO_NARROW' | 'COLSPAN_OVERFLOW' | 'ENCRYPTION_NOT_AVAILABLE' | 'UNSUPPORTED_LANGUAGE' | 'SVG_RENDER_FAILED' | 'WATERMARK_ROTATION_OUT_OF_RANGE' | 'SVG_INVALID_MARKUP' | 'ASSEMBLY_EMPTY' | 'ASSEMBLY_FAILED' | 'FORM_FIELD_NAME_DUPLICATE' | 'FORM_FLATTEN_FAILED';
|
|
3
3
|
export declare class PretextPdfError extends Error {
|
|
4
4
|
readonly code: ErrorCode;
|
|
5
5
|
constructor(code: ErrorCode, message: string);
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,MAAM,MAAM,SAAS,GACjB,kBAAkB,GAClB,kBAAkB,GAClB,mBAAmB,GACnB,gBAAgB,GAChB,oBAAoB,GACpB,qBAAqB,GACrB,mBAAmB,GACnB,uBAAuB,GACvB,gBAAgB,GAChB,uBAAuB,GACvB,yBAAyB,GACzB,yBAAyB,GACzB,wBAAwB,GACxB,iBAAiB,GACjB,yBAAyB,GACzB,kBAAkB,GAClB,0BAA0B,GAC1B,sBAAsB,GACtB,mBAAmB,GACnB,iCAAiC,GACjC,oBAAoB,GACpB,gBAAgB,GAChB,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,MAAM,MAAM,SAAS,GACjB,kBAAkB,GAClB,kBAAkB,GAClB,mBAAmB,GACnB,gBAAgB,GAChB,oBAAoB,GACpB,qBAAqB,GACrB,mBAAmB,GACnB,uBAAuB,GACvB,gBAAgB,GAChB,uBAAuB,GACvB,yBAAyB,GACzB,yBAAyB,GACzB,wBAAwB,GACxB,iBAAiB,GACjB,yBAAyB,GACzB,kBAAkB,GAClB,0BAA0B,GAC1B,sBAAsB,GACtB,mBAAmB,GACnB,iCAAiC,GACjC,oBAAoB,GACpB,gBAAgB,GAChB,iBAAiB,GACjB,2BAA2B,GAC3B,qBAAqB,CAAA;AAEzB,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;gBAEZ,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM;CAS7C"}
|
package/dist/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AA4BA,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAC/B,IAAI,CAAW;IAExB,YAAY,IAAe,EAAE,OAAe;QAC1C,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAA;QAC7B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,qCAAqC;QACrC,IAAI,KAAK,CAAC,iBAAiB,EAAE,CAAC;YAC5B,KAAK,CAAC,iBAAiB,CAAC,IAAI,EAAE,eAAe,CAAC,CAAA;QAChD,CAAC;IACH,CAAC;CACF"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PdfDocument } from './types.js';
|
|
2
|
-
export type { PdfDocument, DocumentMetadata, ContentElement, ParagraphElement, HeadingElement, SpacerElement, TableElement, ColumnDef, TableRow, TableCell, ImageElement, SvgElement, ListElement, ListItem, HorizontalRuleElement, PageBreakElement, CodeBlockElement, RichParagraphElement, BlockquoteElement, InlineSpan, RichLine, RichFragment, FontSpec, HeaderFooterSpec, WatermarkSpec, EncryptionSpec, BookmarkConfig, HyphenationConfig, Margins, CommentElement, AnnotationSpec, AssemblyPart, } from './types.js';
|
|
2
|
+
export type { PdfDocument, DocumentMetadata, ContentElement, ParagraphElement, HeadingElement, SpacerElement, TableElement, ColumnDef, TableRow, TableCell, ImageElement, SvgElement, ListElement, ListItem, HorizontalRuleElement, PageBreakElement, CodeBlockElement, RichParagraphElement, BlockquoteElement, InlineSpan, RichLine, RichFragment, FontSpec, HeaderFooterSpec, WatermarkSpec, EncryptionSpec, SignatureSpec, BookmarkConfig, HyphenationConfig, Margins, CommentElement, CalloutElement, AnnotationSpec, AssemblyPart, FormFieldElement, } from './types.js';
|
|
3
3
|
export { PretextPdfError } from './errors.js';
|
|
4
4
|
export type { ErrorCode } from './errors.js';
|
|
5
5
|
export type { NamedPageSize } from './page-sizes.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAmD,MAAM,YAAY,CAAA;AAY9F,YAAY,EACV,WAAW,EACX,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,YAAY,EACZ,SAAS,EACT,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,UAAU,EACV,WAAW,EACX,QAAQ,EACR,qBAAqB,EACrB,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,EACpB,iBAAiB,EACjB,UAAU,EACV,QAAQ,EACR,YAAY,EACZ,QAAQ,EACR,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,OAAO,EACP,cAAc,EACd,cAAc,EACd,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAmD,MAAM,YAAY,CAAA;AAY9F,YAAY,EACV,WAAW,EACX,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,YAAY,EACZ,SAAS,EACT,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,UAAU,EACV,WAAW,EACX,QAAQ,EACR,qBAAqB,EACrB,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,EACpB,iBAAiB,EACjB,UAAU,EACV,QAAQ,EACR,YAAY,EACZ,QAAQ,EACR,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,aAAa,EACb,cAAc,EACd,iBAAiB,EACjB,OAAO,EACP,cAAc,EACd,cAAc,EACd,cAAc,EACd,YAAY,EACZ,gBAAgB,GACjB,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC7C,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAC5C,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACxC,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAErD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,MAAM,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAuIlE;AAED;;;;GAIG;AACH,wBAAsB,KAAK,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC,CAgBnE;AAED;;;;GAIG;AACH,wBAAsB,QAAQ,CAAC,KAAK,EAAE,OAAO,YAAY,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC,CAoB9F"}
|