Files
2026-07-12 10:17:17 -04:00

2.2 KiB

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:

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.

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:

// ❌ 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