Python生成PDF:Reportlab的六种使用方式
Reportlab是Python创建PDF文档的功能库
这里是整理过的六种Reportlab使用方式,主要参考的是《ReportLab User Guide》
一、使用文档模板DocTemplate
Reportlab的基础使用方式是创建内容块(Flowable),再使用文档模板(DocTemplate)创建Pdf文档。
关注点:
- Paragraph(段落)
- Image(图像)
- Table(表格)
- VerticalBarChart(柱形图表)

from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont from reportlab.lib.styles import getSampleStyleSheet
from reportlab.platypus import Paragraph, SimpleDocTemplate, Image, Table
from reportlab.platypus import Spacer
from reportlab.graphics.shapes import Drawing
from reportlab.graphics.charts.barcharts import VerticalBarChart
from reportlab.graphics.charts.legends import Legend
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import cm def draw_text(st, text: str):
return Paragraph(text, st) def draw_img(path):
img = Image(path) # 读取指定路径下的图片
img.drawWidth = 6*cm # 设置图片的宽度
img.drawHeight = 5*cm # 设置图片的高度
return img def draw_table(*args):
col_width = 120
style = [
('FONTNAME', (0, 0), (-1, -1), 'song'), # 字体
('FONTSIZE', (0, 0), (-1, 0), 12), # 第一行的字体大小
('FONTSIZE', (0, 1), (-1, -1), 10), # 第二行到最后一行的字体大小
('BACKGROUND', (0, 0), (-1, 0), '#d5dae6'), # 设置第一行背景颜色
('ALIGN', (0, 0), (-1, -1), 'CENTER'), # 第一行水平居中
('ALIGN', (0, 1), (-1, -1), 'LEFT'), # 第二行到最后一行左右左对齐
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), # 所有表格上下居中对齐
('TEXTCOLOR', (0, 0), (-1, -1), colors.darkslategray), # 设置表格内文字颜色
('GRID', (0, 0), (-1, -1), 0.5, colors.grey), # 设置表格框线为grey色,线宽为0.5
('SPAN', (0, 1), (2, 1)), # 合并第二行一二三列
]
table = Table(args, colWidths=col_width, style=style)
return table def draw_bar(bar_data: list, ax: list, items: list):
drawing = Drawing(500, 200)
bc = VerticalBarChart()
bc.x = 45 # 整个图表的x坐标
bc.y = 45 # 整个图表的y坐标
bc.height = 150 # 图表的高度
bc.width = 350 # 图表的宽度
bc.data = bar_data
bc.strokeColor = colors.black # 顶部和右边轴线的颜色
bc.valueAxis.valueMin = 0 # 设置y坐标的最小值
bc.valueAxis.valueMax = 20 # 设置y坐标的最大值
bc.valueAxis.valueStep = 5 # 设置y坐标的步长
bc.categoryAxis.labels.dx = 2
bc.categoryAxis.labels.dy = -8
bc.categoryAxis.labels.angle = 20
bc.categoryAxis.labels.fontName = 'song'
bc.categoryAxis.categoryNames = ax # 图示
leg = Legend()
leg.fontName = 'song'
leg.alignment = 'right'
leg.boxAnchor = 'ne'
leg.x = 475 # 图例的x坐标
leg.y = 140
leg.dxTextSpace = 10
leg.columnMaximum = 3
leg.colorNamePairs = items
drawing.add(leg)
drawing.add(bc)
return drawing

(所有源码下载见后)
二、使用页面模板PageTemplate
上述的排版都是线性的,如果要有一些混排,比如列式排版,可以使用BalancedColumns,
有一些页面排版比较复杂,那可以使用页面模板(PageTemplate)。
其实还可以用传统Web艺能——Table来做排版,我试了一下,只需要指定BOX,GRID为白色即可,线宽为0不行。
关注点:
- PageTemplate(页面模板)
- Frame(框架)

