Initial commit
This commit is contained in:
12
.claude-plugin/plugin.json
Normal file
12
.claude-plugin/plugin.json
Normal 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
3
README.md
Normal 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
375
SKILL.md
Normal 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
|
||||

|
||||

|
||||

|
||||
```
|
||||
|
||||
### Slide Backgrounds
|
||||
|
||||
```markdown
|
||||

|
||||

|
||||

|
||||
```
|
||||
|
||||
## 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
297
markdown_to_pdf.py
Executable 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
215
marp_to_pdf.py
Executable 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
53
plugin.lock.json
Normal 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": []
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user