Skill: ivangdavila/pdf-generator (version 1.0.1)

--- name: PDF Generator slug: pdf-generator version: 1.0.1 homepage: https://clawic.com/skills/pdf-generator description: Generate professional PDFs from Markdown, HTML, data, or code. Reports, invoices, contracts, and documents with best practices. metadata: {"clawdbot":{"emoji":"📄","requires":{"bins":[]},"os":["linux","darwin","win32"]}} --- ## When to Use User needs to create, generate, or export PDF documents. Agent handles document generation from multiple sources (Markdown, HTML, JSON, templates), formatting, styling, and batch processing. ## Scope This skill ONLY: - Provides code patterns and implementation guidance for PDF generation - Explains tool selection, CSS for print, and document structure - Shows reference examples for common document types This skill NEVER: - Executes code or generates files directly - Makes network requests - Accesses files outside user's working directory All code examples are reference patterns for the user to implement. ## Quick Reference | Topic | File | |-------|------| | Tool selection | `tools.md` | | Document types | `templates.md` | | Advanced operations | `advanced.md` | ## Core Rules ### 1. Choose the Right Tool | Source | Best Tool | Why | |--------|-----------|-----| | Markdown | pandoc | Native support, TOC, templates | | HTML/CSS | weasyprint | Best CSS support, no LaTeX | | Data/JSON | reportlab | Programmatic, precise control | | Simple text | fpdf2 | Lightweight, fast | **Default recommendation:** weasyprint for most HTML-based documents. ### 2. Structure Before Style ```python # CORRECT: semantic structure html = """ <article> <header><h1>Report Title</h1></header> <section> <h2>Summary</h2> <p>Content...</p> </section> </article> """ # WRONG: style-first approach html = "<div style='font-size:24px'>Report Title</div>" ``` ### 3. Handle Page Breaks Explicitly ```css /* Force page break before */ .new-page { page-break-before: always; } /* Keep together */ .keep-together { page-break-inside: avoid; } /* Headers never orphaned */ h2, h3 { page-break-after: avoid; } ``` ### 4. Always Set Metadata ```python # Example pattern for weasyprint html = """ <html> <head> <title>Document Title</title> <meta name="author" content="Author Name"> </head> ... """ ``` ### 5. Use Print-Optimized CSS ```css @media print { body { font-family: 'Georgia', serif; font-size: 11pt; line-height: 1.5; } @page { size: A4; margin: 2cm; } .no-print { display: none; } } ``` ### 6. Validate Output After generating any PDF: 1. Check file size (0 bytes = failed) 2. Open and verify page count 3. Verify fonts render correctly ## Common Traps | Trap | Consequence | Fix | |------|-------------|-----| | Missing fonts | Fallback to defaults | Use web-safe fonts | | Absolute image paths | Images missing | Use relative paths | | No page size | Unpredictable layout | Set `@page { size: A4; }` | | Large images | Huge files | Compress before use | ## Security & Privacy **This is a reference skill.** It provides patterns and guidance only. **Data that stays local:** - All PDF generation happens on user's machine - No data sent externally **This skill does NOT:** - Execute code or make files - Make network requests - Access system files ## Feedback - If useful: `clawhub star pdf-generator` - Stay updated: `clawhub sync`

path: EXTRA_FILES.txt
content: tools.md
templates.md
advanced.md


path: advanced.md
content: # Advanced PDF Operations

## Merge PDFs

```python
from pypdf import PdfWriter, PdfReader

def merge_pdfs(input_files, output_file):
    writer = PdfWriter()
    
    for pdf_path in input_files:
        reader = PdfReader(pdf_path)
        for page in reader.pages:
            writer.add_page(page)
    
    with open(output_file, "wb") as f:
        writer.write(f)

# Usage
merge_pdfs(["doc1.pdf", "doc2.pdf"], "merged.pdf")
```

## Split PDF

