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 =https://www.cnblogs.com/windfic/p/ 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 drawingView 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.uj5u.com/houduan/545535.html
標籤:Python