pdf-studio

Professional PDF document generator. Use when user needs to create reports, invoices, certificates, portfolios, or any publication-ready PDF. Supports images, tables, charts, TOC, headers/footers, and cross-platform fonts. Generates print-quality PDFs. PDF文档生成、专业报告、发票制作。

Safety Notice

This listing is from the official public ClawHub registry. Review SKILL.md and referenced scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "pdf-studio" with this command: npx skills add tobewin/pdf-studio

PDF Studio

Professional PDF document generator that creates publication-ready PDFs with perfect typography and layout.

Features

  • 📑 Print Quality: 300 DPI, CMYK support, bleed marks
  • 🎨 Professional Layout: Multi-column, headers/footers, page numbers
  • 🖼️ Image Support: Embedded images, captions, watermarks
  • 📊 Tables & Charts: Formatted tables, bar charts, pie charts
  • 📐 Typography: Professional fonts, kerning, ligatures
  • 🌍 Multi-Language: Chinese, English, Japanese, etc.
  • Cross-Platform: Windows, macOS, Linux
  • 📱 PDF/A Compliant: Archival quality

Trigger Conditions

  • "生成PDF" / "Generate PDF"
  • "帮我做一份报告" / "Create a report"
  • "制作发票" / "Create an invoice"
  • "生成证书" / "Generate certificate"
  • "制作作品集" / "Create portfolio"
  • "pdf-studio"

Document Types

Business (商务)

  • 年度报告
  • 商业计划书
  • 发票/账单
  • 合同/协议
  • 产品手册

Academic (学术)

  • 论文排版
  • 学位证书
  • 研究报告
  • 会议论文

Government (政府)

  • 公文
  • 公告
  • 证书
  • 表格

Personal (个人)

  • 简历
  • 作品集
  • 证书
  • 邀请函

Step 1: Understand Requirements

请提供以下信息:

文档类型:(报告/发票/证书/简历/其他)
纸张大小:(A4/Letter/A3/自定义)
方向:(纵向/横向)
页边距:(默认/自定义)
字体要求:(默认/指定字体)
语言:(中文/英文)
特殊要求:(水印/页眉页脚/目录等)

Step 2: Generate PDF

Using FPDF2 (Recommended for simplicity)

# Install dependencies
pip install fpdf2 pillow

Python Script Template (with Unicode Support)

python3 << 'PYEOF'
import os
from fpdf import FPDF
from datetime import datetime

