| name | pdf |
| description | Process PDF files: extract text and tables, fill forms (fillable and non-fillable), merge, split, rotate, encrypt/decrypt and add or remove password protection, extract metadata, convert pages to images, add invisible text layers, and OCR scanned documents to produce searchable PDFs. Supports page orientation detection, deskewing, annotation-based form filling, and watermarking. Use whenever the user mentions a .pdf file or asks to produce, read, modify, secure, scan, search, or analyze one. |
| compatibility | Requires uv and Python 3.11+. OCR scripts require tesseract. |
| allowed-tools | Bash(${CLAUDE_SKILL_DIR}/scripts/:*) Read |
PDF Processing
Quick Start
All scripts run via uv run with no manual dependency installation:
uv run ${CLAUDE_SKILL_DIR}/scripts/extract_text.py document.pdf
Decision Tree
Reading / extracting content:
- Run
extract_text.py first (fast, works on born-digital PDFs)
- If little/no text is returned, the PDF is scanned — use
ocr_text.py
- For tables specifically,
extract_text.py --tables outputs structured data
Filling a form:
- Run
check_fields.py to detect fillable form fields
- If fillable: follow the Fillable Form Workflow
- If not fillable: follow the Non-Fillable Form Workflow
- See references/form-filling.md for detailed
instructions on the non-fillable workflow (coordinate systems, visual
estimation, hybrid approach)
Common operations:
| Task | Script |
|---|
| Extract text | extract_text.py input.pdf |
| Extract tables | extract_text.py --tables input.pdf |
| OCR scanned PDF to text | ocr_text.py input.pdf |
| Add OCR text layer to PDF | ocr_pdf.py -i input.pdf or ocr_pdf.py input.pdf -o output.pdf |
| Detect page orientation | detect_orientation.py input.pdf |
| OCR + auto-rotate | ocr_pdf.py --rotate -i input.pdf |
| Merge PDFs | merge.py -o out.pdf a.pdf b.pdf c.pdf |
| Split PDF | split.py input.pdf output_dir/ |
| Split page range | split.py input.pdf output.pdf --pages 1-5 |
| Rotate pages | rotate.py -i --angle 90 input.pdf or rotate.py input.pdf -o output.pdf --angle 90 |
| Rotate specific pages | rotate.py -i --angle 90 --pages 1,3,5 input.pdf |
| Show metadata | metadata.py input.pdf |
| Encrypt PDF | encrypt.py input.pdf -o output.pdf --user-password secret |
| Decrypt PDF | decrypt.py input.pdf -o output.pdf --password secret |
| Convert to images | pdf_to_images.py input.pdf -d output_dir/ |
| Check for form fields | check_fields.py input.pdf |
| Add text layer | add_text_layer.py input.pdf output.pdf "transcription" |
| Add text layer (file) | add_text_layer.py input.pdf output.pdf --file transcript.txt |
All scripts accept --help for full usage.
Batch processing — these scripts accept multiple input files:
uv run ${CLAUDE_SKILL_DIR}/scripts/ocr_pdf.py -i inbox/*.pdf
uv run ${CLAUDE_SKILL_DIR}/scripts/detect_orientation.py inbox/*.pdf
uv run ${CLAUDE_SKILL_DIR}/scripts/extract_text.py inbox/*.pdf
uv run ${CLAUDE_SKILL_DIR}/scripts/rotate.py -i --angle 90 inbox/*.pdf
uv run ${CLAUDE_SKILL_DIR}/scripts/metadata.py --porcelain inbox/*.pdf
uv run ${CLAUDE_SKILL_DIR}/scripts/encrypt.py -i --user-password secret inbox/*.pdf
uv run ${CLAUDE_SKILL_DIR}/scripts/decrypt.py -d decrypted/ --password secret inbox/*.pdf
uv run ${CLAUDE_SKILL_DIR}/scripts/check_fields.py --porcelain inbox/*.pdf
uv run ${CLAUDE_SKILL_DIR}/scripts/pdf_to_images.py -d images/ inbox/*.pdf
uv run ${CLAUDE_SKILL_DIR}/scripts/split.py -d pages/ inbox/*.pdf
Batch flags: --porcelain (machine-readable output), --fail-fast (stop on
first error; default is continue and report). File-producing scripts accept
-i/--in-place or -d/--output-dir for batch output.
Custom PDF work (reportlab, advanced pdfplumber, etc.):
See references/python-libraries.md and
references/cli-tools.md for recipes.
Script Reference
Every script is self-contained with inline dependencies (PEP 723). Run any
script with uv run ${CLAUDE_SKILL_DIR}/scripts/<name>.py.
Text Extraction
extract_text.py — Extract text or tables from born-digital PDFs.
extract_text.py input.pdf — print all text to stdout
extract_text.py *.pdf — batch: prints with === file === headers
extract_text.py --tables input.pdf — extract tables as CSV
extract_text.py --pages 1-3 input.pdf — specific page range
--porcelain — JSONL output; --fail-fast — stop on first error
ocr_text.py — OCR scanned/image PDFs to text via tesseract.
ocr_text.py input.pdf — print OCR text to stdout
ocr_text.py *.pdf — batch with file headers
ocr_text.py --pages 1-3 input.pdf — specific page range
--porcelain — JSONL output; --fail-fast — stop on first error
- Requires
tesseract installed on the system
ocr_pdf.py — Add searchable text layer to a scanned PDF.
ocr_pdf.py input.pdf -o output.pdf — produce searchable PDF
ocr_pdf.py -i input.pdf — OCR in-place (atomic temp file + rename)
ocr_pdf.py -i *.pdf — batch in-place
ocr_pdf.py -d output_dir/ *.pdf — batch to output directory
ocr_pdf.py --rotate -i input.pdf — auto-rotate pages first
ocr_pdf.py --deskew -i input.pdf — deskew before OCR
--porcelain — tab-delimited status output; --fail-fast — stop on first error
- Requires
tesseract installed on the system
- Legacy
ocr_pdf.py input.pdf output.pdf syntax still works (with warning)
detect_orientation.py — Detect page rotation using tesseract OSD.
detect_orientation.py input.pdf — report orientation per page
detect_orientation.py *.pdf — batch with file headers
detect_orientation.py --pages 1-3 input.pdf — specific pages
--porcelain — JSONL output; --fail-fast — stop on first error
- Requires
tesseract installed on the system
Text Layers
add_text_layer.py — Add invisible text layer to image-based PDFs.
add_text_layer.py input.pdf output.pdf "transcription text" — add text
add_text_layer.py input.pdf output.pdf --file transcript.txt — from file
add_text_layer.py -i input.pdf "text here" — modify in-place
- Useful when OCR fails (e.g., handwriting) but manual transcription exists
- Makes scanned pages searchable/selectable without OCR
Manipulation
merge.py — Combine multiple PDFs into one.
merge.py -o merged.pdf a.pdf b.pdf c.pdf
split.py — Split a PDF into pages or a range.
split.py input.pdf -d output_dir/ — one file per page
split.py input.pdf -o output.pdf --pages 1-5 — extract page range
split.py -d output_dir/ *.pdf — batch: each PDF gets a subdirectory
--porcelain — tab-delimited status output; --fail-fast — stop on first error
- Legacy
split.py input.pdf output_dir/ syntax still works
rotate.py — Rotate PDF pages.
rotate.py input.pdf -o output.pdf --angle 90 — all pages
rotate.py -i --angle 90 input.pdf — rotate in-place
rotate.py -i --angle 90 *.pdf — batch in-place
rotate.py -d output_dir/ --angle 90 *.pdf — batch to output directory
rotate.py -o output.pdf --angle 90 --pages 1,3 input.pdf — specific pages
--porcelain — tab-delimited status output; --fail-fast — stop on first error
- Legacy
rotate.py input.pdf output.pdf --angle 90 syntax still works (with warning)
metadata.py — Display PDF metadata.
metadata.py input.pdf — print title, author, creator, etc.
metadata.py *.pdf — batch with file headers
metadata.py --json input.pdf — output as JSON
--porcelain — JSONL output; --fail-fast — stop on first error
Security
encrypt.py — Add password protection.
encrypt.py input.pdf -o output.pdf --user-password read_pw
encrypt.py input.pdf -o output.pdf --user-password read_pw --owner-password admin_pw
encrypt.py -i --user-password secret *.pdf — batch in-place
encrypt.py -d encrypted/ --user-password secret *.pdf — batch to directory
--porcelain — tab-delimited status output; --fail-fast — stop on first error
- Legacy
encrypt.py input.pdf output.pdf --user-password ... syntax still works
decrypt.py — Remove password protection.
decrypt.py input.pdf -o output.pdf --password secret
decrypt.py -i --password secret *.pdf — batch in-place
decrypt.py -d decrypted/ --password secret *.pdf — batch to directory
--porcelain — tab-delimited status output; --fail-fast — stop on first error
- Skips non-encrypted files with a message
- Legacy
decrypt.py input.pdf output.pdf --password ... syntax still works
Conversion
pdf_to_images.py — Convert PDF pages to PNG images.
pdf_to_images.py input.pdf -d output_dir/ — all pages
pdf_to_images.py --pages 1-3 --dpi 300 input.pdf -d output_dir/
pdf_to_images.py -d images/ *.pdf — batch: each PDF gets a subdirectory
--porcelain — tab-delimited status output; --fail-fast — stop on first error
- Uses pypdfium2 (no poppler dependency)
- Legacy
pdf_to_images.py input.pdf output_dir/ syntax still works
Form Filling
check_fields.py — Detect whether a PDF has fillable form fields.
check_fields.py input.pdf — prints result and exits 0 (has fields) or 1
check_fields.py *.pdf — batch with per-file results
--porcelain — JSONL output; --fail-fast — stop on first error
extract_fields.py — Dump fillable field metadata to JSON.
extract_fields.py input.pdf fields.json
- Output includes field IDs, types, pages, bounding boxes
fill_fields.py — Fill fillable form fields from a JSON values file.
fill_fields.py input.pdf values.json output.pdf
- Validates field IDs and values before writing
extract_structure.py — Extract layout from non-fillable PDFs.
extract_structure.py input.pdf structure.json
- Outputs text labels, horizontal lines, checkboxes, row boundaries
fill_annotations.py — Fill non-fillable PDFs via text annotations.
fill_annotations.py input.pdf fields.json output.pdf
- Accepts both PDF and image coordinate systems
check_boxes.py — Validate bounding boxes in a fields.json file.
check_boxes.py fields.json — checks for overlaps and sizing errors
validation_image.py — Overlay bounding boxes on a page image for QA.
validation_image.py --page 1 fields.json page_1.png output.png
Fillable Form Workflow
-
Confirm the PDF has fillable fields:
uv run ${CLAUDE_SKILL_DIR}/scripts/check_fields.py input.pdf
-
Extract field metadata:
uv run ${CLAUDE_SKILL_DIR}/scripts/extract_fields.py input.pdf field_info.json
-
Convert to images to understand each field's purpose:
uv run ${CLAUDE_SKILL_DIR}/scripts/pdf_to_images.py input.pdf images/
-
Examine the images and field_info.json. Create values.json:
[
{
"field_id": "last_name",
"page": 1,
"value": "Smith"
},
{
"field_id": "citizen_yes",
"page": 1,
"value": "/On"
}
]
- For checkboxes, use the
checked_value / unchecked_value from field_info
- For radio groups, use one of the
radio_options values
- For choice fields, use one of the
choice_options values
-
Fill the form:
uv run ${CLAUDE_SKILL_DIR}/scripts/fill_fields.py input.pdf values.json output.pdf
-
Verify by converting the output to images:
uv run ${CLAUDE_SKILL_DIR}/scripts/pdf_to_images.py output.pdf verify/
Non-Fillable Form Workflow
For PDFs without fillable form fields, text is added as annotations. This
workflow requires determining where to place text on each page.
Read references/form-filling.md for the
complete workflow. Summary of the three approaches:
Approach A: Structure-Based (preferred)
- Extract form structure:
uv run ${CLAUDE_SKILL_DIR}/scripts/extract_structure.py input.pdf structure.json
- If
structure.json has meaningful text labels, use their coordinates to
build fields.json (see reference for format)
- Validate:
uv run ${CLAUDE_SKILL_DIR}/scripts/check_boxes.py fields.json
- Fill:
uv run ${CLAUDE_SKILL_DIR}/scripts/fill_annotations.py input.pdf fields.json output.pdf
Approach B: Visual Estimation (fallback)
- Convert to images:
uv run ${CLAUDE_SKILL_DIR}/scripts/pdf_to_images.py input.pdf images/
- Examine images, estimate field positions in pixel coordinates
- Use
magick to crop and zoom for precision (see reference)
- Build
fields.json with image_width/image_height keys
- Validate and fill as above
Approach C: Hybrid
Use structure extraction for most fields, visual estimation for any the
structure extraction missed (e.g., circular checkboxes). Convert all
coordinates to PDF coordinate space. See reference for conversion formulas.
Reportlab Warning
Never use Unicode subscript/superscript characters in reportlab PDFs. The
built-in fonts lack these glyphs, rendering them as black boxes. Use
<sub> and <super> tags in Paragraph objects instead.