75 lines
2.2 KiB
Markdown
75 lines
2.2 KiB
Markdown
# PDF Generation — Pagination & Layout
|
|
|
|
Applies to: `src/lib/pdf.ts` in spq-v2.
|
|
|
|
## Page Break Logic
|
|
|
|
The correct page-break pattern for jsPDF 4.x:
|
|
|
|
```typescript
|
|
const CONTENT_BOTTOM = PAGE_H - MARGIN; // 772
|
|
const FOOTER_HEIGHT = 35; // reserved for footer separator + text + page number
|
|
|
|
// Before rendering each service, check if it fits:
|
|
let svcH = 0;
|
|
svcH += 5; // decision badge (if present)
|
|
svcH += 6; // name + price
|
|
svcH += 16; // priority (if present)
|
|
svcH += 8; // recommendation (if present)
|
|
svcH += doc.splitTextToSize(svc.explanation, CW).length * 5 + 2; // explanation
|
|
svcH += 8; // parts flags (each, if present)
|
|
svcH += 6; // separator + spacing
|
|
|
|
if (y + svcH > CONTENT_BOTTOM - FOOTER_HEIGHT) {
|
|
y = newPage(doc); // doc.addPage() + y = MARGIN
|
|
// Redraw section header on new page
|
|
doc.setFont(...); doc.text(currentSectionLabel, MARGIN, y); y += 8;
|
|
}
|
|
```
|
|
|
|
**Do NOT use `yPos > PAGE_HEIGHT * 0.7` as a guard** — it blocks breaks in the top 70% of the page, forcing content past the page edge.
|
|
|
|
## Footer Rendering
|
|
|
|
```typescript
|
|
function drawFooter(doc, pageNum, totalPages) {
|
|
doc.setPage(pageNum);
|
|
const fy = PAGE_H - MARGIN - FOOTER_HEIGHT + 5;
|
|
doc.line(MARGIN, fy, PAGE_W - MARGIN, fy);
|
|
doc.text('message', PAGE_W / 2, fy + 15, { align: 'center' });
|
|
doc.text(`Page ${pageNum} of ${totalPages}`, PAGE_W - MARGIN, PAGE_H - 10, { align: 'right' });
|
|
}
|
|
// After all content: loop over pages to draw footer on EVERY page
|
|
for (let i = 1; i <= doc.getNumberOfPages(); i++) drawFooter(doc, i, totalPages);
|
|
```
|
|
|
|
## Text Rendering (jspdf 4.x)
|
|
|
|
Never pass arrays to `doc.text()`. Render `splitTextToSize` results one line at a time:
|
|
|
|
```typescript
|
|
// ❌ Causes overlap in jspdf 4.x:
|
|
doc.text(lines, MARGIN, yPos);
|
|
yPos += lines.length * 6;
|
|
|
|
// ✅ Correct:
|
|
lines.forEach((line) => { doc.text(line, MARGIN, yPos); yPos += 6; });
|
|
```
|
|
|
|
## Height Estimator Constants
|
|
|
|
The height estimator must match actual yPos increments exactly:
|
|
|
|
| Element | yPos Advance |
|
|
|---------|-------------|
|
|
| Decision badge | 5 |
|
|
| Name + price | 6 |
|
|
| Priority label | 6 |
|
|
| Priority reason | 10 |
|
|
| Recommendation | 8 |
|
|
| Explanation line | 5 |
|
|
| Explanation gap | 2 |
|
|
| Parts flag | 8 |
|
|
| Aftermarket flag | 8 |
|
|
| Separator + spacing | 6 |
|