Files
hermes-config/skills/software-development/web-ui-repair/references/jspdf-pitfalls.md
T
2026-07-12 10:17:17 -04:00

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:

  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:

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.