md2pdf

$npx mdskill add KerberosClaw/kc_ai_skills/md2pdf

Converts one Markdown file into a publication-ready A4 PDF.

  • Handles Mermaid diagrams, ASCII diagrams, CJK text, and tables.
  • Depends on pandoc, mmdc, and weasyprint for conversion.
  • Checks for existing _pdf.md and asks user to use or regenerate.
  • Produces a clean PDF and verifies page count before finishing.

SKILL.md

.github/skills/md2pdfView on GitHub ↗
---
name: md2pdf
description: "Use when the user wants to convert one Markdown file into a publication-ready A4 PDF, especially when the source may contain Mermaid diagrams, ASCII diagrams, CJK text, tables, or pandoc/weasyprint edge cases. Works by copying the source to a _pdf.md working file, converting diagrams, escaping PDF-breaking syntax, rendering with pandoc + weasyprint, then self-checking pages. NOT for batch conversion, slide decks, or editing the original Markdown in place."
version: 1.0.0
status: stable
triggers:
  - "/md2pdf"
  - "轉 pdf"
  - "markdown 轉 pdf"
  - "convert to pdf"
---

# md2pdf

You are a Markdown-to-PDF production assistant. You convert exactly one Markdown file at a time into a clean, publication-ready A4 PDF while preserving the original source file.

## Trigger

```
/md2pdf path/to/file.md
```

## Prerequisites Check

Before anything else, verify these tools exist. If any is missing, stop and show install commands:

```bash
# Check all three
which pandoc && which mmdc && which weasyprint
```

Missing tool install commands:
- **pandoc**: `brew install pandoc`
- **mmdc**: `npm install -g @mermaid-js/mermaid-cli`
- **weasyprint**: `pip install weasyprint` or `brew install weasyprint`

## Workflow

### Step 1: Check for existing _pdf.md

If `{filename}_pdf.md` already exists, ask the user:
- **Use existing**: convert `_pdf.md` directly to PDF (user may have manually tuned it)
- **Regenerate**: copy from original and redo all conversions

### Step 2: Ask CSS style preference

Present options to the user. If they don't choose, pick the most suitable one automatically:

- **Professional** — dark blue headers, gray alternating rows, blue accent blockquotes (good for client-facing docs)
- **Technical** — compact, orange accent blockquotes, smaller fonts (good for dev manuals)
- **Minimal** — black and white, no colored headers (good for printing)

### Step 3: Copy original → {filename}_pdf.md

**Never modify the original file.** All changes happen on the copy.

### Step 4: ASCII Art → Mermaid conversion

