Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 09:08:14 +08:00
commit 6ea11d9f3a
6 changed files with 955 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
{
"name": "markdown-to-pdf",
"description": "Convert Markdown files to professional PDF presentations using Marp. Supports multiple themes, code syntax highlighting, math equations, and Chinese/CJK characters.",
"version": "0.0.0-2025.11.28",
"author": {
"name": "Yong Gao",
"email": "zhongweili@tubi.tv"
},
"skills": [
"./"
]
}

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# markdown-to-pdf
Convert Markdown files to professional PDF presentations using Marp. Supports multiple themes, code syntax highlighting, math equations, and Chinese/CJK characters.

375
SKILL.md Normal file
View File

@@ -0,0 +1,375 @@
# Markdown to PDF Converter Skill
This skill helps you convert Markdown files to PDF format with support for both document-style and presentation-style outputs using Marp.
## When to Use This Skill
Use this skill when the user wants to:
- Convert Markdown (.md) files to PDF presentations
- Generate slide decks from Markdown
- Create professional PDFs from documentation
- Export README files as PDFs
- Generate presentation-ready slides with Marp themes
## Available Conversion Methods
### Method 1: Marp Presentation (Recommended)
Best for: Presentations, slide decks, visual content
**Command:**
```bash
python marp_to_pdf.py input.md
```
**Features:**
- Beautiful presentation slides from Markdown
- Multiple built-in themes (default, gaia, uncover)
- Automatic slide breaks with `---`
- Support for presenter notes
- Code syntax highlighting
- Chinese/CJK character support
- Outputs: `input.pdf`
### Method 2: Marp with Custom Theme
Best for: Branded presentations, custom styling
**Command:**
```bash
python marp_to_pdf.py input.md --theme gaia
```
**Supported Themes:**
- `default` - Clean and simple
- `gaia` - Modern and colorful
- `uncover` - Minimalist and elegant
## Required Dependencies
The skill requires Marp CLI via npm:
```bash
# Install Node.js first (if not installed)
# Then install Marp CLI globally
npm install -g @marp-team/marp-cli
# Or use npx (no installation needed)
npx @marp-team/marp-cli --version
```
## Marp Markdown Syntax
### Basic Slide Structure
```markdown
---
marp: true
theme: default
---
# Title Slide
Your presentation content
---
## Second Slide
- Bullet point 1
- Bullet point 2
---
## Code Example
\`\`\`python
def hello():
print("Hello, World!")
\`\`\`
```
### Slide Directives
```markdown
<!-- _class: lead -->
# Centered Title Slide
<!-- _class: invert -->
## Inverted Color Slide
<!-- backgroundColor: #123456 -->
## Custom Background
```
### Presenter Notes
```markdown
## Slide Title
Slide content here
<!--
These are presenter notes
They won't appear in the PDF
-->
```
## Step-by-Step Instructions
### For Standard Marp Presentation:
1. **Create Markdown file with Marp frontmatter:**
```markdown
---
marp: true
theme: default
---
# Your Title
---
## Content
```
2. **Run the converter:**
```bash
python ~/.claude/skills/markdown-to-pdf/marp_to_pdf.py presentation.md
```
3. **Open the result:**
```bash
open presentation.pdf
```
### For Custom Themed Presentation:
1. **Run with theme option:**
```bash
python ~/.claude/skills/markdown-to-pdf/marp_to_pdf.py slides.md --theme gaia
```
2. **View the PDF:**
```bash
open slides.pdf
```
## Supported Markdown Features
✅ **Headers** (slide titles)
✅ **Bold, Italic, Strikethrough**
✅ **Lists** (ordered, unordered, nested)
✅ **Code blocks** with syntax highlighting
✅ **Inline code**
✅ **Tables**
✅ **Images** (auto-scaled to fit slides)
✅ **Links**
✅ **Blockquotes**
✅ **Math equations** (KaTeX)
✅ **Emojis** 😊
✅ **Slide backgrounds**
✅ **Custom CSS**
✅ **Chinese/CJK characters**
## Marp-Specific Features
### Two-Column Layout
```markdown
<div class="columns">
<div>
## Left Column
Content here
</div>
<div>
## Right Column
Content here
</div>
</div>
```
### Image Sizing
```markdown
![width:500px](image.png)
![height:300px](image.png)
![bg](background-image.png)
```
### Slide Backgrounds
```markdown
![bg](background.jpg)
![bg left](left-background.jpg)
![bg right](right-background.jpg)
```
## Troubleshooting
### Issue: Marp CLI not found
**Solution:**
```bash
npm install -g @marp-team/marp-cli
# Or use npx without installation
npx @marp-team/marp-cli --version
```
### Issue: Slides not breaking correctly
**Solution:** Ensure you use `---` to separate slides:
```markdown
# Slide 1
---
# Slide 2
```
### Issue: Theme not applied
**Solution:** Add Marp frontmatter at the top:
```markdown
---
marp: true
theme: gaia
---
```
### Issue: Chinese characters not displaying
**Solution:** Marp automatically handles system fonts for CJK characters.
## Output Files
After conversion, you'll get:
**Marp Method:**
- `filename.pdf` - Presentation-style PDF with slides
**Optional HTML output:**
- `filename.html` - Interactive HTML presentation
## Best Practices
1. **For presentations:** Use Marp with clear slide breaks
- One main idea per slide
- Use `---` to separate slides
- Choose appropriate theme
2. **For code demonstrations:**
- Use syntax highlighting with language tags
- Keep code snippets concise per slide
3. **For visual impact:**
- Use background images
- Apply custom themes
- Use the `_class: lead` for title slides
## Examples
### Example 1: Simple Presentation
```bash
python ~/.claude/skills/markdown-to-pdf/marp_to_pdf.py slides.md
# Output: slides.pdf
```
### Example 2: Gaia Theme Presentation
```bash
python ~/.claude/skills/markdown-to-pdf/marp_to_pdf.py slides.md --theme gaia
```
### Example 3: Generate HTML and PDF
```bash
python ~/.claude/skills/markdown-to-pdf/marp_to_pdf.py slides.md --html
# Output: slides.pdf and slides.html
```
### Example 4: Batch Convert Multiple Presentations
```bash
for file in *.md; do
python ~/.claude/skills/markdown-to-pdf/marp_to_pdf.py "$file"
done
```
## Sample Marp Markdown
```markdown
---
marp: true
theme: gaia
paginate: true
---
<!-- _class: lead -->
# My Presentation
## Subtitle Here
Your Name
Date
---
## Agenda
1. Introduction
2. Main Content
3. Conclusion
---
## Code Example
\`\`\`python
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
\`\`\`
---
<!-- _class: lead -->
# Thank You!
Questions?
```
## Performance Expectations
- **Processing Speed**: ~2-5 seconds for typical presentations
- **Memory Usage**: ~100-200 MB during conversion
- **PDF File Size**: 500KB - 5MB depending on images
## Success Criteria
A successful conversion should:
1. ✅ Generate PDF without errors
2. ✅ Create proper slide breaks
3. ✅ Apply theme correctly
4. ✅ Display code with syntax highlighting
5. ✅ Handle Chinese/CJK characters correctly
6. ✅ Scale images appropriately
7. ✅ Maintain visual consistency
## Quick Reference
| Need | Command | Output |
|------|---------|--------|
| Default theme | `marp_to_pdf.py file.md` | PDF with default theme |
| Gaia theme | `marp_to_pdf.py file.md --theme gaia` | PDF with Gaia theme |
| Uncover theme | `marp_to_pdf.py file.md --theme uncover` | PDF with Uncover theme |
| HTML output | `marp_to_pdf.py file.md --html` | PDF + HTML presentation |
## Additional Resources
- [Marp Official Documentation](https://marp.app/)
- [Marp CLI Documentation](https://github.com/marp-team/marp-cli)
- [Marp Themes Gallery](https://github.com/marp-team/marp-core/tree/main/themes)

297
markdown_to_pdf.py Executable file
View File

@@ -0,0 +1,297 @@
#!/usr/bin/env python3
"""
Markdown to PDF Converter
Converts Markdown files directly to PDF with proper formatting and syntax highlighting.
"""
import sys
import os
from pathlib import Path
def check_dependencies():
"""Check and install required dependencies."""
required = {
'markdown': 'markdown',
'weasyprint': 'weasyprint',
'pygments': 'pygments'
}
missing = []
for module, package in required.items():
try:
__import__(module)
except ImportError:
missing.append(package)
if missing:
print(f"📦 Installing missing dependencies: {', '.join(missing)}")
import subprocess
subprocess.check_call([sys.executable, '-m', 'pip', 'install'] + missing)
print("✅ Dependencies installed!\n")
check_dependencies()
import markdown
from weasyprint import HTML, CSS
from markdown.extensions import fenced_code, tables, codehilite, toc
def get_css_style():
"""Return CSS styling for the PDF."""
return """
@page {
size: A4;
margin: 2cm;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 11pt;
line-height: 1.6;
color: #333;
max-width: 100%;
}
h1, h2, h3, h4, h5, h6 {
font-weight: 600;
margin-top: 1.5em;
margin-bottom: 0.5em;
color: #000;
page-break-after: avoid;
}
h1 {
font-size: 24pt;
border-bottom: 2px solid #eee;
padding-bottom: 0.3em;
}
h2 {
font-size: 20pt;
border-bottom: 1px solid #eee;
padding-bottom: 0.3em;
}
h3 { font-size: 16pt; }
h4 { font-size: 14pt; }
h5 { font-size: 12pt; }
h6 { font-size: 11pt; }
p {
margin: 0.8em 0;
}
a {
color: #0366d6;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
code {
font-family: "SF Mono", Monaco, Menlo, Consolas, "Courier New", monospace;
font-size: 9.5pt;
background-color: #f6f8fa;
padding: 0.2em 0.4em;
border-radius: 3px;
}
pre {
background-color: #f6f8fa;
padding: 1em;
border-radius: 5px;
overflow-x: auto;
page-break-inside: avoid;
margin: 1em 0;
}
pre code {
background-color: transparent;
padding: 0;
font-size: 9pt;
}
blockquote {
margin: 1em 0;
padding-left: 1em;
border-left: 4px solid #ddd;
color: #666;
}
ul, ol {
margin: 0.8em 0;
padding-left: 2em;
}
li {
margin: 0.3em 0;
}
table {
border-collapse: collapse;
width: 100%;
margin: 1em 0;
page-break-inside: avoid;
}
th, td {
border: 1px solid #ddd;
padding: 0.5em 0.8em;
text-align: left;
}
th {
background-color: #f6f8fa;
font-weight: 600;
}
tr:nth-child(even) {
background-color: #f9f9f9;
}
img {
max-width: 100%;
height: auto;
display: block;
margin: 1em 0;
}
hr {
border: none;
border-top: 1px solid #eee;
margin: 2em 0;
}
/* Syntax highlighting styles */
.codehilite {
background-color: #f6f8fa;
border-radius: 5px;
padding: 1em;
margin: 1em 0;
page-break-inside: avoid;
}
.codehilite .hll { background-color: #ffffcc }
.codehilite .c { color: #6a737d; font-style: italic } /* Comment */
.codehilite .k { color: #d73a49; font-weight: bold } /* Keyword */
.codehilite .o { color: #d73a49 } /* Operator */
.codehilite .cm { color: #6a737d; font-style: italic } /* Comment.Multiline */
.codehilite .cp { color: #d73a49; font-weight: bold } /* Comment.Preproc */
.codehilite .c1 { color: #6a737d; font-style: italic } /* Comment.Single */
.codehilite .cs { color: #6a737d; font-style: italic } /* Comment.Special */
.codehilite .kc { color: #005cc5; font-weight: bold } /* Keyword.Constant */
.codehilite .kd { color: #d73a49; font-weight: bold } /* Keyword.Declaration */
.codehilite .kn { color: #d73a49; font-weight: bold } /* Keyword.Namespace */
.codehilite .kp { color: #d73a49; font-weight: bold } /* Keyword.Pseudo */
.codehilite .kr { color: #d73a49; font-weight: bold } /* Keyword.Reserved */
.codehilite .kt { color: #d73a49; font-weight: bold } /* Keyword.Type */
.codehilite .m { color: #005cc5 } /* Literal.Number */
.codehilite .s { color: #032f62 } /* Literal.String */
.codehilite .na { color: #6f42c1 } /* Name.Attribute */
.codehilite .nb { color: #005cc5 } /* Name.Builtin */
.codehilite .nc { color: #6f42c1; font-weight: bold } /* Name.Class */
.codehilite .nf { color: #6f42c1; font-weight: bold } /* Name.Function */
.codehilite .nn { color: #6f42c1 } /* Name.Namespace */
.codehilite .nt { color: #22863a } /* Name.Tag */
.codehilite .nv { color: #e36209 } /* Name.Variable */
"""
def convert_markdown_to_pdf(input_file, output_file=None):
"""Convert Markdown file to PDF."""
# Validate input file
if not os.path.exists(input_file):
print(f"❌ Error: Input file '{input_file}' not found!")
return False
# Determine output file
if output_file is None:
output_file = Path(input_file).with_suffix('.pdf')
print("=" * 70)
print("Markdown转PDF工具")
print("=" * 70)
print(f"\n📄 转换Markdown为PDF")
print(f" 输入: {input_file}")
print(f" 输出: {output_file}\n")
try:
# Read markdown file
print("⏳ 读取Markdown文件...")
with open(input_file, 'r', encoding='utf-8') as f:
md_content = f.read()
# Convert markdown to HTML with extensions
print("🔄 转换Markdown为HTML...")
md = markdown.Markdown(extensions=[
'fenced_code',
'tables',
'codehilite',
'toc',
'nl2br',
'sane_lists'
], extension_configs={
'codehilite': {
'css_class': 'codehilite',
'linenums': False,
'guess_lang': True
}
})
html_content = md.convert(md_content)
# Wrap in HTML document
full_html = f"""
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{Path(input_file).stem}</title>
</head>
<body>
{html_content}
</body>
</html>
"""
# Convert HTML to PDF
print("📄 生成PDF...")
HTML(string=full_html, base_url=str(Path(input_file).parent.absolute())).write_pdf(
output_file,
stylesheets=[CSS(string=get_css_style())]
)
# Get file size
size_kb = os.path.getsize(output_file) / 1024
print(f"\n✅ 成功生成PDF")
print(f" 大小: {size_kb:.1f} KB")
print(f"\n💡 打开查看:")
print(f" open {output_file}")
return True
except Exception as e:
print(f"\n❌ 转换失败: {str(e)}")
import traceback
traceback.print_exc()
return False
def main():
"""Main entry point."""
if len(sys.argv) < 2:
print("Usage: python markdown_to_pdf.py input.md [output.pdf]")
print("\nExamples:")
print(" python markdown_to_pdf.py README.md")
print(" python markdown_to_pdf.py docs.md custom_output.pdf")
sys.exit(1)
input_file = sys.argv[1]
output_file = sys.argv[2] if len(sys.argv) > 2 else None
success = convert_markdown_to_pdf(input_file, output_file)
sys.exit(0 if success else 1)
if __name__ == '__main__':
main()

215
marp_to_pdf.py Executable file
View File

@@ -0,0 +1,215 @@
#!/usr/bin/env python3
"""
Marp Markdown to PDF Converter
Converts Markdown files to beautiful presentation PDFs using Marp CLI.
"""
import sys
import os
import subprocess
import shutil
from pathlib import Path
import argparse
def check_marp_cli():
"""Check if Marp CLI is installed."""
# Try marp-cli
if shutil.which('marp'):
return 'marp'
# Try npx
if shutil.which('npx'):
try:
result = subprocess.run(
['npx', '@marp-team/marp-cli', '--version'],
capture_output=True,
text=True,
timeout=10
)
if result.returncode == 0:
return 'npx'
except:
pass
return None
def install_instructions():
"""Print installation instructions."""
print("\n❌ Marp CLI not found!")
print("\n请安装Marp CLI:")
print("\n方法1: 使用npm全局安装")
print(" npm install -g @marp-team/marp-cli")
print("\n方法2: 使用npx (无需安装)")
print(" npx @marp-team/marp-cli --version")
print("\n方法3: 使用Homebrew (macOS)")
print(" brew install marp-cli")
print()
def convert_markdown_to_pdf(input_file, output_file=None, theme='default', html_output=False):
"""Convert Markdown to PDF using Marp."""
# Validate input file
if not os.path.exists(input_file):
print(f"❌ 错误: 输入文件 '{input_file}' 不存在!")
return False
# Check Marp CLI
marp_cmd = check_marp_cli()
if not marp_cmd:
install_instructions()
return False
# Determine output file
if output_file is None:
output_file = Path(input_file).with_suffix('.pdf')
print("=" * 70)
print("Marp Markdown转PDF工具")
print("=" * 70)
print(f"\n📄 转换Markdown为演示文稿PDF")
print(f" 输入: {input_file}")
print(f" 输出: {output_file}")
print(f" 主题: {theme}\n")
try:
# Build Marp command
if marp_cmd == 'marp':
cmd = ['marp']
else: # npx
cmd = ['npx', '@marp-team/marp-cli']
cmd.extend([
input_file,
'--pdf',
'--allow-local-files',
'--theme', theme,
'-o', str(output_file)
])
# Run Marp conversion
print("⏳ 生成PDF...")
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=60
)
if result.returncode != 0:
print(f"❌ 转换失败!")
print(f"错误信息: {result.stderr}")
return False
# Get file size
if os.path.exists(output_file):
size_kb = os.path.getsize(output_file) / 1024
print(f"\n✅ 成功生成PDF!")
print(f" 大小: {size_kb:.1f} KB")
# Generate HTML if requested
if html_output:
html_file = Path(output_file).with_suffix('.html')
print(f"\n📄 生成HTML版本...")
html_cmd = cmd.copy()
# Remove --pdf and change output
html_cmd = [c for c in html_cmd if c != '--pdf']
html_cmd[-1] = str(html_file)
result = subprocess.run(
html_cmd,
capture_output=True,
text=True,
timeout=60
)
if result.returncode == 0 and os.path.exists(html_file):
html_size_kb = os.path.getsize(html_file) / 1024
print(f"✅ HTML生成成功!")
print(f" 大小: {html_size_kb:.1f} KB")
print(f"\n💡 打开查看:")
print(f" PDF: open {output_file}")
print(f" HTML: open {html_file}")
else:
print(f"\n💡 打开查看:")
print(f" PDF: open {output_file}")
else:
print(f"\n💡 打开查看:")
print(f" open {output_file}")
return True
except subprocess.TimeoutExpired:
print("\n❌ 转换超时!")
return False
except Exception as e:
print(f"\n❌ 转换失败: {str(e)}")
import traceback
traceback.print_exc()
return False
def ensure_marp_frontmatter(input_file):
"""Check if Markdown has Marp frontmatter, add if missing."""
with open(input_file, 'r', encoding='utf-8') as f:
content = f.read()
# Check if already has marp frontmatter
if content.startswith('---\nmarp:') or 'marp: true' in content[:100]:
return False # Already has frontmatter
# Add Marp frontmatter
frontmatter = """---
marp: true
theme: default
paginate: true
---
"""
with open(input_file, 'w', encoding='utf-8') as f:
f.write(frontmatter + content)
return True # Added frontmatter
def main():
"""Main entry point."""
parser = argparse.ArgumentParser(
description='Convert Markdown to PDF presentation using Marp'
)
parser.add_argument('input', help='Input Markdown file')
parser.add_argument('output', nargs='?', help='Output PDF file (optional)')
parser.add_argument(
'--theme',
choices=['default', 'gaia', 'uncover'],
default='default',
help='Marp theme to use'
)
parser.add_argument(
'--html',
action='store_true',
help='Also generate HTML output'
)
parser.add_argument(
'--add-frontmatter',
action='store_true',
help='Add Marp frontmatter if missing'
)
args = parser.parse_args()
# Add frontmatter if requested
if args.add_frontmatter:
if ensure_marp_frontmatter(args.input):
print("✅ 已添加Marp前置元数据\n")
success = convert_markdown_to_pdf(
args.input,
args.output,
args.theme,
args.html
)
sys.exit(0 if success else 1)
if __name__ == '__main__':
main()

53
plugin.lock.json Normal file
View File

@@ -0,0 +1,53 @@
{
"$schema": "internal://schemas/plugin.lock.v1.json",
"pluginId": "gh:yonggao/claude-plugins:skills/markdown-to-pdf",
"normalized": {
"repo": null,
"ref": "refs/tags/v20251128.0",
"commit": "6a790640bdd262c92cdb86a160b8be3e553756be",
"treeHash": "76b0b2a2303a9f518a45ea42176e82903cc540da301f6c52ada3e15bc1e9d1b5",
"generatedAt": "2025-11-28T10:29:12.913747Z",
"toolVersion": "publish_plugins.py@0.2.0"
},
"origin": {
"remote": "git@github.com:zhongweili/42plugin-data.git",
"branch": "master",
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
},
"manifest": {
"name": "markdown-to-pdf",
"description": "Convert Markdown files to professional PDF presentations using Marp. Supports multiple themes, code syntax highlighting, math equations, and Chinese/CJK characters.",
"version": null
},
"content": {
"files": [
{
"path": "markdown_to_pdf.py",
"sha256": "b78ed3d47561b5d7489de5988332a2f4c371acf201df0ff59e6499a7ebbded4f"
},
{
"path": "README.md",
"sha256": "2e6de32a3776871ff3b5c6356bb5d305cd710d3bc0e9a64fff7dd51bd32482c0"
},
{
"path": "marp_to_pdf.py",
"sha256": "30bf46c80ad49732b88f5d2b8ae582acd2dda5ed838fa1ffcdb1d3ba16dc56aa"
},
{
"path": "SKILL.md",
"sha256": "c7ee4e91fb8d76639c934fa8b7db98e064ce2c04726a2eb4f5ba188458bdae5f"
},
{
"path": ".claude-plugin/plugin.json",
"sha256": "82b4884d44a6c29f7b25f45f1bd33df943bc721cd4328eced490378c6b6a96a0"
}
],
"dirSha256": "76b0b2a2303a9f518a45ea42176e82903cc540da301f6c52ada3e15bc1e9d1b5"
},
"security": {
"scannedAt": null,
"scannerVersion": null,
"flags": []
}
}