from reportlab.lib.colors import Color
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import cm
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfgen import canvas
from reportlab.lib import colors
from reportlab.platypus import BaseDocTemplate, Frame, Paragraph, NextPageTemplate, PageBreak, PageTemplate, Image def draw_text(st, text: str):
return Paragraph(text, st) def draw_img(path):
img = Image(path) # 读取指定路径下的图片
img.drawWidth = 5*cm # 设置图片的宽度
img.drawHeight = 4*cm # 设置图片的高度
return img def main(filename):
pdfmetrics.registerFont(TTFont('微软雅黑', 'msyh.ttf')) style = getSampleStyleSheet() ts = style['Heading1']
ts.fontName = '微软雅黑' # 字体名
ts.fontSize = 18 # 字体大小
ts.leading = 30 # 行间距
ts.alignment = 1 # 居中
ts.bold = True hs = style['Heading2']
hs.fontName = '微软雅黑' # 字体名
hs.fontSize = 15 # 字体大小
hs.leading = 20 # 行间距
hs.textColor = colors.red # 字体颜色 ns = style['Normal']
ns.fontName = '微软雅黑'
ns.fontSize = 12
ns.wordWrap = 'CJK' # 设置自动换行
ns.alignment = 0 # 左对齐
ns.firstLineIndent = 32 # 第一行开头空格
ns.leading = 20 doc = BaseDocTemplate(filename, showBoundary=0, pagesize=A4) frameT = Frame(doc.leftMargin, doc.bottomMargin, doc.width, doc.height, id='normal') w = doc.width / 3
h = w
bm = doc.height - h
frame1 = Frame(doc.leftMargin, bm, w, h, id='col1')
frame2 = Frame(doc.leftMargin + w, bm, doc.width-w, h, id='col2')
frame3 = Frame(doc.leftMargin, doc.bottomMargin, doc.width , bm-doc.topMargin, id='col3') doc.addPageTemplates([
PageTemplate(id='TwoCol', frames=[frame1, frame2, frame3]),
PageTemplate(id='OneCol', frames=frameT),
]) elements = [] elements.append(draw_img("images/title.jpg"))
elements.append(draw_text(ns, ' 。'))
elements.append(NextPageTemplate('OneCol'))
elements.append(PageBreak())
elements.append(draw_text(ns,"Frame one column, ")) doc.build(elements)

三、继承BaseDocTemplate
前两种方式都不能精确输出,依赖于模板的排版,精确输出需要Canvas接口。
如果你要在每一页上显示页眉和页脚,那么你可以继承文档模板(BaseDocTemplate)。
- handle_documentBegin
- handle_pageBegin
- handle_pageEnd
- handle_frameBegin
- handle_frameEnd
- handle_flowable
- handle_nextPageTemplate
- handle_currentFrame
- handle_nextFrame
- afterInit
- beforeDocument
- beforePage
- afterPage
- filterFlowables
- afterFlowable
关注点:
- BaseDocTemplate(文档模板)
- bookmarkPage(书签)
- addOutlineEntry(大纲)

from reportlab.lib.styles import ParagraphStyle
from reportlab.platypus import PageBreak
from reportlab.platypus.paragraph import Paragraph
from reportlab.platypus.doctemplate import PageTemplate, BaseDocTemplate
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.platypus.frames import Frame
from reportlab.lib.units import cm class MyDocTemplate(BaseDocTemplate): def __init__(self, filename, **kw):
self.allowSplitting = 0
BaseDocTemplate.__init__(self, filename, **kw)
template = PageTemplate('normal', [Frame(2.5*cm, 2.5*cm, 15*cm, 25*cm, id='F1')])
self.addPageTemplates(template)
self.chapter = 0
self.section = 0 def afterFlowable(self, flowable):
if isinstance(flowable, Paragraph):
text = flowable.getPlainText()
style = flowable.style.name
if style == 'Title':
self.chapter += 1
self.canv.bookmarkPage(f"chapter{self.chapter}")
self.canv.addOutlineEntry(f"Chapter {self.chapter}", f"chapter{self.chapter}", level=0)
elif style == 'Heading1':
self.section += 1
self.canv.bookmarkPage(f"section{self.section}")
self.canv.addOutlineEntry(f"Section {self.section}", f"section{self.section}", level=1) def main(filename):
pdfmetrics.registerFont(TTFont('微软雅黑', 'msyh.ttf')) title = ParagraphStyle(name = 'Title',
fontName = '微软雅黑',
fontSize = 22,
leading = 16,
alignment = 1,
spaceAfter = 20) h1 = ParagraphStyle(
name = 'Heading1',
fontSize = 14,
leading = 16) story = [] story.append(Paragraph('继承BaseDocTemplate', title))
story.append(Paragraph('Section 1', h1))
story.append(Paragraph('Text in Section 1.1'))
story.append(PageBreak())
story.append(Paragraph('Section 2', h1))
story.append(Paragraph('Text in Section 1.2'))
story.append(PageBreak())
story.append(Paragraph('Chapter 2', title))
story.append(Paragraph('Section 1', h1))
story.append(Paragraph('Text in Section 2.1')) doc = MyDocTemplate(filename)
doc.build(story)

