# PDF Generation Pagination Source: `src/lib/pdf.ts` ## Page-break logic Each service calculates its height before rendering. Page break triggers when `y + serviceHeight > CONTENT_BOTTOM - FOOTER_HEIGHT`. Key constants: - `CONTENT_BOTTOM = PAGE_H - MARGIN` (772) - `FOOTER_HEIGHT = 35` (reserved for separator + footer text + page number) ## Height calculation (must match rendering yPos increments) ``` svcH = 0 if hasDecision: svcH += 5 // badge svcH += 6 // name + price if priority: svcH += 16 // priority label + reason if recommendation: svcH += 8 if explanation: svcH += lines * 5 + 2 if partsNotInStock: svcH += 8 + (deliveryTime ? 8 : 0) if aftermarket: svcH += 8 + (partsList ? 8 : 0) svcH += 6 // separator + spacing ``` ## After page break - Call `doc.addPage()` then set `yPos = MARGIN` - **Redraw current section header** (SERVICES APPROVED / POSTPONED SERVICES) so new page has context - Summary box uses same FOOTER_HEIGHT check before rendering ## Footer Drawn on EVERY page via post-render loop: ```ts const totalPages = doc.getNumberOfPages(); for (let i = 1; i <= totalPages; i++) { drawFooter(doc, i, totalPages, settings, shopChargeAmount); } ``` Footer includes: separator line, shop message, validity note, "Page N of M" ## String rendering (jspdf 4.x) NEVER pass arrays to `doc.text()` — jspdf 4.x uses different internal line spacing than manual yPos tracking, causing cascading desync and text overlap. Always render `splitTextToSize()` results per-line: ```ts doc.splitTextToSize(text, CW).forEach((line) => { doc.text(line, MARGIN, y); y += 5; }); ```