```python
from pypdf import PdfReader, PdfWriter

def split_pdf(input_file, output_dir="."):
    reader = PdfReader(input_file)
    
    for i, page in enumerate(reader.pages):
        writer = PdfWriter()
        writer.add_page(page)
        with open(f"{output_dir}/page_{i+1}.pdf", "wb") as f:
            writer.write(f)

def split_by_range(input_file, ranges, output_file):
    """Extract specific pages. ranges = [(1,3), (5,7)]"""
    reader = PdfReader(input_file)
    writer = PdfWriter()
    
    for start, end in ranges:
        for i in range(start - 1, end):
            writer.add_page(reader.pages[i])
    
    with open(output_file, "wb") as f:
        writer.write(f)
```

## Fill PDF Forms

```python
from pypdf import PdfReader, PdfWriter

def fill_form(input_pdf, output_pdf, field_data):
    """
    field_data = {"field_name": "value"}
    """
    reader = PdfReader(input_pdf)
    writer = PdfWriter()
    
    writer.append(reader)
    writer.update_page_form_field_values(writer.pages[0], field_data)
    
    with open(output_pdf, "wb") as f:
        writer.write(f)

# Get field names from PDF
def list_form_fields(pdf_path):
    reader = PdfReader(pdf_path)
    fields = reader.get_form_text_fields()
    for name, value in fields.items():
        print(f"{name}: {value}")
```

## Add Watermark

```python
from pypdf import PdfReader, PdfWriter
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from io import BytesIO

def create_watermark(text):
    buffer = BytesIO()
    c = canvas.Canvas(buffer, pagesize=A4)
    c.setFont("Helvetica", 50)
    c.rotate(45)
    c.drawString(200, 100, text)
    c.save()
    buffer.seek(0)
    return buffer

def add_watermark(input_pdf, output_pdf, watermark_text):
    watermark_buffer = create_watermark(watermark_text)
    watermark_reader = PdfReader(watermark_buffer)
    watermark_page = watermark_reader.pages[0]
    
    reader = PdfReader(input_pdf)
    writer = PdfWriter()
    
    for page in reader.pages:
        page.merge_page(watermark_page)
        writer.add_page(page)
    
    with open(output_pdf, "wb") as f:
        writer.write(f)
```

## Batch Processing

```python
from pathlib import Path
from weasyprint import HTML

def batch_generate(template_html, data_list, output_dir):
    """Generate multiple PDFs from template + data."""
    Path(output_dir).mkdir(exist_ok=True)
    
    for item in data_list:
        html = template_html.format(**item['data'])
        output_path = f"{output_dir}/{item['filename']}.pdf"
        HTML(string=html).write_pdf(output_path)

# Usage
template = """
<html><body>
<h1>Invoice for {client}</h1>
<p>Amount: ${amount}</p>
</body></html>
"""

data = [
    {"filename": "invoice-001", "data": {"client": "Acme", "amount": 1000}},
    {"filename": "invoice-002", "data": {"client": "Beta", "amount": 2000}},
]

batch_generate(template, data, "./invoices")
```

## Add Bookmarks

```python
from pypdf import PdfWriter, PdfReader

def add_bookmarks(input_pdf, output_pdf, bookmarks):
    """
    bookmarks = [
        {"title": "Chapter 1", "page": 0},
        {"title": "Chapter 2", "page": 5},
    ]
    """
    reader = PdfReader(input_pdf)
    writer = PdfWriter()
    
    for page in reader.pages:
        writer.add_page(page)
    
    for bm in bookmarks:
        writer.add_outline_item(bm["title"], bm["page"])
    
    with open(output_pdf, "wb") as f:
        writer.write(f)
```

## Rotate Pages

```python
from pypdf import PdfReader, PdfWriter

def rotate_pages(input_pdf, output_pdf, rotation=90, pages=None):
    """
    rotation: 90, 180, 270
    pages: list of page numbers (1-indexed) or None for all
    """
    reader = PdfReader(input_pdf)
    writer = PdfWriter()
    
    for i, page in enumerate(reader.pages):
        if pages is None or (i + 1) in pages:
            page.rotate(rotation)
        writer.add_page(page)
    
    with open(output_pdf, "wb") as f:
        writer.write(f)
```

