@xenterprises/fastify-xpdf 1.0.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/.env.example +11 -0
- package/CHANGELOG.md +106 -0
- package/LICENSE +15 -0
- package/QUICK_START.md +462 -0
- package/README.md +580 -0
- package/SECURITY.md +417 -0
- package/package.json +57 -0
- package/server/app.js +151 -0
- package/src/index.js +7 -0
- package/src/services/forms.js +163 -0
- package/src/services/generator.js +147 -0
- package/src/services/merger.js +115 -0
- package/src/utils/helpers.js +220 -0
- package/src/xPDF.js +126 -0
- package/test/xPDF.test.js +903 -0
package/.env.example
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Puppeteer Configuration (optional - uses defaults if not provided)
|
|
2
|
+
PUPPETEER_HEADLESS=true
|
|
3
|
+
PUPPETEER_ARGS=--no-sandbox,--disable-setuid-sandbox
|
|
4
|
+
|
|
5
|
+
# xPDF Configuration
|
|
6
|
+
PDF_DEFAULT_FORMAT=A4
|
|
7
|
+
PDF_DEFAULT_FOLDER=pdfs
|
|
8
|
+
PDF_USE_STORAGE=false
|
|
9
|
+
|
|
10
|
+
# Port for example server
|
|
11
|
+
PORT=3000
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0] - 2024-12-29
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Initial release of xPDF Fastify v5 plugin
|
|
12
|
+
- HTML to PDF generation using Puppeteer
|
|
13
|
+
- Markdown to PDF generation with styled formatting
|
|
14
|
+
- PDF form filling with support for text, checkbox, radio, and dropdown fields
|
|
15
|
+
- PDF form flattening (make fields non-editable)
|
|
16
|
+
- PDF merging - combine multiple PDFs into one
|
|
17
|
+
- Optional xStorage integration for cloud storage (S3-compatible)
|
|
18
|
+
- Lazy Puppeteer browser initialization for resource efficiency
|
|
19
|
+
- Automatic browser cleanup on Fastify shutdown
|
|
20
|
+
- Comprehensive error handling and input validation
|
|
21
|
+
- Configurable page formats (A4, Letter, A3, A5, etc.)
|
|
22
|
+
- Support for custom margins and landscape orientation
|
|
23
|
+
- Helper utilities for PDF operations
|
|
24
|
+
- 64 comprehensive tests across 14 test suites
|
|
25
|
+
- Complete documentation (README + QUICK_START)
|
|
26
|
+
- Example HTTP server implementation
|
|
27
|
+
|
|
28
|
+
### Features
|
|
29
|
+
- **Library pattern**: Decorate methods on Fastify instance, not HTTP routes
|
|
30
|
+
- **xStorage integration**: Optional, auto-detected, seamless cloud storage
|
|
31
|
+
- **Configuration profiles**: Development, production, and high-performance configs
|
|
32
|
+
- **Memory efficient**: Pages closed after use, lazy initialization
|
|
33
|
+
- **Concurrent operations**: Stress tested with 5+ simultaneous PDF generations
|
|
34
|
+
- **Unicode support**: Handles emoji, CJK, Arabic, and other Unicode content
|
|
35
|
+
|
|
36
|
+
### Documentation
|
|
37
|
+
- Complete README with API reference
|
|
38
|
+
- Quick Start guide with 10-step setup
|
|
39
|
+
- 11 real-world usage examples
|
|
40
|
+
- Configuration guide with profiles
|
|
41
|
+
- Troubleshooting section
|
|
42
|
+
- Best practices guide
|
|
43
|
+
|
|
44
|
+
### Security
|
|
45
|
+
- Input validation on HTML/Markdown content
|
|
46
|
+
- PDF buffer validation before processing
|
|
47
|
+
- No eval or dynamic code execution
|
|
48
|
+
- Sandboxing configuration for Puppeteer
|
|
49
|
+
- Content type enforcement for storage
|
|
50
|
+
|
|
51
|
+
### Tested Environments
|
|
52
|
+
- Node.js v20+ (ES modules)
|
|
53
|
+
- Fastify v5.1.0+
|
|
54
|
+
- macOS and Linux
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## [Future Versions - Planned]
|
|
59
|
+
|
|
60
|
+
### [1.1.0] - Planned
|
|
61
|
+
- Configuration validation using Zod
|
|
62
|
+
- Browser initialization retry logic
|
|
63
|
+
- Concurrency limiting with optional queue support
|
|
64
|
+
- Prometheus metrics integration
|
|
65
|
+
- Error code standardization
|
|
66
|
+
- Production error sanitization
|
|
67
|
+
|
|
68
|
+
### [1.2.0] - Planned
|
|
69
|
+
- PDF text extraction
|
|
70
|
+
- PDF page splitting
|
|
71
|
+
- PDF watermarking
|
|
72
|
+
- PDF encryption with password protection
|
|
73
|
+
- Digital signature support
|
|
74
|
+
- Content Security Policy headers for HTML
|
|
75
|
+
- Optional HTML sanitization integration
|
|
76
|
+
|
|
77
|
+
### [2.0.0] - Planned (Breaking Changes)
|
|
78
|
+
- Separate Puppeteer pool service option
|
|
79
|
+
- WebWorker-based page handling
|
|
80
|
+
- Streaming API for large PDFs
|
|
81
|
+
- Performance optimizations
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Notes
|
|
86
|
+
|
|
87
|
+
### Dependencies
|
|
88
|
+
- **puppeteer**: ^23.0.0 (HTML to PDF rendering engine)
|
|
89
|
+
- **pdf-lib**: ^1.17.1 (PDF manipulation and form operations)
|
|
90
|
+
- **marked**: ^15.0.0 (Markdown to HTML conversion)
|
|
91
|
+
- **fastify-plugin**: ^5.0.0 (Fastify plugin wrapper)
|
|
92
|
+
|
|
93
|
+
### System Requirements
|
|
94
|
+
- Node.js >= 20.0.0
|
|
95
|
+
- Fastify >= 5.0.0
|
|
96
|
+
- Chrome/Chromium browser (downloaded automatically by Puppeteer)
|
|
97
|
+
- At least 512MB RAM (recommended 1GB+)
|
|
98
|
+
|
|
99
|
+
### License
|
|
100
|
+
ISC
|
|
101
|
+
|
|
102
|
+
### Author
|
|
103
|
+
Tim Mushen
|
|
104
|
+
|
|
105
|
+
### Repository
|
|
106
|
+
https://gitlab.com/x-enterprises/fastify-plugins/fastify-x-pdf
|
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Tim Mushen
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/QUICK_START.md
ADDED
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
# Quick Start Guide
|
|
2
|
+
|
|
3
|
+
Get up and running with xPDF in 5 minutes.
|
|
4
|
+
|
|
5
|
+
## 1. Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @xenterprises/fastify-xpdf puppeteer marked pdf-lib
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**For optional storage integration:**
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @xenterprises/fastify-xstorage
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## 2. Basic Setup
|
|
18
|
+
|
|
19
|
+
```javascript
|
|
20
|
+
import Fastify from "fastify";
|
|
21
|
+
import xPDF from "@xenterprises/fastify-xpdf";
|
|
22
|
+
|
|
23
|
+
const fastify = Fastify({ logger: true });
|
|
24
|
+
|
|
25
|
+
// Register xPDF
|
|
26
|
+
await fastify.register(xPDF, {
|
|
27
|
+
headless: true,
|
|
28
|
+
useStorage: false,
|
|
29
|
+
defaultFolder: "pdfs",
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
await fastify.listen({ port: 3000 });
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 3. Generate PDF from HTML
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
// Simple HTML to PDF
|
|
39
|
+
const result = await fastify.xPDF.generateFromHtml(
|
|
40
|
+
"<h1>Hello World</h1><p>This is a PDF</p>",
|
|
41
|
+
{
|
|
42
|
+
filename: "hello.pdf",
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
console.log(result);
|
|
47
|
+
// {
|
|
48
|
+
// buffer: Buffer,
|
|
49
|
+
// filename: "hello.pdf",
|
|
50
|
+
// size: 12345
|
|
51
|
+
// }
|
|
52
|
+
|
|
53
|
+
// Save buffer to file
|
|
54
|
+
import fs from "fs";
|
|
55
|
+
fs.writeFileSync("hello.pdf", result.buffer);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 4. Generate PDF from Markdown
|
|
59
|
+
|
|
60
|
+
```javascript
|
|
61
|
+
const markdown = `
|
|
62
|
+
# My Document
|
|
63
|
+
|
|
64
|
+
This is **bold** and this is *italic*.
|
|
65
|
+
|
|
66
|
+
## Section
|
|
67
|
+
|
|
68
|
+
- Point 1
|
|
69
|
+
- Point 2
|
|
70
|
+
- Point 3
|
|
71
|
+
|
|
72
|
+
\`\`\`javascript
|
|
73
|
+
console.log("Code blocks work!");
|
|
74
|
+
\`\`\`
|
|
75
|
+
`;
|
|
76
|
+
|
|
77
|
+
const result = await fastify.xPDF.generateFromMarkdown(markdown, {
|
|
78
|
+
filename: "document.pdf",
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
fs.writeFileSync("document.pdf", result.buffer);
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## 5. Fill PDF Forms
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
// Read a PDF form
|
|
88
|
+
const pdfBuffer = fs.readFileSync("form-template.pdf");
|
|
89
|
+
|
|
90
|
+
// Fill form fields
|
|
91
|
+
const result = await fastify.xPDF.fillForm(
|
|
92
|
+
pdfBuffer,
|
|
93
|
+
{
|
|
94
|
+
firstName: "John",
|
|
95
|
+
lastName: "Doe",
|
|
96
|
+
email: "john@example.com",
|
|
97
|
+
agreeToTerms: true,
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
flatten: true, // Make non-editable
|
|
101
|
+
filename: "form-filled.pdf",
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
fs.writeFileSync("form-filled.pdf", result.buffer);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## 6. List Form Fields
|
|
109
|
+
|
|
110
|
+
Before filling a form, see what fields are available:
|
|
111
|
+
|
|
112
|
+
```javascript
|
|
113
|
+
const pdfBuffer = fs.readFileSync("form-template.pdf");
|
|
114
|
+
|
|
115
|
+
const fields = await fastify.xPDF.listFormFields(pdfBuffer);
|
|
116
|
+
|
|
117
|
+
console.log(fields);
|
|
118
|
+
// [
|
|
119
|
+
// { name: "firstName", type: "text", value: null },
|
|
120
|
+
// { name: "lastName", type: "text", value: null },
|
|
121
|
+
// { name: "email", type: "text", value: null },
|
|
122
|
+
// { name: "agreeToTerms", type: "checkbox", value: false }
|
|
123
|
+
// ]
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## 7. Merge PDFs
|
|
127
|
+
|
|
128
|
+
```javascript
|
|
129
|
+
// Read multiple PDFs
|
|
130
|
+
const page1 = fs.readFileSync("page1.pdf");
|
|
131
|
+
const page2 = fs.readFileSync("page2.pdf");
|
|
132
|
+
const page3 = fs.readFileSync("page3.pdf");
|
|
133
|
+
|
|
134
|
+
// Merge them
|
|
135
|
+
const result = await fastify.xPDF.mergePDFs([page1, page2, page3], {
|
|
136
|
+
filename: "merged.pdf",
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
console.log(result);
|
|
140
|
+
// {
|
|
141
|
+
// buffer: Buffer,
|
|
142
|
+
// filename: "merged.pdf",
|
|
143
|
+
// size: 345678,
|
|
144
|
+
// pageCount: 15 // Total pages
|
|
145
|
+
// }
|
|
146
|
+
|
|
147
|
+
fs.writeFileSync("merged.pdf", result.buffer);
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## 8. With xStorage Integration
|
|
151
|
+
|
|
152
|
+
```javascript
|
|
153
|
+
import xStorage from "@xenterprises/fastify-xstorage";
|
|
154
|
+
import xPDF from "@xenterprises/fastify-xpdf";
|
|
155
|
+
|
|
156
|
+
const fastify = Fastify();
|
|
157
|
+
|
|
158
|
+
// Register xStorage first
|
|
159
|
+
await fastify.register(xStorage, {
|
|
160
|
+
endpoint: "https://nyc3.digitaloceanspaces.com",
|
|
161
|
+
region: "nyc3",
|
|
162
|
+
accessKeyId: process.env.DO_SPACES_KEY,
|
|
163
|
+
secretAccessKey: process.env.DO_SPACES_SECRET,
|
|
164
|
+
bucket: "my-bucket",
|
|
165
|
+
publicUrl: "https://my-bucket.nyc3.digitaloceanspaces.com",
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Register xPDF
|
|
169
|
+
await fastify.register(xPDF, {
|
|
170
|
+
useStorage: true,
|
|
171
|
+
defaultFolder: "pdfs",
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Now PDFs save automatically to storage
|
|
175
|
+
const result = await fastify.xPDF.generateFromHtml("<h1>Invoice</h1>", {
|
|
176
|
+
filename: "invoice.pdf",
|
|
177
|
+
saveToStorage: true,
|
|
178
|
+
folder: "invoices",
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
console.log(result.url);
|
|
182
|
+
// https://my-bucket.nyc3.digitaloceanspaces.com/invoices/invoice-xxx.pdf
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## 9. HTTP Endpoints
|
|
186
|
+
|
|
187
|
+
Use xPDF in your routes:
|
|
188
|
+
|
|
189
|
+
```javascript
|
|
190
|
+
// HTML to PDF endpoint
|
|
191
|
+
fastify.post("/api/pdf/html", async (request, reply) => {
|
|
192
|
+
const { html, filename = "document.pdf" } = request.body;
|
|
193
|
+
|
|
194
|
+
const result = await fastify.xPDF.generateFromHtml(html, {
|
|
195
|
+
filename,
|
|
196
|
+
saveToStorage: true,
|
|
197
|
+
folder: "generated",
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
success: true,
|
|
202
|
+
url: result.url,
|
|
203
|
+
filename: result.filename,
|
|
204
|
+
};
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Markdown to PDF endpoint
|
|
208
|
+
fastify.post("/api/pdf/markdown", async (request, reply) => {
|
|
209
|
+
const { markdown } = request.body;
|
|
210
|
+
|
|
211
|
+
const result = await fastify.xPDF.generateFromMarkdown(markdown, {
|
|
212
|
+
saveToStorage: true,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
return { success: true, url: result.url };
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Form filling endpoint
|
|
219
|
+
fastify.post("/api/pdf/fill-form", async (request, reply) => {
|
|
220
|
+
const { formUrl, fieldValues } = request.body;
|
|
221
|
+
|
|
222
|
+
// Download form from storage
|
|
223
|
+
const key = formUrl.replace(process.env.STORAGE_PUBLIC_URL + "/", "");
|
|
224
|
+
const pdfBuffer = await fastify.xStorage.download(key);
|
|
225
|
+
|
|
226
|
+
// Fill and save
|
|
227
|
+
const result = await fastify.xPDF.fillForm(pdfBuffer, fieldValues, {
|
|
228
|
+
flatten: true,
|
|
229
|
+
saveToStorage: true,
|
|
230
|
+
folder: "submitted-forms",
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
return { success: true, url: result.url };
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Merge PDFs endpoint
|
|
237
|
+
fastify.post("/api/pdf/merge", async (request, reply) => {
|
|
238
|
+
const { pdfUrls } = request.body;
|
|
239
|
+
|
|
240
|
+
// Download all PDFs
|
|
241
|
+
const buffers = await Promise.all(
|
|
242
|
+
pdfUrls.map((url) => {
|
|
243
|
+
const key = url.replace(process.env.STORAGE_PUBLIC_URL + "/", "");
|
|
244
|
+
return fastify.xStorage.download(key);
|
|
245
|
+
})
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
// Merge
|
|
249
|
+
const result = await fastify.xPDF.mergePDFs(buffers, {
|
|
250
|
+
saveToStorage: true,
|
|
251
|
+
folder: "merged",
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
success: true,
|
|
256
|
+
url: result.url,
|
|
257
|
+
pages: result.pageCount,
|
|
258
|
+
};
|
|
259
|
+
});
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## 10. Common Use Cases
|
|
263
|
+
|
|
264
|
+
### Invoice Generation
|
|
265
|
+
|
|
266
|
+
```javascript
|
|
267
|
+
const invoiceHtml = `
|
|
268
|
+
<h1>Invoice #INV-2024-001</h1>
|
|
269
|
+
<p><strong>Date:</strong> January 1, 2024</p>
|
|
270
|
+
<table>
|
|
271
|
+
<tr>
|
|
272
|
+
<th>Item</th>
|
|
273
|
+
<th>Qty</th>
|
|
274
|
+
<th>Price</th>
|
|
275
|
+
</tr>
|
|
276
|
+
<tr>
|
|
277
|
+
<td>Widget</td>
|
|
278
|
+
<td>10</td>
|
|
279
|
+
<td>$100.00</td>
|
|
280
|
+
</tr>
|
|
281
|
+
</table>
|
|
282
|
+
<h2>Total: $1,000.00</h2>
|
|
283
|
+
`;
|
|
284
|
+
|
|
285
|
+
const result = await fastify.xPDF.generateFromHtml(invoiceHtml, {
|
|
286
|
+
filename: "invoice-001.pdf",
|
|
287
|
+
saveToStorage: true,
|
|
288
|
+
folder: "invoices",
|
|
289
|
+
});
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Certificate Generation
|
|
293
|
+
|
|
294
|
+
```javascript
|
|
295
|
+
const certificateHtml = `
|
|
296
|
+
<div style="text-align: center; padding: 2cm;">
|
|
297
|
+
<h1>Certificate of Completion</h1>
|
|
298
|
+
<p style="font-size: 18px; margin: 2cm 0;">
|
|
299
|
+
This certifies that <strong>Jane Doe</strong>
|
|
300
|
+
</p>
|
|
301
|
+
<p>has successfully completed the course</p>
|
|
302
|
+
<p style="font-size: 16px; color: #0066cc;">
|
|
303
|
+
<strong>Advanced Web Development</strong>
|
|
304
|
+
</p>
|
|
305
|
+
<p style="margin-top: 3cm;">January 1, 2024</p>
|
|
306
|
+
</div>
|
|
307
|
+
`;
|
|
308
|
+
|
|
309
|
+
const result = await fastify.xPDF.generateFromHtml(certificateHtml, {
|
|
310
|
+
filename: "certificate-jane-doe.pdf",
|
|
311
|
+
format: "A4",
|
|
312
|
+
landscape: false,
|
|
313
|
+
});
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Report with Multiple Sections
|
|
317
|
+
|
|
318
|
+
```javascript
|
|
319
|
+
const sections = [
|
|
320
|
+
{ title: "Executive Summary", content: "..." },
|
|
321
|
+
{ title: "Findings", content: "..." },
|
|
322
|
+
{ title: "Recommendations", content: "..." },
|
|
323
|
+
];
|
|
324
|
+
|
|
325
|
+
const reportHtml = sections
|
|
326
|
+
.map((s) => `<h2>${s.title}</h2><p>${s.content}</p>`)
|
|
327
|
+
.join("");
|
|
328
|
+
|
|
329
|
+
const result = await fastify.xPDF.generateFromHtml(reportHtml, {
|
|
330
|
+
filename: "report.pdf",
|
|
331
|
+
saveToStorage: true,
|
|
332
|
+
folder: "reports",
|
|
333
|
+
});
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Tips & Tricks
|
|
337
|
+
|
|
338
|
+
### Styled HTML Documents
|
|
339
|
+
|
|
340
|
+
```javascript
|
|
341
|
+
const styledHtml = `
|
|
342
|
+
<style>
|
|
343
|
+
body { font-family: Arial, sans-serif; }
|
|
344
|
+
h1 { color: #0066cc; border-bottom: 2px solid #0066cc; }
|
|
345
|
+
table { width: 100%; border-collapse: collapse; }
|
|
346
|
+
th { background: #f4f4f4; padding: 8px; text-align: left; }
|
|
347
|
+
td { padding: 8px; border-bottom: 1px solid #ddd; }
|
|
348
|
+
</style>
|
|
349
|
+
|
|
350
|
+
<h1>Report</h1>
|
|
351
|
+
<table>
|
|
352
|
+
<tr><th>Col 1</th><th>Col 2</th></tr>
|
|
353
|
+
<tr><td>Data</td><td>Data</td></tr>
|
|
354
|
+
</table>
|
|
355
|
+
`;
|
|
356
|
+
|
|
357
|
+
await fastify.xPDF.generateFromHtml(styledHtml);
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Custom Page Margins
|
|
361
|
+
|
|
362
|
+
```javascript
|
|
363
|
+
await fastify.xPDF.generateFromHtml(html, {
|
|
364
|
+
margin: {
|
|
365
|
+
top: "2cm",
|
|
366
|
+
right: "1.5cm",
|
|
367
|
+
bottom: "2cm",
|
|
368
|
+
left: "1.5cm",
|
|
369
|
+
},
|
|
370
|
+
});
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### Different Page Sizes
|
|
374
|
+
|
|
375
|
+
```javascript
|
|
376
|
+
// Letter size (US)
|
|
377
|
+
await fastify.xPDF.generateFromHtml(html, { format: "Letter" });
|
|
378
|
+
|
|
379
|
+
// A3 (larger)
|
|
380
|
+
await fastify.xPDF.generateFromHtml(html, { format: "A3" });
|
|
381
|
+
|
|
382
|
+
// A5 (smaller)
|
|
383
|
+
await fastify.xPDF.generateFromHtml(html, { format: "A5" });
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### Landscape Orientation
|
|
387
|
+
|
|
388
|
+
```javascript
|
|
389
|
+
await fastify.xPDF.generateFromHtml(html, {
|
|
390
|
+
landscape: true,
|
|
391
|
+
});
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### Batch Form Filling
|
|
395
|
+
|
|
396
|
+
```javascript
|
|
397
|
+
const applicationData = [
|
|
398
|
+
{ firstName: "John", lastName: "Doe", email: "john@example.com" },
|
|
399
|
+
{ firstName: "Jane", lastName: "Smith", email: "jane@example.com" },
|
|
400
|
+
{ firstName: "Bob", lastName: "Johnson", email: "bob@example.com" },
|
|
401
|
+
];
|
|
402
|
+
|
|
403
|
+
for (const data of applicationData) {
|
|
404
|
+
const template = await fastify.xStorage.download("forms/template.pdf");
|
|
405
|
+
|
|
406
|
+
await fastify.xPDF.fillForm(template, data, {
|
|
407
|
+
flatten: true,
|
|
408
|
+
filename: `application-${data.lastName}.pdf`,
|
|
409
|
+
saveToStorage: true,
|
|
410
|
+
folder: "applications",
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
## Troubleshooting
|
|
416
|
+
|
|
417
|
+
### Chrome Not Found
|
|
418
|
+
|
|
419
|
+
Install Chromium:
|
|
420
|
+
|
|
421
|
+
```bash
|
|
422
|
+
# macOS
|
|
423
|
+
brew install chromium
|
|
424
|
+
|
|
425
|
+
# Linux
|
|
426
|
+
apt-get install chromium-browser
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
Or use sandbox args:
|
|
430
|
+
|
|
431
|
+
```javascript
|
|
432
|
+
await fastify.register(xPDF, {
|
|
433
|
+
args: ["--no-sandbox", "--disable-setuid-sandbox"],
|
|
434
|
+
});
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### Timeout Errors
|
|
438
|
+
|
|
439
|
+
Reduce HTML complexity or use async images:
|
|
440
|
+
|
|
441
|
+
```javascript
|
|
442
|
+
// ❌ Large external image
|
|
443
|
+
const html = '<img src="https://example.com/huge-image.jpg">';
|
|
444
|
+
|
|
445
|
+
// ✅ Optimized image
|
|
446
|
+
const html = '<img src="/images/logo.png" style="width: 200px;">';
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
### Form Fields Not Filling
|
|
450
|
+
|
|
451
|
+
List fields first to verify names:
|
|
452
|
+
|
|
453
|
+
```javascript
|
|
454
|
+
const fields = await fastify.xPDF.listFormFields(pdfBuffer);
|
|
455
|
+
console.log(fields); // Check exact names
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
## Next Steps
|
|
459
|
+
|
|
460
|
+
- [Full Documentation](./README.md)
|
|
461
|
+
- [Complete Examples](./EXAMPLES.md)
|
|
462
|
+
- [Testing Guide](./TESTING.md)
|