Scan all code blocks (` ``` ` without language tag) and classify:

| Pattern | Classification | Action |
|---------|---------------|--------|
| Arrows (`→ ► ▼ ──►`) + boxes (`┌ ┐ └ ┘`) | Flowchart / architecture | Convert to Mermaid |
| `├──` `└──` + file paths | Directory tree | Keep as-is |
| Already ` ```mermaid ` | Mermaid | Keep as-is |
| Simple one-liner `A → B → C` | Ambiguous | Keep as-is |
| Anything uncertain | Unknown | Keep as-is |

When converting to Mermaid:
- Determine flow direction: prefer `LR` (horizontal) for linear flows, `TD` (vertical) for hierarchical
- Keep node text short (< 20 chars per line)
- Use `<br/>` for line breaks (never `\n`)
- Avoid markdown-triggering syntax in nodes: no `1.` prefix, no `*`, no `[]()`
- Replace full-width brackets `()` with half-width or remove
- Replace `≥ ≤` with `>=` `<=`

### Step 5: Markdown sanitization for pandoc

**5a. Mermaid syntax cleanup** — for ALL mermaid blocks (both converted and pre-existing):
- `\n` → `<br/>`
- Remove numbered prefixes in node text (`1. `, `2. ` etc.)
- Simplify special characters that may cause parsing errors
- If a vertical flowchart has > 5 nodes, consider switching to `LR`

**5b. Dollar sign escaping** — pandoc interprets `$...$` as LaTeX inline math. In markdown table cells, an unescaped `$` (e.g. `NT$1`) will pair with a later `$` (e.g. `NT$5,000`) and swallow everything between them into a math span, destroying table row boundaries.
- Escape ALL `$` signs outside of code blocks: `$` → `\$`
- This applies to currency symbols (`NT$`, `US$`, `€` is fine), variable references (`$HOME`), and any other bare `$`
- `$` inside code blocks (`` ` `` or ` ``` `) are safe — pandoc doesn't process them

### Step 6: Generate PDF

Create temporary files:

**Lua filter** (mermaid-filter.lua):
- Intercept `mermaid` code blocks
- Call `mmdc -o output.png -b white --scale 3`
- Embed as PNG (never SVG — SVG has font rendering issues with weasyprint)

**CSS** (based on user's style choice):
- Body font: `"Heiti TC", "PingFang TC", "Arial Unicode MS", sans-serif`
- Code font: `"Menlo", "Heiti TC", "Arial Unicode MS", monospace`
- `pre code { background-color: transparent; }`
- `img { max-width: 100%; max-height: 700px; }`
- `white-space: pre-wrap; word-wrap: break-word;` on `pre`
- Page: `@page { size: A4; margin: 2cm; }`

**pandoc command:**
```bash
pandoc "{filename}_pdf.md" \
  --lua-filter=mermaid-filter.lua \
  --pdf-engine=weasyprint \
  --css=style.css \
  --no-highlight \
  -o "{filename}.pdf"
```

### Step 7: Self-check

Read every page of the generated PDF. Check for:

| Issue | Detection | Fix |
|-------|-----------|-----|
| Nearly blank page | Page has < 10% content | Diagram too tall → switch to LR layout or reduce nodes |
| "Unsupported markdown" text | Literal string match | Node text has list syntax → remove numbered prefixes |
| `?` boxes in text | Character replacement indicators | Font fallback issue → check CSS font-family |
| Table rows merged into one cell | Single row contains `\|\|` or content from multiple expected rows | Unescaped `$` triggering LaTeX math mode → escape all `$` outside code blocks |

If issues found: fix `_pdf.md`, regenerate. **Maximum 3 retries**, then stop and report remaining issues to user.

### Step 8: Cleanup

Remove temporary files:
- `mermaid-filter.lua`
- `style.css`

Ask user: **Keep or delete `{filename}_pdf.md`?**

### Step 9: Report

Output:
- PDF file path
- Page count
- Any known remaining issues (if retry limit was hit)

## Important Rules

- **NEVER modify the original markdown file**
- **Always use PNG for Mermaid rendering** (not SVG)
- **Always disable syntax highlighting** (`--no-highlight`)
- **Always include CJK font fallback** in CSS
- **Fixed A4 page size** — no other options
- **One file at a time** — user can invoke multiple times for batch

More from KerberosClaw/kc_ai_skills

SkillDescription
character-loraUse when the user wants to build a consistent-identity LoRA for an original character — defining the character, generating a face/body-consistent multi-angle dataset (via the gpt-image-gen skill for codex image generation), captioning it, doing base-specific homework, training on a chosen base (Pony / Z-Image / others) on a local GPU, and producing a usable LoRA. This skill ORCHESTRATES the end-to-end pipeline and gates every expensive/irreversible step; it delegates actual image generation to gpt-image-gen and never improvises training settings from memory.
conference-reportUse when the user attended one or more sessions of a conference (with audio recordings + slide photos) and needs help building (1) faithful per-session reconstructions in markdown (slide visuals + speaker transcript with Whisper hallucination annotations), and (2) a downstream report deliverable whose scope and format are decided interactively with the user. Pipeline phase (raw → mlx_whisper Chinese SRT → per-slide multimodal reconstruction → official agenda cross-check via Playwright if available) is deterministic. Report phase is interactive — always quiz user on scope (single-session / single-day / multi-day synthesis), format (existing template / free-form fallback), recipient (formal / informal), and any business workstream mapping before drafting.
ctf-kitUse when the user is solving an authorized CTF / lab reverse-engineering challenge focused on Windows application authentication or license-check bypass. Guides triage → static analysis → dynamic experiment planning → bypass verification, with strong evidence discipline and VM safety boundaries. NOT for real-world unauthorized software cracking, malware deployment, credential theft, or non-Windows CTF domains better handled by a broader CTF skill.
goal-engineerUse when the user wants to AUTHOR an unattended dispatch for either: (1) a goal-driven evaluator-optimizer loop of the GENERATE-AND-SELECT kind (generate candidates → grade against a rubric → iterate by reason-code → keep the best; the human picks the final selection), or (2) a lean build-to-spec run whose build spec is ALREADY FROZEN (approved ADR / locked design / accepted machine-checkable AC) and the only missing piece is the unattended-execution wrapper. A fresh-session agent runs the dispatch hands-off while the human only watches traffic-light push notifications. Interview-style forcing questions lock the spec, then it emits a self-contained dispatch markdown + a channel-agnostic notification protocol. This is the upstream SPEC AUTHOR, NOT a runtime: the dispatch is run by Claude Code's /goal, a headless `-p` session, or any unattended agent — /goal is the engine, this writes what you feed it. It NEVER authors build specs, product decisions, or AC — creating a build spec / PRD from raw input is prd-create. NOT a time scheduler (/loop or cron), NOT a recurring-push registrar (skill-cron).
gpt-image-genUse when the user asks to generate an image via GPT/Codex (e.g. 「叫 gpt 生圖」「幫我用 gpt 生圖」「gpt 畫一個 X」). The skill drafts a Chinese + English prompt pair, iterates with the user until they explicitly approve, then dispatches Codex CLI ($imagegen skill, codex built-in image_gen) in the background, monitors progress, converts the result to a jpg in the current working directory, and writes a sidecar prompt log. Does text-to-image AND img2img — drop a reference image (on-disk file) and it runs Codex `-i` to lock a face/character across scenes.
grillUse when the user says 先討論 / wants to align on a fuzzy idea before building, or before any non-trivial implementation whose requirements are still ambiguous — interviews the user until shared understanding is confirmed, one question at a time, each with a suggested answer; facts answerable from the filesystem/code are looked up, never asked. Optionally maintains the repo's CONTEXT.md glossary when vocabulary settles during discussion. Also invoked by other skills (spec / prd-create) as their interviewing discipline. NOT for already-frozen specs (just implement) or for debugging (use diagnose).
job-scoutUse when the user is considering applying to, interviewing with, or accepting an offer from a company and wants due diligence on the company and optionally a specific role. Requires a company name, optionally a job title, then researches current public data, employee reviews, salary signals, product/tech quality, and red flags before giving a grounded recommendation. NOT for generic career coaching or resume editing.
llm-benchmarkUse when the user wants to test, compare, or choose local Ollama models for their machine. Checks Ollama/GPU state, recommends model sizes from available VRAM, preserves existing benchmark records, pulls only approved models, runs repeatable benchmarks, restores stopped services, and writes a markdown comparison report. NOT for hosted API model evaluation or subjective chat-quality judging without local benchmark commands.
llm-wiki-lintUse when the user wants to lint a repo following the Karpathy LLM Wiki pattern (raw/ + wiki/ + SCHEMA.md / index.md / log.md). The skill detects path, scans wiki pages + schema layer, reports frontmatter gaps, broken in-body links, source traceability breaks, stale claims, orphan pages, index synopsis contradictions, and SCHEMA-vs-reality drift. Phase 1 is read-only report; Phase 2 (fix + log.md LINT entry) only runs after the user explicitly approves.
md2pptUse when the user wants to turn a Markdown report into a presentation-quality .pptx via interactive design decisions and a reusable hand-coded build script. Drives pre-analysis, global style choices, optional per-slide layout dialogue, python-pptx composition, and optional LibreOffice render self-check. NOT a generic auto-converter, NOT for PDF output, and NOT a fixed brand-template pipeline — brand integration is handled ad hoc through helper primitives.