真正能稳定生成带格式合同PDF的方案是「Jinja2渲染HTML+WeasyPrint转PDF」或「DocxTemplate+python-docx+docxtopdf」,前者排版控制强,后者更贴合Word原生习惯。

直接用 pdfplumber 解析模板 PDF 再填内容?别试了,它不支持写入。真正能稳定生成带格式合同 PDF 的方案,是「Jinja2 渲染 HTML + WeasyPrint 转 PDF」或「DocxTemplate + python-docx + docxtopdf」——前者对排版控制强,后者更贴近 Word 原生合同习惯。
为什么不用 ReportLab 直接画 PDF?
ReportLab 确实能生成 PDF,但合同里常有复杂表格、页眉页脚、自动分页、条款编号、条件性段落(比如“如乙方违约,则……”),纯代码定位坐标写文本极易失控。你得手动算行高、留白、换页点,改个字体大小可能整页错位。
常见错误现象:Canvas.drawString() 文字重叠、表格列宽崩塌、中文换行失效(没配中文字体)、页码无法动态插入。
除非合同极简(单页无格式),否则投入产出比极低。
Jinja2 + WeasyPrint:适合需要精确样式和响应式布局的合同
把合同当网页写:用 HTML 写结构,CSS 控制字体、间距、分页、打印媒体查询;用 Jinja2 注入变量和逻辑({% if %}、{% for %});最后用 WeasyPrint 一次性转成 PDF。
使用场景:甲方提供带品牌色、固定页眉/页脚、多级标题、条款自动编号(counter-reset)、附件列表需另起一页等。
实操建议:
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
- HTML 模板中用
@page { @top-center { content: "XX公司采购合同"; } }控制页眉,WeasyPrint 支持大部分 CSS Paged Media 规范 - 中文字体必须显式加载:
weasyprint.HTML(...).write_pdf(..., stylesheets=[weasyprint.CSS(string='@font-face { src: url("simhei.ttf"); font-family: "SimHei"; } body { font-family: "SimHei"; }')]) - 避免用
float布局,改用display: grid或flex;WeasyPrint 对 float 支持不稳定,易导致内容截断 - 测试时先用
html.write_html("debug.html")输出中间 HTML,浏览器打开确认渲染正确再转 PDF
DocxTemplate + python-docx:适合已有 Word 合同模板、法务不愿改格式的团队
直接复用 .docx 文件作为模板,用 DocxTemplate 替换占位符(如 {{ party_a }}),插入表格、图片、分节符;再用 docxtopdf(Windows)或 libreoffice --headless(Linux/macOS)转 PDF。
参数差异:DocxTemplate 支持 {% for %} 循环生成条款列表,但不支持条件性隐藏整节——需在 Python 层预处理数据,传空列表让循环不执行。
容易踩的坑:
立即学习“Python免费学习笔记(深入)”;
-
docxtopdf仅 Windows 可用,Linux/macOS 必须调用 LibreOffice,命令为:soffice --headless --convert-to pdf --outdir ./out ./template.docx,注意路径权限和后台进程残留 - Word 模板里所有占位符必须用「纯文本」,不能是文本框、内容控件或域代码,否则
DocxTemplate找不到 - 中文页眉页脚、目录、题注编号在转换后常丢失——LibreOffice 渲染引擎与 MS Word 不一致,建议 PDF 输出后人工抽检关键页
最麻烦的从来不是生成 PDF,而是字段对齐:合同里的「签约日期」在 Word 模板里是右对齐,在 HTML 里你得写 text-align: right,而 python-docx 需调 paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT。同一份合同,两种路径下对齐、缩进、行距的实现方式完全不同,别指望一套模板通吃。


















