Markdown to PDF (CJK)
/install md2pdf-cjk
Markdown to PDF (CJK)
Convert Markdown files to well-formatted PDF with full CJK (Chinese/Japanese/Korean) support, code blocks, and tables.
Toolchain
- Rendering engine: WeasyPrint (pre-installed,
/usr/local/bin/weasyprint) - Fonts: Noto Sans CJK SC / Noto Serif CJK SC (system-installed)
- Pipeline: Markdown → HTML (emoji replacement) → WeasyPrint → PDF
Steps
1. Emoji Processing
WeasyPrint does not render emoji characters. Replace them with text labels:
emoji_map = {
'🦞': '[Lobster]', '🌅': '[Morning]', '🌙': '[Moon]', '🏛': '[Architecture]',
'🔧': '[Tool]', '🔄': '[Restart]', '📰': '[Report]', '💪': '[Strong]',
'⚡': '[Lightning]', '⏰': '[Alarm]', '👆': '[Up]', '⬆️': '[Up]',
'🌟': '[Star]', '👇': '[Down]', '✅': '[Done]', '📊': '[Chart]',
'🎯': '[Target]', '💡': '[Idea]', '🧰': '[Toolbox]', '🤖': '[AI]',
'🏗️': '[Build]', '📅': '[Calendar]', '🧘': '[Meditation]', '📖': '[Read]',
'📝': '[Note]', '🏃': '[Run]', '⭐': '[Fav]', '📬': '[Mail]',
'🌐': '[Web]', '🐙': '[GitHub]', '💬': '[Chat]', '▪': '·',
'❌': '[X]', '⚠️': '[!]', '📌': '[PIN]', '🔔': '[Bell]',
'🤔': '[Think]', '😂': '[Laugh]', '🔒': '[Lock]', '📁': '[Folder]',
'💰': '[Money]', '👨👧👦': '[Family]', '😴': '[Sleep]', '👶': '[Baby]',
'1️⃣': '1.', '2️⃣': '2.', '3️⃣': '3.', '4️⃣': '4.', '5️⃣': '5.',
}
For emoji not in the map, strip variant selectors (
\ufe0f) and replace with[?]. Preserve CJK punctuation marks (·…——''""etc.).
2. Markdown → HTML Conversion
Lightweight conversion using Python regex (no extra dependencies):
import re
html = md_content
html = re.sub(r'^# (.+)$', r'\x3Ch1>\1\x3C/h1>', html, flags=re.MULTILINE)
html = re.sub(r'^## (.+)$', r'\x3Ch2>\1\x3C/h2>', html, flags=re.MULTILINE)
html = re.sub(r'^### (.+)$', r'\x3Ch3>\1\x3C/h3>', html, flags=re.MULTILINE)
html = re.sub(r'\*\*(.+?)\*\*', r'\x3Cstrong>\1\x3C/strong>', html)
html = re.sub(r'\*(.+?)\*', r'\x3Cem>\1\x3C/em>', html)
html = re.sub(r'^> (.+)$', r'\x3Cblockquote>\1\x3C/blockquote>', html, flags=re.MULTILINE)
html = re.sub(r'^---$', r'\x3Chr/>', html, flags=re.MULTILINE)
html = re.sub(r'^- (.+)$', r'\x3Cli>\1\x3C/li>', html, flags=re.MULTILINE)
html = re.sub(r'`([^`]+)`', r'\x3Ccode>\1\x3C/code>', html)
html = re.sub(r'```(\w*)\
(.*?)```', r'\x3Cpre>\x3Ccode>\2\x3C/code>\x3C/pre>', html, flags=re.DOTALL)
# Paragraphs
html = re.sub(r'\
\
', r'\x3C/p>\x3Cp>', html)
html = '\x3Cp>' + html + '\x3C/p>'
# Clean empty tags
html = re.sub(r'\x3Cp>\s*\x3C/p>', '', html)
html = re.sub(r'\x3Cp>\s*(\x3Ch[123]>.*?\x3C/h[123]>)\s*\x3C/p>', r'\1', html, flags=re.DOTALL)
html = re.sub(r'\x3Cp>\s*(\x3Chr/>)\s*\x3C/p>', r'\1', html)
html = re.sub(r'\x3Cp>\s*(\x3Cblockquote>.*?\x3C/blockquote>)\s*\x3C/p>', r'\1', html, flags=re.DOTALL)
3. HTML Template
\x3C!DOCTYPE html>
\x3Chtml>
\x3Chead>
\x3Cmeta charset="utf-8">
\x3Cstyle>
@page { size: A4; margin: 2cm 2.5cm 2cm 2.5cm; }
body {
font-family: "Noto Sans CJK SC", "Noto Serif CJK SC", sans-serif;
font-size: 11pt; line-height: 1.8; color: #333;
max-width: 100%; word-wrap: break-word;
}
h1 { font-size: 20pt; color: #d32f2f; border-bottom: 2px solid #d32f2f; padding-bottom: 8px; margin-top: 30px; }
h2 { font-size: 15pt; color: #333; margin-top: 25px; border-left: 4px solid #d32f2f; padding-left: 10px; }
h3 { font-size: 13pt; color: #555; margin-top: 20px; }
blockquote { border-left: 3px solid #ccc; padding-left: 15px; color: #666; margin: 15px 0; font-style: italic; }
code { background: #f5f5f5; padding: 2px 6px; border-radius: 3px; font-size: 10pt; }
pre { background: #f8f8f8; padding: 12px; border-radius: 5px; font-size: 9.5pt; line-height: 1.6; }
pre code { background: none; padding: 0; }
strong { color: #d32f2f; }
hr { border: none; border-top: 1px solid #ddd; margin: 25px 0; }
li { margin: 5px 0; }
table { border-collapse: collapse; width: 100%; margin: 15px 0; }
th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
th { background: #f5f5f5; font-weight: bold; }
\x3C/style>
\x3C/head>
\x3Cbody>{{HTML_CONTENT}}\x3C/body>
\x3C/html>
4. Generate PDF
weasyprint /tmp/input.html /tmp/output.pdf
One-line Script
All steps are wrapped into a single script:
#!/bin/bash
# Usage: md2pdf.sh input.md [output.pdf]
# Markdown → PDF (weasyprint + CJK + emoji replacement)
INPUT="${1:-/dev/stdin}"
OUTPUT="${2:-/tmp/output.pdf}"
python3 /path/to/md2pdf.py "$INPUT" "$OUTPUT"
Important Notes
- Do NOT use pandoc + xelatex: Emoji renders as blank, and compilation is slow
- Do NOT use pandoc + wkhtmltopdf: Not installed by default
- Always use WeasyPrint: The only solution that supports CJK + emoji replacement
- Output path: Always use absolute paths under
/tmp/ - Pre-send check: Verify PDF file exists and is > 10KB before sending
- 确保已安装 OpenClaw(本地或 Docker 部署)
- 在对话框中输入安装命令:
/install md2pdf-cjk - 安装完成后,直接呼叫该 Skill 的名称或使用
/md2pdf-cjk触发 - 根据 Skill 的参数说明提供必要输入,即可获得结构化输出
Markdown to PDF (CJK) 是什么?
Markdown to PDF converter with CJK (Chinese/Japanese/Korean) support. Uses WeasyPrint + Noto CJK fonts for proper rendering. Features: emoji replacement, cus... 它是一个面向 Claude Code / OpenClaw 的 AI Agent Skill 插件,目前累计下载 63 次。
如何安装 Markdown to PDF (CJK)?
在 OpenClaw 或 Claude Code 对话框中运行命令「/install md2pdf-cjk」即可一键安装,无需额外配置。
Markdown to PDF (CJK) 是免费的吗?
是的,Markdown to PDF (CJK) 完全免费,采用 MIT-0 许可证,可自由下载、安装和使用。
Markdown to PDF (CJK) 支持哪些平台?
Markdown to PDF (CJK) 跨平台运行,可在任意部署了 OpenClaw / Claude Code 的环境中使用(cross-platform)。
谁开发了 Markdown to PDF (CJK)?
由 Shimon Xin(@shimonxin)开发并维护,当前版本 v1.0.1。