最近接到了一个需求-将项目下的样本信息汇总并以PDF的形式展示出来,第一次接到这种PDF的操作的功能,还是有点慌的,还好找到了reportlab这个包,可以定制化向PDF写内容!
reportlab是久经考验的,超强大的开源引擎,用于创建复杂的,数据驱动的 PDF 文档和自定义矢量图形。它是免费的,开源的,并且是用 Python 编写的。

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()

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)
| 属性 | 说明 | Normal样式规格 | 备注 |
|---|---|---|---|
| name | 样式名称 | Normal样式 | 值设置建议在word、wps等设置后直接应用 |
| parent | 父对象 | None | |
| alignment | 文字对齐 | 0 | 0-左对齐,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 |

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 = """表1: 学生成绩表 """
# 可以配置上下左右页边距,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)

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)
from reportlab.platypus import Spacer
# 引用方式
spacerObj = Spacer(1, 75)

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.5、图片>中是不是发现图片排版可见为竖放,样式有点怪怪的,如果选择横放会不会更好呢?这就涉及到框架Frame的概念了,它可以在一个区域内将段落、表格、图表混排,定制化排版
from reportlab.platypus import Frame
# Frame类调用
Frame(x1, y1, width,height, leftPadding=6, bottomPadding=6, rightPadding=6, topPadding=6, id=None, showBoundary=0)


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()
合格的pdf、word等文件都是配备有页脚页眉等信息,诸如公司Logo、页码。

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("
" % (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)
| 中文 | 英文 | 网络搜索方式方法 |
|---|---|---|
| 宋体 | simsun | simsun.ttc |
| 楷体 | simkai | simkai.ttf |
| 微软雅黑 | msyh | msyh.ttc |
| 黑体 | simhei | simhei.ttf |
| 仿宋 | simfang | simfang.ttf |
| 等线 | Deng | Deng.ttf |
import aspose.cells
from aspose.cells import Workbook
workbook = Workbook(r"D:\demo.xlsx")
workbook.save("demo.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"))
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)
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)
# 三种方式
# 方式一,在使用,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" # 可惜的是保存时会多一层路径,有点小瑕疵
)