# PDF Sign

> This skill should be used when the user asks to "sign this PDF", "fill in this form", "add my signature to", or mentions signing letters, claim forms, or contracts. Overlays a signature image, ticks checkboxes, and fills date/name/phone fields on scanned or digital PDFs using pymupdf, with OCR fallback for scanned documents.

- Skill: `dwmkerr/pdf-sign` (Agent Skill)
- Install (CLI): `npx skillmds@latest add dwmkerr/pdf-sign`
- Raw SKILL.md: https://api.skillmd.com/api/skills/dwmkerr/pdf-sign/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Docs & Writing
- Author: dwmkerr (https://skillmd.com/u/dwmkerr)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/dwmkerr/pdf-sign

---


# PDF Sign

Sign and fill PDF forms programmatically. Works on both digital PDFs (with a text layer) and scanned PDFs (via OCR).

## Signature image

**Always ask the user where their signature image is** — never assume a location, and never commit a signature image to a repository. A scanned signature (dark ink on white) works best.

If the image has a white background (typical JPG scan), convert it to a transparent PNG first — otherwise the overlay blanks out any text or lines beneath it:

```python
from PIL import Image
img = Image.open(SRC_JPG).convert("RGBA")
img.putdata([(0, 0, 0, 0 if (r+g+b)//3 > 200 else 255-(r+g+b)//3)
             for r, g, b, a in img.getdata()])
img.save(OUT_PNG)
```

Save the transparent version next to the original so it can be reused.

## Setup

pymupdf in a venv (system Python is often externally managed):

```bash
python3 -m venv ./scratch/venv   # or /tmp outside a repo
./scratch/venv/bin/pip install -q pymupdf pillow
```

OCR (scanned PDFs) needs tesseract: `brew install tesseract`, and set `TESSDATA_PREFIX` (Homebrew: `/opt/homebrew/share/tessdata`).

## Approach

1. **Try text search first.** `page.search_for("SIGNATURE:")` — a digital PDF locates fields directly.
2. **Scanned PDF → `page.get_text()` returns empty.** Fall back to an OCR textpage:
   ```python
   tp = page.get_textpage_ocr(full=True, dpi=200)
   hits = page.search_for("SIGNATURE:", textpage=tp)
   ```
3. **Place content relative to the found label rects** (see recipe).
4. **Always render and visually verify** each modified page before declaring done:
   ```python
   page.get_pixmap(dpi=100).save("check.png")
   ```
   Read the PNG — misplaced marks are common on the first pass.

## Recipe

```python
import os
os.environ.setdefault("TESSDATA_PREFIX", "/opt/homebrew/share/tessdata")
import pymupdf

doc = pymupdf.open(SRC)
page = doc[0]
tp = page.get_textpage_ocr(full=True, dpi=200)  # omit for digital PDFs

def find(needle):
    hits = page.search_for(needle, textpage=tp)
    return hits[0] if hits else None

# Signature: transparent PNG spanning the line, right of the label
r = find("SIGNATURE:")
page.insert_image(pymupdf.Rect(r.x1 + 40, r.y0 - 32, r.x1 + 180, r.y1 + 4),
                  filename=SIGNATURE_PNG, keep_proportion=True)

# Text field: right of label, baseline-aligned
r = find("DATE:")
page.insert_text((r.x1 + 15, r.y1 - 2), "01/31/2026", fontsize=11, color=(0, 0, 0.55))

# Checkbox: box sits ~22pt left of its label text
r = find("Check here if your name")
page.insert_text((r.x0 - 22, r.y1 - 2), "X", fontsize=12, color=(0, 0, 0.55))

doc.save(OUT)  # never overwrite the source
```

Blue-ish ink (`color=(0, 0, 0.55)`) reads as pen on printed forms. Match the form's date format (US forms: MM/DD/YYYY).

## Gotchas

| Problem | Fix |
|---|---|
| `search_for` finds nothing, no error | Scanned page with no text layer — check `len(page.get_text()) == 0`, use OCR textpage |
| Label matches twice (e.g. `DATE:` also hits `Check Date:` header) | Filter hits by y-position, e.g. same line as the SIGNATURE match: `abs(h.y1 - rsig.y1) < 8` |
| Partial-phrase match places mark mid-sentence | Anchor x on a reliably-found label (e.g. checkbox column from the first checkbox label), use the phrase match only for y |
| Checkbox X lands on text | Checkbox is left of label: `x0 - 22` works for typical forms; verify visually |
| Signature overlay blanks out text/lines beneath it | White image background — convert to transparent PNG (see above) |
| Multi-copy documents (same letter × N pages) | Loop the letter pages, skip FAQ/back pages |
| Draft email already carries the old attachment after re-signing | Mail clients attach a snapshot; discard the draft and recreate it with the new file |
| `import fitz` deprecation warning | Use `import pymupdf` |

## Workflow

1. Ask the user for the signature image location (and any fill values: date, printed name, phone)
2. Save/locate the source PDF
3. Sign to a new file `<name> - Signed.pdf` — never overwrite the source
4. Render pages → view the PNGs → verify placement
5. Iterate until correct, then hand back or attach to a draft email