## Extract Text

```python
from pypdf import PdfReader

def extract_text(pdf_path):
    reader = PdfReader(pdf_path)
    text = ""
    for page in reader.pages:
        text += page.extract_text()
    return text
```


path: templates.md
content: # Document Templates

## Invoice

```python
from weasyprint import HTML, CSS

def generate_invoice(data):
    html = f"""
    <!DOCTYPE html>
    <html>
    <head>
        <style>
            body {{ font-family: Arial, sans-serif; margin: 40px; }}
            .header {{ display: flex; justify-content: space-between; }}
            .company {{ font-size: 24px; font-weight: bold; }}
            .invoice-title {{ font-size: 32px; color: #333; }}
            table {{ width: 100%; border-collapse: collapse; margin: 20px 0; }}
            th, td {{ padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }}
            th {{ background: #f5f5f5; }}
            .total {{ font-size: 18px; font-weight: bold; text-align: right; }}
        </style>
    </head>
    <body>
        <div class="header">
            <div class="company">{data['company']['name']}</div>
            <div class="invoice-title">INVOICE</div>
        </div>
        
        <p><strong>Invoice #:</strong> {data['invoice']['number']}</p>
        <p><strong>Date:</strong> {data['invoice']['date']}</p>
        <p><strong>Due:</strong> {data['invoice']['due_date']}</p>
        
        <h3>Bill To:</h3>
        <p>{data['client']['name']}<br>{data['client']['address']}</p>
        
        <table>
            <tr><th>Description</th><th>Qty</th><th>Rate</th><th>Amount</th></tr>
            {''.join(f"<tr><td>{item['desc']}</td><td>{item['qty']}</td><td>${item['rate']}</td><td>${item['qty'] * item['rate']}</td></tr>" for item in data['items'])}
        </table>
        
        <p class="total">Total: ${sum(item['qty'] * item['rate'] for item in data['items'])}</p>
    </body>
    </html>
    """
    HTML(string=html).write_pdf(f"invoice-{data['invoice']['number']}.pdf")

# Usage
invoice_data = {
    "company": {"name": "Acme Inc"},
    "client": {"name": "Client Corp", "address": "123 Main St"},
    "invoice": {"number": "INV-001", "date": "2025-02-19", "due_date": "2025-03-19"},
    "items": [
        {"desc": "Consulting", "qty": 10, "rate": 150},
        {"desc": "Development", "qty": 20, "rate": 200},
    ]
}
generate_invoice(invoice_data)
```

## Report

```python
from weasyprint import HTML

def generate_report(title, sections):
    toc = "".join(f'<li><a href="#section-{i}">{s["title"]}</a></li>' 
                  for i, s in enumerate(sections))
    
    content = "".join(f'''
        <section id="section-{i}">
            <h2>{s["title"]}</h2>
            <p>{s["content"]}</p>
        </section>
    ''' for i, s in enumerate(sections))
    
    html = f"""
    <!DOCTYPE html>
    <html>
    <head>
        <style>
            @page {{ size: A4; margin: 2cm; }}
            body {{ font-family: Georgia, serif; line-height: 1.6; }}
            h1 {{ color: #2c3e50; border-bottom: 2px solid #3498db; }}
            h2 {{ color: #34495e; page-break-after: avoid; }}
            section {{ page-break-inside: avoid; }}
            .toc {{ background: #f9f9f9; padding: 20px; margin: 20px 0; }}
        </style>
    </head>
    <body>
        <h1>{title}</h1>
        <div class="toc">
            <h3>Table of Contents</h3>
            <ol>{toc}</ol>
        </div>
        {content}
    </body>
    </html>
    """
    HTML(string=html).write_pdf("report.pdf")
```

## Contract / Legal Document

