3.3 KiB
v2 PDF Generation — Known Issues and Fixes
jspdf 4.x Array-Based doc.text() Causes Text Overlap
Symptom: Text in the generated PDF overlaps heavily — job titles, descriptions, prices, and status flags render on top of each other.
Root cause: When you pass a string array to doc.text(array, x, y), jspdf 4.x uses internal matrix-based line spacing that does NOT match manual yPos += array.length * lineHeight calculations. This causes yPos to desync from where jspdf actually rendered the text, cascading into overlap throughout the entire document.
Fix: NEVER pass arrays to doc.text(). Instead, iterate through each line and render individually:
// BROKEN (jspdf 4.x):
const lines = doc.splitTextToSize(message, maxWidth);
doc.text(lines, x, yPos); // jspdf uses its own spacing
yPos += lines.length * 6 + 15; // does NOT match actual render position
// FIXED:
const lines = doc.splitTextToSize(message, maxWidth);
lines.forEach((line) => {
doc.text(line, x, yPos);
yPos += 6; // explicit control per line
});
yPos += 15;
Service Height Calculation Must Match Actual yPos Increments
The page-break estimator (serviceHeight) must use the EXACT same multipliers as the actual yPos advances. Mismatched values cause services to overflow the page edge without triggering a page break.
| Element | Actual yPos advance | Required in estimate |
|---|---|---|
| Customer decision badge | yPos += 5 |
serviceHeight += 5 |
| Service name + price | yPos += 6 |
serviceHeight += 6 |
| Priority (label + reason) | yPos += 6; yPos += 10 |
serviceHeight += 16 |
| Recommendation | yPos += 8 |
serviceHeight += 8 |
| Explanation (per line) | yPos += 5 then yPos += 2 |
lines.length * 5 + 2 |
| Parts flags (each) | yPos += 8 (or 16 with extra line) |
8 or 16 |
| Separator | yPos += 6 |
serviceHeight += 6 |
boxBottomY Stale After Summary Page Break
When the pricing summary box triggers a page break, boxBottomY must be updated to the new page's coordinates. Declare it with let (not const) and reassign in the page-break branch:
let boxBottomY = boxTopY + boxPadding + boxContentHeight + boxPadding;
if (boxBottomY + 50 > PAGE_HEIGHT - 20) {
doc.addPage();
// ... re-render box ...
boxBottomY = bby; // update for new page position
}
// ...
yPos = boxBottomY + 10; // consistent regardless of page break
Customer Info Not Populating When Loading Saved Quote
Symptom: Services load from PocketBase but customer info fields remain empty.
Causes and fixes:
-
reset()ordering:reset()wipes all state includingcustomerInfo. Must callreset()BEFOREsetCustomerInfo():reset(); if (data.customerInfo) setCustomerInfo(data.customerInfo); // after reset -
PocketBase JSON field type: If the PocketBase collection stores
customerInfoas a text field (not json), the retrieved value is a JSON string. Handle both cases:const ci = typeof data.customerInfo === 'string' ? JSON.parse(data.customerInfo) : data.customerInfo; if (ci && typeof ci === 'object') setCustomerInfo(ci);Apply the same defensive parsing to
servicesanddiscountfields.