admin管理员组

文章数量:1649938

前言

最近接到了一个需求-将项目下的样本信息汇总并以PDF的形式展示出来,第一次接到这种PDF的操作的功能,还是有点慌的,还好找到了reportlab这个包,可以定制化向PDF写内容!

  • 好的文章必须得推荐啦!

让我们由简入深进行讲解

一、reportlab是什么?

reportlab是久经考验的,超强大的开源引擎,用于创建复杂的,数据驱动的 PDF 文档和自定义矢量图形。它是免费的,开源的,并且是用 Python 编写的。

二、canvas绘制图形文字元素

  • 页面绘制是以坐标系为基准,左下角坐标为(0, 0)
  • 中文显示效果需(百度)下载包并将其存放至路径(1、绝对路径2、相对路径3、python包路径/xxx/python/site-packages/reportlab/fonts/)

2.1 样式预览

2.2、代码

from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from reportlab.pdfbase import pdfmetrics, ttfonts
from reportlab.lib.colors import red, blue

# pagesize可以指定创建的画布尺寸
pdfObj = canvas.Canvas("test.pdf", pagesize=A4)  # 也可以自定义尺寸,如pdfObj.setPageSize((1200,800))


#################################START字符串绘制#################################
# 绘制字符串左对齐,以给定坐标为起始点
pdfObj.drawString(300, 750, "Welcome! Ladies and gentlemen 0")
# 绘制字符串居中,以给定坐标系为字符串中心
pdfObj.drawCentredString(300, 700, "Welcome! Ladies and gentlemen 1")
# 绘制字符串右对齐,以给定坐标系为字符串结束点
pdfObj.drawRightString(300, 650, "Welcome! Ladies and gentlemen 2")
# 绘制<<中文>>字母,需要注册字体并配置字号,注simsun.ttc文件的存储位置可以是绝对或相对路径
pdfmetrics.registerFont(ttfonts.TTFont("宋体", "simsun.ttc"))
# 配置字号
pdfObj.setFont("宋体", 30)
pdfObj.drawString(300, 600, "大学生开学季")
# 文字倾斜,45度角
pdfObj.rotate(45)
#################################END字符串绘制#################################

#################################START图片的绘制#################################
# 绘制图片
pdfObj.drawImage(r"D:\test\baidu.png", 300, 400, 190, 72)
#################################END图片的绘制#################################

#################################START图形的绘制#################################
# 绘制中横线,参数为起点,终点坐标
pdfObj.line(50, 350, 300, 350)
pdfObj.setLineWidth(1)  # 中横线厚度
# 绘制长方形
pdfObj.setFillColor(red)  # 颜色对象
pdfObj.rect(300, 300, 190, 20, stroke=0, fill=1)  # 长方形区域属性
# 附加属性
pdfObj.setFillColor(blue)  # 颜色对象
# pdfObj.setFillGray(0.75)  # 灰度配置,用的少,先关掉了
pdfObj.setFillAlpha(0.3)  # 透明度配置
pdfObj.rect(300, 650, 200, 50, stroke=0, fill=1)  # 长方形区域属性
#################################END图形的绘制#################################

#################################START继承的绘制#################################
# 将form内包含的绘制保存,以便再下一页继续应用。可用在页眉、页脚、背景色等处
pdfObj.beginForm("new")  # 创建继承体
pdfObj.line(50, 200, 300, 200)
pdfObj.setLineWidth(1)  # 中横线厚度
pdfObj.endForm()  # 结束并保存继承体

for i in range(2):
    pdfObj.doForm("new")  # 应用继承体
    pdfObj.showPage()  # 结束本页翻转下一页
#################################END继承的绘制#################################
# 保存生效
pdfObj.save()

三、页面布局platypus应用

