commit 6ea11d9f3a8506ba9456e00797984786bd662f09 Author: Zhongwei Li Date: Sun Nov 30 09:08:14 2025 +0800 Initial commit diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..d81e994 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -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": [ + "./" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..7a985b6 --- /dev/null +++ b/README.md @@ -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. diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..35b0b06 --- /dev/null +++ b/SKILL.md @@ -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 + +# Centered Title Slide + + +## Inverted Color Slide + + +## Custom Background +``` + +### Presenter Notes + +```markdown +## Slide Title + +Slide content here + + +``` + +## 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 +
+
+ +## Left Column + +Content here + +
+
+ +## Right Column + +Content here + +
+
+``` + +### 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 +--- + + + +# 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) +\`\`\` + +--- + + + +# 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) diff --git a/markdown_to_pdf.py b/markdown_to_pdf.py new file mode 100755 index 0000000..ae58fe8 --- /dev/null +++ b/markdown_to_pdf.py @@ -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""" + + + + + {Path(input_file).stem} + + + {html_content} + + + """ + + # 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() diff --git a/marp_to_pdf.py b/marp_to_pdf.py new file mode 100755 index 0000000..9f5c894 --- /dev/null +++ b/marp_to_pdf.py @@ -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() diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..6d20b65 --- /dev/null +++ b/plugin.lock.json @@ -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": [] + } +} \ No newline at end of file