agentsclimarketplace

Pdf handling

Skill furkangonel/cowrangler/bundled_skills/productivity/pdf-handling

Autonomous terminal AI agent for workflows and feasible project procedures. Co-Worker Co-Wrangler πŸ™

Install
npx -y skills add furkangonel/cowrangler --skill pdf-handling

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

PDF creation, text extraction, merging, splitting, and form-filling.

SKILL.md

10.2 KB, ~2.9k tokens by cl100k_base, as published. Nobody here has run it

PDF Handling SOP

Select the right tool for each PDF job and execute it correctly. Covers text extraction, merging, splitting, HTML-to-PDF conversion, form filling, and scanned PDF (OCR) handling.

When to Use

  • User wants to extract text or tables from a PDF
  • User wants to merge multiple PDFs into one
  • User wants to split a PDF into separate files
  • User wants to create a PDF from HTML, Markdown, or data
  • User wants to fill fields in a PDF form
  • User wants to process a scanned (image-based) PDF

Part 1 β€” Tool Selection Guide

TaskBest toolInstall
Extract text (digital PDF)pdfplumberpip install pdfplumber
Extract tablespdfplumberpip install pdfplumber
Merge / split / rotate pagespypdfpip install pypdf
Create PDF from scratch (programmatic)reportlabpip install reportlab
Convert HTML / CSS to PDFweasyprintpip install weasyprint
Convert Markdown to PDFweasyprint + markdownpip install weasyprint markdown
Fill PDF formspypdf or pdfrwpip install pypdf
Scanned PDF β€” OCRpytesseract + pdf2imagepip install pytesseract pdf2image + system tesseract
Encrypt / decryptpypdfpip install pypdf

Part 2 β€” Text Extraction

Digital PDF β€” Plain Text

import pdfplumber

with pdfplumber.open("document.pdf") as pdf:
    for page_num, page in enumerate(pdf.pages, 1):
        text = page.extract_text()
        if text:
            print(f"=== Page {page_num} ===")
            print(text)

Extract Text from Specific Pages

with pdfplumber.open("report.pdf") as pdf:
    # Pages 2-5 (0-indexed: pages[1:5])
    for page in pdf.pages[1:5]:
        text = page.extract_text(x_tolerance=3, y_tolerance=3)
        print(text)

Extract Text with Coordinates (for layout-aware parsing)

with pdfplumber.open("form.pdf") as pdf:
    page = pdf.pages[0]
    words = page.extract_words()
    for word in words:
        print(f"  '{word['text']}' at x={word['x0']:.0f}, y={word['top']:.0f}")

Extract All Text to a File

import pdfplumber

def pdf_to_text(input_path, output_path):
    with pdfplumber.open(input_path) as pdf:
        with open(output_path, "w", encoding="utf-8") as out:
            for page in pdf.pages:
                text = page.extract_text() or ""
                out.write(text)
                out.write("\n\n")
    print(f"Extracted {len(pdf.pages)} pages to {output_path}")

pdf_to_text("input.pdf", "output.txt")

Part 3 β€” Table Extraction

import pdfplumber, csv

with pdfplumber.open("report.pdf") as pdf:
    all_tables = []
    for page in pdf.pages:
        tables = page.extract_tables()
        for table in tables:
            all_tables.extend(table)