在第一章中,所有的元素都基于坐标进行绘制,每次的排版都需要计算,应用起来有点复杂及繁琐,所以为了减少这种重复劳动,引入模板和样式platypus(Page Layout and Typography Using Scripts),它致力于把文档的样式和内容分开,段落、表格都直接套用相应的格式,页面也可以套用页面模版。
  • 明晰platypus几大层面划分,包含关系由小到大。页面元素(flowables)、页面框架(Frame)、页面模板(PageTemplate)、文档模板(DocTemplate)
  • 页面元素(flowables),如段落、表格、空白、分页符、图片等

3.1、段落Paragraph

3.1.1、样式预览

3.1.2、代码

from reportlab.lib.styles import getSampleStyleSheet
from reportlab.platypus import SimpleDocTemplate
from reportlab.lib.pagesizes import A4
from reportlab.pdfbase import pdfmetrics, ttfonts
from reportlab.platypus import Paragraph

# 文本数据
txt1 = "尊敬的家长,亲爱的同学们:"
txt2 = "在硕果累累的金秋时节,伴着纤云翩翩,伴着枫红菊香,你们怀揣着无限的憧憬,来到了xx学校。你们的到来,犹如徐徐清风,让我们的校园更为清新宜人,璀璨多姿。xx学院全体师生期盼着你们的到来,我们用比较诚挚的心意衷心的祝福你们,欢迎你们!"
txt3 = "当你跨进这所美丽的校园,你就成了我们大伙庭的一员,在这个大伙庭里,充满着真情,充满着友爱,充满着对一切美好事物的追求。在这个大伙庭里,你将在这优美的校园环境中陶冶你的情操,情发挥你的特长,丰富你的学识,攀登科学的高峰,实现你的梦想。"
txt4 = "新的.面孔、新的价值观念和标准,新的生活方式,需要你用理性的目光和胆识、用辛勤的劳动和汗水,去实现你走向人生成功与辉煌的又一起点。"
txt5 = "谢谢大家!"

# 创建字体样式对象
styleObj = getSampleStyleSheet()
# <<中文>>需要注册字体并配置字号,注simsun.ttc文件的存储位置可以是绝对或相对路径
pdfmetrics.registerFont(ttfonts.TTFont("宋体", "simsun.ttc"))

# 配置段落标题与正文属性,方式一
styleObj["Title"].fontName, styleObj["Title"].fontSize = "宋体", 15
styleObj["Title"].paragraphObjpaceAfter, styleObj["Normal"].paragraphObjpaceBefore = 30, 10
styleObj["Normal"].fontName, styleObj["Normal"].fontSize = "宋体", 10
styleObj["Normal"].leading = 30
styleObj["Normal"].firparagraphObjtLineIndent = 40

# # 也可以用下面的方式配置段落属性,方式二
# # 中文写入不识别,未查明怎么不生效
# from reportlab.lib.styles import ParagraphStyle
# paragraphStyle = ParagraphStyle(name="A1",fontName="宋体",fontSize=10, firstLineIndent=0)
# styleObj.add(paragraphStyle)

# 创建段落对象
paragraphObj1 = Paragraph(txt1, styleObj["Title"])
paragraphObj2 = Paragraph(txt2, styleObj["Normal"])
paragraphObj3 = Paragraph(txt3, styleObj["Normal"])
paragraphObj4 = Paragraph(txt4, styleObj["Normal"])
paragraphObj5 = Paragraph(txt5, styleObj["Normal"])

# 此处不再使用canvas创建pdf对象,改为文档模板doctemplate模块的SimpleDocTemplate类,页面由点构成。设置下边距72点,即1英寸的高度
doc = SimpleDocTemplate(r"test1.pdf", pagesize=A4, bottomMargin=72)
story_text = [paragraphObj1, paragraphObj2, paragraphObj3, paragraphObj4, paragraphObj5]
doc.build(story_text)

3.1.2、段落定制化属性参数一览

