梅州市万年长

Python实现将Markdown一键打印为A4专业文档

2026-04-03 11:59:02 浏览次数:1
详细信息

完整方案代码

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排版

样式优化

多种输出格式

附加功能

使用方便

使用方式

# 转换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语法,并提供了美观的打印样式。

相关推荐