# Write to CSV
with open("tables.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.writer(f)
    writer.writerows(all_tables)

print(f"Extracted {len(all_tables)} rows")

Fine-tuned Table Extraction

with pdfplumber.open("complex.pdf") as pdf:
    page = pdf.pages[0]
    table = page.extract_table({
        "vertical_strategy":   "lines",   # "lines", "text", "explicit"
        "horizontal_strategy": "lines",
        "snap_tolerance":      3,
        "join_tolerance":      3,
        "edge_min_length":     3,
        "min_words_vertical":  1,
    })
    for row in table:
        print(row)

Part 4 β€” Merge, Split, and Rotate

Merge Multiple PDFs

from pypdf import PdfWriter

writer = PdfWriter()
files = ["chapter1.pdf", "chapter2.pdf", "appendix.pdf"]

for path in files:
    writer.append(path)

with open("combined.pdf", "wb") as out:
    writer.write(out)

print(f"Merged {len(files)} files β†’ combined.pdf")

Split β€” Extract a Range of Pages

from pypdf import PdfReader, PdfWriter

reader = PdfReader("large_report.pdf")
writer = PdfWriter()

# Extract pages 3–7 (0-indexed: 2–6)
for page_num in range(2, 7):
    writer.add_page(reader.pages[page_num])

with open("pages_3_to_7.pdf", "wb") as out:
    writer.write(out)

Split β€” One File Per Page

from pypdf import PdfReader, PdfWriter
import os

reader = PdfReader("document.pdf")
os.makedirs("pages", exist_ok=True)

for i, page in enumerate(reader.pages):
    writer = PdfWriter()
    writer.add_page(page)
    with open(f"pages/page_{i+1:03d}.pdf", "wb") as out:
        writer.write(out)

print(f"Split into {len(reader.pages)} files in pages/")

Rotate Pages

from pypdf import PdfReader, PdfWriter

reader = PdfReader("sideways.pdf")
writer = PdfWriter()

for page in reader.pages:
    page.rotate(90)  # 90, 180, or 270 degrees
    writer.add_page(page)

with open("rotated.pdf", "wb") as out:
    writer.write(out)

Part 5 β€” Create PDFs

HTML / CSS β†’ PDF (weasyprint)

from weasyprint import HTML, CSS

html_content = """
<!DOCTYPE html>
<html>
<head>
<style>
  body { font-family: Arial, sans-serif; margin: 40px; }
  h1   { color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 8px; }
  table { width: 100%; border-collapse: collapse; margin-top: 20px; }
  th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
  th { background: #3498db; color: white; }
  tr:nth-child(even) { background: #f2f2f2; }
  @page { margin: 20mm; }
</style>
</head>
<body>
  <h1>Quarterly Report β€” Q2 2026</h1>
  <p>Generated: May 18, 2026</p>
  <table>
    <tr><th>Item</th><th>Revenue</th><th>Growth</th></tr>
    <tr><td>Product A</td><td>$120,000</td><td>+12%</td></tr>
    <tr><td>Product B</td><td>$85,000</td><td>+5%</td></tr>
  </table>
</body>
</html>
"""

HTML(string=html_content).write_pdf("report.pdf")
print("Created report.pdf")

Markdown β†’ PDF

import markdown
from weasyprint import HTML

with open("README.md", "r") as f:
    md_content = f.read()

html_body = markdown.markdown(md_content, extensions=["tables", "fenced_code"])

full_html = f"""<!DOCTYPE html>
<html>
<head>
<style>
  body {{ font-family: Georgia, serif; max-width: 800px; margin: 40px auto; }}
  code {{ background: #f4f4f4; padding: 2px 4px; border-radius: 3px; font-size: 0.9em; }}
  pre  {{ background: #f4f4f4; padding: 16px; border-radius: 6px; overflow-x: auto; }}
  @page {{ margin: 20mm; }}
</style>
</head>
<body>{html_body}</body>
</html>"""

HTML(string=full_html).write_pdf("output.pdf")

Programmatic PDF with reportlab

from reportlab.lib.pagesizes import A4
from reportlab.lib.units import mm
from reportlab.pdfgen import canvas

def create_invoice(filename, data):
    c = canvas.Canvas(filename, pagesize=A4)
    width, height = A4

    # Header
    c.setFont("Helvetica-Bold", 24)
    c.drawString(20*mm, height - 30*mm, "INVOICE")
    c.setFont("Helvetica", 11)
    c.drawString(20*mm, height - 42*mm, f"Invoice #: {data['number']}")
    c.drawString(20*mm, height - 50*mm, f"Date: {data['date']}")

    # Line items
    y = height - 80*mm
    c.setFont("Helvetica-Bold", 10)
    c.drawString(20*mm, y, "Description")
    c.drawString(120*mm, y, "Amount")
    y -= 8*mm
    c.line(20*mm, y, 190*mm, y)
    y -= 6*mm

    c.setFont("Helvetica", 10)
    for item in data["items"]:
        c.drawString(20*mm, y, item["description"])
        c.drawRightString(190*mm, y, f"${item['amount']:,.2f}")
        y -= 7*mm

    # Total
    y -= 5*mm
    c.line(20*mm, y, 190*mm, y)
    y -= 8*mm
    c.setFont("Helvetica-Bold", 12)
    c.drawString(120*mm, y, "Total:")
    c.drawRightString(190*mm, y, f"${data['total']:,.2f}")

    c.save()

create_invoice("invoice.pdf", {
    "number": "INV-2026-042",
    "date": "May 18, 2026",
    "items": [
        {"description": "Consulting β€” 10 hours @ $150/hr", "amount": 1500},
        {"description": "Setup fee", "amount": 250},
    ],
    "total": 1750,
})

Part 6 β€” Scanned PDFs (OCR)

Scanned PDFs are images. pdfplumber returns no text β€” you must OCR first.

# System requirements
brew install tesseract          # macOS
sudo apt-get install tesseract-ocr  # Ubuntu/Debian
pip install pytesseract pdf2image
import pytesseract
from pdf2image import convert_from_path

# Convert PDF pages to images, then OCR each
pages = convert_from_path("scanned.pdf", dpi=300)

full_text = []
for i, page_image in enumerate(pages, 1):
    text = pytesseract.image_to_string(page_image, lang="eng")
    print(f"=== Page {i} ===")
    print(text)
    full_text.append(text)

with open("scanned_output.txt", "w", encoding="utf-8") as f:
    f.write("\n\n".join(full_text))

Part 7 β€” Encrypt and Decrypt

from pypdf import PdfReader, PdfWriter

# Encrypt
reader = PdfReader("document.pdf")
writer = PdfWriter()
writer.append_pages_from_reader(reader)
writer.encrypt(user_password="view123", owner_password="admin456", use_128bit=True)
with open("protected.pdf", "wb") as f:
    writer.write(f)

# Decrypt
reader = PdfReader("protected.pdf")
if reader.is_encrypted:
    reader.decrypt("view123")
writer = PdfWriter()
writer.append_pages_from_reader(reader)
with open("unlocked.pdf", "wb") as f:
    writer.write(f)

Common Pitfalls

ProblemCauseFix
extract_text() returns empty stringScanned PDF (image-based)Use OCR (Part 6)
Garbled text orderComplex multi-column layoutTry extract_words() with coordinate sorting
weasyprint missing fontsSystem fonts not foundInstall system font packages
Table extraction misses columnsPoor line detectionAdjust vertical_strategy to "text"
Large PDF slow to processAll pages loaded at onceProcess page-by-page in a loop

Checklist

  • Identified whether PDF is digital (has selectable text) or scanned (image)
  • Chose correct tool for the task (extraction vs creation vs merge)
  • Scanned PDFs routed through OCR pipeline
  • For HTMLβ†’PDF, tested output at target paper size (A4 or Letter)
  • Merged output verified with page count check
  • Large PDFs processed page-by-page to avoid memory issues

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.