属性说明Normal样式规格备注
name样式名称Normal样式值设置建议在word、wps等设置后直接应用
parent父对象None
alignment文字对齐00-左对齐,1-居中,2-右对齐
allowOrphans页底段落最小行数0
allowWidows页顶段落最小行数1
backColor背景颜色None
borderColor边框颜色None
borderPadding内容与边距的距离0
borderRadius圆角的边框None
borderWidth边框宽度0
firstLineIndent首行缩进0
fontName字体名称Helvetica
fontSize字体大小10
leading行距12
leftIndent左缩进0
rightIndent右缩进0
spaceAfter段后间隔0
spaceBefore段前间隔0
textColor文字颜色Color(0,0,0,1)
wordWrap单词中换行None

3.2、表格Table

3.2.1、样式预览

3.2.2、代码

from reportlab.lib.styles import getSampleStyleSheet
from reportlab.platypus import Table
from reportlab.lib.units import inch, cm, pica
from reportlab.platypus import TableStyle
from reportlab.lib import colors
from reportlab.pdfbase import pdfmetrics, ttfonts

pdfmetrics.registerFont(ttfonts.TTFont("宋体", "simsun.ttc"))
# table数据,二维数组
data = [
    ["姓名", "语文", "数学", "英语", "体育"],
    ["张三", 91, 97, 79, "良好"],
    ["李四", 99, 87, 73, "优秀"],
    ["王五", 86, 89, 83, "良好"],
    ["赵六", 95, 88, 86, "良好"],
    # 当需要将数据在一个单元格分两列的话,可以用下面的语法
    ["孙七", 79, 95, 98, "良"+"\n"+"好"],
]
# 配置行高(第一、第二、第三...行高)、列宽(第一、第二、第三...列宽),如有需要,也可以配置单位,如5 * [3 * cm]
col_widths, row_heights = [80, 100, 100, 100, 100], [60, 50, 50, 50, 50, 50]
# 表格行列的表达形式为,同excel,左上方第一个单元格为(0, 0), 右下角单元格为(-1, -1),围起来就是整个表格
table_style = TableStyle([
    ("FONT", (0, 0), (0, -1), "宋体", 30),  # 配置字体
    ("FONT", (0, 0), (-1, 0), "宋体", 30),
    ("FONT", (1, 1), (-1, -1), "宋体", 15),
    ("ALIGN", (0, 0), (-1, -1), "CENTER"),  # 水平居中
    ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),  #  垂直居中
    ("INNERGRID", (0, 0), (-1, -1), 0.25, colors.black),  # 单元格分割线
    ("BOX", (0, 0), (-1, -1), 0.25, colors.black),  # 边框
    ("BACKGROUND", (0, 0), (-1, -1), colors.lightgrey),  # 背景色
    ("TEXTCOLOR", (0, 0), (-1, 0), colors.red),  # 区域字体颜色
    ("LINEBELOW", (0,-1), (-1,-1), 0, colors.white),  # 移除最后一行的下框线,延申LINEABOVE(上框线)、LINEBEFORE(左框线)、LINEAFTER(右框线)
    # ("GRID", (0, 0), (-1, -1), 0.5, colors.black),  # 表格框线为灰色,线宽为0.5
    # ("SPAN", (0, 3), (-1, 3)),  # 合并单元格
    
])
## 如临时需要加入某种表格样式
# table_style.add('BACKGROUND',(column,row),(column,row),colors.red)
table = Table(data, colWidths=col_widths, rowHeights=row_heights, style=table_style)
# 编辑表格标题及样式
tabletitle = """<para alignment=center fontName="宋体" fontSize=20 spaceAfter=30>表1: 学生成绩表</para>"""
# 可以配置上下左右页边距,topMargin=1*cm,bottomMargin=1*cm,leftMargin=1*cm,rightMargin=1*cm
doc = SimpleDocTemplate(r"test2.pdf", pagesize=A4)
story_table = [Paragraph(tabletitle, getSampleStyleSheet()["Normal"]), table]
doc.build(story_table)

