可参考内容:
Python学习——使用ReportLab生成带表格和图文的PDF - 知乎
【Python 实战】一键生成 PDF 报告,图文并茂,代码全公开_python 生成 pdf 报告模块 - CSDN 博客
4.7. 使用 reportlab 模块 — Python 3 教程 文档
一、ReportLab 简介
ReportLab 是一个 Python 用于生成指定格式、复杂的 PDF 库,自定义程度很高,可以生成文字、图片、表格(复杂表格)、控制页面的各种格式的软件库。
ReportLab 对 PDF 的操作分为了几个层级,从上到下,依次为:
DocTemplates:文档的最外层容器;
PageTemplates:各种页面布局容器;
Frames:页面中放置文本,图像的区块;
Flowables:可排列的文本或者图像元素,包含图片、段落、表格、分隔符等等,但不包含页脚和固定位置的页面图像。
Canvas:接收所有其它层级信息并绘制文档的最底层。

二、字体设置
如果要使用中文字体,需要在 reportlab 里注册,方法如下:
1 2 3 4 5 6 7 8 9 10 11
| from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
font_path = 'MicrosoftYaHei.ttf'
pdfmetrics.registerFont(TTFont('hei', font_path))
|
在某些地方需要使用字体的时候,只需要把字体设置为 hei 即可:
1 2 3 4 5 6 7 8 9 10 11 12 13
| def draw_text(self, text: str): ct = self.style['Normal'] ct.fontName = 'hei' ct.fontSize = 12 ct.leading = 1.5 * ct.fontSize ct.wordWrap = 'CJK' ct.alignment = 0 ct.firstLineIndent = 0 text = text.replace('\n', '<br/>') return Paragraph(text, ct)
|
[!TIP]
和 matplotlab 联动的时候(用 matplotlab 绘制各种图表),需要按照 matplotlab 的字体设置方式进行设置:
1 2 3 4
| from matplotlib import font_manager
custom_font = font_manager.FontProperties(fname=_font_path)
|
对于各种统计图,如直方图、饼图、折线图等,建议在绘制函数的参数指定字体,如对于饼图里的字体问题,建议使用 textprops 参数,而不是 rcParams 参数。title中也指定 fontproperties 参数。
三、Paragraph 的使用
创建 Paragraph 实例
Paragraph
类是 ReportLab 中用于创建和渲染文本的核心类之一。它位于 reportlab.platypus
模块中,主要用于处理段落文本内容,并支持样式、对齐、自动换行等功能。
[!TIP]
建议涉及文本的内容,都转换为 Paragraph
进行使用,方便对于文字的样式进行各种个性化设置。
创建一个 Paragraph
实例:
1 2 3 4 5
| Paragraph( text, style, bulletText=None )
|
text
为段落文本,style为段落样式,bulletText
对于文档中的各个标题,可以使用不同样式的 Paragraph 作为不同等级的标题:
一级标题:
1 2 3 4 5 6 7 8 9 10 11 12
| def draw_1_title(self, title1: str): ct = self.style['Heading1'] ct.fontName = 'hei' ct.fontSize = 20 ct.leading = 1.5 * ct.fontSize ct.spaceBefore = 4 ct.spaceAfter = 4 ct.textColor = colors.black ct.alignment = 1 return Paragraph(title1, ct)
|
对于其他级别的标题,可以根据需求设置字体、字体大小、行间距以及段前段后距离。
Paragraph 样式
Paragraphs 的默认样式,一般用得到的也就那么多了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| class ParagraphStyle(PropertySet): defaults = { 'fontName': _baseFontName, 'fontSize': 10, 'leading': 12, 'leftIndent': 0, 'rightIndent': 0, 'firstLineIndent': 0, 'alignment': TA_LEFT, 'spaceBefore': 0, 'spaceAfter': 0, 'bulletFontName': _baseFontName, 'bulletFontSize': 10, 'bulletIndent': 0, 'textColor': black, 'backColor': None, 'wordWrap': None, 'borderWidth': 0, 'borderPadding': 0, 'borderColor': None, 'borderRadius': None, 'allowWidows': 1, 'allowOrphans': 0, 'textTransform': None, 'endDots': None, 'splitLongWords': 1, 'underlineWidth': _baseUnderlineWidth, 'bulletAnchor': 'start', 'justifyLastLine': 0, 'justifyBreaks': 0, 'spaceShrinkage': _spaceShrinkage, 'strikeWidth': _baseStrikeWidth, 'underlineOffset': _baseUnderlineOffset, 'underlineGap': _baseUnderlineGap, 'strikeOffset': _baseStrikeOffset, 'strikeGap': _baseStrikeGap, 'linkUnderline': _platypus_link_underline, 'underlineColor': None, 'strikeColor': None, 'hyphenationLang': _hyphenationLang, 'embeddedHyphenation': _embeddedHyphenation, 'uriWasteReduce': _uriWasteReduce, }
|
四、Tables 的使用
创建 Table 实例
reportlab 的表格功能十分强大,可以生成各种各样的表格,甚至是嵌套表格。Tables 的一般用法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| Table( data, colWidths=None, rowHeights=None, style=None, splitByRow=1, repeatRows=0, repeatCols=0, rowSplitRange=None, spaceBefore=None, spaceAfter=None, cornerRadii=None )
|
[!NOTE]
-
这里的 data 如果二维列表中的某一行,还是列表,这个时候 reportlab 会把它解析为一个嵌套列表(可以理解为合并单元格的效果)。
-
如果 data 的不同行的个数不同,reportlab 会自动识别列数最少的列把它作为每一列的个数,如对于 [ [1], [1, 2, 3] ],reportlab 会识别为两行一列的表格,也就是两行 1。
-
splitByRow 在使用的时候,如果某一行的高度超过了一页,就会报错,需要额外调整。
创建 Table 实例方法的简单样例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| def draw_table(data): style = TableStyle( [ ('FONTNAME', (0, 0), (-1, -1), 'hei'), ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), ('TEXTCOLOR', (0, 0), (-1, -1), colors.black), ('ALIGN', (0, 0), (-1, -1), 'CENTER'), ] ) col_width = [100, 100, 100, 100] style.add('SPAN', (0, 0), (1, 0)) style.add('SPAN', (2, 0), (3, 0)) return Table(args, colWidths=col_width, style=style, splitByRow=True)
|
TableStyle 样式
表格样式 TableStyle 主要分为 3 类,分别为:单元格式命令、行命令、合并命令。
表格样式的创建例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| LIST_STYLE = TableStyle( [ ('LINEABOVE', (0, 0), (-1, 0), 2, colors.green), ('LINEABOVE', (0, 1), (-1, -1), 0.25, colors.black), ('LINEBELOW', (0, -1), (-1, -1), 2, colors.green), ('ALIGN', (1, 1), (-1, -1), 'RIGHT') ] )
|
TableStyle 用户方法
添加样式到 TableStyle
向一个 TableStyle 实例加入一个样式设置:
1
| TableStyle.add(commandSequence)
|
例子:
1
| LIST_STYLE.add('BACKGROUND', (0,0), (-1,0), colors.Color(0,0.7,0.7))
|
获取样式命令
获取 TableStyle 所有定义的样式命令:
1
| cmds = LIST_STYLE.getCommands()
|
TableStyle 单元格式命令
使用 TableStyle 之前,需要注意坐标参数:
这个坐标参数代表的含义是从第一个坐标参数开始,到第二个坐标参数结束。
如上面的 LIST_STYLE.add('BACKGROUND', (0,0), (-1,0), colors.Color(0,0.7,0.7))
,指的就是从 (0, 0)
位置开始,到 (-1, 0)
位置结束,每个位置参数的第一位代表列、第二位代表行,先列后行。这里第二位都是 0,则表示都是第一行;第一位从 0 到 -1,则表示所有的列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| FONT - 设置字体,同时可指定字体名称、字号和行距。
('FONT', (0, 0), (-1, -1), 'Helvetica')
FONTNAME (或 FACE) - 只设置字体名称。
('FONTNAME', (0, 0), (-1, -1), 'Helvetica')
FONTSIZE (或 SIZE) - 设置字体大小(单位为点)。
('FONTSIZE', (0, 0), (-1, -1), 10)
LEADING - 设置行距(单位为点)。
('LEADING', (0, 0), (-1, -1), 14)
TEXTCOLOR - 设置文本颜色,可以使用颜色名称或 (R, G, B) 元组。
('TEXTCOLOR', (0, 0), (-1, -1), colors.red)
ALIGNMENT (或 ALIGN) - 设置文本的对齐方式,可选值包括 LEFT、RIGHT、CENTRE(或 CENTER)和 DECIMAL。
('ALIGN', (0, 0), (-1, -1), 'CENTER')
LEFTPADDING - 设置单元格左内边距,默认值为6。
('LEFTPADDING', (0, 0), (-1, -1), 10)
RIGHTPADDING - 设置单元格右内边距,默认值为6。
('RIGHTPADDING', (0, 0), (-1, -1), 10)
BOTTOMPADDING - 设置单元格底部内边距,默认值为3。
('RIGHTPADDING', (0, 0), (-1, -1), 10)
TOPPADDING - 设置单元格顶部内边距,默认值为3。
('BOTTOMPADDING', (0, 0), (-1, -1), 5)
BACKGROUND - 设置单元格背景色,可以是颜色对象、颜色名称或数值元组/列表。
('BACKGROUND', (0, 0), (-1, -1), colors.blue)
('BACKGROUND', (0, 0), (-1, -1), ['VERTICAL', colors.white, colors.blue])
ROWBACKGROUNDS - 接受一个颜色列表,用于循环设置每行的背景色。
('ROWBACKGROUNDS', [colors.lightgrey, colors.white])
COLBACKGROUNDS - 接受一个颜色列表,用于循环设置每列的背景色。
('COLBACKGROUNDS', [colors.lightblue, colors.white])
VALIGN - 设置单元格中内容的垂直对齐方式,可选值为 TOP、MIDDLE 或默认的 BOTTOM。
('VALIGN', (0, 0), (-1, -1), 'MIDDLE')
|
TableStyle 行命令
行命令可以灵活地控制表格的线条样式。每个命令的作用是指定某些单元格区域的边框或者其他线条的样式。具体命令的格式包括 起始和结束单元格的坐标、线条粗细 和 线条颜色。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| GRID - 相当于同时使用 BOX 和 INNERGRID。 它会在表格的每个单元格周围绘制边框,并在表格的所有单元格之间绘制内部网格线。
('GRID', (0, 0), (-1, -1), 1, colors.black)
BOX - 绘制整个表格的外边框,类似于给整个表格加一个边框线。
('BOX', (0, 0), (-1, -1), 2, colors.blue)
OUTLINE - 和 BOX 命令等价,都用于给整个表格加上一个外部轮廓边框。
('OUTLINE', (0, 0), (-1, -1), 2, colors.green)
INNERGRID - 绘制表格内部的网格线,但不绘制外部的边框。即,它只在单元格之间绘制分隔线。
('INNERGRID', (0, 0), (-1, -1), 0.5, colors.grey)
LINEBELOW - 在指定行的下方绘制一条水平线,也就是下边线
('LINEBELOW', (0, 1), (-1, 1), 1, colors.red)
LINEABOVE - 在指定行的上方绘制一条水平线,也就是上边线
('LINEBEFORE', (1, 0), (1, -1), 0.5, colors.green)
LINEAFTER - 在指定列的右侧绘制一条垂直线,也就是右边线
('LINEAFTER', (2, 0), (2, -1), 0.5, colors.yellow)
|
TableStyle 合并单元格命令
SPAN
命令用于合并单元格,命令如下:
1
| SPAN, (sc, sr), (ec, er)
|
表示从列 sc
到 ec
、从行 sr
到 er
的单元格应合并成一个超大单元格,其内容由单元格 (sc, sr)
决定。
[!IMPORTANT]
这里合并时,每行单元格的数量应当一致,不需要的位置用空白代替,不然合并的时候会丢失内容。
如对于二维数组: [ [姓名, 得分], [张三, 李四, 99, 98] ]
这里在合并时,对于第一列内容要先进行扩充,扩充为[‘姓名’, ‘’, ‘得分’, ‘’],需要注意,合并的单元格内容为左侧单元格的内容。
合并的命令为:
1 2
| ('SPAN', (0, 0), (1, 0)) ('SPAN', (2, 0), (3, 0))
|
Table 中可能会遇到格式的问题
嵌套表格上下界额外空白间隙
这个问题导致的原因是,嵌套表格同一行的前后某个单元格的高度,超过了内部嵌套单元格的长度,导致出现了额外的空白间隙。