四、使用SimpleDocTemplate
SimpleDocTemplate就是继承BaseDocTemplate的一种简单实现,它覆盖了接口handle_pageBegin,重载了build接口。
它把页面分成两种:首页和后续页,对应回调两个过程onFirstPage=, onLaterPages=,只需要实现这两个回调过程即可。
适用显示页眉和页脚,其它的功能就有限了。
关注点:
- SimpleDocTemplate(文档模板)
- QrCode(二维码)
- drawOn(显示Flowable)

from reportlab.platypus import SimpleDocTemplate, Paragraph
from reportlab.platypus import PageBreak
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.colors import Color
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import mm
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.graphics.barcode import qr #首页
def myFirstPage(canvas, doc):
canvas.saveState()
canvas.setFillColorRGB(0, 0, 0)
canvas.setFont('微软雅黑',12)
str="(内部资料)"
canvas.drawCentredString(doc.width/2, 25*mm, str)
myLaterPages(canvas, doc)
canvas.restoreState() #页眉页脚
def myLaterPages(canvas, doc):
canvas.saveState()
canvas.setStrokeColorRGB(0.8, 0.8, 0.8)
canvas.line(0, 32, doc.width, 32)
canvas.line(0, A4[1]-45, doc.width, A4[1]-45)
canvas.setFillColorRGB(0, 0, 0)
canvas.setFont('微软雅黑',10)
str=f"Page {doc.page}"
canvas.drawCentredString(doc.width/2, 5*mm, str)
canvas.setFillColorRGB(1, 0, 0)
canvas.drawCentredString(doc.width/2, A4[1]-9*mm, "XX有限公司版权所有")
qr_code = qr.QrCode('https://www.cnblogs.com/windfic', width=45, height=45)
canvas.setFillColorRGB(0, 0, 0)
qr_code.drawOn(canvas, 0, A4[1]-45)
canvas.restoreState() def main(filename):
pdfmetrics.registerFont(TTFont('微软雅黑', 'msyh.ttf')) doc = SimpleDocTemplate(filename, pagesize=A4, leftMargin=10, rightMargin=10) title = ParagraphStyle(name = 'Title',
fontName = '微软雅黑',
fontSize = 22,
leading = 16,
alignment = 1,
spaceAfter = 20) contents = []
contents.append(Paragraph('使用SimpleDocTemplate', title))
contents.append(Paragraph('Hello'))
contents.append(PageBreak())
contents.append(Paragraph('World')) doc.build(contents, onFirstPage=myFirstPage, onLaterPages=myLaterPages)

五、继承Canvas
控制Canvas的另一种方法是继承Canvas。
与继承文档模板(DocTemplate)类似,不过网上能找到的例子也就是显示页码,不是很实用。

from reportlab.platypus import SimpleDocTemplate, Image, Paragraph, PageBreak
from reportlab.pdfgen import canvas
from reportlab.lib.units import mm
from reportlab.lib.colors import Color
from reportlab.lib.pagesizes import A4
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.lib.styles import ParagraphStyle class NumberedCanvas(canvas.Canvas):
def __init__(self, *args, **kwargs):
canvas.Canvas.__init__(self, *args, **kwargs)
self._saved_page_states = [] def showPage(self):
self._saved_page_states.append(dict(self.__dict__))
self._startPage() def save(self):
"""add page info to each page (page x of y)"""
num_pages = len(self._saved_page_states)
for state in self._saved_page_states:
self.__dict__.update(state)
self.draw_page_number(num_pages)
canvas.Canvas.showPage(self)
canvas.Canvas.save(self) def draw_page_number(self, page_count):
self.setFont("Helvetica", 9)
self.setStrokeColor(Color(0, 0, 0, alpha=0.5))
self.line(10*mm, 15*mm, A4[0] - 10*mm, 15*mm)
self.setFillColor(Color(0, 0, 0, alpha=0.5))
self.drawCentredString(A4[0]/2, 10*mm, "Page %d of %d" % (self._pageNumber, page_count)) def main(filename):
pdfmetrics.registerFont(TTFont('微软雅黑', 'msyh.ttf')) title = ParagraphStyle(name = 'Title',
fontName = '微软雅黑',
fontSize = 22,
leading = 16,
alignment = 1,
spaceAfter = 20) image = Image("images/title.jpg")
image.drawWidth = 160
image.drawHeight = 160*(image.imageHeight/image.imageWidth)
elements = [
Paragraph('继承Canvas', title),
Paragraph("Hello"),
image,
PageBreak(),
Paragraph("world"),
]
doc = SimpleDocTemplate(filename)
doc.build(elements, canvasmaker=NumberedCanvas)
六、直接使用Canvas
当你的PDF内容非常复杂,难以用以上的方法实现,可以直接使用Canvas创建PDF
直接使用Canvas类,可以精确输出,但需要自己排版,而且它的坐标原点在左下角。
其中也可以放置Flowable,需要排版的Flowable,如Table等,调用warp函数即可自动排版。
如果是内容已经排版的格式转换程序,非常推荐使用这种方式。

from reportlab.pdfgen import canvas
from reportlab.platypus import Image
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import mm
from reportlab.lib.colors import Color
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont def draw_page_number(c, page, count):
c.setFont("微软雅黑", 9)
c.setStrokeColor(Color(0, 0, 0, alpha=0.5))
c.line(10*mm, 15*mm, A4[0] - 10*mm, 15*mm)
c.setFillColor(Color(0, 0, 0, alpha=0.5))
c.drawCentredString(A4[0]/2, 10*mm, "Page %d of %d" % (page, count)) def main(filename):
pdfmetrics.registerFont(TTFont('微软雅黑', 'msyh.ttf')) c = canvas.Canvas(filename)
c.bookmarkPage("title")
c.addOutlineEntry("my book", "title", level=0)
c.setFont("微软雅黑", 18)
c.drawCentredString(A4[0]/2, A4[1] - 50, "单独使用Canvas")
c.setFont("微软雅黑", 12)
c.drawString(100, A4[1] - 76, "Hello"*100) img = Image("images/title.jpg")
img.drawWidth = 160
img.drawHeight = 160*(img.imageHeight/img.imageWidth)
img.drawOn(c, 100, A4[1] - 200) draw_page_number(c, 1, 2)
c.bookmarkPage("section1")
c.addOutlineEntry("first section", "section1", level=1)
c.showPage() c.drawString(100, A4[1] - 50, "World")
draw_page_number(c, 2, 2)
c.bookmarkPage("section2")
c.addOutlineEntry("second section", "section2", level=1)
c.showPage() c.showOutline()
c.save()

