
- 121 installs
- 70 repo stars
- Updated July 26, 2026
- rysweet/amplihack
Create, read, edit, merge, or extract content from PDF files as part of documentation and deliverable generation pipelines.
About
The pdf skill in rysweet/amplihack provides agent-guided PDF document operations including extraction, editing, merging, splitting, and formatted export generation. It supports build-stage documentation workflows for content products, SaaS reporting features, and CLI automation where PDFs are standard deliverables for specs, invoices, and customer-facing artifacts.
- Reads and extracts text or tables from PDFs
- Edits, merges, and splits PDF document files
- Supports report and export generation workflows
- Integrates document output into agent pipelines
- Handles common PDF cleanup and formatting tasks
Pdf by the numbers
- 121 all-time installs (skills.sh)
- +1 installs in the week ending Jul 26, 2026 (Skillselion tracking)
- Ranked #308 of 687 Office & Documents skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/rysweet/amplihack --skill pdfAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 121 |
|---|---|
| repo stars | ★ 70 |
| Last updated | July 26, 2026 |
| Repository | rysweet/amplihack ↗ |
What it does
Create, read, edit, merge, or extract content from PDF files as part of documentation and deliverable generation pipelines.
Files
PDF Processing Guide
Overview
This guide covers essential PDF processing operations using Python libraries and command-line tools. For advanced features, JavaScript libraries, and detailed examples, see reference.md. If you need to fill out a PDF form, read forms.md and follow its instructions.
Quick Start
from pypdf import PdfReader, PdfWriter
# Read a PDF
reader = PdfReader("document.pdf")
print(f"Pages: {len(reader.pages)}")
# Extract text
text = ""
for page in reader.pages:
text += page.extract_text()Python Libraries
pypdf - Basic Operations
Merge PDFs
from pypdf import PdfWriter, PdfReader
writer = PdfWriter()
for pdf_file in ["doc1.pdf", "doc2.pdf", "doc3.pdf"]:
reader = PdfReader(pdf_file)
for page in reader.pages:
writer.add_page(page)
with open("merged.pdf", "wb") as output:
writer.write(output)Split PDF
reader = PdfReader("input.pdf")
for i, page in enumerate(reader.pages):
writer = PdfWriter()
writer.add_page(page)
with open(f"page_{i+1}.pdf", "wb") as output:
writer.write(output)Extract Metadata
reader = PdfReader("document.pdf")
meta = reader.metadata
print(f"Title: {meta.title}")
print(f"Author: {meta.author}")
print(f"Subject: {meta.subject}")
print(f"Creator: {meta.creator}")Rotate Pages
reader = PdfReader("input.pdf")
writer = PdfWriter()
page = reader.pages[0]
page.rotate(90) # Rotate 90 degrees clockwise
writer.add_page(page)
with open("rotated.pdf", "wb") as output:
writer.write(output)pdfplumber - Text and Table Extraction
Extract Text with Layout
import pdfplumber
with pdfplumber.open("document.pdf") as pdf:
for page in pdf.pages:
text = page.extract_text()
print(text)Extract Tables
with pdfplumber.open("document.pdf") as pdf:
for i, page in enumerate(pdf.pages):
tables = page.extract_tables()
for j, table in enumerate(tables):
print(f"Table {j+1} on page {i+1}:")
for row in table:
print(row)Advanced Table Extraction
import pandas as pd
with pdfplumber.open("document.pdf") as pdf:
all_tables = []
for page in pdf.pages:
tables = page.extract_tables()
for table in tables:
if table: # Check if table is not empty
df = pd.DataFrame(table[1:], columns=table[0])
all_tables.append(df)
# Combine all tables
if all_tables:
combined_df = pd.concat(all_tables, ignore_index=True)
combined_df.to_excel("extracted_tables.xlsx", index=False)reportlab - Create PDFs
Basic PDF Creation
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas
c = canvas.Canvas("hello.pdf", pagesize=letter)
width, height = letter
# Add text
c.drawString(100, height - 100, "Hello World!")
c.drawString(100, height - 120, "This is a PDF created with reportlab")
# Add a line
c.line(100, height - 140, 400, height - 140)
# Save
c.save()Create PDF with Multiple Pages
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 = []
# Add content
title = Paragraph("Report Title", styles['Title'])
story.append(title)
story.append(Spacer(1, 12))
body = Paragraph("This is the body of the report. " * 20, styles['Normal'])
story.append(body)
story.append(PageBreak())
# Page 2
story.append(Paragraph("Page 2", styles['Heading1']))
story.append(Paragraph("Content for page 2", styles['Normal']))
# Build PDF
doc.build(story)Command-Line Tools
pdftotext (poppler-utils)
# Extract text
pdftotext input.pdf output.txt
# Extract text preserving layout
pdftotext -layout input.pdf output.txt
# Extract specific pages
pdftotext -f 1 -l 5 input.pdf output.txt # Pages 1-5qpdf
# Merge PDFs
qpdf --empty --pages file1.pdf file2.pdf -- merged.pdf
# Split pages
qpdf input.pdf --pages . 1-5 -- pages1-5.pdf
qpdf input.pdf --pages . 6-10 -- pages6-10.pdf
# Rotate pages
qpdf input.pdf output.pdf --rotate=+90:1 # Rotate page 1 by 90 degrees
# Remove password
qpdf --password=mypassword --decrypt encrypted.pdf decrypted.pdfpdftk (if available)
# Merge
pdftk file1.pdf file2.pdf cat output merged.pdf
# Split
pdftk input.pdf burst
# Rotate
pdftk input.pdf rotate 1east output rotated.pdfCommon Tasks
Extract Text from Scanned PDFs
# Requires: pip install pytesseract pdf2image
import pytesseract
from pdf2image import convert_from_path
# Convert PDF to images
images = convert_from_path('scanned.pdf')
# OCR each page
text = ""
for i, image in enumerate(images):
text += f"Page {i+1}:\n"
text += pytesseract.image_to_string(image)
text += "\n\n"
print(text)Add Watermark
from pypdf import PdfReader, PdfWriter
# Create watermark (or load existing)
watermark = PdfReader("watermark.pdf").pages[0]
# Apply to all pages
reader = PdfReader("document.pdf")
writer = PdfWriter()
for page in reader.pages:
page.merge_page(watermark)
writer.add_page(page)
with open("watermarked.pdf", "wb") as output:
writer.write(output)Extract Images
# Using pdfimages (poppler-utils)
pdfimages -j input.pdf output_prefix
# This extracts all images as output_prefix-000.jpg, output_prefix-001.jpg, etc.Password Protection
from pypdf import PdfReader, PdfWriter
reader = PdfReader("input.pdf")
writer = PdfWriter()
for page in reader.pages:
writer.add_page(page)
# Add password
writer.encrypt("userpassword", "ownerpassword")
with open("encrypted.pdf", "wb") as output:
writer.write(output)Quick Reference
| Task | Best Tool | Command/Code |
|---|---|---|
| Merge PDFs | pypdf | writer.add_page(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 | qpdf | qpdf --empty --pages ... |
| OCR scanned PDFs | pytesseract | Convert to image first |
| Fill PDF forms | pdf-lib or pypdf (see forms.md) | See forms.md |
Next Steps
- For advanced pypdfium2 usage, see reference.md
- For JavaScript libraries (pdf-lib), see reference.md
- If you need to fill out a PDF form, follow the instructions in forms.md
- For troubleshooting guides, see reference.md
Dependencies for PDF Skill
Overview
The PDF skill requires Python packages for PDF manipulation and optionally system packages for enhanced functionality like OCR and command-line processing. This document provides complete installation instructions for all dependencies.
Dependency Categories
Required (Core Functionality)
These packages are required for basic PDF skill functionality:
Python Packages:
pypdf>=4.0.0- PDF manipulation (merge, split, rotate, metadata)pdfplumber>=0.10.0- Text and table extraction with layout preservationreportlab>=4.0.0- PDF generation and creationpandas>=2.0.0- Data manipulation for table processing
Optional (Enhanced Functionality)
These packages enable additional features but the skill works without them:
Python Packages:
pytesseract>=0.3.10- OCR for scanned PDFs (requires tesseract engine)pdf2image>=1.16.0- PDF to image conversion for OCR (requires poppler)pillow>=10.0.0- Image processing support
System Packages:
poppler-utils- Command-line PDF tools (pdftotext, pdfimages, pdftoppm)qpdf- Advanced PDF manipulation and repairpdftk- PDF toolkit for complex operationstesseract-ocr- OCR engine for pytesseract
Installation Instructions
Quick Install (Required Only)
Install core Python packages for basic PDF functionality:
pip install pypdf pdfplumber reportlab pandasComplete Install (All Features)
Install all packages for full functionality:
# Python packages
pip install pypdf pdfplumber reportlab pandas pytesseract pdf2image pillow
# System packages (see platform-specific instructions below)Platform-Specific Installation
macOS
# Install Homebrew if not already installed
# /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# Python packages
pip install pypdf pdfplumber reportlab pandas pytesseract pdf2image pillow
# System packages
brew install poppler qpdf tesseract
# Note: pdftk is no longer maintained for macOS
# Alternative: use pypdf or qpdf for equivalent operationsUbuntu/Debian Linux
# Python packages
pip install pypdf pdfplumber reportlab pandas pytesseract pdf2image pillow
# System packages
sudo apt-get update
sudo apt-get install -y poppler-utils qpdf pdftk tesseract-ocr
# Additional tesseract language packs (optional)
sudo apt-get install -y tesseract-ocr-eng tesseract-ocr-spaFedora/RHEL/CentOS
# Python packages
pip install pypdf pdfplumber reportlab pandas pytesseract pdf2image pillow
# System packages
sudo dnf install -y poppler-utils qpdf pdftk tesseract
# Additional tesseract language packs (optional)
sudo dnf install -y tesseract-langpack-eng tesseract-langpack-spaWindows
# Python packages
pip install pypdf pdfplumber reportlab pandas pytesseract pdf2image pillow
# System packages
# Install via Chocolatey (recommended)
choco install poppler qpdf tesseract
# Or download manually:
# - Poppler: https://github.com/oschwartz10612/poppler-windows/releases
# - QPDF: https://qpdf.sourceforge.io/
# - Tesseract: https://github.com/UB-Mannheim/tesseract/wiki
# - PDFtk: https://www.pdflabs.com/tools/pdftk-the-pdf-toolkit/
# Add installation directories to PATH environment variableVerification
Verify installations with these commands:
Python Packages
# Check pypdf
python -c "import pypdf; print(f'pypdf {pypdf.__version__}')"
# Check pdfplumber
python -c "import pdfplumber; print(f'pdfplumber {pdfplumber.__version__}')"
# Check reportlab
python -c "import reportlab; print(f'reportlab {reportlab.Version}')"
# Check pandas
python -c "import pandas; print(f'pandas {pandas.__version__}')"
# Check pytesseract (optional)
python -c "import pytesseract; print('pytesseract installed')"
# Check pdf2image (optional)
python -c "import pdf2image; print('pdf2image installed')"System Packages
# Check poppler-utils
pdftotext -v
# Check qpdf
qpdf --version
# Check pdftk (may not be available on all platforms)
pdftk --version
# Check tesseract
tesseract --versionAutomated Verification
Use the verification script to check all dependencies:
cd .claude/skills
python common/verification/verify_skill.py pdfExpected output:
Verifying pdf skill dependencies...
Python packages:
pypdf: Installed
pdfplumber: Installed
reportlab: Installed
pandas: Installed
pytesseract: Installed (or Not installed)
pdf2image: Installed (or Not installed)
System commands:
pdftotext: Available (or Not available)
qpdf: Available (or Not available)
pdftk: Available (or Not available)
tesseract: Available (or Not available)
✓ pdf skill is ready (or ✗ pdf skill is missing dependencies)Dependency Details
pypdf
Purpose: Core PDF manipulation library for Python
Capabilities:
- Merge multiple PDFs
- Split PDFs into individual pages
- Rotate, crop, and scale pages
- Extract metadata (title, author, creation date)
- Encrypt and decrypt PDFs
- Extract text (basic)
License: BSD-3-Clause
Documentation: https://pypdf.readthedocs.io/
pdfplumber
Purpose: Advanced text and table extraction from PDFs
Capabilities:
- Extract text with layout preservation
- Extract tables with cell boundaries
- Access detailed page information (lines, curves, rectangles)
- Visual debugging tools
- Better handling of complex layouts than pypdf
License: MIT
Documentation: https://github.com/jsvine/pdfplumber
reportlab
Purpose: PDF generation from Python code
Capabilities:
- Create PDFs from scratch
- Draw text, shapes, images
- Support for forms and interactive elements
- Multi-page documents with templates
- Tables and flowable layouts (Platypus)
License: BSD-3-Clause
Documentation: https://www.reportlab.com/docs/reportlab-userguide.pdf
pandas
Purpose: Data manipulation for extracted tables
Capabilities:
- Convert extracted tables to DataFrames
- Export to Excel, CSV, JSON
- Data cleaning and transformation
- Analysis and aggregation
License: BSD-3-Clause
Documentation: https://pandas.pydata.org/docs/
pytesseract (Optional)
Purpose: OCR (Optical Character Recognition) for scanned PDFs
Capabilities:
- Extract text from image-based PDFs
- Multi-language support
- Confidence scores for recognized text
Requirements: Requires tesseract-ocr system package
License: Apache-2.0
Documentation: https://github.com/madmaze/pytesseract
pdf2image (Optional)
Purpose: Convert PDF pages to images for OCR processing
Capabilities:
- Convert PDF to PIL Image objects
- Specify DPI for quality control
- Page range selection
Requirements: Requires poppler-utils system package
License: MIT
Documentation: https://github.com/Belval/pdf2image
poppler-utils (Optional System Package)
Purpose: Command-line PDF processing tools
Tools Included:
pdftotext: Extract text from PDFspdfimages: Extract images from PDFspdftoppm: Convert PDF to PPM/PNG imagespdfinfo: Display PDF metadatapdfseparate: Split PDF into pagespdfunite: Merge PDFs
License: GPL
Documentation: https://poppler.freedesktop.org/
qpdf (Optional System Package)
Purpose: Command-line PDF transformation and inspection
Capabilities:
- Merge and split PDFs
- Rotate pages
- Encrypt and decrypt
- Linearize for web optimization
- Repair corrupted PDFs
License: Apache-2.0
Documentation: https://qpdf.sourceforge.io/
pdftk (Optional System Package)
Purpose: PDF toolkit for complex operations
Capabilities:
- Merge, split, rotate PDFs
- Apply watermarks
- Fill PDF forms
- Update metadata
- Attach files
Note: No longer actively maintained, may not be available on newer systems
License: GPL
Documentation: https://www.pdflabs.com/docs/pdftk-man-page/
tesseract-ocr (Optional System Package)
Purpose: OCR engine for extracting text from images
Capabilities:
- Text recognition from images
- 100+ language support
- Configurable recognition modes
- Output in multiple formats
License: Apache-2.0
Documentation: https://github.com/tesseract-ocr/tesseract
Troubleshooting
ImportError: No module named 'pypdf'
Solution: Install pypdf
pip install pypdfModuleNotFoundError: No module named 'pdfplumber'
Solution: Install pdfplumber
pip install pdfplumberpytesseract.pytesseract.TesseractNotFoundError
Solution: Install tesseract-ocr system package
# macOS
brew install tesseract
# Ubuntu/Debian
sudo apt-get install tesseract-ocr
# Windows
choco install tesseractpdf2image requires poppler
Solution: Install poppler-utils
# macOS
brew install poppler
# Ubuntu/Debian
sudo apt-get install poppler-utils
# Windows
choco install popplerCommand not found: qpdf
Solution: Install qpdf
# macOS
brew install qpdf
# Ubuntu/Debian
sudo apt-get install qpdf
# Windows
choco install qpdfCommand not found: pdftk
Solution: pdftk may not be available on your platform. Use alternatives:
- Use pypdf for merge/split operations
- Use qpdf for advanced operations
- Use pypdf for form filling
Permission denied errors
Solution: Use pip with --user flag or virtual environment
pip install --user pypdf pdfplumber reportlab pandasVersion conflicts
Solution: Use virtual environment for isolation
python -m venv pdf_skill_env
source pdf_skill_env/bin/activate # Linux/macOS
# or
pdf_skill_env\Scripts\activate # Windows
pip install pypdf pdfplumber reportlab pandasMinimal Installation
For testing or minimal functionality:
# Absolute minimum (merge, split, basic text extraction)
pip install pypdf
# Recommended minimum (adds table extraction and PDF creation)
pip install pypdf pdfplumber reportlab pandasDocker Installation
For containerized environments:
FROM python:3.11-slim
# Install system dependencies
RUN apt-get update && apt-get install -y \
poppler-utils \
qpdf \
tesseract-ocr \
&& rm -rf /var/lib/apt/lists/*
# Install Python packages
RUN pip install --no-cache-dir \
pypdf \
pdfplumber \
reportlab \
pandas \
pytesseract \
pdf2image \
pillowCI/CD Considerations
For GitHub Actions or other CI environments:
# .github/workflows/test.yml
- name: Install PDF skill dependencies
run: |
pip install pypdf pdfplumber reportlab pandas pytesseract pdf2image pillow
sudo apt-get update
sudo apt-get install -y poppler-utils qpdf tesseract-ocrNote: Tests should skip gracefully if optional dependencies are missing.
Upgrading Dependencies
To upgrade to latest versions:
# Upgrade all PDF skill dependencies
pip install --upgrade pypdf pdfplumber reportlab pandas pytesseract pdf2image pillow
# Or upgrade individually
pip install --upgrade pypdf
pip install --upgrade pdfplumberDependency Licenses Summary
| Package | License | Commercial Use |
|---|---|---|
| pypdf | BSD-3-Clause | Yes |
| pdfplumber | MIT | Yes |
| reportlab | BSD-3-Clause | Yes |
| pandas | BSD-3-Clause | Yes |
| pytesseract | Apache-2.0 | Yes |
| pdf2image | MIT | Yes |
| poppler-utils | GPL | Yes (linking allowed) |
| qpdf | Apache-2.0 | Yes |
| pdftk | GPL | Yes (linking allowed) |
| tesseract-ocr | Apache-2.0 | Yes |
All dependencies are permissive licenses compatible with commercial use.
---
Last Updated: 2025-11-08 Maintained By: amplihack project
PDF Skill Usage Examples
Overview
This document provides practical examples of using the PDF skill in Claude Code conversations. These examples demonstrate common workflows and expected outcomes.
Example 1: Extract Text from a Research Paper
Scenario
You have a research paper PDF and need to extract all text for analysis.
Conversation
User: Extract all text from research_paper.pdf and save it to a text file
Claude: I'll extract the text from the PDF using pdfplumber for better layout preservation.
import pdfplumber
with pdfplumber.open("research_paper.pdf") as pdf:
text = ""
for page in pdf.pages:
text += page.extract_text()
text += "\n\n--- Page Break ---\n\n"
with open("research_paper_text.txt", "w", encoding="utf-8") as f:
f.write(text)
print(f"Extracted text from {len(pdf.pages)} pages")Expected Output
research_paper_text.txtfile created with all extracted text- Text preserves paragraph structure and layout
- Page breaks clearly marked
---
Example 2: Convert Tables to Excel
Scenario
You have a financial report PDF with multiple tables that need to be extracted and analyzed in Excel.
Conversation
User: Extract all tables from financial_report.pdf and save them to Excel
Claude: I'll use pdfplumber to extract tables and pandas to create a structured Excel file.
import pdfplumber
import pandas as pd
all_tables = []
with pdfplumber.open("financial_report.pdf") as pdf:
for page_num, page in enumerate(pdf.pages, 1):
tables = page.extract_tables()
for table_num, table in enumerate(tables, 1):
if table and len(table) > 1:
# First row as headers
df = pd.DataFrame(table[1:], columns=table[0])
df['source_page'] = page_num
df['table_number'] = table_num
all_tables.append(df)
if all_tables:
# Save each table to separate sheet
with pd.ExcelWriter("financial_tables.xlsx", engine='openpyxl') as writer:
for i, df in enumerate(all_tables, 1):
sheet_name = f"Table_{i}_Page_{df['source_page'].iloc[0]}"
df.drop(['source_page', 'table_number'], axis=1).to_excel(
writer, sheet_name=sheet_name, index=False
)
print(f"Extracted {len(all_tables)} tables to financial_tables.xlsx")
else:
print("No tables found in PDF")Expected Output
financial_tables.xlsxfile with multiple sheets- Each sheet contains one extracted table
- Sheet names indicate source page
- Headers preserved from PDF
---
Example 3: Create a Report PDF
Scenario
Generate a multi-page report PDF with title, sections, and formatted text.
Conversation
User: Create a sales report PDF with title "Q4 2024 Sales Report", three sections, and a summary table
Claude: I'll use reportlab to create a structured PDF with proper formatting.
from reportlab.lib.pagesizes import letter
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import inch
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, PageBreak
from reportlab.lib import colors
doc = SimpleDocTemplate("Q4_2024_Sales_Report.pdf", pagesize=letter)
styles = getSampleStyleSheet()
story = []
# Title
title = Paragraph("Q4 2024 Sales Report", styles['Title'])
story.append(title)
story.append(Spacer(1, 0.5*inch))
# Executive Summary
story.append(Paragraph("Executive Summary", styles['Heading1']))
story.append(Paragraph(
"Total sales increased 15% compared to Q3 2024, driven by strong performance "
"in the enterprise segment. Key highlights include...",
styles['Normal']
))
story.append(Spacer(1, 0.3*inch))
# Section 1: Revenue Breakdown
story.append(Paragraph("Revenue Breakdown", styles['Heading1']))
story.append(Paragraph(
"Revenue distribution across segments showed significant growth...",
styles['Normal']
))
story.append(Spacer(1, 0.2*inch))
# Summary Table
data = [
['Segment', 'Q3 2024', 'Q4 2024', 'Growth'],
['Enterprise', '$2.5M', '$3.1M', '24%'],
['SMB', '$1.8M', '$2.0M', '11%'],
['Consumer', '$0.9M', '$1.0M', '11%'],
['Total', '$5.2M', '$6.1M', '17%']
]
table = Table(data, colWidths=[2*inch, 1.5*inch, 1.5*inch, 1*inch])
table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.grey),
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, 0), 12),
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
('BACKGROUND', (0, 1), (-1, -1), colors.beige),
('GRID', (0, 0), (-1, -1), 1, colors.black)
]))
story.append(table)
story.append(PageBreak())
# Section 2: Regional Performance
story.append(Paragraph("Regional Performance", styles['Heading1']))
story.append(Paragraph(
"North America led growth with 20% increase, followed by Europe at 15%...",
styles['Normal']
))
story.append(Spacer(1, 0.3*inch))
# Section 3: Future Outlook
story.append(Paragraph("Future Outlook", styles['Heading1']))
story.append(Paragraph(
"Looking ahead to Q1 2025, we anticipate continued momentum with...",
styles['Normal']
))
# Build PDF
doc.build(story)
print("Created Q4_2024_Sales_Report.pdf")Expected Output
- Professional multi-page PDF report
- Formatted title, sections, and body text
- Table with styling and borders
- Page breaks between major sections
---
Example 4: Merge Multiple Invoices
Scenario
Combine multiple invoice PDFs into a single document for archiving.
Conversation
User: Merge all PDF files in the invoices/ directory into a single archive file
Claude: I'll use pypdf to merge all PDFs while preserving their content.
from pypdf import PdfWriter, PdfReader
from pathlib import Path
writer = PdfWriter()
invoices_dir = Path("invoices")
pdf_files = sorted(invoices_dir.glob("*.pdf"))
if not pdf_files:
print("No PDF files found in invoices/ directory")
else:
for pdf_file in pdf_files:
print(f"Adding {pdf_file.name}...")
reader = PdfReader(pdf_file)
for page in reader.pages:
writer.add_page(page)
# Add metadata
writer.add_metadata({
'/Title': 'Invoice Archive',
'/Author': 'Automated System',
'/Subject': f'Combined invoices from {len(pdf_files)} files'
})
output_file = "invoices_archive_2024.pdf"
with open(output_file, "wb") as output:
writer.write(output)
print(f"\nMerged {len(pdf_files)} invoices into {output_file}")
print(f"Total pages: {len(writer.pages)}")Expected Output
- Single
invoices_archive_2024.pdffile - All invoices combined in order
- Metadata indicating source count
- Console output showing merge progress
---
Example 5: Extract Images from Catalog
Scenario
Extract all product images from a catalog PDF for use in a website.
Conversation
User: Extract all images from product_catalog.pdf and save them as separate files
Claude: I'll use poppler-utils' pdfimages command to extract all images.
# Create output directory
mkdir -p extracted_images
# Extract images (JPEG format)
pdfimages -j product_catalog.pdf extracted_images/product
# List extracted images
ls -lh extracted_images/
echo "Images extracted to extracted_images/"Alternative Python approach:
# If pdfimages is not available, use pdf2image
from pdf2image import convert_from_path
from pathlib import Path
output_dir = Path("extracted_images")
output_dir.mkdir(exist_ok=True)
# Convert each page to image
images = convert_from_path("product_catalog.pdf", dpi=300)
for i, image in enumerate(images, 1):
image.save(output_dir / f"page_{i}.jpg", "JPEG")
print(f"Extracted {len(images)} page images to {output_dir}/")Expected Output
extracted_images/directory created- Individual image files (product-000.jpg, product-001.jpg, etc.)
- High-quality JPEG images suitable for web use
---
Example 6: OCR Scanned Document
Scenario
A scanned invoice PDF needs text extraction for accounting software.
Conversation
User: Extract text from scanned_invoice.pdf using OCR
Claude: I'll use pytesseract to perform OCR on the scanned document.
import pytesseract
from pdf2image import convert_from_path
# Convert PDF to images
print("Converting PDF to images...")
images = convert_from_path('scanned_invoice.pdf', dpi=300)
# OCR each page
full_text = ""
for i, image in enumerate(images, 1):
print(f"Processing page {i}...")
text = pytesseract.image_to_string(image)
full_text += f"--- Page {i} ---\n\n"
full_text += text
full_text += "\n\n"
# Save extracted text
with open("scanned_invoice_text.txt", "w", encoding="utf-8") as f:
f.write(full_text)
print(f"OCR complete. Extracted text from {len(images)} pages")
print("Saved to scanned_invoice_text.txt")Expected Output
scanned_invoice_text.txtwith recognized text- Processing messages for each page
- Text accuracy depends on scan quality
---
Example 7: Split PDF by Pages
Scenario
Break a large document into individual page files for review.
Conversation
User: Split large_document.pdf into individual page files
Claude: I'll extract each page as a separate PDF file.
from pypdf import PdfReader, PdfWriter
from pathlib import Path
reader = PdfReader("large_document.pdf")
output_dir = Path("split_pages")
output_dir.mkdir(exist_ok=True)
for i, page in enumerate(reader.pages, 1):
writer = PdfWriter()
writer.add_page(page)
output_file = output_dir / f"page_{i:03d}.pdf"
with open(output_file, "wb") as output:
writer.write(output)
print(f"Split {len(reader.pages)} pages into {output_dir}/")Expected Output
split_pages/directory with individual PDFs- Files named page_001.pdf, page_002.pdf, etc.
- Each file contains exactly one page
---
Example 8: Add Password Protection
Scenario
Protect a confidential document with a password before sharing.
Conversation
User: Add password protection to confidential_report.pdf
Claude: I'll encrypt the PDF with a password.
from pypdf import PdfReader, PdfWriter
reader = PdfReader("confidential_report.pdf")
writer = PdfWriter()
# Copy all pages
for page in reader.pages:
writer.add_page(page)
# Add password protection
user_password = "view123" # Password to open the PDF
owner_password = "admin456" # Password for full permissions
writer.encrypt(
user_password=user_password,
owner_password=owner_password,
permissions_flag=0b0100 # Allow printing only
)
# Save encrypted PDF
with open("confidential_report_protected.pdf", "wb") as output:
writer.write(output)
print("PDF encrypted successfully")
print(f"User password: {user_password}")
print(f"Owner password: {owner_password}")
print("Permissions: Print only")Expected Output
confidential_report_protected.pdfcreated- Password required to open
- Limited permissions (print only)
- Original file unchanged
---
Example 9: Rotate Pages
Scenario
Some pages in a scanned document are oriented incorrectly.
Conversation
User: Rotate pages 2 and 3 in document.pdf by 90 degrees clockwise
Claude: I'll rotate the specified pages and save the result.
from pypdf import PdfReader, PdfWriter
reader = PdfReader("document.pdf")
writer = PdfWriter()
for i, page in enumerate(reader.pages):
# Rotate pages 2 and 3 (indices 1 and 2)
if i in [1, 2]:
page.rotate(90)
writer.add_page(page)
with open("document_rotated.pdf", "wb") as output:
writer.write(output)
print(f"Rotated pages 2 and 3")
print(f"Total pages: {len(writer.pages)}")Expected Output
document_rotated.pdfwith corrected orientation- Pages 2 and 3 rotated 90 degrees clockwise
- Other pages unchanged
---
Example 10: Extract Metadata
Scenario
Review PDF properties and metadata for document management.
Conversation
User: Show me all metadata from annual_report.pdf
Claude: I'll extract and display all available PDF metadata.
from pypdf import PdfReader
from datetime import datetime
reader = PdfReader("annual_report.pdf")
meta = reader.metadata
print("PDF Metadata:")
print("=" * 50)
print(f"Title: {meta.title or 'Not set'}")
print(f"Author: {meta.author or 'Not set'}")
print(f"Subject: {meta.subject or 'Not set'}")
print(f"Creator: {meta.creator or 'Not set'}")
print(f"Producer: {meta.producer or 'Not set'}")
if meta.creation_date:
print(f"Created: {meta.creation_date}")
if meta.modification_date:
print(f"Modified: {meta.modification_date}")
print(f"\nDocument Info:")
print(f"Pages: {len(reader.pages)}")
print(f"Encrypted: {reader.is_encrypted}")
# Get first page dimensions
page = reader.pages[0]
width = page.mediabox.width
height = page.mediabox.height
print(f"Page size: {width} x {height} points")Expected Output
PDF Metadata:
==================================================
Title: Annual Report 2024
Author: John Doe
Subject: Financial Annual Report
Creator: Microsoft Word
Producer: Adobe PDF Library
Created: 2024-01-15 14:30:00
Modified: 2024-01-20 09:15:00
Document Info:
Pages: 45
Encrypted: False
Page size: 612.0 x 792.0 points---
Common Patterns
Error Handling
from pypdf import PdfReader
from pypdf.errors import PdfReadError
try:
reader = PdfReader("document.pdf")
# Process PDF
except FileNotFoundError:
print("Error: PDF file not found")
except PdfReadError:
print("Error: Could not read PDF (may be corrupted)")
except Exception as e:
print(f"Unexpected error: {e}")Progress Tracking
from pypdf import PdfReader
reader = PdfReader("large_document.pdf")
total_pages = len(reader.pages)
for i, page in enumerate(reader.pages, 1):
# Process page
text = page.extract_text()
# Show progress
progress = (i / total_pages) * 100
print(f"Progress: {progress:.1f}% ({i}/{total_pages})", end='\r')
print("\nComplete!")Batch Processing
from pypdf import PdfReader
from pathlib import Path
pdf_files = Path("documents").glob("*.pdf")
results = []
for pdf_file in pdf_files:
try:
reader = PdfReader(pdf_file)
results.append({
'file': pdf_file.name,
'pages': len(reader.pages),
'status': 'success'
})
except Exception as e:
results.append({
'file': pdf_file.name,
'pages': 0,
'status': f'error: {str(e)}'
})
# Summary
print(f"\nProcessed {len(results)} files")
for result in results:
print(f" {result['file']}: {result['pages']} pages - {result['status']}")---
Tips and Best Practices
1. Choose the right tool:
- pypdf: Basic operations (merge, split, rotate)
- pdfplumber: Text and table extraction
- reportlab: PDF creation
- OCR: Only for scanned documents
2. Memory management:
- Process large PDFs page by page
- Close file handles explicitly
- Use context managers (
withstatements)
3. Quality vs. Speed:
- Higher DPI (300+) for better OCR accuracy
- Lower DPI (150) for faster processing
- Balance based on requirements
4. Error handling:
- Always handle FileNotFoundError
- Check for corrupted PDFs
- Validate extracted data
5. Testing:
- Test with various PDF sources
- Verify table extraction accuracy
- Check character encoding in text
---
Next Steps
- Review SKILL.md for complete API reference
- Check DEPENDENCIES.md for installation requirements
- See README.md for integration details
- Run tests:
pytest tests/ -v
---
Last Updated: 2025-11-08 Maintained By: amplihack project
PDF Skill Integration
Overview
The PDF skill provides comprehensive PDF manipulation capabilities for Claude Code, enabling text and table extraction, PDF creation, document merging/splitting, and form handling. This is the first Office skill integrated into amplihack, establishing the foundation for the broader Office skills integration.
Capabilities
- Extract text from PDFs with layout preservation
- Extract tables and convert to structured data (Excel, CSV)
- Create new PDFs programmatically with text, shapes, and multi-page layouts
- Merge multiple PDFs into a single document
- Split PDFs into individual pages
- Rotate pages and manipulate PDF structure
- Extract and manipulate PDF metadata
- Add watermarks to documents
- Password protection and decryption
- OCR for scanned documents (with optional dependencies)
- Extract images from PDFs
- Fill and process PDF forms
Integration with amplihack
The PDF skill follows amplihack's brick philosophy:
- Self-contained: All PDF processing code and dependencies isolated in this directory
- Clear contract: Well-defined inputs (PDF files) and outputs (text, tables, new PDFs)
- Regeneratable: Can be rebuilt from SKILL.md specification
- Zero-BS: No placeholders - all functionality works or gracefully degrades
- Independent: Works without other Office skills, no cross-dependencies
Quick Start
1. Install dependencies (see DEPENDENCIES.md) 2. Verify installation: python ../common/verification/verify_skill.py pdf 3. Use the skill in Claude Code conversations
Example conversation:
User: Extract all tables from this sales report PDF and save to Excel
Claude: [Uses PDF skill to extract tables with pdfplumber and saves to .xlsx]Architecture
- SKILL.md: Official skill definition from Anthropic (copied verbatim)
- README.md: This file - amplihack-specific integration notes
- DEPENDENCIES.md: Complete dependency documentation with installation instructions
- examples/: Practical usage examples
- tests/: Verification tests that skip gracefully if dependencies missing
Dependencies
The PDF skill has minimal required dependencies and several optional ones:
Required (Core functionality):
- pypdf: PDF manipulation
- pdfplumber: Text and table extraction
- reportlab: PDF creation
- pandas: Data manipulation
Optional (Enhanced functionality):
- pytesseract: OCR for scanned PDFs
- pdf2image: PDF to image conversion
- poppler-utils: Command-line PDF tools
- qpdf: Advanced PDF manipulation
- pdftk: PDF toolkit
See DEPENDENCIES.md for detailed installation instructions.
Testing
Run tests to verify the skill:
cd .claude/skills/pdf
pytest tests/ -vTests will skip gracefully if dependencies are not installed, showing which features are available.
Usage Examples
See examples/example_usage.md for common workflows:
- Extracting text from research papers
- Converting tables from financial reports to Excel
- Creating automated report PDFs
- Merging multiple invoices
- Bulk processing document archives
Known Limitations
1. OCR requires additional setup: pytesseract and tesseract engine must be installed separately 2. Large PDFs: Memory-intensive operations on very large files may require streaming approaches 3. Complex layouts: Table extraction accuracy depends on document structure 4. Scanned documents: Text extraction requires OCR, which is slower and less accurate 5. Platform differences: Some command-line tools (pdftk) may not be available on all platforms
Philosophy Compliance
This integration follows amplihack's core principles:
- Ruthless simplicity: Uses established libraries, no custom PDF parsers
- Modular design: PDF skill is a brick with clear studs (public API)
- Explicit dependencies: All requirements documented, no automatic installation
- Graceful degradation: Optional features skip cleanly if dependencies missing
- Documentation-first: Complete docs before code execution
Troubleshooting
Skill not recognized:
1. Verify SKILL.md exists in this directory 2. Check YAML frontmatter is valid 3. Restart Claude Code session
ImportError for dependencies:
1. Run verification script: python ../common/verification/verify_skill.py pdf 2. Install missing dependencies from DEPENDENCIES.md 3. Re-run tests to confirm
OCR not working:
1. Install tesseract engine (system package) 2. Install pytesseract Python package 3. Verify: tesseract --version
Table extraction poor quality:
1. Try different pages or PDFs 2. Check if PDF is scanned (requires OCR first) 3. Consider manual extraction for complex layouts
Contributing
This skill is sourced from Anthropic's official skills repository. For issues:
1. amplihack integration issues: Open issue in amplihack repository 2. Skill functionality issues: Report to Anthropic skills repository 3. Documentation improvements: Submit PR to amplihack
References
- SKILL.md - Official skill documentation
- DEPENDENCIES.md - Complete dependency list
- examples/example_usage.md - Usage examples
- tests/test_pdf_skill.py - Verification tests
- Anthropic Skills Repository
License
The PDF skill is provided by Anthropic under their proprietary license. See SKILL.md and Anthropic's LICENSE.txt for complete terms. The amplihack integration code (this README, DEPENDENCIES.md, tests, examples) follows amplihack's license.
---
Integration Status: Complete (PR #1) Last Updated: 2025-11-08 Maintained By: amplihack project
"""Basic verification tests for PDF skill.
These tests verify the PDF skill integration:
- Level 1: Skill file structure
- Level 2: Dependency availability
- Level 3: Basic functionality (if dependencies available)
- Level 4: Integration (future)
Tests skip gracefully if dependencies are missing.
"""
import sys
from pathlib import Path
import pytest
import yaml
# Add common verification utilities to path
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "common" / "verification"))
# Define skill dependencies
PYTHON_PACKAGES_REQUIRED = ["pypdf", "pdfplumber", "reportlab", "pandas"]
PYTHON_PACKAGES_OPTIONAL = ["pytesseract", "pdf2image"]
SYSTEM_COMMANDS_REQUIRED = [] # No system commands required for basic functionality
SYSTEM_COMMANDS_OPTIONAL = ["pdftotext", "qpdf", "pdftk", "tesseract"]
# Level 1: Skill Load Tests
def test_skill_file_exists():
"""Verify SKILL.md exists."""
skill_file = Path(__file__).parent.parent / "SKILL.md"
assert skill_file.exists(), "SKILL.md not found"
def test_skill_yaml_valid():
"""Verify SKILL.md has valid YAML frontmatter."""
skill_file = Path(__file__).parent.parent / "SKILL.md"
content = skill_file.read_text()
assert content.startswith("---"), "SKILL.md missing YAML frontmatter"
# Extract and parse YAML
parts = content.split("---")
assert len(parts) >= 3, "Invalid YAML structure in SKILL.md"
metadata = yaml.safe_load(parts[1])
assert isinstance(metadata, dict), "YAML frontmatter is not a dictionary"
assert "name" in metadata, "YAML missing 'name' field"
assert metadata["name"] == "pdf", "YAML name field should be 'pdf'"
assert "description" in metadata, "YAML missing 'description' field"
def test_readme_exists():
"""Verify README.md exists with integration notes."""
readme = Path(__file__).parent.parent / "README.md"
assert readme.exists(), "README.md not found"
content = readme.read_text()
assert "amplihack" in content.lower(), "README missing amplihack context"
assert "pdf" in content.lower(), "README should mention PDF"
def test_dependencies_file_exists():
"""Verify DEPENDENCIES.md exists."""
deps_file = Path(__file__).parent.parent / "DEPENDENCIES.md"
assert deps_file.exists(), "DEPENDENCIES.md not found"
content = deps_file.read_text()
# Check for key dependencies mentioned
assert "pypdf" in content.lower(), "DEPENDENCIES.md should mention pypdf"
assert "pdfplumber" in content.lower(), "DEPENDENCIES.md should mention pdfplumber"
assert "reportlab" in content.lower(), "DEPENDENCIES.md should mention reportlab"
def test_examples_exist():
"""Verify examples directory and content exist."""
examples_dir = Path(__file__).parent.parent / "examples"
assert examples_dir.exists(), "examples/ directory not found"
example_file = examples_dir / "example_usage.md"
assert example_file.exists(), "examples/example_usage.md not found"
content = example_file.read_text()
assert len(content) > 100, "example_usage.md appears to be empty or too short"
# Level 2: Dependency Tests
def check_python_package(package: str) -> bool:
"""Check if Python package is installed."""
try:
__import__(package)
return True
except ImportError:
return False
def check_system_command(command: str) -> bool:
"""Check if system command is available."""
import subprocess
try:
subprocess.run(
[command, "--version"],
capture_output=True,
check=True,
timeout=5,
)
return True
except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired):
return False
def check_dependencies():
"""Check if required dependencies are available."""
return all(check_python_package(pkg) for pkg in PYTHON_PACKAGES_REQUIRED)
@pytest.mark.skipif(
not check_dependencies(),
reason="Required dependencies not installed (pypdf, pdfplumber, reportlab, pandas)",
)
def test_required_dependencies():
"""Test that required Python packages are available."""
for package in PYTHON_PACKAGES_REQUIRED:
assert check_python_package(package), f"Required package {package} not installed"
def test_optional_dependencies_status():
"""Report status of optional dependencies (does not fail)."""
print("\n\nOptional Python packages:")
for package in PYTHON_PACKAGES_OPTIONAL:
status = "✓ Installed" if check_python_package(package) else "✗ Not installed"
print(f" {package}: {status}")
print("\nOptional system commands:")
for command in SYSTEM_COMMANDS_OPTIONAL:
status = "✓ Available" if check_system_command(command) else "✗ Not available"
print(f" {command}: {status}")
# Level 3: Basic Functionality Tests
@pytest.mark.skipif(
not check_dependencies(),
reason="Required dependencies not installed",
)
def test_pypdf_basic_functionality():
"""Test basic pypdf functionality."""
from io import BytesIO
from pypdf import PdfReader, PdfWriter
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas
# Create a simple test PDF in memory
buffer = BytesIO()
c = canvas.Canvas(buffer, pagesize=letter)
c.drawString(100, 750, "Test PDF Document")
c.drawString(100, 730, "This is a test page")
c.save()
# Read it back
buffer.seek(0)
reader = PdfReader(buffer)
assert len(reader.pages) == 1, "PDF should have 1 page"
# Extract text
page = reader.pages[0]
text = page.extract_text()
assert "Test PDF Document" in text, "Text extraction failed"
# Test write functionality
writer = PdfWriter()
writer.add_page(page)
output_buffer = BytesIO()
writer.write(output_buffer)
# Verify written PDF
output_buffer.seek(0)
verify_reader = PdfReader(output_buffer)
assert len(verify_reader.pages) == 1, "Written PDF should have 1 page"
@pytest.mark.skipif(
not check_dependencies(),
reason="Required dependencies not installed",
)
def test_pdfplumber_basic_functionality():
"""Test basic pdfplumber functionality."""
from io import BytesIO
import pdfplumber
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas
# Create a test PDF with text
buffer = BytesIO()
c = canvas.Canvas(buffer, pagesize=letter)
c.drawString(100, 750, "Test Document")
c.drawString(100, 730, "Line 1")
c.drawString(100, 710, "Line 2")
c.save()
# Read with pdfplumber
buffer.seek(0)
with pdfplumber.open(buffer) as pdf:
assert len(pdf.pages) == 1, "PDF should have 1 page"
page = pdf.pages[0]
text = page.extract_text()
assert text is not None, "Text extraction should not return None"
assert "Test Document" in text, "Should extract 'Test Document'"
@pytest.mark.skipif(
not check_dependencies(),
reason="Required dependencies not installed",
)
def test_reportlab_basic_functionality():
"""Test basic reportlab PDF creation."""
from io import BytesIO
from pypdf import PdfReader
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas
# Create PDF
buffer = BytesIO()
c = canvas.Canvas(buffer, pagesize=letter)
width, height = letter
c.drawString(100, height - 100, "Hello World")
c.line(100, height - 120, 400, height - 120)
c.save()
# Verify PDF was created
buffer.seek(0)
reader = PdfReader(buffer)
assert len(reader.pages) == 1, "Created PDF should have 1 page"
page = reader.pages[0]
# Check dimensions
assert page.mediabox.width == letter[0], "Page width should match letter size"
assert page.mediabox.height == letter[1], "Page height should match letter size"
@pytest.mark.skipif(
not check_dependencies(),
reason="Required dependencies not installed",
)
def test_pandas_table_export():
"""Test pandas integration for table export."""
from io import BytesIO
import pandas as pd
# Create test DataFrame
data = {
"Name": ["Alice", "Bob", "Charlie"],
"Age": [25, 30, 35],
"City": ["New York", "London", "Paris"],
}
df = pd.DataFrame(data)
# Export to Excel (in memory)
buffer = BytesIO()
df.to_excel(buffer, index=False, engine="openpyxl")
# Read back and verify
buffer.seek(0)
df_read = pd.read_excel(buffer, engine="openpyxl")
assert df.equals(df_read), "DataFrame should round-trip through Excel"
assert list(df_read.columns) == ["Name", "Age", "City"], "Column names should match"
assert len(df_read) == 3, "Should have 3 rows"
@pytest.mark.skipif(
not check_dependencies(),
reason="Required dependencies not installed",
)
def test_pdf_merge():
"""Test merging multiple PDFs."""
from io import BytesIO
from pypdf import PdfReader, PdfWriter
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas
# Create two test PDFs
pdf1 = BytesIO()
c1 = canvas.Canvas(pdf1, pagesize=letter)
c1.drawString(100, 750, "Document 1")
c1.save()
pdf2 = BytesIO()
c2 = canvas.Canvas(pdf2, pagesize=letter)
c2.drawString(100, 750, "Document 2")
c2.save()
# Merge PDFs
writer = PdfWriter()
pdf1.seek(0)
reader1 = PdfReader(pdf1)
for page in reader1.pages:
writer.add_page(page)
pdf2.seek(0)
reader2 = PdfReader(pdf2)
for page in reader2.pages:
writer.add_page(page)
# Verify merged PDF
output = BytesIO()
writer.write(output)
output.seek(0)
merged_reader = PdfReader(output)
assert len(merged_reader.pages) == 2, "Merged PDF should have 2 pages"
@pytest.mark.skipif(
not check_dependencies(),
reason="Required dependencies not installed",
)
def test_pdf_rotation():
"""Test page rotation."""
from io import BytesIO
from pypdf import PdfReader, PdfWriter
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas
# Create test PDF
buffer = BytesIO()
c = canvas.Canvas(buffer, pagesize=letter)
c.drawString(100, 750, "Test Page")
c.save()
# Rotate page
buffer.seek(0)
reader = PdfReader(buffer)
writer = PdfWriter()
page = reader.pages[0]
page.rotate(90)
writer.add_page(page)
# Verify rotation
output = BytesIO()
writer.write(output)
output.seek(0)
rotated_reader = PdfReader(output)
rotated_page = rotated_reader.pages[0]
# After 90 degree rotation, width and height should be swapped
# (Note: exact behavior may vary by PDF library version)
assert rotated_page is not None, "Rotated page should exist"
@pytest.mark.skipif(
not check_dependencies(),
reason="Required dependencies not installed",
)
def test_pdf_metadata():
"""Test metadata extraction and modification."""
from io import BytesIO
from pypdf import PdfReader, PdfWriter
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas
# Create test PDF
buffer = BytesIO()
c = canvas.Canvas(buffer, pagesize=letter)
c.setTitle("Test Document")
c.setAuthor("Test Author")
c.drawString(100, 750, "Test")
c.save()
# Read metadata
buffer.seek(0)
reader = PdfReader(buffer)
meta = reader.metadata
# Note: reportlab may not set all metadata fields
assert meta is not None, "Metadata should be accessible"
# Add metadata to new PDF
writer = PdfWriter()
writer.add_page(reader.pages[0])
writer.add_metadata(
{
"/Title": "Updated Title",
"/Author": "Updated Author",
"/Subject": "Test Subject",
}
)
output = BytesIO()
writer.write(output)
# Verify new metadata
output.seek(0)
new_reader = PdfReader(output)
new_meta = new_reader.metadata
assert new_meta.title == "Updated Title", "Title should be updated"
assert new_meta.author == "Updated Author", "Author should be updated"
# Level 4: Integration Tests (Future)
# These tests will verify skill usage in Claude Code context
@pytest.mark.skip(reason="Integration tests not yet implemented")
def test_skill_invocation():
"""Test that skill can be invoked in Claude Code."""
# Future: Test skill invocation through Claude Code API
@pytest.mark.skip(reason="Integration tests not yet implemented")
def test_skill_with_real_pdf():
"""Test skill with a real PDF file."""
# Future: Test with actual PDF files in fixtures
# Utility function for manual testing
def print_dependency_report():
"""Print comprehensive dependency report."""
print("\n" + "=" * 60)
print("PDF Skill Dependency Report")
print("=" * 60)
print("\nRequired Python Packages:")
for package in PYTHON_PACKAGES_REQUIRED:
status = "✓ Installed" if check_python_package(package) else "✗ MISSING"
print(f" {package:20s}: {status}")
print("\nOptional Python Packages:")
for package in PYTHON_PACKAGES_OPTIONAL:
status = "✓ Installed" if check_python_package(package) else "✗ Not installed"
print(f" {package:20s}: {status}")
print("\nOptional System Commands:")
for command in SYSTEM_COMMANDS_OPTIONAL:
status = "✓ Available" if check_system_command(command) else "✗ Not available"
print(f" {command:20s}: {status}")
print("\n" + "=" * 60)
all_required = all(check_python_package(pkg) for pkg in PYTHON_PACKAGES_REQUIRED)
if all_required:
print("✓ PDF skill is ready to use (core functionality)")
else:
print("✗ PDF skill is missing required dependencies")
print("\nInstall with: pip install pypdf pdfplumber reportlab pandas")
print("=" * 60 + "\n")
if __name__ == "__main__":
# When run directly, print dependency report
print_dependency_report()
# Run tests
import sys
sys.exit(pytest.main([__file__, "-v", "--tb=short"]))