这个问题也很好解决,有两个解决方案:
- 只要表格的内部分割线
- 手动计算并指定嵌套表格的高度
对于方案一,是最简单的方案,使用 INNERGRID
命令即可。就是如果这个间隙十分大,看起来会有些怪。代码如下:
1
| style.add('INNERGRID', (0, 0), (-1, -1), 1, colors.black)
|
效果如下:

但是这个方案不完美,因为分割线不连贯。
对于方案二:
这个方案就是逐个表格的每个行高,如果外部的那个单元格大于内部的表格的行高,那么就重新指定一下行高;否则,我们就按照原始的行高来,这里需要指定 Table 的参数。
五、BaseDocTemplate 的使用
BaseDocTemplate 定义
BaseDocTemplate
是 ReportLab 库中的一个类,用于处理多个页面模板(PageTemplate
)。它提供了一个 build 方法来处理页面内容并生成 PDF 文档。BaseDocTemplate
允许你创建自定义布局,它为页面的布局提供了基本机制,并且允许通过 Flowables
来管理、组织文档内容,并最终输出 PDF 文件。
BaseDocTemplate
的定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| BaseDocTemplate( self, filename, pagesize=defaultPageSize, pageTemplates=[], showBoundary=0, leftMargin=inch, rightMargin=inch, topMargin=inch, bottomMargin=inch, allowSplitting=1, title=None, author=None, _pageBreakQuick=1, encrypt=None )
|
这里的 filename 参数同样可以是一个字符串,表示接收生成的 PDF 文档的文件名;或者它可以是一个具有 write
方法的对象,例如 BytesIO
、文件或套接字。
BaseDocTemplate 用户方法
添加列表到 BaseDocTemplate 中
将一个或多个(列表)PageTemplates 加入到 BaseDocTemplate 中。
用法:
1
| BaseDocTemplate.addPageTemplates(self, pageTemplates)
|
构建 PDF 文档
当文档实例的内容正确设置时,build
方法将以 flowables 列表的形式接收文档内容,并逐一处理这些 flowables。实际上,这使得 BaseDocTemplate 实例依次调用其 handle_XXX
方法来处理不同的事件。
用法:
1
| BaseDocTemplate.build(self, flowables, filename=None, canvasmaker=canvas.Canvas)
|
通常构建 PDF 文件时会和 Python 的 IO 一起使用,以下是一个简单的案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| def generate_pdf(content, graphs): """ 生成 PDF 并且返回 PDF 的字节流 @param content: PDF 的内容 @param graphs: PDF 绘制类 @return: PDF 字节流 """ buffer = io.BytesIO() doc = BaseDocTemplate(buffer, leftMargin=inch / 10, rightMargin=inch / 10) template = PageTemplate( id="test", frames=frame_footer, onPage=graphs.header, onPageEnd=graphs.footer ) doc.addPageTemplates([template]) doc.build(content) return buffer.getvalue()
|
六、其他常用类
Image 图片类
Image 类用法比较简单,用来读取本地的图片文件,同时可以指定高度和宽度:
Image 的用法:
1
| Image(filename, width=None, height=None)
|
这里有时候 filename 可能不是一个文件路径,可能会是一个加密的 base64_string,使用起来也很简单,只要把 base64_string 解密,然后使用 Python 的 IO 进行读取即可。
Spacer 空白间隔区类
空白间隔区 Spacer 可以用来在文档中插入一定的空白间隔,用法如下:
PageBreak 换页符类
PageBreak 类用来在一个页面中插入一个“换页符”,让下面的内容到下一页去,用法如下: