
Huashu Md To Pdf
- 940 installs
- 1.3k repo stars
- Updated April 21, 2026
- alchaincyf/huashu-skills
huashu-md-to-pdf is an agent skill that converts Markdown files or doc folders into styled PDFs with metadata, tables of contents, and custom titles for developers who need publishable documentation output.
About
huashu-md-to-pdf is a huashu-skills generator that runs convert.py to turn Markdown sources into clean, professionally styled PDFs. The pipeline reads a file, extracts YAML metadata, builds a table of contents—example runs report 5 main chapters and 12 sub-chapters—renders HTML, and writes a PDF with optional custom output names, titles, and authors. Developers reach for it when README files, tech guides, or whitepapers must ship as PDF attachments without manual Pandoc or LaTeX setup. Commands support basic conversion, custom output filenames like 产品说明书.pdf, and full metadata overrides from the CLI.
- Converts single Markdown files or entire directories to PDF
- Supports custom title, author, and output filename flags
- Automatically extracts and renders hierarchical table of contents
- Embeds metadata and generates styled HTML intermediate step
- Outputs file size, location, and ready-to-open confirmation
Huashu Md To Pdf by the numbers
- 940 all-time installs (skills.sh)
- +12 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #270 of 1,901 Documentation skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
npx skills add https://github.com/alchaincyf/huashu-skills --skill huashu-md-to-pdfAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 940 |
|---|---|
| repo stars | ★ 1.3k |
| Security audit | 3 / 3 scanners passed |
| Last updated | April 21, 2026 |
| Repository | alchaincyf/huashu-skills ↗ |
How do you convert Markdown docs to styled PDFs?
Instantly turn any Markdown file or folder of docs into clean, professionally styled PDFs with metadata, table of contents, and custom titles.
Who is it for?
Developers who need fast Markdown-to-PDF conversion with TOC and metadata from a single convert.py script.
Skip if: Developers needing interactive web documentation sites or complex LaTeX academic typesetting beyond the skill's HTML pipeline.
When should I use this skill?
The user asks to convert a .md file or docs folder to PDF with custom title, author, or output filename.
What you get
Styled PDF files, extracted metadata blocks, auto-generated tables of contents, and HTML intermediate renders.
- Styled PDF file
- Extracted metadata
- Generated table of contents
By the numbers
- Example conversion extracted 5 main chapters and 12 sub-chapters
- Example output PDF size logged at 0.8 MB
Files
Markdown to PDF Skill
将 Markdown 文档转换为专业的苹果设计风格 PDF 白皮书。
核心功能
1. 专业排版:书籍级排版质量,自动处理分页、孤行寡行 2. 苹果设计:SF 字体系统、现代简洁风格、专业配色 3. 完整目录:自动提取章节结构,双列布局,可点击跳转 4. Markdown 完美支持:代码块、表格、引用、列表等全部正确渲染
使用方法
基础用法
# 转换单个文件
python scripts/convert.py input.md
# 指定输出文件名
python scripts/convert.py input.md -o "我的白皮书.pdf"
# 自定义标题和作者
python scripts/convert.py input.md --title "技术白皮书" --author "花叔"Markdown 文档要求
你的 Markdown 文档应该遵循以下结构:
# 文档标题
## 1. 第一章
### 1.1 第一节
### 1.2 第二节
## 2. 第二章
### 2.1 第一节关键规则:
- 主章节:
## 1. 标题(数字 + 点 + 空格 + 标题) - 子章节:
### 1.1 标题(数字.数字 + 空格 + 标题) - 这样才能正确提取目录
设计特点
封面设计
- 淡灰色渐变背景
- 大标题:64pt,简洁现代
- 副标题和元信息
目录设计
- 双列布局,单页展示
- 主章节粗体,子章节缩进
- 可点击跳转到对应章节
正文排版
- SF 字体系列(苹果设计语言)
- 行高 1.7,舒适阅读
- 章节自动分页
- 孤行寡行控制
代码块
- 浅灰背景 + 细边框
- 圆角 8px
- SF Mono 等宽字体
- 自动避免分页
表格
- 清晰网格线
- 浅灰表头
- 自动保留表头(长表格分页时)
配置选项
如果需要自定义样式,可以修改 scripts/convert.py 中的 CSS 变量:
# 主色调
PRIMARY_COLOR = '#06c' # 苹果蓝
TEXT_COLOR = '#1d1d1f' # 主文本黑色
GRAY_COLOR = '#86868b' # 浅灰色
# 字体大小
COVER_TITLE_SIZE = '64pt'
H2_SIZE = '22pt'
H3_SIZE = '17pt'
BODY_SIZE = '11pt'常见问题
Q: 目录为什么是空的?
A: 确保你的 Markdown 使用了正确的章节格式:
## 1. 标题而不是## 标题### 1.1 标题而不是### 标题
Q: 代码块显示不正确?
A: 确保使用三个反引号包裹: ````markdown
def hello():
print("Hello")````
Q: 表格格式乱了?
A: 使用标准的 Markdown 表格语法:
| 列1 | 列2 |
|-----|-----|
| 值1 | 值2 |Q: 如何修改字体?
A: 编辑 scripts/convert.py 中的 CSS,修改 font-family 属性。
Q: 生成的 PDF 太大?
A: 检查是否有大量图片,考虑压缩图片或使用外链。
依赖安装
首次使用需要安装 Python 依赖:
pip3 install markdown2 weasyprint如果遇到 WeasyPrint 安装问题(macOS):
brew install pango
pip3 install weasyprint示例
生成技术文档
python scripts/convert.py tech-guide.md -o "技术指南.pdf"生成白皮书
python scripts/convert.py whitepaper.md --title "产品白皮书" --author "团队"脚本说明
scripts/convert.py- 主转换脚本scripts/styles.css- CSS 样式定义(已嵌入脚本)templates/cover.html- 封面模板(已嵌入脚本)
技术实现
本 Skill 使用:
- markdown2:Markdown 解析(支持扩展语法)
- WeasyPrint:HTML to PDF 转换(支持 CSS3)
- 苹果设计系统:SF 字体、专业配色、现代排版
更新日志
v1.0 (2025-12-24)
- 初始版本
- 支持完整 Markdown 语法
- 苹果设计风格
- 自动目录生成
- 书籍级排版质量
---
花叔出品 | AI Native Coder · 独立开发者
公众号「花叔」| 30万+粉丝 | AI工具与效率提升
代表作:小猫补光灯(AppStore付费榜Top1)·《一本书玩转DeepSeek》
Markdown to PDF Skill 使用示例
示例 1: 基础转换
用户提问:
帮我把 tech-guide.md 转成 PDFClaude 执行:
python .claude/skills/markdown-to-pdf/scripts/convert.py tech-guide.md输出:
📖 读取文件: tech-guide.md
📑 提取元数据...
📂 提取目录结构...
✓ 找到 5 个主章节
✓ 找到 12 个子章节
🎨 处理 Markdown 内容...
📄 生成 HTML...
📝 生成 PDF...
✅ 成功生成: tech-guide.pdf
📊 文件大小: 0.8 MB---
示例 2: 自定义输出
用户提问:
把 README.md 转成 PDF,输出文件名叫"产品说明书.pdf"Claude 执行:
python .claude/skills/markdown-to-pdf/scripts/convert.py README.md -o "产品说明书.pdf"---
示例 3: 完整自定义
用户提问:
转换 whitepaper.md,标题改成"技术白皮书2025",作者写"技术团队"Claude 执行:
python .claude/skills/markdown-to-pdf/scripts/convert.py whitepaper.md \
--title "技术白皮书2025" \
--author "技术团队" \
-o "技术白皮书2025.pdf"---
示例 4: 批量转换
用户提问:
把当前目录下所有 .md 文件都转成 PDFClaude 执行:
for file in *.md; do
python .claude/skills/markdown-to-pdf/scripts/convert.py "$file"
done---
示例 5: 转换后预览
用户提问:
转换 guide.md 并告诉我文件在哪Claude 执行:
python .claude/skills/markdown-to-pdf/scripts/convert.py guide.mdClaude 回复:
✅ 已成功生成 PDF: guide.pdf
📍 文件位置: /Users/xxx/Documents/guide.pdf
📊 文件大小: 1.2 MB
你可以直接打开查看,或者用以下命令预览:
open guide.pdf---
Markdown 文档模板
如果你的文档还没有结构,可以参考这个模板:
# 技术文档标题
## 1. 简介
### 1.1 背景
这里写背景介绍...
### 1.2 目标
这里写文档目标...
## 2. 核心概念
### 2.1 概念A
解释概念...
### 2.2 概念B
解释概念...
## 3. 使用方法
### 3.1 安装
安装步骤...
\`\`\`bash
npm install xxx
\`\`\`
### 3.2 配置
配置说明...
| 参数 | 说明 | 默认值 |
|------|------|--------|
| foo | 配置项 | true |
## 4. 最佳实践
### 4.1 实践A
实践说明...
### 4.2 实践B
实践说明...
## 5. 常见问题
### 5.1 问题1
解答...
### 5.2 问题2
解答...---
高级技巧
技巧 1: 在脚本中调用
from pathlib import Path
import subprocess
# 批量转换
md_files = Path('.').glob('*.md')
for md_file in md_files:
subprocess.run([
'python',
'.claude/skills/markdown-to-pdf/scripts/convert.py',
str(md_file)
])技巧 2: 添加到快捷命令
在 .bash_profile 或 .zshrc 中添加:
alias md2pdf='python ~/.claude/skills/markdown-to-pdf/scripts/convert.py'然后就可以直接使用:
md2pdf document.md
md2pdf report.md -o "2025年度报告.pdf"技巧 3: 与 Git Hooks 结合
在 .git/hooks/pre-commit 中:
#!/bin/bash
# 自动生成 PDF 版本
if [ -f README.md ]; then
python .claude/skills/markdown-to-pdf/scripts/convert.py README.md
git add README.pdf
fi---
真实使用案例
案例 1: Claude Skills 白皮书
- 输入:2万字 Markdown 文档
- 输出:1.4 MB 专业 PDF
- 特点:10个主章节,38个子章节,双列目录
案例 2: 技术文档
- 输入:API 文档 Markdown
- 输出:带目录的技术手册
- 特点:代码高亮、表格清晰
案例 3: 产品说明书
- 输入:产品介绍 Markdown
- 输出:专业说明书 PDF
- 特点:苹果设计风格、易读性强
Markdown to PDF Skill
将 Markdown 文档转换为专业的苹果设计风格 PDF 白皮书。
快速开始
1. 安装依赖(仅首次)
pip3 install markdown2 weasyprint2. 基础使用
# 转换 Markdown 文件
python .claude/skills/markdown-to-pdf/scripts/convert.py your-file.md
# 指定输出文件名
python .claude/skills/markdown-to-pdf/scripts/convert.py your-file.md -o "我的白皮书.pdf"
# 自定义标题和作者
python .claude/skills/markdown-to-pdf/scripts/convert.py your-file.md --title "技术白皮书" --author "花叔"Markdown 格式要求
你的文档应该使用带序号的章节格式:
# 文档标题
## 1. 第一章
### 1.1 第一节
内容...
### 1.2 第二节
内容...
## 2. 第二章
### 2.1 第一节
...关键点:
- ✅
## 1. 标题- 正确(数字.空格标题) - ❌
## 标题- 错误(无序号) - ✅
### 1.1 标题- 正确 - ❌
### 标题- 错误
设计特点
- 📖 书籍级排版:自动分页、孤行寡行控制
- 🎨 苹果设计语言:SF 字体、现代简洁
- 📑 自动目录:双列布局、可点击跳转
- 💻 完美代码块:语法高亮、圆角边框
- 📊 专业表格:清晰网格、自动表头
文件结构
.claude/skills/markdown-to-pdf/
├── SKILL.md # Skill 说明文档
├── README.md # 本文件
└── scripts/
└── convert.py # 转换脚本示例
在 Claude Code 中使用:
用户:帮我把这个 Markdown 文档转成 PDF
Claude:好的,我使用 markdown-to-pdf skill 来转换
[执行] python .claude/skills/markdown-to-pdf/scripts/convert.py document.md
✅ 已生成 document.pdf常见问题
Q: WeasyPrint 安装失败?
# macOS
brew install pango
pip3 install weasyprint
# Linux
sudo apt-get install libpango-1.0-0 libpangocairo-1.0-0
pip3 install weasyprintQ: 目录为空? 确保使用 ## 1. 和 ### 1.1 格式。
Q: 代码块显示不正确? 使用三个反引号包裹代码。
更新日志
- v1.0 (2025-12-24): 初始版本
#!/usr/bin/env python3
"""
Markdown to PDF 转换器 - 苹果设计风格
使用方法:
python convert.py input.md
python convert.py input.md -o output.pdf
python convert.py input.md --title "标题" --author "作者"
"""
import argparse
import markdown2
from weasyprint import HTML, CSS
import re
import os
from pathlib import Path
def extract_metadata(md_content):
"""提取文档元数据"""
metadata = {
'title': None,
'subtitle': None,
'author': None,
'date': None,
'created_for': None, # 为谁创建
'based_on': None, # 基于
}
# 尝试提取第一个 h1 作为标题
h1_match = re.search(r'^# (.+)$', md_content, re.MULTILINE)
if h1_match:
metadata['title'] = h1_match.group(1).strip()
# 提取 **字段**: 值 格式的元数据
# 创建者
creator_match = re.search(r'\*\*创建者\*\*:\s*(.+?)$', md_content, re.MULTILINE)
if creator_match:
metadata['author'] = creator_match.group(1).strip()
# 为谁创建
for_match = re.search(r'\*\*为谁创建\*\*:\s*(.+?)$', md_content, re.MULTILINE)
if for_match:
# 提取链接文本和URL
link_match = re.search(r'\[(.+?)\]\((.+?)\)', for_match.group(1))
if link_match:
metadata['created_for'] = link_match.group(1)
metadata['created_for_url'] = link_match.group(2)
else:
metadata['created_for'] = for_match.group(1).strip()
# 基于
based_match = re.search(r'\*\*基于\*\*:\s*(.+?)$', md_content, re.MULTILINE)
if based_match:
metadata['based_on'] = based_match.group(1).strip()
# 最后更新
date_match = re.search(r'\*\*最后更新\*\*:\s*(.+?)$', md_content, re.MULTILINE)
if date_match:
metadata['date'] = date_match.group(1).strip()
return metadata
def extract_toc_structure(md_content):
"""提取带序号的章节目录"""
lines = md_content.split('\n')
toc = []
for line in lines:
# 主章节:## 1. 标题
match_h2 = re.match(r'^## (\d+)\.\s+(.+)$', line)
if match_h2:
num = match_h2.group(1)
title = match_h2.group(2).strip()
# 移除 emoji
title = re.sub(r'[\U0001F300-\U0001F9FF]', '', title).strip()
toc.append({
'level': 2,
'number': num,
'title': title,
'id': f"{num}-{title}".replace(' ', '-').replace(':', '').lower()
})
# 子章节:### 1.1 标题
match_h3 = re.match(r'^### (\d+\.\d+)\s+(.+)$', line)
if match_h3:
num = match_h3.group(1)
title = match_h3.group(2).strip()
title = re.sub(r'[\U0001F300-\U0001F9FF]', '', title).strip()
# 截断过长标题
if len(title) > 50:
title = title[:47] + '...'
toc.append({
'level': 3,
'number': num,
'title': title,
'id': f"{num}-{title}".replace(' ', '-').replace(':', '').replace('.', '-').lower()
})
return toc
def generate_toc_html(toc_items):
"""生成目录 HTML"""
if not toc_items:
return ""
toc_html = ""
for item in toc_items:
if item['level'] == 2:
toc_html += f'''
<div class="toc-item toc-h2">
<a href="#{item['id']}" class="toc-link">
<span class="toc-number">{item['number']}</span>
<span class="toc-title">{item['title']}</span>
</a>
</div>
'''
else:
toc_html += f'''
<div class="toc-item toc-h3">
<a href="#{item['id']}" class="toc-link">
<span class="toc-number">{item['number']}</span>
<span class="toc-title">{item['title']}</span>
</a>
</div>
'''
return toc_html
def create_cover_and_toc(metadata, toc_html):
"""创建封面和目录页"""
title = metadata.get('title', '文档标题')
subtitle = metadata.get('subtitle', '')
author = metadata.get('author', '')
date = metadata.get('date', '')
created_for = metadata.get('created_for', '')
created_for_url = metadata.get('created_for_url', '')
based_on = metadata.get('based_on', '')
toc_section = ""
if toc_html:
toc_section = f"""
<!-- 目录 -->
<div class="toc-page">
<h2 class="toc-header">目录</h2>
<div class="toc-content">
{toc_html}
</div>
</div>
"""
# 构建元信息区域
meta_items = []
if subtitle:
meta_items.append(f'<p class="cover-subtitle">{subtitle}</p>')
if based_on:
meta_items.append(f'<p class="cover-based">{based_on}</p>')
if created_for:
if created_for_url:
meta_items.append(f'<p class="cover-for">为 <a href="{created_for_url}">{created_for}</a> 用户创建</p>')
else:
meta_items.append(f'<p class="cover-for">为 {created_for} 用户创建</p>')
if author:
meta_items.append(f'<p class="cover-author">{author}</p>')
if date:
meta_items.append(f'<p class="cover-date">{date}</p>')
meta_html = '\n'.join(meta_items)
return f"""
<!-- 封面 -->
<div class="apple-cover">
<div class="cover-main">
<h1 class="cover-title">{title}</h1>
<div class="cover-meta">
{meta_html}
</div>
</div>
</div>
{toc_section}
"""
def process_markdown(md_content):
"""处理 Markdown 内容"""
# 移除第一个 h1(已用于封面)
md_content = re.sub(r'^# .+?\n', '', md_content, count=1, flags=re.MULTILINE)
# 移除开头的元数据行(**字段**: 值 格式)
# 这些信息已经提取到封面,不需要在正文中重复显示
metadata_patterns = [
r'^\*\*创建者\*\*:.+?$',
r'^\*\*为谁创建\*\*:.+?$',
r'^\*\*基于\*\*:.+?$',
r'^\*\*最后更新\*\*:.+?$',
r'^\*\*适用场景\*\*:.+?$',
]
for pattern in metadata_patterns:
md_content = re.sub(pattern, '', md_content, flags=re.MULTILINE)
# 移除 emoji
md_content = re.sub(r'[\U0001F300-\U0001F9FF]', '', md_content)
# 处理 h2 主章节 - 添加 ID 和分页
def add_h2_id(match):
num = match.group(1)
title = match.group(2).strip()
id_str = f"{num}-{title}".replace(' ', '-').replace(':', '').lower()
full_title = f"{num}. {title}"
return f'\n<div class="chapter-break"></div>\n\n<h2 id="{id_str}">{full_title}</h2>\n'
md_content = re.sub(r'\n## (\d+)\.\s+(.+?)\n', add_h2_id, md_content)
# 处理 h3 子章节 - 添加 ID
def add_h3_id(match):
num = match.group(1)
title = match.group(2).strip()
id_str = f"{num}-{title}".replace(' ', '-').replace(':', '').replace('.', '-').lower()
full_title = f"{num} {title}"
return f'\n<h3 id="{id_str}">{full_title}</h3>\n'
md_content = re.sub(r'\n### (\d+\.\d+)\s+(.+?)\n', add_h3_id, md_content)
# 转换 Markdown
extras = [
'fenced-code-blocks',
'tables',
'break-on-newline',
'code-friendly',
'cuddled-lists',
'strike',
'task_list',
]
html = markdown2.markdown(md_content, extras=extras)
# 修复渲染
html = re.sub(r'<table>', r'<table class="content-table">', html)
html = re.sub(r'<pre><code', r'<pre class="code-block"><code', html)
html = re.sub(r'<blockquote>', r'<blockquote class="quote-block">', html)
return html
def get_apple_css():
"""获取苹果设计风格 CSS"""
return """
@page {
size: A4;
margin: 2.5cm 2cm 2cm 2cm;
@top-left {
content: string(doc-title);
font-size: 8.5pt;
color: #86868b;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
@top-right {
content: counter(page);
font-size: 8.5pt;
color: #86868b;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
}
@page:first {
margin: 0;
@top-left { content: none; }
@top-right { content: none; }
}
@page:nth(2) {
@top-left { content: none; }
@top-right { content: none; }
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'PingFang SC', sans-serif;
font-size: 11pt;
line-height: 1.7;
color: #1d1d1f;
background: white;
-webkit-font-smoothing: antialiased;
}
/* 封面 */
.apple-cover {
height: 100vh;
background: linear-gradient(135deg, #f5f5f7 0%, #ffffff 100%);
display: flex;
align-items: center;
justify-content: center;
page-break-after: always;
}
.cover-main {
text-align: center;
padding: 60px;
}
.cover-title {
font-size: 64pt;
font-weight: 600;
color: #1d1d1f;
margin-bottom: 24px;
letter-spacing: -2px;
font-family: -apple-system, 'SF Pro Display', sans-serif;
string-set: doc-title content();
}
.cover-subtitle {
font-size: 24pt;
font-weight: 400;
color: #1d1d1f;
margin-bottom: 24px;
}
.cover-meta {
font-size: 12pt;
color: #86868b;
line-height: 2;
margin-top: 36px;
}
.cover-based {
font-size: 11pt;
color: #86868b;
margin-bottom: 8px;
}
.cover-for {
font-size: 13pt;
color: #1d1d1f;
font-weight: 500;
margin-bottom: 8px;
}
.cover-for a {
color: #06c;
text-decoration: none;
}
.cover-author {
font-size: 11pt;
color: #86868b;
margin-bottom: 8px;
}
.cover-date {
font-size: 11pt;
color: #86868b;
font-weight: 500;
}
/* 目录 */
.toc-page {
padding: 60px 50px;
page-break-after: always;
min-height: 100vh;
}
.toc-header {
font-size: 28pt;
font-weight: 600;
color: #1d1d1f;
margin-bottom: 32px;
}
.toc-content {
column-count: 2;
column-gap: 40px;
}
.toc-item {
break-inside: avoid;
margin-bottom: 6px;
}
.toc-h2 {
margin-top: 14px;
margin-bottom: 4px;
}
.toc-h2 .toc-link {
font-size: 11.5pt;
font-weight: 600;
color: #1d1d1f;
}
.toc-h2 .toc-number {
color: #06c;
font-weight: 700;
margin-right: 8px;
}
.toc-h3 {
margin-left: 16px;
}
.toc-h3 .toc-link {
font-size: 10pt;
font-weight: 400;
color: #424245;
}
.toc-h3 .toc-number {
color: #86868b;
margin-right: 6px;
font-size: 9.5pt;
}
.toc-link {
display: block;
text-decoration: none;
padding: 4px 0;
}
.toc-number {
font-feature-settings: "tnum";
}
/* 标题 */
.chapter-break {
page-break-before: always;
height: 0;
}
h2 {
font-size: 22pt;
font-weight: 600;
color: #1d1d1f;
margin-top: 0;
margin-bottom: 28px;
padding-bottom: 12px;
border-bottom: 2px solid #d2d2d7;
page-break-after: avoid;
}
h3 {
font-size: 17pt;
font-weight: 600;
color: #1d1d1f;
margin-top: 36px;
margin-bottom: 18px;
page-break-after: avoid;
}
h4 {
font-size: 13pt;
font-weight: 600;
color: #424245;
margin-top: 24px;
margin-bottom: 12px;
page-break-after: avoid;
}
/* 正文 */
p {
margin-bottom: 16px;
}
ul, ol {
margin-left: 24px;
margin-bottom: 20px;
}
li {
margin-bottom: 10px;
}
/* 代码块 */
.code-block {
background: #f5f5f7;
border: 1px solid #d2d2d7;
border-radius: 8px;
padding: 20px;
margin: 24px 0;
overflow-x: auto;
font-family: 'SF Mono', 'Monaco', monospace;
font-size: 10pt;
line-height: 1.6;
page-break-inside: avoid;
}
.code-block code {
background: none;
padding: 0;
color: #1d1d1f;
}
code {
background: #f5f5f7;
padding: 3px 6px;
border-radius: 4px;
font-family: 'SF Mono', monospace;
font-size: 10pt;
color: #d70050;
font-weight: 500;
}
/* 表格 */
.content-table {
width: 100%;
border-collapse: collapse;
margin: 28px 0;
font-size: 10.5pt;
}
.content-table thead {
background: #f5f5f7;
}
.content-table th {
padding: 14px 16px;
text-align: left;
font-weight: 600;
border-bottom: 2px solid #d2d2d7;
}
.content-table td {
padding: 12px 16px;
border-bottom: 1px solid #d2d2d7;
color: #424245;
page-break-inside: avoid;
}
/* 引用 */
.quote-block {
border-left: 3px solid #06c;
padding-left: 20px;
margin: 24px 0;
color: #424245;
page-break-inside: avoid;
}
/* 强调 */
strong {
color: #1d1d1f;
font-weight: 600;
}
a {
color: #06c;
text-decoration: none;
}
hr {
border: none;
border-top: 1px solid #d2d2d7;
margin: 36px 0;
}
/* 印刷质量 */
p, li, .quote-block {
orphans: 3;
widows: 3;
}
h2, h3, h4 {
page-break-after: avoid;
}
.code-block, .content-table, .quote-block {
page-break-inside: avoid;
}
"""
def convert_markdown_to_pdf(input_file, output_file=None, title=None, author=None, subtitle=None):
"""主转换函数"""
# 读取输入文件
print(f"📖 读取文件: {input_file}")
with open(input_file, 'r', encoding='utf-8') as f:
md_content = f.read()
# 提取元数据
print("📑 提取元数据...")
metadata = extract_metadata(md_content)
# 命令行参数覆盖
if title:
metadata['title'] = title
if author:
metadata['author'] = author
if subtitle:
metadata['subtitle'] = subtitle
# 提取目录
print("📂 提取目录结构...")
toc_structure = extract_toc_structure(md_content)
print(f" ✓ 找到 {len([t for t in toc_structure if t['level'] == 2])} 个主章节")
print(f" ✓ 找到 {len([t for t in toc_structure if t['level'] == 3])} 个子章节")
# 生成目录 HTML
toc_html = generate_toc_html(toc_structure)
# 处理 Markdown
print("🎨 处理 Markdown 内容...")
html_content = process_markdown(md_content)
# 生成完整 HTML
print("📄 生成 HTML...")
full_html = f"""
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>{metadata.get('title', '文档')}</title>
</head>
<body>
{create_cover_and_toc(metadata, toc_html)}
<div class="content">
{html_content}
</div>
</body>
</html>
"""
# 生成 PDF
print("📝 生成 PDF...")
if not output_file:
output_file = str(Path(input_file).with_suffix('.pdf'))
css = CSS(string=get_apple_css())
HTML(string=full_html).write_pdf(output_file, stylesheets=[css])
print(f"✅ 成功生成: {output_file}")
# 显示文件大小
size_mb = os.path.getsize(output_file) / (1024 * 1024)
print(f"📊 文件大小: {size_mb:.1f} MB")
def main():
parser = argparse.ArgumentParser(
description='将 Markdown 转换为苹果设计风格的 PDF 白皮书'
)
parser.add_argument('input', help='输入的 Markdown 文件')
parser.add_argument('-o', '--output', help='输出的 PDF 文件(默认:与输入文件同名)')
parser.add_argument('--title', help='自定义文档标题')
parser.add_argument('--subtitle', help='自定义副标题')
parser.add_argument('--author', help='自定义作者')
args = parser.parse_args()
try:
convert_markdown_to_pdf(
args.input,
args.output,
args.title,
args.author,
args.subtitle
)
except Exception as e:
print(f"❌ 转换失败: {e}")
import traceback
traceback.print_exc()
return 1
return 0
if __name__ == '__main__':
exit(main())
Related skills
FAQ
How does huashu-md-to-pdf convert files?
huashu-md-to-pdf runs convert.py to read Markdown, extract metadata, build a table of contents from headings, render HTML, and write a styled PDF. Example output logged 5 main chapters and 12 sub-chapters from one guide.
Can huashu-md-to-pdf customize output names and titles?
huashu-md-to-pdf accepts CLI flags for custom output filenames, document titles, and author strings—e.g., converting README.md to 产品说明书.pdf with a renamed title and team author.
Is Huashu Md To Pdf safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.