class PDFGenerator:
    def __init__(self, config):
        self.config = config
        self.html_content = []
        self.css_content = []
        self.font_config = FontConfiguration()
        
    def add_page_setup(self, size='A4', orientation='portrait', margins=None):
        """Setup page dimensions"""
        if margins is None:
            margins = {'top': '2cm', 'right': '2cm', 'bottom': '2cm', 'left': '2cm'}
        
        page_size = {
            'A4': '210mm 297mm',
            'Letter': '216mm 279mm',
            'A3': '297mm 420mm'
        }.get(size, size)
        
        self.css_content.append(f'''
        @page {{
            size: {page_size} {orientation};
            margin: {margins['top']} {margins['right']} {margins['bottom']} {margins['left']};
            
            @top-center {{
                content: "{self.config.get('header_text', '')}";
                font-size: 9pt;
                color: #666;
            }}
            
            @bottom-center {{
                content: "Page " counter(page) " of " counter(pages);
                font-size: 9pt;
                color: #666;
            }}
            
            @bottom-right {{
                content: "{self.config.get('footer_text', '')}";
                font-size: 9pt;
                color: #666;
            }}
        }}
        ''')
        
    def add_base_styles(self, font_family='Noto Serif SC', font_size='12pt'):
        """Add base CSS styles"""
        self.css_content.append(f'''
        body {{
            font-family: '{font_family}', 'Noto Serif', serif;
            font-size: {font_size};
            line-height: 1.8;
            color: #333;
        }}
        
        h1 {{
            font-family: '{font_family.replace('Serif', 'Sans')}', 'Noto Sans', sans-serif;
            font-size: 24pt;
            color: #1a365d;
            border-bottom: 3px solid #3182ce;
            padding-bottom: 0.5em;
            margin-top: 1.5em;
            page-break-after: avoid;
        }}
        
        h2 {{
            font-family: '{font_family.replace('Serif', 'Sans')}', 'Noto Sans', sans-serif;
            font-size: 18pt;
            color: #2c5282;
            border-left: 4px solid #3182ce;
            padding-left: 1em;
            margin-top: 1.2em;
            page-break-after: avoid;
        }}
        
        h3 {{
            font-family: '{font_family.replace('Serif', 'Sans')}', 'Noto Sans', sans-serif;
            font-size: 14pt;
            color: #3182ce;
            margin-top: 1em;
            page-break-after: avoid;
        }}
        
        p {{
            text-align: justify;
            text-indent: 2em;
            margin: 0.5em 0;
        }}
        
        table {{
            width: 100%;
            border-collapse: collapse;
            margin: 1em 0;
            font-size: 10pt;
            page-break-inside: avoid;
        }}
        
        th {{
            background-color: #3182ce;
            color: white;
            padding: 0.8em;
            text-align: left;
            font-weight: 600;
        }}
        
        td {{
            border: 1px solid #e2e8f0;
            padding: 0.6em 0.8em;
        }}
        
        tr:nth-child(even) {{
            background-color: #f7fafc;
        }}
        
        .cover {{
            text-align: center;
            padding-top: 30%;
            page-break-after: always;
        }}
        
        .cover h1 {{
            border: none;
            font-size: 28pt;
        }}
        
        .cover .subtitle {{
            font-size: 14pt;
            color: #4a5568;
            margin-top: 1em;
        }}
        
        .cover .author {{
            font-size: 12pt;
            margin-top: 2em;
        }}
        
        .cover .date {{
            font-size: 12pt;
            color: #718096;
            margin-top: 1em;
        }}
        
        .toc {{
            page-break-after: always;
        }}
        
        .toc a {{
            color: #3182ce;
            text-decoration: none;
        }}
        
        .figure {{
            text-align: center;
            margin: 1em 0;
            page-break-inside: avoid;
        }}
        
        .figure img {{
            max-width: 100%;
            height: auto;
        }}
        
        .figure-caption {{
            font-size: 10pt;
            color: #718096;
            margin-top: 0.5em;
        }}
        
        .code {{
            background-color: #2d3748;
            color: #e2e8f0;
            padding: 1em;
            border-radius: 5px;
            font-family: 'Fira Code', 'Consolas', monospace;
            font-size: 9pt;
            white-space: pre-wrap;
            page-break-inside: avoid;
        }}
        
        .highlight {{
            background-color: #ebf8ff;
            border: 1px solid #90cdf4;
            border-radius: 5px;
            padding: 1em;
            margin: 1em 0;
        }}
        
        .warning {{
            background-color: #fffaf0;
            border: 1px solid #f6ad55;
            border-radius: 5px;
            padding: 1em;
            margin: 1em 0;
        }}
        
        blockquote {{
            border-left: 4px solid #718096;
            background-color: #f7fafc;
            padding: 1em;
            margin: 1em 0;
            font-style: italic;
        }}
        
        ul, ol {{
            margin: 0.5em 0;
            padding-left: 2em;
        }}
        
        li {{
            margin-bottom: 0.3em;
        }}
        
        strong {{
            color: #1a365d;
        }}
        
        .page-break {{
            page-break-before: always;
        }}
        ''')
    
    def add_cover(self, title, subtitle='', author='', date=None):
        """Add cover page"""
        if date is None:
            date = datetime.now().strftime('%Y年%m月%d日')
        
        self.html_content.append(f'''
        <div class="cover">
            <h1>{title}</h1>
            {f'<div class="subtitle">{subtitle}</div>' if subtitle else ''}
            {f'<div class="author">{author}</div>' if author else ''}
            <div class="date">{date}</div>
        </div>
        ''')
    
    def add_toc(self, title='目录'):
        """Add table of contents placeholder"""
        self.html_content.append(f'''
        <div class="toc">
            <h1>{title}</h1>
            <p style="font-style: italic; color: #718096;">
                (在支持目录生成的PDF阅读器中自动生成)
            </p>
        </div>
        ''')
    
    def add_heading(self, text, level=1):
        """Add heading"""
        self.html_content.append(f'<h{level}>{text}</h{level}>')
    
    def add_paragraph(self, text, indent=True):
        """Add paragraph"""
        if indent:
            self.html_content.append(f'<p>{text}</p>')
        else:
            self.html_content.append(f'<p style="text-indent: 0;">{text}</p>')
    
    def add_table(self, headers, rows, caption=None):
        """Add formatted table"""
        table_html = '<table>'
        
        # Header
        table_html += '<thead><tr>'
        for header in headers:
            table_html += f'<th>{header}</th>'
        table_html += '</tr></thead>'
        
        # Body
        table_html += '<tbody>'
        for row in rows:
            table_html += '<tr>'
            for cell in row:
                table_html += f'<td>{cell}</td>'
            table_html += '</tr>'
        table_html += '</tbody></table>'
        
        if caption:
            table_html = f'<div class="figure"><div class="figure-caption">{caption}</div>{table_html}</div>'
        
        self.html_content.append(table_html)
    
    def add_image(self, image_path, caption=None, width='80%'):
        """Add image with caption"""
        # Convert to data URI for embedding
        import base64
        with open(image_path, 'rb') as f:
            img_data = base64.b64encode(f.read()).decode()
        
        ext = os.path.splitext(image_path)[1].lower()
        mime = {
            '.png': 'image/png',
            '.jpg': 'image/jpeg',
            '.jpeg': 'image/jpeg',
            '.gif': 'image/gif',
            '.svg': 'image/svg+xml'
        }.get(ext, 'image/png')
        
        img_html = f'<div class="figure">'
        img_html += f'<img src="data:{mime};base64,{img_data}" style="max-width: {width};">'
        if caption:
            img_html += f'<div class="figure-caption">{caption}</div>'
        img_html += '</div>'
        
        self.html_content.append(img_html)
    
    def add_code_block(self, code, language=''):
        """Add code block"""
        self.html_content.append(f'<div class="code">{code}</div>')
    
    def add_blockquote(self, text):
        """Add blockquote"""
        self.html_content.append(f'<blockquote>{text}</blockquote>')
    
    def add_list(self, items, ordered=False):
        """Add list"""
        tag = 'ol' if ordered else 'ul'
        list_html = f'<{tag}>'
        for item in items:
            list_html += f'<li>{item}</li>'
        list_html += f'</{tag}>'
        self.html_content.append(list_html)
    
    def add_page_break(self):
        """Add page break"""
        self.html_content.append('<div class="page-break"></div>')
    
    def render(self):
        """Render HTML and CSS"""
        html = f'''
        <!DOCTYPE html>
        <html>
        <head>
            <meta charset="UTF-8">
            <style>
            {''.join(self.css_content)}
            </style>
        </head>
        <body>
            {''.join(self.html_content)}
        </body>
        </html>
        '''
        return html
    
    def save(self, output_path):
        """Save to PDF"""
        html_content = self.render()
        css_content = '\n'.join(self.css_content)
        
        html = HTML(string=html_content)
        css = CSS(string=css_content, font_config=self.font_config)
        
        html.write_pdf(output_path, stylesheets=[css], font_config=self.font_config)
        
        return output_path