七、总结及源码下载
综合以上六种方式来看,前五种基本上是同一频道,可以结合起来使用。但第六种,给我个人的感觉是更自在一点,不用去摸索,想怎么来就怎么来。
本来想推荐前五种方式融合的方案,但是当我用第六种方式实现了所有的内容,却发现代码更少,更直观。
因此,对比之下,我更推荐使用第六种方式了。
全部源码:点此下载
(全文完)
Python生成PDF:Reportlab的六种使用方式的更多相关文章
- .net生成PDF文件的几种方式
以下为在.net mvc中,生成pdf的几种方式,资料都是在做项目时网上找的 1.使用Microsoft.Office.Interop.Word.dll将word转换为PDF dll可以单独下载,一般 ...
- python生成pdf报告、python实现html转换为pdf报告
1.先说下html转换为pdf:其实支持直接生成,有三个函数pdfkit.f 安装python包:pip Install pdfkit 系统安装wkhtmltopdf:参考 https://githu ...
- python生成pdf
代码 需要先安装wkhtmltopdf,下载路径https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.5/wkhtmlto ...
- 【python】用python生成pdf文件
转自:https://www.davidfischer.name/2015/08/generating-pdfs-with-and-without-python/ from reportlab.pla ...
- Python数据生成pdf文件
sklearn实战-乳腺癌细胞数据挖掘 https://study.163.com/course/introduction.htm?courseId=1005269003&utm_campai ...
- python之reportlab生成PDF文件
项目需要,需要自动生成PDF测试报告.经过对比之后,选择使用了reportlab模块. 项目背景:开发一个测试平台,供测试维护测试用例,执行测试用例,并且生成测试报告(包含PDF和excel),将生成 ...
- 【转】pyhton之Reportlab模块——生成pdf文件
[转]pyhton之Reportlab模块 reportlab模块是用python语言生成pdf文件的模块 安装:pip install reportlab 模块默认不支持中文,如果使用中文需要注册 ...
- Python处理PDF及生成多层PDF
Python提供了众多的PDF支持库,本文是在Python3环境下,试用了两个库来完成PDF的生成的功能.PyPDF对于读取PDF支持较好,但是没找到生成多层PDF的方法.Reportlab看起来更成 ...
- Python之将Python字符串生成PDF
笔者在今天的工作中,遇到了一个需求,那就是如何将Python字符串生成PDF.比如,需要把Python字符串'这是测试文件'生成为PDF, 该PDF中含有文字'这是测试文件'. 经过一番检索, ...
- salesforce 替代默认生成pdf页面方式
salesforce默认的pdf页面只能设置Arial Unicode MS字体,而该字体默认的中文是日本字符,目前看来没办法设置成标准的中文字符,导致一些字看起来是不对的.如下图:(将确骨写) 这样 ...
随机推荐
- C语言:贮油点建设问题(详解题目意思)
!!!!先看解析,后面附有代码!!!!!!! ,希望大家不懂的能认真看看,这些都是我在写的过程中不能理解,遇到的困难,然后弄懂之后总结出来给大家的,想学的一定要认真看完. 规律是: 贮油点之间相差50 ...
- 服务器电源管理(Power Management States)
目录 文章目录 目录 EIST(智能降频技术) 硬件 固件 操作系统 EIST(智能降频技术) EIST 能够根据不同的 OS(操作系统)工作量自动调节 CPU 的电压和频率,以减少耗电量和发热量.它 ...
- SpringMVC 项目集成 PageOffice V6 最简单代码
本文描述了PageOffice产品在SpringMVC项目中如何集成调用. 新建SpringMVC项目:pageoffice6-springmvc-simple 在您项目的pom.xml中通过下面的代 ...
- go高并发之路——go语言如何解决并发问题
一.选择GO的原因 作为一个后端开发,日常工作中接触最多的两门语言就是PHP和GO了.无可否认,PHP确实是最好的语言(手动狗头哈哈),写起来真的很舒爽,没有任何心智负担,字符串和整型压根就不用区分, ...
- C# winfrom 局域网版多人成语接龙(二)
功能基本上是完成了,要两个人完才好玩,目前 倒计时,每组游戏玩家数量这些控制变量,都是写死再代码里的,等以后想改的时候再改,这个项目核心的功能算是实现了,但还可以扩展,比如记录一下用户的游戏数据,答对 ...
- Html简要笔记
html在线文档: https://www.w3school.com.cn 怎么创建文件我已经会了 1,html快速入门 <!--文档类型说明 注释 --> <!DOCTYPE ht ...
- Android OpenMAX(一)漫谈
在开始正式的学习前,我们先来聊一聊Android音视频开发中的一些问题.感受与想法.(有一点要事先说明,我的问题与答案.想法并不一定正确,请读者带着审慎的思考来阅读,后续的文章也是一样,希望读者边阅读 ...
- 算法金 | 详解过拟合和欠拟合!性感妩媚 VS 大杀四方
大侠幸会,在下全网同名「算法金」 0 基础转 AI 上岸,多个算法赛 Top 「日更万日,让更多人享受智能乐趣」 今天我们来战 过拟合和欠拟合,特别是令江湖侠客闻风丧胆的 过拟合,简称过儿, Emmm ...
- Vulkan的VkImage和OpenGL的Texture2D互转的3种方法
Vulkan的纹理和OpenGL的纹理之间共享的解决方案, 因为项目的功能需要同时引入OpenGL和Vulkan,又因为效率的影响必须想办法优化,两者之间需要互相访问互相转换的高效方案. Vulkan ...
- fontawesome-webfont.woff:1 Failed to load resource: the server responded with a status of 404 ()
fontawesome-webfont.woff2:1 Failed to load resource: the server responded with a status of 404 ()fon ...