3.3、图表

  • 一个多种图表的参考范例

3.3.1、样式预览

3.3.2、代码

from reportlab.graphics.shapes import Drawing
from reportlab.lib.pagesizes import A4
from reportlab.platypus import Spacer
from reportlab.graphics.charts.barcharts import VerticalBarChart
from reportlab.graphics.charts.textlabels import Label
from reportlab.platypus import SimpleDocTemplate

# 写入中文,注册中文字体
pdfmetrics.registerFont(ttfonts.TTFont("宋体", "simsun.ttc"))
# 创建绘图区
drawArea = Drawing(100, 100)
# 创建垂直条形图对象
bar = VerticalBarChart()
# 设置图表的属性
bar.x, bar.y, bar.height, bar.width, bar.valueAxis.valueMin = 30, -200, 280, 400, 0
bar.categoryAxis.categoryNames = ["2005", "2006", "2007", "2008", "2009"]
bar.data = [[15, 14, 19, 23, 27]]
bar.bars[0].fillColor, bar.barLabels.nudge = colors.black, 18
bar.barLabelFormat, bar.valueAxis.labels.fontSize = "%0.0f", 20
bar.categoryAxis.labels.fontSize, bar.barLabels.fontSize = 20, 30
# 放置图表至绘图区
drawArea.add(bar)
# 配置图表标题
title = Label()
title.setText("季度销售量")
title.fontSize, title.fontName, title.dx, title.dy = 20, "宋体", 210, 160
drawArea.add(title)
# 生成文档对象
doc = SimpleDocTemplate(r"test3.pdf", pagesize=A4)
# 防止太靠近顶端,在绘图区上方添加空白
story_chart = [Spacer(1, 75), drawArea]
doc.build(story_chart)

3.4、空白

  • 顾名思义就是空出一段留白,常用作排版样式
from reportlab.platypus import Spacer
# 引用方式
spacerObj = Spacer(1, 75)

3.5、图片

3.5.1、样式预览

3.5.2、代码

from reportlab.lib.units import inch
from reportlab.lib.pagesizes import A4
from reportlab.platypus import Image, Spacer
from reportlab.platypus import SimpleDocTemplate



imgObj1 = Image(r"D:\0Im1.png", 2 * inch, 2 * inch)
imgObj2 = Image(r"D:\0Im2.jpg", 2 * inch, 2 * inch)
# 设定高度宽度另一种方式
# imgObj.drawHeight = 2 * inch
# imgObj.drawWidth = 2 * inch

doc = SimpleDocTemplate(r"test4.pdf", pagesize=A4)
story_image = [Spacer(1, 10), imgObj1, Spacer(1, 20), imgObj2]
doc.build(story_image)

3.6、升华Frame

在<3.5、图片>中是不是发现图片排版可见为竖放,样式有点怪怪的,如果选择横放会不会更好呢?这就涉及到框架Frame的概念了,它可以在一个区域内将段落、表格、图表混排,定制化排版

3.6.1、Frame构图结构

from reportlab.platypus import Frame

# Frame类调用
Frame(x1, y1, width,height, leftPadding=6, bottomPadding=6, rightPadding=6, topPadding=6, id=None, showBoundary=0)

  • leftPadding、bottomPadding、rightPadding、topPadding可以联想到word中页边距,加快理解速度

3.6.2、样式预览

3.6.3、代码

from reportlab.lib.units import inch
from reportlab.lib.pagesizes import A4
from reportlab.pdfgen.canvas import Canvas
from reportlab.platypus import Image, Frame



imgObj1 = Image(r"D:\0Im1.png", 2 * inch, 2 * inch)
imgObj2 = Image(r"D:\0Im2.jpg", 2 * inch, 2 * inch)

