pdf

/home/avalon/.hermes/skills/productivity/pdf/SKILL.md · raw

PDF Skill

Create, combine, split, transform, and secure PDF files — merging, page manipulation, form filling, watermarks, encryption, and text/table extraction. For heavy text extraction from scanned documents prefer the ocr-and-documents skill; for natural-language edits to existing PDF text prefer nano-pdf.

When to Use

Use this skill whenever the user wants to do anything with PDF files: reading or extracting text/tables, combining or merging multiple PDFs, splitting PDFs apart, rotating pages, adding watermarks, creating new PDFs, filling PDF forms, encrypting/decrypting, extracting images, or OCR on scanned PDFs. If the user mentions a .pdf file or asks to produce one, use this skill.

Prerequisites

pip install pypdf pdfplumber reportlab
which pdftotext || sudo apt install -y poppler-utils   # pdftotext, pdftoppm, pdfimages
which qpdf || sudo apt install -y qpdf                 # CLI merge/split/decrypt

macOS: brew install poppler qpdf. OCR extras: pip install pytesseract pdf2image + sudo apt install -y tesseract-ocr.

Script paths below are relative to this skill's directory. Form filling has its own workflow — read forms.md and follow it. Advanced library usage (pypdfium2, pdf-lib) and troubleshooting: reference.md.

Quick Reference

Task Best Tool Command/Code
Merge PDFs pypdf writer.add_page(page) per page
Split PDFs pypdf One page per file
Extract text pdfplumber page.extract_text()
Extract tables pdfplumber page.extract_tables()
Create PDFs reportlab Canvas or Platypus
Command-line merge/split qpdf qpdf --empty --pages ...
OCR scanned PDFs pytesseract Convert to images first (or use ocr-and-documents)
Fill PDF forms see forms.md scripts/fill_fillable_fields.py etc.
Edit existing text nano-pdf skill nano-pdf edit file.pdf <page> "<instruction>"

Common operations

Merge / split / rotate (pypdf)

from pypdf import PdfReader, PdfWriter

# Merge
writer = PdfWriter()
for pdf_file in ["doc1.pdf", "doc2.pdf"]:
    for page in PdfReader(pdf_file).pages:
        writer.add_page(page)
with open("merged.pdf", "wb") as f:
    writer.write(f)

# Split: one file per page
reader = PdfReader("input.pdf")
for i, page in enumerate(reader.pages):
    w = PdfWriter(); w.add_page(page)
    with open(f"page_{i+1}.pdf", "wb") as f:
        w.write(f)

# Rotate
page = reader.pages[0]
page.rotate(90)  # clockwise

Extract text and tables (pdfplumber)

import pdfplumber, pandas as pd

with pdfplumber.open("document.pdf") as pdf:
    text = "\n".join(page.extract_text() or "" for page in pdf.pages)
    tables = [pd.DataFrame(t[1:], columns=t[0])
              for page in pdf.pages
              for t in page.extract_tables() if t]

Create PDFs (reportlab)

from reportlab.lib.pagesizes import letter
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak
from reportlab.lib.styles import getSampleStyleSheet

doc = SimpleDocTemplate("report.pdf", pagesize=letter)
styles = getSampleStyleSheet()
story = [Paragraph("Report Title", styles["Title"]), Spacer(1, 12),
         Paragraph("Body text...", styles["Normal"]), PageBreak(),
         Paragraph("Page 2", styles["Heading1"])]
doc.build(story)

Subscripts/superscripts: never use Unicode sub/superscript characters (₀₁₂, ⁰¹²) — the built-in fonts lack the glyphs and render solid black boxes. Use <sub>/<super> markup inside Paragraph objects: Paragraph("H<sub>2</sub>O", styles['Normal']). For canvas-drawn text, adjust font size and position manually.

Command-line tools

pdftotext -layout input.pdf output.txt                     # text, layout preserved
pdftotext -f 1 -l 5 input.pdf output.txt                   # pages 1-5
qpdf --empty --pages file1.pdf file2.pdf -- merged.pdf     # merge
qpdf input.pdf --pages . 1-5 -- pages1-5.pdf               # split range
qpdf input.pdf output.pdf --rotate=+90:1                   # rotate page 1
qpdf --password=pw --decrypt encrypted.pdf decrypted.pdf   # remove password
pdfimages -j input.pdf img                                 # extract images

Watermark

from pypdf import PdfReader, PdfWriter

watermark = PdfReader("watermark.pdf").pages[0]
reader, writer = PdfReader("document.pdf"), PdfWriter()
for page in reader.pages:
    page.merge_page(watermark)
    writer.add_page(page)
with open("watermarked.pdf", "wb") as f:
    writer.write(f)

Password protection

writer.encrypt("userpassword", "ownerpassword")

OCR scanned PDFs

import pytesseract
from pdf2image import convert_from_path

pages = convert_from_path("scanned.pdf")
text = "\n\n".join(pytesseract.image_to_string(img) for img in pages)

For batch/structured extraction from scans, the ocr-and-documents skill (pymupdf, marker-pdf) is the better path.

Form filling

Read forms.md first — it distinguishes fillable (AcroForm) PDFs from flat scanned forms and walks through the helper scripts:

Pitfalls

Verification

  1. Open the output with PdfReader and assert the expected page count.
  2. Re-extract text from the output (pdftotext or pdfplumber) and confirm the content you added is present.
  3. For anything visual (watermarks, filled forms, created reports): pdftoppm -jpeg -r 100 output.pdf page and inspect the images with vision_analyze.

ocr-and-documents (scanned-document text extraction), nano-pdf (NL text edits in place), docx (Word), xlsx (spreadsheets), powerpoint (decks).