3.2 KiB
jsPDF PDF Generation Pitfalls
Common issues when generating PDFs client-side with jsPDF, observed across ShopProQuote quote generation.
1. Fill Covering Text — Drawing Order
Symptom: Background boxes (rounded rects) appear as blank gray areas — text that was drawn afterward is invisible.
Root cause: Calling doc.roundedRect(x, y, w, h, r, r, 'FD') (fill + draw) AFTER text has been placed. In PDF, later operations paint on top. The fill covers the text.
Fix: Split into two operations:
- Draw fill-only box BEFORE text:
doc.roundedRect(x, y, w, h, r, r, 'F') - Draw border-only box AFTER text:
doc.roundedRect(x, y, w, h, r, r, 'D')
Use a generous fill height initially (wider than content) and trim with the border pass using the actual computed height.
fill_height = generous_estimate // e.g. 80pt
doc.setFillColor(248, 248, 248)
doc.roundedRect(x, y, w, fill_height, 2, 2, 'F') // BEFORE text
// ... draw all text ...
actual_height = computed_from_content
doc.setDrawColor(220, 220, 220)
doc.roundedRect(x, y, w, actual_height, 2, 2, 'D') // AFTER text
2. Border Invisibility — Color Contrast
Symptom: Box border doesn't appear at all, even though setDrawColor and setLineWidth are called.
Root cause: Colors that are too close to white — e.g. setDrawColor(230, 230, 230) at setLineWidth(0.5) on a white page is invisible to the human eye, especially on screen. The fill at (250, 250, 250) vs white (255, 255, 255) is also nearly imperceptible.
Fix: Use colors with at least 30-35 points of contrast from background:
- Fill:
(248, 248, 248)— visible light gray, not(250, 250, 250)or(251, 251, 251) - Border:
(220, 220, 220)at 0.5pt — visible but subtle, not(230, 230, 230) - For print, these values are still very light and professional
Diagnosis: Check the PDF visually. If you can't clearly see the box boundary, the values are too close to white. Use PIL/pixel analysis to verify:
from PIL import Image
img = Image.open('box.png')
# Check mid-region pixels — if they're > 248, the fill is invisible
3. Spacing After Box — yPos Drift
Symptom: Content after a background box overlaps with the box or starts inside it.
Root cause: After drawing text inside the box, yPos is at the last text line. But the box has bottom padding that extends below yPos. Adding a fixed margin (yPos += 15) doesn't account for the actual padding.
Fix: Track the box's computed bottom coordinate and position subsequent content relative to it:
boxBottomY = yPos_after_content + bottom_padding
nextY = boxBottomY + margin // start next section below box
Never use a fixed increment from the last text line — always compute from the box boundary.
4. Duplicate Variable Declarations in Scope
Symptom: A variable like pdfApprovedServices is declared with const in one place, then redeclared further down. Silent bug in JS (temporal dead zone or reference-before-declaration).
Fix: Declare variables once at the top of the scope. Use let for values that need to be computed after other operations. When moving code around, check for duplicate const/let declarations.