# 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: 1. Draw **fill-only** box BEFORE text: `doc.roundedRect(x, y, w, h, r, r, 'F')` 2. 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: ```python 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.