Publish hermes-skills-productivity via gitea-publish skill
This commit is contained in:
+174
@@ -0,0 +1,174 @@
|
||||
---
|
||||
name: pdf
|
||||
description: "Create, merge, split, fill, and secure PDF files."
|
||||
version: 1.0.0
|
||||
author: Anthropic (adapted by Nous Research)
|
||||
license: Proprietary. LICENSE.txt has complete terms
|
||||
platforms: [linux, macos, windows]
|
||||
metadata:
|
||||
hermes:
|
||||
tags: [PDF, Documents, Forms, Office, Productivity]
|
||||
category: productivity
|
||||
related_skills: [ocr-and-documents, nano-pdf, docx, xlsx]
|
||||
---
|
||||
|
||||
# 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
|
||||
|
||||
```bash
|
||||
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](forms.md) and follow it. Advanced library usage (pypdfium2, pdf-lib) and troubleshooting: [reference.md](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](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)
|
||||
|
||||
```python
|
||||
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)
|
||||
|
||||
```python
|
||||
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)
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
```python
|
||||
writer.encrypt("userpassword", "ownerpassword")
|
||||
```
|
||||
|
||||
### OCR scanned PDFs
|
||||
|
||||
```python
|
||||
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](forms.md) first — it distinguishes fillable (AcroForm) PDFs from flat scanned forms and walks through the helper scripts:
|
||||
|
||||
- `scripts/check_fillable_fields.py` — does the PDF have AcroForm fields?
|
||||
- `scripts/extract_form_field_info.py` / `scripts/extract_form_structure.py` — enumerate fields
|
||||
- `scripts/fill_fillable_fields.py` — fill AcroForm fields
|
||||
- `scripts/fill_pdf_form_with_annotations.py` — overlay text on flat forms
|
||||
- `scripts/check_bounding_boxes.py`, `scripts/create_validation_image.py` — verify placement visually
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- `page.extract_text()` returns `None` on image-only pages — guard with `or ""` and fall back to OCR.
|
||||
- pypdf preserves encryption flags: reading an encrypted PDF requires `PdfReader(path, password=...)` before pages are accessible.
|
||||
- reportlab coordinates are bottom-left origin, points (1/72″) — not top-left.
|
||||
- When filling flat forms by annotation overlay, always render a validation image and check the placement before delivering.
|
||||
|
||||
## 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`.
|
||||
|
||||
## Related skills
|
||||
|
||||
`ocr-and-documents` (scanned-document text extraction), `nano-pdf` (NL text edits in place), `docx` (Word), `xlsx` (spreadsheets), `powerpoint` (decks).
|
||||
Reference in New Issue
Block a user