|
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
复制代码 View Code
(所有源码下载见后)
二、使用页面模板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)
复制代码 View Code
三、继承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)
复制代码 View Code
四、使用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)
复制代码 View Code
五、继承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)
-
复制代码 View Code
六、直接使用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()
复制代码 View Code
七、总结及源码下载
综合以上六种方式来看,前五种基本上是同一频道,可以结合起来使用。但第六种,给我个人的感觉是更自在一点,不用去摸索,想怎么来就怎么来。
本来想推荐前五种方式融合的方案,但是当我用第六种方式实现了所有的内容,却发现代码更少,更直观。
因此,对比之下,我更推荐使用第六种方式了。
全部源码:点此下载
(全文完)
来源:https://www.cnblogs.com/windfic/p/17157841.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|