# 示例使用
def create_sample_pdf():
    config = {
        'header_text': 'Sample Report',
        'footer_text': 'Confidential'
    }
    
    pdf = PDFGenerator(config)
    pdf.add_page_setup('A4', 'portrait')
    pdf.add_base_styles('Noto Serif SC', '12pt')
    
    # 封面
    pdf.add_cover(
        '2026年AI行业发展趋势报告',
        '从大模型到智能体时代的全面变革',
        'AI研究院'
    )
    
    # 目录
    pdf.add_toc()
    
    # 内容
    pdf.add_heading('一、市场概况', 1)
    pdf.add_paragraph('2026年,全球人工智能市场规模突破3.2万亿美元,年复合增长率达到47%。')
    pdf.add_paragraph('企业AI采用率从2025年的72%提升至89%,标志着AI技术正式进入全面普及阶段。')
    
    # 表格
    pdf.add_heading('二、核心数据', 1)
    pdf.add_table(
        ['指标', '2025年', '2026年', '增长率'],
        [
            ['全球市场规模', '$2.1万亿', '$3.2万亿', '52%'],
            ['企业采用率', '72%', '89%', '24%'],
            ['风险投资', '$790亿', '$1,280亿', '62%']
        ],
        caption='表2-1:AI市场核心指标'
    )
    
    # 列表
    pdf.add_heading('三、技术趋势', 1)
    pdf.add_list([
        '大模型能力持续提升',
        'Agent技术走向成熟',
        '开源生态蓬勃发展'
    ])
    
    return pdf

