# Markdown to PDF (any2pdf)

Convert Markdown to professionally typeset PDF with reportlab. Supports syntax-highlighted code blocks, tables, cover pages, TOC with page numbers, PDF bookmarks, and watermarks.

## Prerequisites

```bash
pip install reportlab Pillow pygments python-dotenv
```

For CJK support, ensure system fonts are available:
- macOS: Noto Sans SC / PingFang SC (usually pre-installed)
- Linux: `apt install fonts-noto-cjk`

## Usage

Generate the conversion script, then run:

```bash
python3 md2pdf.py --input report.md --output report.pdf --theme warm-academic
```

**Note**: The agent generates a tailored conversion script on first use. Below are the key implementation patterns with reportlab.

### Core: Document Setup & CJK Fonts

```python
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import mm
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, Image, PageBreak
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.lib.colors import HexColor

# Register CJK font (macOS example)
pdfmetrics.registerFont(TTFont("NotoSansSC", "/System/Library/Fonts/PingFang.ttc", subfontIndex=0))

doc = SimpleDocTemplate("output.pdf", pagesize=A4,
    topMargin=25*mm, bottomMargin=20*mm, leftMargin=20*mm, rightMargin=20*mm)
styles = getSampleStyleSheet()
story = []
```

### Cover Page

```python
# Theme colors (warm-academic example)
ACCENT = HexColor("#8B4513")
BG = HexColor("#FDF5E6")

cover_title = ParagraphStyle("CoverTitle", parent=styles["Title"],
    fontName="NotoSansSC", fontSize=32, leading=40, textColor=ACCENT, alignment=1)
cover_sub = ParagraphStyle("CoverSub", parent=styles["Normal"],
    fontName="NotoSansSC", fontSize=14, textColor=HexColor("#666"), alignment=1)

story.append(Spacer(1, 80*mm))
story.append(Paragraph("文档标题", cover_title))
story.append(Spacer(1, 10*mm))
story.append(Paragraph("副标题 — 作者名", cover_sub))
story.append(PageBreak())
```

### Headings & Paragraphs

```python
h1 = ParagraphStyle("H1", fontName="NotoSansSC", fontSize=22, leading=28,
    textColor=ACCENT, spaceBefore=16, spaceAfter=8)
h2 = ParagraphStyle("H2", fontName="NotoSansSC", fontSize=16, leading=22,
    textColor=HexColor("#555"), spaceBefore=12, spaceAfter=6)
body = ParagraphStyle("Body", fontName="NotoSansSC", fontSize=11, leading=18,
    textColor=HexColor("#333"), spaceBefore=4, spaceAfter=4)

story.append(Paragraph("一、概述", h1))
story.append(Paragraph("这是正文段落，支持<b>加粗</b>和<i>斜体</i>。", body))
```

### Code Blocks (Syntax Highlighted)

```python
from reportlab.platypus import Preformatted
from pygments import highlight
from pygments.lexers import PythonLexer
from pygments.formatters import HtmlFormatter

code_style = ParagraphStyle("Code", fontName="Courier", fontSize=9, leading=13,
    backColor=HexColor("#F5F5F5"), leftIndent=10, rightIndent=10,
    spaceBefore=6, spaceAfter=6)

# For simple code blocks:
story.append(Preformatted("def hello():\n    print('world')", code_style))
```

### Tables

```python
data = [["公司", "营收", "净利"],
        ["公司A", "100亿", "20亿"],
        ["公司B", "80亿", "15亿"]]

t = Table(data, colWidths=[50*mm, 40*mm, 40*mm])
t.setStyle(TableStyle([
    ("BACKGROUND", (0, 0), (-1, 0), ACCENT),
    ("TEXTCOLOR", (0, 0), (-1, 0), HexColor("#FFF")),
    ("FONTNAME", (0, 0), (-1, -1), "NotoSansSC"),
    ("FONTSIZE", (0, 0), (-1, -1), 10),
    ("GRID", (0, 0), (-1, -1), 0.5, HexColor("#CCC")),
    ("ROWBACKGROUNDS", (0, 1), (-1, -1), [HexColor("#FFF"), HexColor("#F9F5EE")]),
    ("ALIGN", (1, 0), (-1, -1), "CENTER"),
]))
story.append(t)
```

### Images & Watermark

```python
# Embed image (auto-scale to fit width)
from reportlab.lib.utils import ImageReader
img = Image("chart.png", width=160*mm, height=90*mm, kind="proportional")
story.append(img)

# Watermark via page template callback
def add_watermark(canvas, doc):
    canvas.saveState()
    canvas.setFont("NotoSansSC", 40)
    canvas.setFillColor(HexColor("#000000"), alpha=0.05)
    canvas.translate(A4[0]/2, A4[1]/2)
    canvas.rotate(45)
    canvas.drawCentredString(0, 0, "CONFIDENTIAL")
    canvas.restoreState()

doc.build(story, onFirstPage=add_watermark, onLaterPages=add_watermark)
```

### TOC & Bookmarks

```python
from reportlab.platypus import TableOfContents
from reportlab.platypus.doctemplate import BaseDocTemplate, PageTemplate, Frame

# Add TOC (auto-collects headings)
toc = TableOfContents()
toc.levelStyles = [
    ParagraphStyle("TOC1", fontName="NotoSansSC", fontSize=12, leftIndent=0, leading=20),
    ParagraphStyle("TOC2", fontName="NotoSansSC", fontSize=10, leftIndent=20, leading=16),
]
story.insert(1, toc)  # after cover page break

# When adding headings, notify TOC:
# story.append(Paragraph("章节标题", h1))
# In afterFlowable callback: self.notify('TOCEntry', (level, text, self.page))

doc.build(story)
```

## Options

| Option | Default | Description |
|--------|---------|-------------|
| `--input` | (required) | Markdown file path |
| `--output` | `output.pdf` | Output PDF path |
| `--title` | From H1 | Cover page title |
| `--subtitle` | | Subtitle |
| `--author` | | Author name |
| `--theme` | `warm-academic` | Color theme |
| `--watermark` | | Watermark text |
| `--cover` | `true` | Generate cover page |
| `--toc` | `true` | Generate table of contents |
| `--frontispiece` | | Full-page image after cover |

## Themes (14 themes)

**Light:** `warm-academic`, `nord-frost`, `github-light`, `solarized-light`, `paper-classic`, `ocean-breeze`, `tufte`, `classic-thesis`
**CJK-optimized:** `chinese-red`, `ink-wash`, `elegant-book`
**Academic:** `ieee-journal`
**Dark:** `monokai-warm`, `dracula-soft`

## VisionClaw Integration

When used with the document-illustrator sub-skill, images generated via VisionClaw's `gemini` tool can be embedded directly into the PDF. The agent should:

1. Generate illustrations first (using `gemini` tool with `output_modality="image"`)
2. Insert image references into the Markdown
3. Convert to PDF with embedded images
