Generating Custom PDF Documents with ReportLab in Python
This article explains how to use Python's ReportLab library to create highly customized PDF documents, covering basic components like canvas, templates, page headers/footers, and demonstrating single‑column and double‑column layouts with text, charts, images, and tables through detailed code examples.
Background
In many work scenarios we need to handle PDF files, and complex custom PDFs are often difficult to process. This article introduces how to use ReportLab to generate customized PDF files.
ReportLab is a standard Python library that can create PDFs directly from drawing commands without manual intervention, allowing applications to generate PDFs quickly.
Basic Component
The pdfgen package provides the lowest‑level interface for PDF generation. Its canvas object is the core component for drawing; it uses the page’s lower‑left corner as the origin and places elements with absolute X/Y coordinates.
Template
The PageTemplate class is a simple container that holds a list of Frame objects and optional callbacks for page start/end, enabling custom headers and footers. BaseDocTemplate creates a basic document template without default page templates, so custom PageTemplate instances must be added.
Implementation
1. Start a PDF document
from reportlab.graphics import shapes
from reportlab.graphics import widgetbase
from reportlab.graphics.charts.piecharts import Pie
from reportlab.graphics.charts.textlabels import Label
from reportlab.graphics.shapes import Drawing
from reportlab.lib import colors
from reportlab.lib.colors import HexColor
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT
from reportlab.lib.pagesizes import A5
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import mm
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.platypus import BaseDocTemplate, Paragraph, Table, Spacer, PageBreak, Image, PageTemplate, \
Frame, NextPageTemplate, FrameBreak
doc = BaseDocTemplate('test.pdf', pagesize=(A5[0], A5[1]), topMargin=15*mm, bottomMargin=30)
pdfmetrics.registerFont(TTFont('song', r'./STSONG.TTF'))2. Set up single‑column and double‑column frames
# Single‑column frame
frameT = Frame(doc.leftMargin, doc.bottomMargin, doc.width, doc.height, id='normal')
# Double‑column frames
frame1 = Frame(doc.leftMargin, doc.bottomMargin, doc.width/2 - 6, doc.height - doc.bottomMargin, id='col1')
frame2 = Frame(doc.leftMargin + doc.width/2 + 6, doc.bottomMargin, doc.width/2 - 6, doc.height - doc.bottomMargin, id='col2', showBoundary=1)
doc.addPageTemplates([
PageTemplate(id='OneCol', frames=frameT, onPage=header, onPageEnd=footer),
PageTemplate(id='TwoCol', frames=[frame1, frame2], onPage=header, onPageEnd=foot2),
])3. Add header and footer functions
def footer(canvas, doc):
canvas.saveState()
page_num = canvas.getPageNumber()
content = Paragraph(f"单栏第{page_num}页", styleC)
w, h = content.wrap(doc.width, doc.bottomMargin)
content.drawOn(canvas, doc.leftMargin, h)
canvas.restoreState()
def foot2(canvas, doc):
canvas.saveState()
page_num = canvas.getPageNumber()
content = Paragraph(f"双栏第{page_num}页", styleC)
w, h = content.wrap(doc.width, doc.bottomMargin)
content.drawOn(canvas, doc.leftMargin, h)
canvas.restoreState()
def header(canvas, doc):
canvas.saveState()
header_content = Paragraph("这是页眉", styleC)
w, h = header_content.wrap(doc.width, doc.topMargin)
header_content.drawOn(canvas, doc.leftMargin, doc.height + doc.topMargin - h)
canvas.restoreState()4. Single‑column page: add text, a pie chart, and a small widget
def draw_pie(data=[], labels=[], use_colors=[]):
pie = Pie()
pie.x = 30
pie.y = 10
pie.slices.label_boxStrokeColor = colors.white
pie.data = data
pie.labels = labels
pie.simpleLabels = 0
pie.sameRadii = 1
pie.slices.strokeColor = colors.red
pie.strokeWidth = 1
pie.strokeColor = colors.white
pie.slices.label_pointer_piePad = 5
pie.slices.label_pointer_edgePad = 15
pie.width = 220
pie.direction = 'clockwise'
pie.pointerLabelMode = 'LeftRight'
for i, col in enumerate(use_colors):
pie.slices[i].fillColor = col
return pie
data = [10,9,8,7,6,5,4,3,2,1]
labs = ['0000000','1111111','2222222','3333333','4444444','5555555','6666666','7777777','8888888','9999999']
color = [HexColor('#696969'), HexColor('#A9A9A9'), HexColor('#D8BFD8'), HexColor('#DCDCDC'), HexColor('#E6E6FA'), HexColor('#B0C4DE'), HexColor('#778899'), HexColor('#B0C4DE'), HexColor('#6495ED'), HexColor('#483D8B')]
def auto_legender(chart, title='饼图'):
width = 300
height = 150
d = Drawing(width, height)
lab = Label()
lab.x = 140
lab.y = 130
lab.setText(title)
lab.fontName = 'song'
lab.fontSize = 10
d.add(lab)
d.add(chart)
return d
def add_face():
d = shapes.Drawing(200, 100)
f = widgetbase.Face()
f.skinColor = colors.yellow
d.add(f)
return d
Elements = []
Elements.append(Paragraph("单栏第一页 " * 5, styleN))
pie = auto_legender(draw_pie(data, labs, color))
face = add_face()
Elements.append(pie)
Elements.append(face)5. Double‑column page: add text, an image, and a table
Elements.append(PageBreak())
Elements.append(Paragraph("这是双栏第一个区域", styleC))
Elements.append(Spacer(width=doc.width, height=30*mm))
Elements.append(Image('default.png', width=40*mm, height=40*mm))
Elements.append(FrameBreak())
Elements.append(Paragraph("这是双栏第二个区域
", styleC))
Elements.append(Spacer(width=doc.width, height=10*mm))
data = [
[1,2,3,4,5],
['a','b','c','d','e'],
[1,2,3,4,5],
['a','b','c','d','e'],
]
Elements.append(Table(data, style=[
('ALIGN',(0,0),(-1,-1),'CENTER'),
('FONT',(0,0),(-1,-1),'song'),
('GRID',(0,0),(-1,-1),1,colors.black),
]))6. Build the PDF
doc.build(Elements)The article concludes that ReportLab supports highly customizable PDF generation, including HTML/XML text, drawing graphics, and custom tables.
360 Quality & Efficiency
360 Quality & Efficiency focuses on seamlessly integrating quality and efficiency in R&D, sharing 360’s internal best practices with industry peers to foster collaboration among Chinese enterprises and drive greater efficiency value.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.