首页 > 编程语言 >Python生成PDF:Reportlab的六种使用方式

Python生成PDF:Reportlab的六种使用方式

时间:2023-03-02 13:33:29浏览次数:57  
标签:Reportlab Python doc self reportlab Paragraph canvas import PDF

 

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

 

七、总结及源码下载

综合以上六种方式来看,前五种基本上是同一频道,可以结合起来使用。但第六种,给我个人的感觉是更自在一点,不用去摸索,想怎么来就怎么来。

本来想推荐前五种方式融合的方案,但是当我用第六种方式实现了所有的内容,却发现代码更少,更直观。

因此,对比之下,我更推荐使用第六种方式了。

 

全部源码:点此下载

 

(全文完)

 

标签:Reportlab,Python,doc,self,reportlab,Paragraph,canvas,import,PDF
From: https://www.cnblogs.com/windfic/p/17157841.html

相关文章

  • 新手:python里面while循环2——代码优化
    上一笔记里面,有大量重复的代码,这次来进行优化,如果有其他方法,请教教我,respect!点击查看代码#-*-coding:utf-8-*-#__author:AndyLiu#Date:2023/3/2menu={......
  • python存 文件报错
    withopen("regulation_news_02.json","w")asfile:file.write(json.dumps(data,indent=2,ensure_ascii=False))报错:Traceback(mostrecentcalllast):File......
  • python模块xlsxwriter使用
    1.安装pipinstallXlsxWriter2.使用#-*-coding:utf-8-*-fromioimportBytesIOimportqrcode#[email protected]('/atta......
  • python---文件操作
    1.文件操作步骤打开文件-open读---把文件的内容读到变量里-read 写---把变量的值写到文件内容里-write关闭文件-close2.读取一个文件1)打开文件file=open(要打开......
  • django 源码解读 python manage.py makemigrations
    分析命令之前,需要先了解makemigrations调用的一些类。这样对于后面分析命令时很轻松。1.MigrationRecorder类这个类在django/db/migrations/recorder.py文件中,这个类是......
  • 有趣又实用的python脚本
    1.使用Python进行速度测试这个高级脚本帮助你使用Python测试你的Internet速度。只需安装速度测试模块并运行以下代码。#pipinstallpyspeedtest#pipinstalls......
  • Python抓取数据具体流程
    之前看了一段有关爬虫的网课深有启发,于是自己也尝试着如如何过去爬虫百科“python”词条等相关页面的整个过程记录下来,方便后期其他人一起来学习。抓取策略确定目标:重要......
  • 机器学习python环境搭建
    目的:跑通下面代码相关代码fromtorchimportnnimporttorchimportjiebaimportnumpyasnpraw_text="""越努力就越幸运"""words=list(jieba.cut(raw_text))......
  • Python第三天
    8bit(位)=1byte(字节)1024byte=1kbstr表示字符串(只要是双单引号里的都叫字符串)int表示整数(1、2、3、5)float表示浮点数(3.151) type()数据类型bool表示true,falseint()、str()、fl......
  • python 字符串 格式化输出 槽格式 小数的位数与符号控制
    """槽的格式限定冒号:左边填序号或名称,右边填写格式"""#定义一个数num=3.1465926#保留两位小数,并且四舍五入res="{:.2f}".format(num)print(res)#有符号的数字res2=......