```python
from weasyprint import HTML

def generate_contract(data):
    html = f"""
    <!DOCTYPE html>
    <html>
    <head>
        <style>
            @page {{ 
                size: letter; 
                margin: 1in;
                @bottom-center {{ content: "Page " counter(page) " of " counter(pages); }}
            }}
            body {{ font-family: 'Times New Roman', serif; font-size: 12pt; line-height: 1.8; }}
            h1 {{ text-align: center; text-transform: uppercase; }}
            .parties {{ margin: 30px 0; }}
            .clause {{ margin: 20px 0; }}
            .clause-title {{ font-weight: bold; }}
            .signatures {{ margin-top: 50px; display: flex; justify-content: space-between; }}
            .signature-block {{ width: 45%; }}
            .signature-line {{ border-top: 1px solid black; margin-top: 50px; padding-top: 5px; }}
        </style>
    </head>
    <body>
        <h1>{data['title']}</h1>
        
        <div class="parties">
            <p>This Agreement is entered into as of {data['date']} by and between:</p>
            <p><strong>{data['party1']['name']}</strong> ("{data['party1']['short']}"), and</p>
            <p><strong>{data['party2']['name']}</strong> ("{data['party2']['short']}")</p>
        </div>
        
        {''.join(f'<div class="clause"><span class="clause-title">{i+1}. {c["title"]}.</span> {c["text"]}</div>' for i, c in enumerate(data['clauses']))}
        
        <div class="signatures">
            <div class="signature-block">
                <div class="signature-line">{data['party1']['name']}</div>
                <p>Date: _______________</p>
            </div>
            <div class="signature-block">
                <div class="signature-line">{data['party2']['name']}</div>
                <p>Date: _______________</p>
            </div>
        </div>
    </body>
    </html>
    """
    HTML(string=html).write_pdf("contract.pdf")
```

## Certificate

```python
from weasyprint import HTML

def generate_certificate(name, title, date, issuer):
    html = f"""
    <!DOCTYPE html>
    <html>
    <head>
        <style>
            @page {{ size: landscape; margin: 0; }}
            body {{ 
                font-family: 'Georgia', serif;
                text-align: center;
                background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
                padding: 50px;
                min-height: 100vh;
                box-sizing: border-box;
            }}
            .certificate {{ 
                border: 8px double #4a5568;
                padding: 60px;
                background: white;
                max-width: 900px;
                margin: 0 auto;
            }}
            .header {{ font-size: 14px; letter-spacing: 3px; color: #718096; }}
            .title {{ font-size: 48px; color: #2d3748; margin: 30px 0; }}
            .recipient {{ font-size: 36px; color: #4a5568; font-style: italic; margin: 30px 0; }}
            .description {{ font-size: 18px; color: #4a5568; margin: 30px 0; }}
            .footer {{ margin-top: 50px; display: flex; justify-content: space-around; }}
            .signature {{ border-top: 2px solid #4a5568; padding-top: 10px; width: 200px; }}
        </style>
    </head>
    <body>
        <div class="certificate">
            <div class="header">CERTIFICATE OF COMPLETION</div>
            <div class="title">{title}</div>
            <p>This is to certify that</p>
            <div class="recipient">{name}</div>
            <div class="description">has successfully completed the requirements</div>
            <div class="footer">
                <div class="signature">{issuer}</div>
                <div class="signature">{date}</div>
            </div>
        </div>
    </body>
    </html>
    """
    HTML(string=html).write_pdf("certificate.pdf")
```

## Resume / CV