# 生成示例PDF
output_dir = os.environ.get('OPENCLAW_WORKSPACE', os.getcwd())
output_path = os.path.join(output_dir, 'sample-report.pdf')

pdf = create_sample_pdf()
pdf.save(output_path)

print(f"✅ PDF已生成:{output_path}")
PYEOF

Typography Settings (字体设置)

Cross-Platform Fonts

FONTS = {
    'chinese': {
        'serif': 'Noto Serif SC',
        'sans': 'Noto Sans SC',
        'mono': 'Noto Sans Mono CJK SC'
    },
    'english': {
        'serif': 'Liberation Serif',
        'sans': 'Liberation Sans',
        'mono': 'Fira Code'
    },
    'japanese': {
        'serif': 'Noto Serif JP',
        'sans': 'Noto Sans JP',
        'mono': 'Noto Sans Mono CJK JP'
    }
}

Font Features

body {
    font-feature-settings: "liga" 1, "kern" 1;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

Page Layout (页面布局)

Multi-Column Layout

.two-column {
    column-count: 2;
    column-gap: 2em;
    column-rule: 1px solid #e2e8f0;
}

Margin Presets

MARGINS = {
    'normal': {'top': '2.54cm', 'right': '2.54cm', 'bottom': '2.54cm', 'left': '2.54cm'},
    'narrow': {'top': '1.27cm', 'right': '1.27cm', 'bottom': '1.27cm', 'left': '1.27cm'},
    'wide': {'top': '2.54cm', 'right': '5.08cm', 'bottom': '2.54cm', 'left': '5.08cm'}
}

Error Handling

字体缺失       → 使用开源字体回退
图片格式不支持 → 转换为PNG/JPEG
内存不足       → 分页处理大文档
生成失败       → 输出HTML备选

Security Notes (安全说明)

  • ✅ No network calls or external endpoints
  • ✅ No credentials or API keys required
  • ✅ Local file processing only
  • ✅ Open source dependencies (fpdf2, pillow from PyPI)
  • ✅ No data uploaded to external servers
  • ⚠️ Requires python3 and pip install
  • ⚠️ Review code before execution

Notes

  • 使用FPDF2确保跨平台一致性
  • 所有字体可嵌入PDF中
  • 支持PDF/A归档标准
  • 图片自动压缩优化
  • 支持水印和页眉页脚

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

Baoyu Danger Gemini Web

Generates images and text via reverse-engineered Gemini Web API. Supports text generation, image generation from prompts, reference images for vision input,...

Registry SourceRecently Updated
8310wjctim
General

Easypost

EasyPost — shipping labels, rate comparison, package tracking, address verification, and insurance.

Registry SourceRecently Updated
General

Update Advisor

OpenClaw update check and upgrade assistant. Triggers on phrases like "check for updates", "any new version", "is openclaw updated", "run the update", "confi...

Registry SourceRecently Updated
General

Memory Management

Manage and standardize trading decision records, extract lessons, and support history retrieval and comparison within the PAI trading system.

Registry SourceRecently Updated