完整方案代码
import os
import markdown
import pdfkit
from jinja2 import Template
import webbrowser
import tempfile
from datetime import datetime
class MarkdownToA4Printer:
def __init__(self):
# HTML模板,针对A4纸优化
self.html_template = Template('''
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }}</title>
<style>
@page {
size: A4;
margin: 2cm 1.5cm;
@top-left {
content: "{{ title }}";
font-size: 10pt;
color: #666;
}
@bottom-right {
content: "第 " counter(page) " 页";
font-size: 10pt;
color: #666;
}
}
body {
font-family: 'SimSun', '宋体', 'STSong', serif;
line-height: 1.6;
color: #333;
font-size: 12pt;
margin: 0;
padding: 0;
}
.a4-container {
width: 210mm;
min-height: 297mm;
margin: 0 auto;
padding: 25mm 20mm;
box-sizing: border-box;
background: white;
}
/* 标题样式 */
h1, h2, h3, h4, h5, h6 {
font-family: 'SimHei', '黑体', 'STHeiti', sans-serif;
color: #1a365d;
margin-top: 1.5em;
margin-bottom: 0.5em;
page-break-after: avoid;
}
h1 {
font-size: 20pt;
border-bottom: 2px solid #1a365d;
padding-bottom: 0.3em;
}
h2 {
font-size: 16pt;
border-bottom: 1px solid #e2e8f0;
padding-bottom: 0.2em;
}
/* 段落和列表 */
p {
margin: 0.8em 0;
text-align: justify;
}
ul, ol {
margin: 1em 0;
padding-left: 2em;
}
li {
margin: 0.3em 0;
}
/* 代码块 */
pre {
background-color: #f7fafc;
border: 1px solid #e2e8f0;
border-radius: 4px;
padding: 1em;
overflow-x: auto;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 10pt;
page-break-inside: avoid;
}
code {
background-color: #f7fafc;
padding: 0.2em 0.4em;
border-radius: 3px;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 10pt;
}
/* 表格 */
table {
width: 100%;
border-collapse: collapse;
margin: 1em 0;
page-break-inside: avoid;
}
th, td {
border: 1px solid #e2e8f0;
padding: 0.5em;
text-align: left;
}
th {
background-color: #f7fafc;
font-weight: bold;
}
/* 引用 */
blockquote {
border-left: 4px solid #4299e1;
margin: 1em 0;
padding: 0.5em 1em;
background-color: #f7fafc;
font-style: italic;
}
/* 分隔线 */
hr {
border: none;
border-top: 1px solid #e2e8f0;
margin: 2em 0;
}
/* 页眉页脚 */
.header {
text-align: center;
border-bottom: 2px solid #1a365d;
padding-bottom: 1em;
margin-bottom: 2em;
}
.document-title {
font-size: 24pt;
font-weight: bold;
color: #1a365d;
margin-bottom: 0.5em;
}
.document-subtitle {
font-size: 14pt;
color: #666;
margin-bottom: 1em;
}
.document-meta {
font-size: 10pt;
color: #888;
display: flex;
justify-content: space-between;
margin-top: 2em;
}
.footer {
margin-top: 3em;
padding-top: 1em;
border-top: 1px solid #e2e8f0;
font-size: 10pt;
color: #666;
text-align: center;
}
/* 防止元素跨页分割 */
.no-break {
page-break-inside: avoid;
}
/* 强制分页 */
.page-break {
page-break-before: always;
}
/* 封面页 */
.cover-page {
text-align: center;
padding-top: 100px;
}
.cover-title {
font-size: 32pt;
font-weight: bold;
margin: 2em 0 1em 0;
color: #1a365d;
}
.cover-subtitle {
font-size: 18pt;
color: #666;
margin-bottom: 3em;
}
.cover-author {
font-size: 14pt;
margin-bottom: 1em;
}
.cover-date {
font-size: 12pt;
color: #888;
}
</style>
</head>
<body>
<div class="a4-container">
{% if add_cover %}
<div class="cover-page page-break">
<div class="cover-title">{{ title }}</div>
{% if subtitle %}
<div class="cover-subtitle">{{ subtitle }}</div>
{% endif %}
{% if author %}
<div class="cover-author">{{ author }}</div>
{% endif %}
<div class="cover-date">{{ date }}</div>
</div>
{% endif %}
<div class="header">
<div class="document-title">{{ title }}</div>
{% if subtitle %}
<div class="document-subtitle">{{ subtitle }}</div>
{% endif %}
</div>
<div class="content">
{{ content }}
</div>
<div class="footer">
生成时间: {{ generated_time }} | 页数: <span class="page-count">第 <span class="page-number"></span> 页</span>
</div>
</div>
<script>
// 更新页码
document.addEventListener('DOMContentLoaded', function() {
var pageNumbers = document.querySelectorAll('.page-number');
pageNumbers.forEach(function(el, index) {
el.textContent = (index + 1);
});
});
</script>
</body>
</html>
''')
def convert_markdown_to_html(self, markdown_text, title="文档", subtitle=None,
author=None, add_cover=True):
"""
将Markdown转换为HTML
"""
# 转换Markdown
html_content = markdown.markdown(
markdown_text,
extensions=[
'extra', # 支持表格、代码块等
'codehilite', # 代码高亮
'toc', # 目录
'tables', # 表格
'fenced_code', # 围栏代码块
],
extension_configs={
'codehilite': {
'css_class': 'highlight'
}
}
)
# 获取当前时间
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
date_str = datetime.now().strftime("%Y年%m月%d日")
# 渲染HTML模板
html = self.html_template.render(
title=title,
subtitle=subtitle,
author=author,
content=html_content,
date=date_str,
generated_time=now,
add_cover=add_cover
)
return html
def save_as_pdf(self, html_content, output_path):
"""
将HTML保存为PDF
需要安装 wkhtmltopdf: https://wkhtmltopdf.org/
"""
try:
# 配置选项
options = {
'page-size': 'A4',
'margin-top': '25mm',
'margin-right': '20mm',
'margin-bottom': '25mm',
'margin-left': '20mm',
'encoding': "UTF-8",
'no-outline': None,
'enable-local-file-access': None,
'header-left': '[title]',
'header-font-size': '10',
'footer-right': '第 [page] 页',
'footer-font-size': '10',
'print-media-type': None,
}
# 转换为PDF
pdfkit.from_string(html_content, output_path, options=options)
print(f"PDF已保存: {output_path}")
return True
except Exception as e:
print(f"PDF转换失败: {e}")
print("请确保已安装 wkhtmltopdf")
print("安装方法:")
print(" Ubuntu/Debian: sudo apt-get install wkhtmltopdf")
print(" macOS: brew install wkhtmltopdf")
print(" Windows: 从 https://wkhtmltopdf.org/downloads.html 下载安装")
return False
def save_as_html(self, html_content, output_path):
"""
保存为HTML文件,可直接在浏览器中打印
"""
with open(output_path, 'w', encoding='utf-8') as f:
f.write(html_content)
print(f"HTML已保存: {output_path}")
return output_path
def preview_in_browser(self, html_content):
"""
在浏览器中预览
"""
# 创建临时HTML文件
with tempfile.NamedTemporaryFile(mode='w', suffix='.html',
encoding='utf-8', delete=False) as f:
f.write(html_content)
temp_file = f.name
# 在浏览器中打开
webbrowser.open(f'file://{temp_file}')
print("已在浏览器中打开,按 Ctrl+P 打印")
return temp_file
def convert_file(self, markdown_file, output_format='html',
output_path=None, **kwargs):
"""
转换Markdown文件
"""
# 读取Markdown文件
with open(markdown_file, 'r', encoding='utf-8') as f:
markdown_text = f.read()
# 从文件名获取标题
title = os.path.splitext(os.path.basename(markdown_file))[0]
# 转换为HTML
html_content = self.convert_markdown_to_html(
markdown_text,
title=title,
**kwargs
)
# 确定输出路径
if output_path is None:
base_name = os.path.splitext(markdown_file)[0]
if output_format == 'pdf':
output_path = f"{base_name}.pdf"
else:
output_path = f"{base_name}_print.html"
# 保存为指定格式
if output_format.lower() == 'pdf':
return self.save_as_pdf(html_content, output_path)
else:
saved_path = self.save_as_html(html_content, output_path)
self.preview_in_browser(html_content)
return saved_path
# 使用示例
def main():
# 创建转换器实例
converter = MarkdownToA4Printer()
# 示例1: 转换现有Markdown文件
# converter.convert_file(
# 'example.md',
# output_format='html', # 或 'pdf'
# subtitle='项目文档',
# author='作者名称',
# add_cover=True
# )
# 示例2: 直接转换Markdown文本
markdown_text = """
# 项目报告
## 概述
这是一个示例文档,演示如何将Markdown转换为A4专业文档。
### 主要内容
- **功能1**: 支持表格、代码高亮
- **功能2**: 自动分页优化
- **功能3**: 专业排版设计
## 代码示例
```python
def hello_world():
print("Hello, World!")
return "Success"
表格示例
| 项目 |
描述 |
状态 |
|---|
| 任务1 |
完成基础功能 |
✅ |
| 任务2 |
添加高级特性 |
🚧 |
| 任务3 |
测试和优化 |
⏳ |
结论
这是一个非常实用的Markdown转打印文档工具。
"""
# 转换为HTML并预览
html = converter.convert_markdown_to_html(
markdown_text,
title="专业文档",
subtitle="技术报告",
author="技术部",
add_cover=True
)
# 保存为HTML并自动打开浏览器
converter.save_as_html(html, "专业文档_打印版.html")
converter.preview_in_browser(html)
# 如果要保存为PDF(需要wkhtmltopdf)
# converter.save_as_pdf(html, "专业文档.pdf")
if name == "main":
main()
## 使用说明
### 1. 安装依赖
```bash
pip install markdown jinja2 pdfkit
2. 安装wkhtmltopdf(用于PDF输出)
# Ubuntu/Debian
sudo apt-get install wkhtmltopdf
# macOS
brew install wkhtmltopdf
# Windows: 从 https://wkhtmltopdf.org/downloads.html 下载安装
3. 命令行工具版本
#!/usr/bin/env python3
"""
markdown2print.py - Markdown转A4打印工具
"""
import argparse
import sys
from pathlib import Path
def create_cli():
parser = argparse.ArgumentParser(
description='将Markdown文件转换为适合A4打印的专业文档'
)
parser.add_argument('input', help='输入的Markdown文件路径')
parser.add_argument('-o', '--output', help='输出文件路径(默认:同输入文件名)')
parser.add_argument('-f', '--format', choices=['html', 'pdf'],
default='html', help='输出格式(默认:html)')
parser.add_argument('-t', '--title', help='文档标题')
parser.add_argument('-s', '--subtitle', help='文档副标题')
parser.add_argument('-a', '--author', help='作者')
parser.add_argument('--no-cover', action='store_true',
help='不添加封面页')
return parser
def main_cli():
parser = create_cli()
args = parser.parse_args()
# 检查输入文件
input_file = Path(args.input)
if not input_file.exists():
print(f"错误: 文件不存在 {args.input}")
sys.exit(1)
# 创建转换器
converter = MarkdownToA4Printer()
# 转换文件
try:
result = converter.convert_file(
markdown_file=str(input_file),
output_format=args.format,
output_path=args.output,
title=args.title,
subtitle=args.subtitle,
author=args.author,
add_cover=not args.no_cover
)
if result:
print("转换成功!")
else:
print("转换失败")
sys.exit(1)
except Exception as e:
print(f"转换过程中出错: {e}")
sys.exit(1)
if __name__ == "__main__":
main_cli()
4. 高级功能扩展
class AdvancedMarkdownPrinter(MarkdownToA4Printer):
"""增强版打印机,支持更多功能"""
def add_watermark(self, html_content, text="机密"):
"""添加水印"""
watermark_style = f"""
<style>
.watermark {{
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotate(-45deg);
font-size: 80px;
color: rgba(0,0,0,0.1);
pointer-events: none;
z-index: 1000;
}}
</style>
<div class="watermark">{text}</div>
"""
# 在body结束前插入水印
html_content = html_content.replace('</body>', f'{watermark_style}</body>')
return html_content
def add_table_of_contents(self, html_content):
"""自动生成目录"""
# 这里可以扩展为自动提取标题生成目录
toc = '''
<div class="toc">
<h2>目录</h2>
<!-- 目录内容将通过JavaScript动态生成 -->
</div>
<script>
// 生成目录的JavaScript代码
document.addEventListener('DOMContentLoaded', function() {
const toc = document.querySelector('.toc');
const headings = document.querySelectorAll('h2, h3');
let tocHTML = '<ul>';
headings.forEach(heading => {
const level = heading.tagName === 'H2' ? 1 : 2;
const indent = level === 2 ? '20px' : '0';
tocHTML += `<li style="margin-left: ${indent}">
<a href="#${heading.id || heading.textContent.replace(/\\s+/g, '-')}">
${heading.textContent}
</a>
</li>`;
});
tocHTML += '</ul>';
if (toc && headings.length > 0) {
toc.innerHTML += tocHTML;
}
});
</script>
'''
# 在内容开始前插入目录
html_content = html_content.replace(
'<div class="content">',
f'<div class="content">{toc}'
)
return html_content
# 使用增强版
printer = AdvancedMarkdownPrinter()
html = printer.convert_markdown_to_html(markdown_text)
html = printer.add_watermark(html, "内部使用")
html = printer.add_table_of_contents(html)
printer.preview_in_browser(html)
主要特性
专业A4排版:
- 精确的A4尺寸(210mm × 297mm)
- 合适的页边距
- 页眉页脚自动编号
样式优化:
- 中文字体优化(宋体/黑体)
- 代码块语法高亮
- 表格美化
- 分页控制
多种输出格式:
- HTML(直接在浏览器打印)
- PDF(需要wkhtmltopdf)
附加功能:
使用方便:
使用方式
# 转换Markdown文件
python markdown2print.py document.md
# 转换为PDF
python markdown2print.py document.md -f pdf
# 自定义标题和作者
python markdown2print.py document.md -t "项目报告" -a "张三" -s "技术文档"
这个工具可以帮你将Markdown文档转换为适合打印的专业A4格式文档,支持各种常见的Markdown语法,并提供了美观的打印样式。