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

66 lines
3.2 KiB
Markdown

# 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.