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.
73
90%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Sign and fill PDF forms programmatically. Works on both digital PDFs (with a text layer) and scanned PDFs (via OCR).
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:
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.
pymupdf in a venv (system Python is often externally managed):
python3 -m venv ./scratch/venv # or /tmp outside a repo
./scratch/venv/bin/pip install -q pymupdf pillowOCR (scanned PDFs) needs tesseract: brew install tesseract, and set TESSDATA_PREFIX (Homebrew: /opt/homebrew/share/tessdata).
page.search_for("SIGNATURE:") — a digital PDF locates fields directly.page.get_text() returns empty. Fall back to an OCR textpage:
tp = page.get_textpage_ocr(full=True, dpi=200)
hits = page.search_for("SIGNATURE:", textpage=tp)page.get_pixmap(dpi=100).save("check.png")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 sourceBlue-ish ink (color=(0, 0, 0.55)) reads as pen on printed forms. Match the form's date format (US forms: MM/DD/YYYY).
| 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 |
<name> - Signed.pdf — never overwrite the source4316684
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.