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