ReportLab 生成商业发票需手动配置:注册中文字体、用 saveState/restoreState 控制页眉、Table 设置 CJK 换行与对齐、Decimal 格式化金额、关闭压缩并添加元数据及只读权限。

ReportLab 能生成标准商业 PDF 发票,但默认不带开箱即用的“发票模板”——你得自己组织表格、样式、页眉页脚和数字签名逻辑,否则容易导出错位、金额对不齐、中文乱码或打印时截断。
如何正确配置 SimpleDocTemplate 避免页眉页脚错位
商业发票必须每页显示公司信息、发票编号、日期等固定头部,且末页需有合计与签章区。SimpleDocTemplate 的 onFirstPage 和 onLaterPages 是关键入口,但很多人直接在回调里用 canvas.drawString() 硬写坐标,结果换字体或缩放后全部偏移。
- 务必用
canvas.saveState()/canvas.restoreState()包裹页眉绘制逻辑,避免影响后续元素坐标系 - 页眉高度要预留足够空间:通过
topMargin=100(而非默认 72)给页眉留出安全区,否则表格可能顶到抬头文字下方 - 中文抬头建议用
canvas.setFont('SimSun', 10)而非默认 Helvetica;需提前注册字体:pdfmetrics.registerFont(TTFont('SimSun', 'simsum.ttc'))
用 Table 布局发票明细时的对齐与换行陷阱
商品行经常含长名称或规格说明,Table 默认不自动换行,强行塞进列宽会导致文字被裁切或整行消失。
- 为每列指定
style时,必须显式设置['ALIGN', (0,0), (-1,-1), 'LEFT']和['VALIGN', (0,0), (-1,-1), 'MIDDLE'],否则金额右对齐会失效 - 启用自动换行:给对应列数据包裹
Paragraph(内容, styles['Normal']),并确保styles['Normal'].wordWrap = 'CJK'(对中日韩字符生效) - 避免用
colWidths=[100, 200, 80, ...]硬编码宽度;改用相对单位:colWidths=[doc.width * 0.15, doc.width * 0.4, doc.width * 0.12, ...],适配不同纸张尺寸
金额数字格式化与小数点对齐的可靠做法
发票金额列必须严格右对齐、保留两位小数、千分位分隔,且不能依赖系统 locale —— ReportLab 不读取 Python 的 locale 设置。
立即学习“Python免费学习笔记(深入)”;
- 不要用
f"{amount:,.2f}":它依赖当前 locale,导出 PDF 时可能变成逗号/句点混乱(如 1,234.56 → 1.234,56) - 用
decimal.Decimal+ 手动格式化:amount.quantize(decimal.Decimal('0.01')).to_eng_string(),再补空格对齐(例如用str.rjust(12)) - 金额列
TableStyle中必须加['ALIGN', (3,0), (3,-1), 'RIGHT'],且该列所有单元格内容必须是字符串(不是 float),否则对齐失效
生成可打印、防篡改的最终 PDF 的必要步骤
商业发票交付客户前,需确保打印清晰、不可编辑、带校验信息。ReportLab 本身不提供数字签名,但能控制输出质量与元数据。
- 关闭压缩:
canv = Canvas(..., pageCompression=0),避免某些打印机驱动解析压缩流失败导致空白页 - 嵌入字体:注册中文字体时,用
TTFont('SimSun', 'simsum.ttc', subfontIndex=0)显式指定子字体索引,防止部分 PDF 阅读器 fallback 到方块 - 写入标准元数据:
doc.build(story, onFirstPage=..., onLaterPages=..., canvasmaker=NumberedCanvas),并在NumberedCanvas的showPage()中调用self.setAuthor("Your Co., Ltd")和self.setTitle(f"INVOICE-{invoice_no}") - 最后用
pdfrw或PyPDF2追加只读权限(ReportLab 原生不支持加密):这是最容易被跳过的一步,没加权限的 PDF 被客户误编辑后责任难界定
真正卡住人的往往不是“怎么画表格”,而是中文字体注册路径写错、金额列用了 float 导致对齐崩溃、或者页边距和表格高度算差 1pt 就让合计栏掉到下一页——这些细节不逐项验证,生成的 PDF 在财务系统里就通不过校验。



















