p3x-html-pdf 2025.4.153 → 2025.4.155

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
@@ -6,7 +6,7 @@
6
6
 
7
7
 
8
8
 
9
- # 📃 Generates PDF from HTML with custom headers and footers with wkhtmltopdf v2025.4.153
9
+ # 📃 Generates PDF from HTML with custom headers and footers with wkhtmltopdf v2025.4.155
10
10
 
11
11
 
12
12
 
@@ -37,7 +37,7 @@ v22.13.0
37
37
 
38
38
  **p3x-html-pdf** is a Node.js package that generates PDFs from HTML with custom headers and footers using `wkhtmltopdf`. It is a robust tool for creating professional-grade PDFs with features like:
39
39
 
40
- - 📜 **Dynamic Headers and Footers**: Add placeholders for page numbers.
40
+ - 📜 **Dynamic Headers and Footers**: Add placeholders for page numbers, dates, and more.
41
41
  - 🛠️ **Customizable Layouts**: Configure margins, orientation, and paper size.
42
42
  - ⚡ **Async/Await Support**: Modern JavaScript compatibility for efficient workflows.
43
43
  - 🔄 **Dynamic Content**: Render data-driven tables and content dynamically.
@@ -160,16 +160,28 @@ You can use placeholders in your HTML for dynamic data (only these, but it is en
160
160
 
161
161
  - `${page}`: Current page.
162
162
  - `${pages}`: Total pages.
163
-
164
- Example:
163
+ - `${frompage}`: The starting page of the current section.
164
+ - `${topage}`: The ending page of the current section.
165
+ - `${webpage}`: The URL of the web page (if applicable).
166
+ - `${section}`: The name of the current section.
167
+ - `${subsection}`: The name of the current subsection.
168
+ - `${date}`: The current date in a localized format.
169
+ - `${isodate}`: The current date in ISO 8601 format.
170
+ - `${time}`: The current time.
171
+ - `${title}`: The document title.
172
+ - `${doctitle}`: The title of the document as defined in metadata.
173
+ - `${sitepage}`: Current site page number (specific context).
174
+ - `${sitepages}`: Total number of site pages (specific context).
175
+
176
+ ### Example
165
177
 
166
178
  ```html
167
- <div id="p3x-footer" data-height="15mm">
179
+ <div id="p3x-footer" data-height="10mm">
168
180
  <p>Page ${page} of ${pages}</p>
169
181
  </div>
170
182
  ```
171
183
 
172
- The `p3x-footer` and `p3x-header` should not have any styles other than `id` and `data-height`.
184
+ The `p3x-footer` and `p3x-header` should not have any styles other than `id` and `data-height`.
173
185
 
174
186
  ---
175
187
 
@@ -181,6 +193,149 @@ The `p3x-footer` and `p3x-header` should not have any styles other than `id` and
181
193
 
182
194
  ---
183
195
 
196
+ ## 🌟 Headers and Footers in `p3x-html-pdf`
197
+
198
+ This document provides a detailed explanation of how to work with headers and footers using `p3x-html-pdf`, including first-page-specific headers and indexed headers for subsequent pages. With this approach, you can create professional-grade PDFs with precise control over header and footer content.
199
+
200
+ ---
201
+
202
+ ### 📖 Overview
203
+
204
+ Headers and footers in `p3x-html-pdf` are managed via HTML templates. You can:
205
+ - Define **default headers and footers** for all pages.
206
+ - Create **specific headers or footers** for certain pages using indexing (e.g., `p3x-header-1` for the first page).
207
+ - Dynamically calculate content for headers and footers, such as page numbers, document titles, or custom logic.
208
+
209
+ ---
210
+
211
+ ### 🚀 How It Works
212
+
213
+ `p3x-html-pdf` uses the `id` attribute and `data-height` to manage headers and footers. The key attributes and elements are:
214
+
215
+ - **Header IDs**:
216
+ - `p3x-header`: The default header for all pages.
217
+ - `p3x-header-<page>`: A header for a specific page (e.g., `p3x-header-1` for the first page).
218
+ - **Footer IDs**:
219
+ - `p3x-footer`: The default footer for all pages.
220
+ - `p3x-footer-<page>`: A footer for a specific page.
221
+ - **`data-height`**: Specifies the height of the header/footer (in millimeters). Ensure this matches the expected content size to prevent overlap.
222
+
223
+ ---
224
+
225
+ ### 🌟 Example: First Page Header and Default Header
226
+
227
+ The following example demonstrates a **custom header** for the first page and a **default header** for the rest of the document.
228
+
229
+ #### HTML Template
230
+
231
+ ```html
232
+ <div id="p3x-header-1" data-height="40mm">
233
+ <div style="width: 100%; padding: 0px; display: table;">
234
+ <div style="display: table-cell; vertical-align: middle;">
235
+ <img src="http://cdn.corifeus.com/assets/png/patrikx3.png" alt="Header Logo" style="height:40mm; margin:0;"/>
236
+ </div>
237
+ <div style="display: table-cell; vertical-align: middle; text-align: right; width: 100%;">
238
+ <h1 style="margin: 0; font-size: 20px; color: #333;">P3X HTML Invoice - First Page</h1>
239
+ <p style="margin: 5px 0 0; font-size: 14px; color: #555;">Generated: ${new Date().toLocaleDateString()}</p>
240
+ </div>
241
+ </div>
242
+ </div>
243
+
244
+ <div id="p3x-header" data-height="40mm">
245
+ <div style="display: table; width: 100%; height: 40mm; text-align: right;">
246
+ <div style="display: table-cell; vertical-align: middle; text-align: right;">
247
+ <h1 style="margin: 0; font-size: 20px; color: #333;">P3X HTML Invoice</h1>
248
+ <p style="margin: 5px 0 0; font-size: 14px; color: #555;">Generated: ${new Date().toLocaleDateString()}</p>
249
+ </div>
250
+ </div>
251
+ </div>
252
+
253
+ <div id="p3x-footer" data-height="10mm">
254
+ <div style="text-align: right; font-size: 12px; color: #777;">
255
+ Page ${page} of ${pages}
256
+ </div>
257
+ </div>
258
+ ```
259
+
260
+ ---
261
+
262
+ ### 🛠️ Dynamic Header and Footer Logic
263
+
264
+ - Use indexed headers or footers for specific pages.
265
+ - Utilize placeholders like `${page}` and `${pages}` to dynamically display the current page and total pages.
266
+
267
+ #### Example Configuration in JavaScript
268
+
269
+ ```javascript
270
+ const options = {
271
+ settings: {
272
+ save: true,
273
+ template: {
274
+ format: 'A4',
275
+ marginLeft: 10,
276
+ marginRight: 10,
277
+ orientation: 'portrait',
278
+ },
279
+ html: `
280
+ <div id="p3x-header-1" data-height="40mm">
281
+ <!-- Custom header for the first page -->
282
+ </div>
283
+ <div id="p3x-header" data-height="40mm">
284
+ <!-- Default header for subsequent pages -->
285
+ </div>
286
+ <div id="p3x-footer" data-height="10mm">
287
+ <!-- Footer for all pages -->
288
+ </div>
289
+ `,
290
+ },
291
+ title: 'Dynamic Headers and Footers Example',
292
+ saveFile: path.resolve(__dirname, 'output.pdf'),
293
+ };
294
+ ```
295
+
296
+ ---
297
+
298
+ ### 📏 Calculating Headers and Footers per Page
299
+
300
+ When designing headers and footers:
301
+ 1. **Estimate Content Size:**
302
+ - Use `data-height` to reserve enough space for your header or footer content.
303
+ - Example: A header with a logo and text may need `40mm`.
304
+
305
+ 2. **Adjust Margins:**
306
+ - Ensure the margins accommodate both the header/footer and the main content.
307
+
308
+ 3. **Testing for Multi-Page Documents:**
309
+ - For multi-page documents, validate the alignment of headers and footers across all pages.
310
+
311
+ ---
312
+
313
+ #### 📄 Headers and Footers with Indexed Customization
314
+
315
+ `p3x-html-pdf` supports indexed headers and footers, allowing unique designs for specific pages. For example, `p3x-header-1` can define a header for the first page, while `p3x-header` applies to subsequent pages. Similarly, `p3x-footer-1` can be used for a custom first-page footer.
316
+
317
+ ##### Key Points:
318
+ 1. **Indexed IDs**: Use `p3x-header-1`, `p3x-footer-1`, etc., for specific pages. Default headers (`p3x-header`) and footers (`p3x-footer`) are used when no specific index is found.
319
+ 2. **Consistent Heights**: All headers and footers must share the same `data-height` (e.g., `40mm` for headers, `10mm` for footers) to ensure proper alignment and accurate page calculations.
320
+ 3. **Dynamic Content**: Use placeholders like `${page}` and `${pages}` to display page-specific data dynamically.
321
+
322
+ This approach allows tailored styling for specific pages while maintaining consistent layouts throughout the document.
323
+
324
+ ---
325
+
326
+ ### 📊 Advanced Features
327
+
328
+ - Combine **indexed headers/footers** for specific pages with a **default** fallback.
329
+ - Use JavaScript in the `header-footer.html` template to dynamically adjust content.
330
+ - Use indexed configurations to simulate "first-page" or "last-page" behavior.
331
+
332
+ ---
333
+
334
+ ### 🖼️ Example Output
335
+
336
+ You can generate a PDF with the provided configuration to see how headers and footers are applied dynamically. For larger documents, this approach allows consistent styling with room for customization.
337
+
338
+
184
339
  ## 🌍 Architecture
185
340
 
186
341
  `p3x-html-pdf` works seamlessly on Linux, Windows and ARM64.
@@ -354,7 +509,7 @@ All my domains, including [patrikx3.com](https://patrikx3.com), [corifeus.eu](ht
354
509
  ---
355
510
 
356
511
 
357
- [**P3X-HTML-PDF**](https://corifeus.com/html-pdf) Build v2025.4.153
512
+ [**P3X-HTML-PDF**](https://corifeus.com/html-pdf) Build v2025.4.155
358
513
 
359
514
  [![NPM](https://img.shields.io/npm/v/p3x-html-pdf.svg)](https://www.npmjs.com/package/p3x-html-pdf) [![Donate for Corifeus / P3X](https://img.shields.io/badge/Donate-Corifeus-003087.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=QZVM4V6HVZJW6) [![Contact Corifeus / P3X](https://img.shields.io/badge/Contact-P3X-ff9900.svg)](https://www.patrikx3.com/en/front/contact) [![Like Corifeus @ Facebook](https://img.shields.io/badge/LIKE-Corifeus-3b5998.svg)](https://www.facebook.com/corifeus.software)
360
515
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "p3x-html-pdf",
3
- "version": "2025.4.153",
3
+ "version": "2025.4.155",
4
4
  "corifeus": {
5
5
  "prefix": "p3x-",
6
6
  "publish": true,
package/src/index.js CHANGED
@@ -111,6 +111,18 @@ const generate = async (options) => {
111
111
  const lodashTemplateHack = `
112
112
  item = item.replace(/\\$\{page}/g, vars.page);
113
113
  item = item.replace(/\\$\{pages}/g, vars.pages);
114
+ item = item.replace(/\\$\{frompage}/g, vars.frompage);
115
+ item = item.replace(/\\$\{topage}/g, vars.topage);
116
+ item = item.replace(/\\$\{webpage}/g, vars.webpage);
117
+ item = item.replace(/\\$\{section}/g, vars.section);
118
+ item = item.replace(/\\$\{subsection}/g, vars.subsection);
119
+ item = item.replace(/\\$\{date}/g, vars.date);
120
+ item = item.replace(/\\$\{isodate}/g, vars.isodate);
121
+ item = item.replace(/\\$\{time}/g, vars.time);
122
+ item = item.replace(/\\$\{title}/g, vars.title);
123
+ item = item.replace(/\\$\{doctitle}/g, vars.doctitle);
124
+ item = item.replace(/\\$\{sitepage}/g, vars.sitepage);
125
+ item = item.replace(/\\$\{sitepages}/g, vars.sitepages);
114
126
  item = item.replace(/\\$\{qr}/g, qr);
115
127
 
116
128
  `;
@@ -145,10 +157,18 @@ var qr = ${JSON.stringify(mainsSettings.settings.qr)};
145
157
  // console.debug('html', html)
146
158
  // console.debug('header', header)
147
159
 
148
- tmpHtmlPath = await utils.fs.ensureTempFile(html, 'html')
160
+ if (save) {
161
+ tmpPdfPath = saveFile;
162
+ tmpHtmlPath = `${saveFile}.html`;
163
+ await utils.fs.ensureFile(tmpHtmlPath, html);
164
+ } else {
165
+ tmpHtmlPath = await utils.fs.ensureTempFile(html, 'html')
166
+ tmpPdfPath = await utils.fs.tempFileName('pdf');
167
+ }
168
+
149
169
  tmpHtmlPathHeader = await utils.fs.ensureTempFile(header, 'html')
150
170
  tmpHtmlPathFooter = await utils.fs.ensureTempFile(footer, 'html')
151
- tmpPdfPath = await utils.fs.tempFileName('pdf');
171
+
152
172
  //console.debug('header', footer)
153
173
  //console.debug('footer', footer)
154
174
  //console.debug('html', footer)
@@ -189,10 +209,8 @@ var qr = ${JSON.stringify(mainsSettings.settings.qr)};
189
209
 
190
210
  await utils.childProcess.exec(generatePdfCommand, debug);
191
211
 
192
- if (save) {
193
- await fsExtra.move(tmpPdfPath, saveFile)
194
- } else {
195
- return fs.readFile(tmpPdfPath);
212
+ if (!save) {
213
+ return await fs.readFile(tmpPdfPath);
196
214
  }
197
215
  } finally {
198
216
  if (tmpHtmlPath !== undefined) {
package/test/test.js CHANGED
@@ -34,19 +34,29 @@ const fs = require('fs');
34
34
  orientation: 'portrait',
35
35
  },
36
36
  html: `
37
- <div id="p3x-header" data-height="40mm"">
38
- <div style="width: 100%; padding: 0px; display: table;">
37
+ <div id="p3x-header-1" data-height="40mm">
38
+ <div style="width: 100%; padding: 0px; display: table;">
39
39
  <div style="display: table-cell; vertical-align: middle;">
40
40
  <img src="http://cdn.corifeus.com/assets/png/patrikx3.png" alt="Header Logo" style="height:40mm; margin:0;"/>
41
41
  </div>
42
42
  <div style="display: table-cell; vertical-align: middle; text-align: right; width: 100%;">
43
- <h1 style="margin: 0; font-size: 20px; color: #333;">P3X HTML Invoice</h1>
43
+ <h1 style="margin: 0; font-size: 20px; color: #333;">P3X HTML Invoice - First Page</h1>
44
44
  <p style="margin: 5px 0 0; font-size: 14px; color: #555;">Generated: ${new Date().toLocaleDateString()}</p>
45
45
  </div>
46
46
  </div>
47
47
  </div>
48
- <div id="p3x-footer" data-height="10mm" >
49
- <div style="text-align: right; font-size: 12px; color: #777;">Page \${page} of \${pages}</div>
48
+ <div id="p3x-header" data-height="40mm" >
49
+ <div style="display: table; width: 100%; height: 40mm; text-align: right;">
50
+ <div style="display: table-cell; vertical-align: middle; text-align: right;">
51
+ <h1 style="margin: 0; font-size: 20px; color: #333;">P3X HTML Invoice </h1>
52
+ <p style="margin: 5px 0 0; font-size: 14px; color: #555;">Generated: ${new Date().toLocaleDateString()}</p>
53
+ </div>
54
+ </div>
55
+ </div>
56
+ <div id="p3x-footer" data-height="10mm">
57
+ <div style="text-align: right; font-size: 12px; color: #777;">
58
+ Page \${page} of \${pages}
59
+ </div>
50
60
  </div>
51
61
  <div>
52
62
  <h2 style="color: #222;">Invoice Content</h2>
@@ -59,16 +69,16 @@ const fs = require('fs');
59
69
  <th style="border: 1px solid #ddd; padding: 8px; background-color: #f4f4f4;">Total</th>
60
70
  </tr>
61
71
  ${Array.from({ length: 26 }).map((_, i) => {
62
- const price = (i + 1) * 10;
63
- const quantity = (i % 5) + 1;
64
- const total = price * quantity;
65
- return `<tr>
72
+ const price = (i + 1) * 10;
73
+ const quantity = (i % 5) + 1;
74
+ const total = price * quantity;
75
+ return `<tr>
66
76
  <td style="border: 1px solid #ddd; padding: 8px;">Product ${String.fromCharCode(65 + (i % 26))}</td>
67
77
  <td style="border: 1px solid #ddd; padding: 8px; text-align: right;">${quantity}</td>
68
78
  <td style="border: 1px solid #ddd; padding: 8px; text-align: right;">$${price}.00</td>
69
79
  <td style="border: 1px solid #ddd; padding: 8px; text-align: right;">$${total}.00</td>
70
80
  </tr>`;
71
- }).join('')}
81
+ }).join('')}
72
82
  <tr>
73
83
  <td colspan="3" style="border: 1px solid #ddd; padding: 8px; text-align: right; font-weight: bold;">Subtotal</td>
74
84
  <td style="border: 1px solid #ddd; padding: 8px; text-align: right; font-weight: bold;">$${Array.from({ length: 15 }).reduce((acc, _, i) => acc + ((i + 1) * 10 * ((i % 5) + 1)), 0)}.00</td>
@@ -102,7 +112,7 @@ const fs = require('fs');
102
112
  `,
103
113
  },
104
114
  title: 'P3X-HTML-PDF Detailed Invoice',
105
- debug: false,
115
+ debug: true,
106
116
  saveFile: outputPath,
107
117
  };
108
118