# 页面划分两个区域,左下角坐标为(0, 0), showBoundary=1框架线条配置
f1 = Frame(0, 0, 300, 600, showBoundary=0, id='f1')
f2 = Frame(300, 0, 300, 600, showBoundary=0, id='f2')

pdfObj = Canvas(r'test5.pdf', pagesize=A4)

# addFromList接收的值为列表,列表内可以为任意flowables(段落、表格、空白、分页符、图片等)
f1.addFromList([imgObj1, ], pdfObj)
f2.addFromList([imgObj2, ], pdfObj)
# f2.addFromList(story_image, pdfObj)  # story_chart、story_table、story_text

pdfObj.save()

3.7、页眉页脚

合格的pdf、word等文件都是配备有页脚页眉等信息,诸如公司Logo、页码。

3.7.1、样式预览

3.7.2、代码

import datetime
from reportlab.lib.units import inch, cm
from reportlab.pdfbase import pdfmetrics, ttfonts
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.platypus import SimpleDocTemplate, Paragraph, PageBreak, Spacer



def myFirstPage(canvas, doc):
    """
    第一页页眉页脚配置
    :param canvas: 绘画对象,用来写入页眉页脚
    :param doc: 文档对象,可以以此得知文档的页边距信息
    :return:
    """
    # 文档宽度高度(width、height),左右页边距(leftMargin、rightMargin)、上下页边距(topMargin、bottomMargin)
    totalPageHeight = doc.bottomMargin + doc.height + doc.topMargin  # 页面总高度
    totalPageWidth = doc.leftMargin + doc.width + doc.rightMargin  # 页面总宽度

    # 保存之前的画笔格式等状态,并设置新的状态
    canvas.saveState()   
    # 设置字体及大小
    canvas.setFont('宋体', 12)
    # 添置靠左页眉
    canvas.drawImage(
        r"D:\logo.png", doc.leftMargin, totalPageHeight - doc.topMargin, 5.4 * cm, 1.38 * cm
    )
	"""
    # 如果想要在页眉页脚以段落的形式配置logo图片,效果同上<<添置靠左页眉>>
    p = Paragraph("<img src='%s' width='%d' height='%d'/>" % (r"D:\logo.png", 5.4 * cm, 1.38 * cm), getSampleStyleSheet()['Normal'])  # 使用一个Paragraph Flowable存放图片
    # 配置段落可用的宽度高度
    w, h = p.wrap(1*cm, 1*cm)
    # 图片写入画布区,位置(x, y)
    p.drawOn(canvas, doc.leftMargin, totalPageHeight - doc.topMargin)
    """
    # 添置靠右页眉
    canvas.drawRightString(totalPageWidth-doc.rightMargin, totalPageHeight-doc.topMargin, "XXXX有限公司")
    # 添置中横线
    canvas.setLineWidth(1)
    canvas.line(
        doc.leftMargin, totalPageHeight-doc.topMargin-0.25*cm, totalPageWidth-doc.rightMargin, totalPageHeight-doc.topMargin-0.25*cm
    )
    # 变更字体大小
    canvas.setFont('宋体', 9)
    # 添置靠左页脚,一般来说,都是左边距1*inch,下边距0.75*inch
    canvas.drawString(doc.leftMargin, doc.bottomMargin, str(datetime.date.today()))
    # canvas.drawString(inch, 0.75 * inch, str(datetime.date.today()))

    # 添置靠右页脚
    canvas.drawRightString(
        totalPageWidth-doc.rightMargin, doc.bottomMargin, "Confidential and Protected by Copyright Laws"
    )
    # 添置居中页脚
    canvas.drawString(totalPageWidth/2.0, doc.bottomMargin, "1")

    # 将画笔格式等状态还原
    canvas.restoreState()

