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/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
  [![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
+ [![CI](https://github.com/Himaan1998Y/pretext-pdf/actions/workflows/ci.yml/badge.svg)](https://github.com/Himaan1998Y/pretext-pdf/actions)
9
10
  [![TypeScript](https://img.shields.io/badge/typescript-strict-blue)](https://www.typescriptlang.org/)
10
- [![Tests](https://img.shields.io/badge/tests-75%2B-brightgreen)](#test-coverage)
11
+ [![Tests](https://img.shields.io/badge/tests-146%2B-brightgreen)](#test-coverage)
11
12
  [![License](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
12
13
 
13
14
  ---
14
15
 
15
16
  ## Why pretext-pdf?
16
17
 
17
- ### The Problem
18
- - **pdfmake** is easy but produces mediocre typography (no kerning, ligatures, proper line breaking)
19
- - **Puppeteer** renders beautiful PDFs but requires a 400MB browser and is slow at scale
20
- - **Typst** has perfect typography but is Rust-based (not JavaScript)
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
- ### The Solution
23
- **pretext-pdf** bridges the gap: **declarative + professional typography + lightweight**.
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
- Easy | Professional | Lightweight
27
- pdfmake: ✅ | ❌ | ✅
28
- Puppeteer: ❌ | ✅ | ❌
29
- pretext-pdf: ✅ | ✅ | ✅
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
- ## Features
41
+ ## Output Samples
43
42
 
44
- ### Core Capabilities
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
+ | [![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) |
48
+ | [View source](examples/showcase-invoice.ts) | [View source](examples/showcase-report.ts) | [View source](examples/showcase-resume.ts) |
62
49
 
63
- ## Quick Start
50
+ ---
64
51
 
65
- ### Installation
52
+ ## Install
66
53
 
67
54
  ```bash
68
55
  npm install pretext-pdf
69
56
  ```
70
57
 
71
- ### Basic Example
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: 40, right: 40 },
74
+ margins: { top: 40, bottom: 40, left: 50, right: 50 },
75
+ metadata: { title: 'My Invoice', author: 'Acme Corp' },
79
76
  content: [
80
- {
81
- type: 'heading',
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
- // Write to file or send to client
106
- import fs from 'fs'
107
- fs.writeFileSync('invoice.pdf', pdf)
95
+ writeFileSync('invoice.pdf', pdf)
108
96
  ```
109
97
 
110
- ### Using the Builder API
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('This is a fluent, chainable API.')
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
- ## Documentation
112
+ ## Agent / AI Integration
128
113
 
129
- ### Element Types
114
+ pretext-pdf works great as a tool for AI agents generating PDFs on demand.
130
115
 
131
- | Element | Description |
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
- interface DocConfig {
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
- ```typescript
121
+ // Every PdfDocument is a plain JSON object — perfect for AI generation
217
122
  const pdf = await render({
218
- pageSize: 'A4',
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
- ### Builder API: `createPdf(options) ChainableBuilder`
132
+ ### Key facts for AI agents
225
133
 
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
- ```
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
- ### `assemble(parts): Promise<Uint8Array>`
237
- Merge multiple PDFs into a single document.
140
+ ### Element type reference (quick)
238
141
 
239
- ```typescript
240
- const merged = await assemble([
241
- { doc: docConfig1 },
242
- { pdf: existingPdfBytes },
243
- { doc: docConfig2 },
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
- ### `merge(pdfs): Promise<Uint8Array>`
248
- Convenience function to merge pre-rendered PDFs.
149
+ ---
249
150
 
250
- ```typescript
251
- const combined = await merge([pdf1, pdf2, pdf3])
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
- Phase 7 examples are in the `examples/` directory and can be run via npm scripts:
199
+ Run working examples from the `examples/` directory:
259
200
 
260
201
  ```bash
261
- npm run example:watermark # Watermarks
262
- npm run example:bookmarks # Bookmarks & outline
263
- npm run example:toc # Table of contents
264
- npm run example:rtl # RTL text (Arabic/Hebrew)
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
- **Phase 8 examples** (hyperlinks, forms, document assembly, annotations, fonts, inline formatting, digital signatures) coming soon.
219
+ All examples write output to `output/*.pdf`.
269
220
 
270
221
  ---
271
222
 
272
- ## Performance
223
+ ## API Reference
224
+
225
+ ### `render(doc): Promise<Uint8Array>`
226
+
227
+ ```typescript
228
+ import { render } from 'pretext-pdf'
273
229
 
274
- pretext-pdf is **significantly faster** than Puppeteer for high-volume PDF generation:
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
- - **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)
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
- All errors throw a `PretextPdfError` with a specific code:
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
- console.error(err.code) // e.g., 'FONT_LOAD_FAILED', 'IMAGE_TOO_TALL'
295
- console.error(err.message)
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
- ## Comparison with Alternatives
302
+ ## Troubleshooting
305
303
 
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)
304
+ ### Hyphenation language not found
311
305
 
312
- ### vs. Puppeteer
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)
306
+ ```
307
+ UNSUPPORTED_LANGUAGE: Language 'en-US' not supported
308
+ ```
317
309
 
318
- ### vs. Typst
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
- ## Browser Support
336
+ ### PDF is blank or too small
326
337
 
327
- pretext-pdf is **Node.js only**. It requires a Canvas polyfill for text measurement.
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
- For browser usage, see the [Future Roadmap](#future-roadmap).
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
- All phases have comprehensive test coverage:
353
+ 146 tests across all phases:
337
354
 
338
355
  ```bash
339
- npm test # Run all 75+ tests
340
- npm run test:unit # Unit tests (pure pagination logic)
341
- npm run test:visual # Visual regression tests (pixel-perfect comparison)
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
- 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)
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
- ## Contributing
384
+ ## Rich Text
353
385
 
354
- Contributions welcome! Please:
355
- 1. Write tests first (TDD approach)
356
- 2. Ensure 80%+ code coverage
357
- 3. Run `npm run build && npm test` before submitting PR
358
- 4. Update [CHANGELOG.md](CHANGELOG.md)
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
- ### Near-term (Phase 8)
365
- - ✅ All Phase 8 features (hyperlinks, forms, annotations, assembly, signatures)
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
- ### Future (Phase 9+)
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) — Use freely in commercial and personal projects.
444
+ [MIT](LICENSE)
380
445
 
381
446
  ---
382
447
 
383
448
  ## Credits
384
449
 
385
- Built by [Himanshu Jain](https://github.com/Himanshu-Jain-32) on top of:
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
- - **[fontkit](https://github.com/foliojs/fontkit)** — Font parsing & subsetting
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
- **Happy PDF generating!** 🎉
455
+ Questions? [Open an issue](https://github.com/Himaan1998Y/pretext-pdf/issues)