SuperAPI AI Directory

pdf-sign

Office & productivity

View repository

Updated 10-15 21:36

About this skill

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:

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):

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:
    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:
    page.get_pixmap(dpi=100).save("check.png")
    
    Read the PNG — misplaced marks are common on the first pass.

Recipe

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