cosmicstack-labs/any2pdf
Convert Markdown to publication-quality PDF with reportlab — CJK/Latin mixed text, themes, cover pages, watermarks, callouts, formulas, and interactive theme selection
npx skills add https://github.com/cosmicstack-labs/mercury-agent-skills --skill any2pdf
> Credits. This skill is adapted from lovstudio/any2pdf by lovstudio, distributed under the MIT license. The original repository ships the Python implementation (md2pdf.py), preview gallery, and reference theme JSONs. This document is the Mercury-compatible SKILL.md adaptation; the runtime code and design credit belong entirely to the original author.
>
> - Upstream: https://github.com/lovstudio/any2pdf
> - License: MIT (see upstream LICENSE)
> - Author: lovstudio
> - Curator (this entry): cosmicstack-labs
This skill converts any Markdown file into a publication-quality PDF using Python's reportlab. It was developed through extensive iteration on real Chinese technical reports and solves several hard problems that naive MD→PDF converters get wrong — CJK/Latin mixed text wrapping, canvas CJK rendering on cover/headers/footers, code-block whitespace preservation, and mixed-font fallbacks across macOS, Linux, and Windows.
Trigger this skill whenever the user:
.md → .pdfAfter installing this skill via the Mercury CLI:
mercury skills install pdf-generation/any2pdf
the SKILL.md lands at ~/.mercury/skills/pdf-generation/any2pdf/SKILL.md.
The Python implementation (md2pdf.py), preview images, and theme files are not vendored into Mercury Skills — they live in the upstream repository. To use the skill end-to-end, also clone or install the upstream runtime:
# Option A: clone alongside (recommended for development)
git clone https://github.com/lovstudio/any2pdf.git
# The script is at: any2pdf/lovstudio-any2pdf/scripts/md2pdf.py
# Option B: install via the upstream's own installer
npx skills add lovstudio/any2pdf -g -y
When the AI agent runs the conversion, treat the script path as wherever the user cloned it. If unsure, ask:
> "Where is the md2pdf.py script on your system? (e.g. ./any2pdf/lovstudio-any2pdf/scripts/md2pdf.py)"
python <path-to>/md2pdf.py \
--input report.md \
--output report.pdf \
--title "My Report" \
--author "Author Name" \
--theme warm-academic
All parameters except --input are optional — sensible defaults are applied.
IMPORTANT for AI agents: You MUST present these options to the user before running the conversion. Use whatever interactive question primitive your runtime provides (AskUserQuestion, a structured prompt, an MCP tool, etc.). Present all options in a single question so the user answers once.
The tone should be a friendly design assistant, not a config form.
Starting PDF conversion — quick choices first
━━━ Design Style ━━━
a) Warm Academic — terracotta tones, refined and elegant; humanities / social science
b) Classic Thesis — brown tones, LaTeX classicthesis inspired; academic papers
c) Tufte — minimal whitespace, deep red accents; data narratives, technical writing
d) IEEE Journal — navy blue, journal-formal; conferences and journals
e) Elegant Book — coffee tones, book-like; long-form monographs / technical books
f) Chinese Red — vermilion on warm paper; Chinese formal reports / whitepapers
g) Ink Wash — pure grayscale, restrained and elegant; literary / design content
h) GitHub — blue-and-white minimal; developer-familiar
i) Nord Frost — Nordic blue-gray; clean modern
j) Ocean Breeze — teal-green; fresh and natural
━━━ Frontispiece (full-page image after cover) ━━━
1) Skip
2) I'll provide a local image path
3) AI generates one based on document content
━━━ Watermark ━━━
1) None
2) Custom text (e.g. "DRAFT", "Internal Use Only")
━━━ Back Cover Material (business card / QR code / brand) ━━━
1) Skip
2) I'll provide an image
3) Plain text only
Example reply: "a, frontispiece skip, watermark: For Reference Only, back cover: /path/qr.png"
Plain English is fine — no need to memorize the letters.
| Choice | CLI argument |
|---|---|
| Design style a–j | --theme <value-from-table-below> |
| Frontispiece local | --frontispiece <path> |
| Frontispiece AI | Generate image first, then --frontispiece /tmp/frontispiece.png |
| Watermark text | --watermark "TEXT" |
| Back cover image | --banner <path> |
| Back cover text | --disclaimer "..." and/or --copyright "..." |
| Choice | --theme value | Inspiration |
|---|---|---|
| a) Warm Academic | warm-academic | Lovstudio design system |
| b) Classic Thesis | classic-thesis | LaTeX classicthesis |
| c) Tufte | tufte | Edward Tufte's books |
| d) IEEE Journal | ieee-journal | IEEE journal format |
| e) Elegant Book | elegant-book | LaTeX ElegantBook |
| f) Chinese Red | chinese-red | Chinese formal documents |
| g) Ink Wash | ink-wash | 水墨画 / ink wash painting |
| h) GitHub | github-light | GitHub Markdown style |
| i) Nord Frost | nord-frost | Nord color scheme |
| j) Ocean Breeze | ocean-breeze | — |
If the user chose AI generation: read the document title and the first few paragraphs, use an image-generation tool to create a themed illustration matching the chosen design style, show the result for approval, then pass via --frontispiece /path/to/image.png.
Markdown
→ Preprocess (split merged headings)
→ Parse (code-fence aware)
→ Story (reportlab flowables)
→ PDF build
Key components:
_font_wrap() wraps CJK character runs in <font> tags for automatic font switching._draw_mixed() handles CJK/Latin mixed text on canvas (cover, headers, footers).esc_code() preserves indentation and line breaks in reportlab Paragraphs.ChapterMark flowable creates PDF sidebar bookmarks and named anchors._preprocess_md() splits merged headings like # Part## Chapter into separate lines.file://, and remote markdown images are scaled into the body frame with fallback text on errors.> [!NOTE] blocks render as themed boxed callouts.10. Formula renderer — display formulas use optional matplotlib mathtext images, with styled text fallback.
11. Emoji fallback — emojis render as cached Twemoji PNGs when available, or with a local emoji font fallback.
These are real bugs that came out of shipping real reports — preserve the fixes if you're customising the script.
reportlab's Paragraph only uses the font set in ParagraphStyle. If fontName="Mono" but the text contains Chinese, characters render as □. Fix: always apply _font_wrap() to all text that might contain CJK, including code blocks.
reportlab treats \n as whitespace. Fix: esc_code() converts \n → <br/> and all spaces → , preserving indentation and mid-line alignment before _font_wrap().
Default reportlab breaks lines only at spaces, causing ugly splits like Claude\nCode. Fix: set wordWrap='CJK' on body / bullet styles to allow breaks at CJK character boundaries.
drawString() / drawCentredString() with a Latin font can't render 年/月/日 etc. Fix: use _draw_mixed() for all user-content canvas text (dates, stats, disclaimers).
Most options can also be set in the markdown file's YAML frontmatter. Explicit CLI arguments take precedence over frontmatter values.
| CLI Argument | Frontmatter Key | Default | Description |
|---|---|---|---|
| --input | — | (required) | Path to markdown file |
| --output | — | output.pdf | Output PDF path |
| --title | title | From first H1 | Document title for cover page |
| --subtitle | subtitle | "" | Subtitle text |
| --author | author | "" | Author name |
| --date | date | Today | Date string |
| --version | version | "" | Version string for cover |
| --watermark | watermark | "" | Watermark text (empty = none) |
| --theme | theme | warm-academic | Color theme name |
| --theme-file | — | "" | Custom theme JSON file path |
| --cover | cover | true | Generate cover page |
| --toc | toc | true | Generate table of contents |
| --page-size | page-size | A4 | Page size (A4 or Letter) |
| --frontispiece | frontispiece | "" | Full-page image after cover |
| --banner | banner | "" | Back cover banner image |
| --header-title | header-title | "" | Report title in page header |
| --footer-left | footer-left | author | Brand / author in footer |
| --stats-line | stats-line | "" | Stats on cover |
| --stats-line2 | stats-line2 | "" | Second stats line |
| --edition-line | edition-line | "" | Edition line at cover bottom |
| --disclaimer | disclaimer | "" | Back cover disclaimer |
| --copyright | copyright | "" | Back cover copyright |
| --code-max-lines | code-max-lines | 30 | Max lines per code block |
Built-in: warm-academic, classic-thesis, tufte, ieee-journal, elegant-book, chinese-red, ink-wash, github-light, nord-frost, ocean-breeze.
Each theme defines: page background, ink color, accent color, faded text color, border color, code background, and watermark tint. Preview images for every theme live in the upstream repository under previews/.
pip install reportlab
# Optional — render display formulas as images instead of styled text:
pip install matplotlib
sudo apt install fonts-dejavu-core fonts-liberation fonts-freefont-ttf \
fonts-noto fonts-noto-cjk fonts-noto-color-emoji
Fonts are auto-discovered from system paths. Missing fonts produce a helpful error with the exact install command for your OS.
!alt with graceful fallback text> [!NOTE], warnings, tips, quotes, and related callout types$...$ and display $$...$$ / \[...\]; install matplotlib for rendered math imagesThis skill entry is a derivative work licensed under MIT, matching the upstream project.
If you ship this skill in a product, retain the upstream attribution and the MIT license notice from the original repository.
Take cosmicstack-labs/any2pdf from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference pip, npx.
Without those the skill loads but fails at the first command.