```python
from weasyprint import HTML

def generate_resume(data):
    html = f"""
    <!DOCTYPE html>
    <html>
    <head>
        <style>
            @page {{ size: letter; margin: 0.5in; }}
            body {{ font-family: 'Helvetica', sans-serif; font-size: 10pt; line-height: 1.4; }}
            .header {{ text-align: center; margin-bottom: 20px; }}
            .name {{ font-size: 24pt; font-weight: bold; }}
            .contact {{ color: #666; }}
            h2 {{ color: #2c3e50; border-bottom: 1px solid #3498db; font-size: 12pt; margin-top: 15px; }}
            .entry {{ margin: 10px 0; }}
            .entry-header {{ display: flex; justify-content: space-between; }}
            .title {{ font-weight: bold; }}
            .date {{ color: #666; }}
            ul {{ margin: 5px 0; padding-left: 20px; }}
        </style>
    </head>
    <body>
        <div class="header">
            <div class="name">{data['name']}</div>
            <div class="contact">{data['email']} | {data['phone']} | {data['location']}</div>
        </div>
        
        <h2>Experience</h2>
        {''.join(f'''
        <div class="entry">
            <div class="entry-header">
                <span class="title">{exp['title']} - {exp['company']}</span>
                <span class="date">{exp['dates']}</span>
            </div>
            <ul>{''.join(f"<li>{b}</li>" for b in exp['bullets'])}</ul>
        </div>
        ''' for exp in data['experience'])}
        
        <h2>Education</h2>
        {''.join(f'''
        <div class="entry">
            <div class="entry-header">
                <span class="title">{edu['degree']} - {edu['school']}</span>
                <span class="date">{edu['year']}</span>
            </div>
        </div>
        ''' for edu in data['education'])}
        
        <h2>Skills</h2>
        <p>{', '.join(data['skills'])}</p>
    </body>
    </html>
    """
    HTML(string=html).write_pdf("resume.pdf")
```


path: tools.md
content: # PDF Tools - Selection Guide

## Quick Decision Tree

```
Need PDF? - What's the source?

- Markdown/Text - pandoc (with LaTeX for complex)
- HTML/CSS - weasyprint (best CSS)
- Data/Tables - reportlab (programmatic)
- Simple text - fpdf2 (lightweight)
- Existing PDF - pypdf (merge/split)
```

## Tool Comparison

| Tool | Best For | CSS Support | Complexity |
|------|----------|-------------|------------|
| weasyprint | HTML to PDF | Excellent | Low |
| pandoc | Markdown to PDF | Via LaTeX | Medium |
| reportlab | Data to PDF | None | High |
| fpdf2 | Simple text | None | Low |
| pypdf | Merge/split | N/A | Low |

## weasyprint (Recommended)

```python
from weasyprint import HTML, CSS

# From string
html = "<h1>Hello</h1><p>World</p>"
HTML(string=html).write_pdf("output.pdf")

# From file
HTML("document.html").write_pdf("output.pdf")

# With custom CSS
css = CSS(string="body { font-family: Arial; }")
HTML(string=html).write_pdf("output.pdf", stylesheets=[css])
```

## pandoc

```bash
# Basic conversion
pandoc document.md -o output.pdf

# With table of contents
pandoc document.md --toc -o output.pdf

# Custom margins
pandoc document.md -V geometry:margin=1in -o output.pdf
```

## reportlab (Programmatic)

```python
from reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas
from reportlab.lib.units import cm

c = canvas.Canvas("output.pdf", pagesize=A4)
width, height = A4

# Text
c.setFont("Helvetica-Bold", 24)
c.drawString(2*cm, height - 3*cm, "Report Title")

c.setFont("Helvetica", 12)
c.drawString(2*cm, height - 5*cm, "Body text here...")

c.save()
```

## fpdf2 (Lightweight)

```python
from fpdf import FPDF

pdf = FPDF()
pdf.add_page()
pdf.set_font("Helvetica", size=16)
pdf.cell(200, 10, text="Hello World", align="C")

pdf.set_font("Helvetica", size=12)
pdf.multi_cell(0, 10, text="Long paragraph text...")

pdf.output("output.pdf")
```

## pypdf (Merge/Split)

```python
from pypdf import PdfWriter, PdfReader

# Merge PDFs
writer = PdfWriter()
for pdf_file in ["doc1.pdf", "doc2.pdf"]:
    reader = PdfReader(pdf_file)
    for page in reader.pages:
        writer.add_page(page)
writer.write("merged.pdf")

# Split PDF
reader = PdfReader("input.pdf")
for i, page in enumerate(reader.pages):
    writer = PdfWriter()
    writer.add_page(page)
    writer.write(f"page_{i+1}.pdf")
```


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)