def myLaterPages(canvas, doc):
    """
    除第一页外其它页的页眉页脚配置
    :param self:
    :param canvas:
    :param doc:
    :return:
    """
    totalPageWidth = doc.leftMargin + doc.width + doc.rightMargin

    canvas.saveState()
    canvas.setFont('song', 9)
    # 添置居中页脚
    canvas.drawString(
        totalPageWidth / 2.0, doc.bottomMargin, "{}".format(doc.page)
    )

    canvas.restoreState()



# 中文注册
pdfmetrics.registerFont(ttfonts.TTFont("宋体", "simsun.ttc"))

txtList = ["测试页眉页脚demo1", "测试页眉页脚demo2", "测试页眉页脚demo3", "测试页眉页脚demo4"]

styles = getSampleStyleSheet()
styles["Normal"].fontName = "宋体"
styles["Normal"].fontSize = 40

story_demo = []
# 组装信息
for i in txtList:
    story_demo.append(Spacer(1, 200))
    story_demo.append(Paragraph(i, styles["Normal"]))
    story_demo.append(PageBreak())  # PageBreak切换下一页

doc = SimpleDocTemplate(r"test6.pdf", pagesize=A4)
doc.build(story_demo, onFirstPage=myFirstPage, onLaterPages=myLaterPages)

四、中英微软字体对照

中文英文网络搜索方式方法
宋体simsunsimsun.ttc
楷体simkaisimkai.ttf
微软雅黑msyhmsyh.ttc
黑体simheisimhei.ttf
仿宋simfangsimfang.ttf
等线DengDeng.ttf

五、拓展

  • excel转pdf的方式,水印自行清除
import aspose.cells
from aspose.cells import Workbook

workbook = Workbook(r"D:\demo.xlsx")
workbook.save("demo.pdf")
  • 提取pdf中的文本
from PyPDF2 import PdfReader
def get_text_stream(path, page=4):
    reader = PdfReader(path)
    page = reader.pages[page]
    streamData = page.extract_text()
    print(streamData.split("\n"))
  • 提取pdf中的图片
from PyPDF2 import PdfReader

def save_image(path, page=4):
    reader = PdfReader(path)
    page = reader.pages[page]  # page是提取pdf中的第几页的意思
    streamData = b""
    for image_file_object in page.images:
    	with open("demo.png", "wb") as fp:
        	fp.write(image_file_object.data)
  • pdf 截图
from PyPDF2 import PdfReader, PdfWriter

def crop_image(source_path, page, target_path):
    reader = PdfReader(source_path)
    page = reader.pages[page]
	# page.mediabox.width  # 宽度
    page.cropbox.lower_left = (800, 100)  # 截图左下角坐标,须知页面左下角坐标为(0, 0)
    page.cropbox.upper_right = (800, 400)  # 截图右上角坐标

    writer = PdfWriter()
    writer.add_page(page)
    with open(target_path + "output.pdf", "wb") as fp:
        writer.write(fp)
  • pdf转PNG
# 三种方式

# 方式一,在使用,pip install PyMuPDF
import fitz
pdfObj = fitz.open(r"output.pdf")
# pdfObj.page_count  # page页数
pix = pdfObj[0].get_pixmap(alpha=False)  # 页面-光栅图像,默认是720*x尺寸
# 如上所示操作,出图模糊不清,需要配置分辨率,如下三行替代
# zoom = 2
# mat = fitz.Matrix(zoom, zoom)
# pix = pdfObj[0].get_pixmap(alpha=False, matrix=mat, dpi=1200)
pix.save(r"output.png")

# 方式二,提供思路,pip install pdfplumber
import pdfplumber
pdfObj = pdfplumber.open(r"output.pdf")
# pdfObj.pages  # page页数
im = pdfObj.pages[0].to_image(resolution=150)
im.save(r"test.png")

# 方式三, 国内开源开发包,pip install python-office
import office
office.pdf.pdf2imgs(
    pdf_path = r"output.pdf",
    out_dir = r"export.png"  # 可惜的是保存时会多一层路径,有点小瑕疵
)

六、结束!

本文标签: 报告PythonPDF