| @@ -83,11 +83,28 @@ | ||
| 83 | 83 | * @param {Object} pdf jsPDF instance |
| 84 | 84 | */ |
| 85 | 85 | static addA4Page(pdf) { |
| 86 | 86 | pdf.addPage('a4', 'p'); |
| 87 | + this.paintPageWhite(pdf); | |
| 87 | 88 | } |
| 88 | 89 | |
| 89 | 90 | /** |
| 91 | + * Paint the current page white. A PDF page has no background of its | |
| 92 | + * own: where the capture does not cover the sheet — the foot of the | |
| 93 | + * last page, or of a page that broke early at a row — the area is | |
| 94 | + * unpainted, and a renderer is free to show that as black. | |
| 95 | + * | |
| 96 | + * @param {Object} pdf jsPDF instance | |
| 97 | + */ | |
| 98 | + static paintPageWhite(pdf) { | |
| 99 | + const { width, height } = pdf.internal.pageSize; | |
| 100 | + const w = typeof width === 'function' ? width() : width; | |
| 101 | + const h = typeof height === 'function' ? height() : height; | |
| 102 | + pdf.setFillColor(255, 255, 255); | |
| 103 | + pdf.rect(0, 0, w, h, 'F'); | |
| 104 | + } | |
| 105 | + | |
| 106 | + /** | |
| 90 | 107 | * Padding-top + border-top of the invoice/quote root (CSS px), matching visible HTML inset. |
| 91 | 108 | * |
| 92 | 109 | * @param {HTMLElement} el |
| 93 | 110 | * @returns {number} |
| @@ -466,12 +483,20 @@ | ||
| 466 | 483 | // capture doesn't bloat the file to 20MB. |
| 467 | 484 | compress: true, |
| 468 | 485 | precision: 4 |
| 469 | 486 | }); |
| 487 | + this.constructor.paintPageWhite(pdf); | |
| 470 | 488 | const pageWidthMm = a4.width; |
| 471 | 489 | // A4 pages keep a strip clear at the foot for the page label. |
| 472 | 490 | const FOOTER_MM = 9; |
| 473 | - const pageHeightMm = singlePage ? Math.max(a4.height, contentHeightMm) : a4.height - FOOTER_MM; | |
| 491 | + // When the capture is the A4 sheet the design lays out, one | |
| 492 | + // sheet is one page: reserving the label strip left the last | |
| 493 | + // 9mm of every sheet — blank or not — spilling onto a page of | |
| 494 | + // its own. The label for a genuinely multi-sheet document is | |
| 495 | + // drawn inside the sheet's own bottom margin instead. | |
| 496 | + const pageHeightMm = singlePage | |
| 497 | + ? Math.max(a4.height, contentHeightMm) | |
| 498 | + : (box.a4 ? a4.height : a4.height - FOOTER_MM); | |
| 474 | 499 | const imgWidthMm = pageWidthMm; |
| 475 | 500 | |
| 476 | 501 | // 2× the CSS pixel grid — matches retina display density, so text |
| 477 | 502 | // edges stay crisp instead of soft. (Previous 1.5× produced the |