mrkeyoor.com_
Sun 20 Sept 12:44 UTC
PyPIUtilsupdated 20 Sept 2026

reportlab review

ReportLab 5.0.1 generates PDF files directly from Python. Its `pdfgen` canvas draws text, paths, images, forms, links, and barcodes at explicit coordinates; Platypus lays `Paragraph`, `Table`, `Image`, and other flowables into frames across pages. Charts and graphics sit beside those two document APIs. The 5.0.1 patch changes `Canvas.getpdfdata` encoding, lets callers replace canvas construction in `renderPDF.drawToFile`, checks redirected resource hosts against the trusted-host settings, and fixes an indexing/setattr problem. Our Python 3.12 import worked in 0.03 seconds, but the distribution does not publish `py.typed`.

Verdict

ReportLab 5.0.1 installed in 0.4 seconds, occupied 25 MB across 3 packages, imported in 0.03 seconds, and had zero pip-audit findings in our sandbox. Install it when Python must own PDF drawing or pagination; choose HTML/CSS tooling when document authors should work in web layout instead of points and flowables.

We installed it

Lab card: what happened when we installed reportlabScreenshot of reportlab documentation
Install✓ · 0.4s3 packages on disk · 25 MB
Importimport reportlab in 0.03s · pure Python · requires Python >=3.9,<4
Known vulns0(pip-audit)

Answers from our run

Does reportlab install cleanly?

Yes. In a fresh container with an empty cache, pip install reportlab finished in 0.4s, leaving 3 packages and 25 MB on disk. pip-audit reported no known vulnerabilities.

What does reportlab need to run?

Python >=3.9,<4, and nothing compiled: it is pure Python. In our run import reportlab succeeded in 0.03s.

reportlab or weasyprint: which should you use?

weasyprint: Choose it when HTML and print CSS are the maintained document source and browser-like pagination is preferred. ReportLab 5.0.1 installed in 0.4 seconds, occupied 25 MB across 3 packages, imported in 0.03 seconds, and had zero pip-audit findings in our sandbox.

When should you not use reportlab?

The source is already HTML and CSS, and browser-style layout is the team's strongest skill. WeasyPrint is a closer model than Canvas coordinates or Platypus flowables.

API stability4/5ReportLab's user guide still centers the long-standing `Canvas`, `SimpleDocTemplate`, `Paragraph`, `Table`, `Image`, `PageTemplate`, and `Flowable` objects. Version 5.0.1 adds a canvas-construction override without replacing those entry points. Output stability is a separate problem: font metrics, shaping, row splitting, and wrap decisions can change page breaks even when method signatures stay fixed, so rendered golden tests remain necessary.
Docs4/5The main documentation endpoint returned HTTP 200, and the downloadable user guide covers the coordinate system, canvas state, text, fonts, images, forms, encryption, Platypus frames, flowables, paragraphs, tables, charts, and barcodes. It even describes `wrap` and `split`, which helps diagnose pagination. Some material reads like an older manual, and its introduction admits that open-source documentation may lag the code.
Maintenance4/5PyPI published 5.0.1 on 2026-08-20, two months after the 5.0.0 line began. The packaged CHANGES file names four specific fixes, including redirected-host checks and `Canvas.getpdfdata` encoding. Earlier 2026 entries cover URL security, callback security, Python 3.15, table bounds, and layout bugs. PyPI does not provide a public source repository URL for this distribution, so no stars or GitHub cadence are claimed.
Ecosystem4/5The supplied usage figure is about 21.3 million downloads per week. ReportLab spans direct page drawing, Platypus pagination, vector graphics, charts, barcodes, forms, font registration, and several output renderers; optional extras add acceleration, Cairo, bidi, and shaping support. That reach helps Python reporting systems, though the split between the open-source library and ReportLab's commercial RML product can send searches to features outside this package.

Use it if

  • Invoices, labels, certificates, tickets, or reports need exact PDF coordinates and repeatable server-side output.
  • Python data should flow into multi-page paragraphs and tables without running a browser or office suite.
  • The document needs built-in charts, vector drawings, barcodes, links, forms, or font embedding in the same process.
  • The team can test rendered pages and owns the page-break rules for its real content and fonts.
Skip it if

Setup reality

We installed reportlab 5.0.1 in a fresh Python 3.12 Bookworm sandbox. It completed in 0.4 seconds, left 3 packages using 25 MB, and imported in 0.03 seconds. pip-audit found zero known vulnerabilities. The package declares 7 direct dependencies when optional requirements are counted, requires Python 3.9 through 3.x, and is itself pure Python. It does not ship py.typed. PyPI identifies the license as BSD and points to license.txt for its full terms.

No account, daemon, or config file is needed. A canvas works in points with (0, 0) at the lower-left corner, then writes the PDF's final structures when save() runs. Platypus takes a list of flowables and asks each to wrap, split, and draw inside a frame. That is useful for pagination, but it does not make unknown content safe: long tokens, oversized images, tall headers, and wide tables need fixtures built from production data.

Fonts are an application asset. Register each TrueType face, ship the file, check its embedding terms, and test every script used by customers. Version 5.0.1 offers optional bidi and shaping extras, while the change history still labels South Asian shaping and right-to-left work preliminary. Built-in PDF fonts cover a narrower character set. Image decoding also expands memory before the page is compressed, so constrain pixel dimensions before building large reports.

Some helpers can open image or document resources by URL. The 5.0.1 redirect fix applies trustedHosts and trustedSchemes to the destination host as well, yet a public report endpoint still needs its own URL allowlist, response limits, and timeouts. Generation consumes CPU and memory in the request process. Queue large batches, cap rows and images, and create a fresh canvas or story per job instead of sharing mutable document objects across concurrent requests.

Patterns

Draw text at fixed coordinates draw-single-page

from reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas

c = canvas.Canvas('invoice.pdf', pagesize=A4)
c.setFont('Helvetica', 12)
c.drawString(72, 760, 'Invoice 42')
c.save()

Canvas units are points and the default origin is the lower-left corner. `save()` writes the final PDF structures.

Generate a PDF in memory return-pdf-bytes

from io import BytesIO
from reportlab.pdfgen import canvas

buffer = BytesIO()
c = canvas.Canvas(buffer)
c.drawString(72, 720, 'Receipt')
c.save()
pdf_bytes = buffer.getvalue()

Read the buffer only after `save()` completes. The method finalizes cross-references and trailers required by PDF readers.

Lay out a multi-page story build-flowable-document

from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.platypus import Paragraph, SimpleDocTemplate, Spacer

styles = getSampleStyleSheet()
story = [
    Paragraph('Monthly report', styles['Title']),
    Spacer(1, 12),
    Paragraph(body, styles['BodyText']),
]
SimpleDocTemplate('report.pdf', pagesize=A4).build(story)

Platypus wraps each flowable against the current frame and may move or split it when the remaining height is insufficient.

Repeat headings across table pages repeat-table-header

from reportlab.platypus import Table

data = [['Item', 'Quantity', 'Price'], *rows]
table = Table(data, colWidths=[260, 80, 80], repeatRows=1)

`repeatRows=1` repeats the first row after a page split. Column widths still need to fit the document frame.

Apply borders and alignment style-table

from reportlab.lib import colors
from reportlab.platypus import TableStyle

table.setStyle(TableStyle([
    ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#eeeeee')),
    ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
    ('ALIGN', (1, 1), (-1, -1), 'RIGHT'),
    ('VALIGN', (0, 0), (-1, -1), 'TOP'),
]))

A style changes drawing and cell layout; it does not truncate an oversized value or find an optimal width for unknown content.

Draw a footer on every page add-page-footer

def draw_footer(canvas, doc):
    canvas.saveState()
    canvas.setFont('Helvetica', 9)
    canvas.drawRightString(doc.pagesize[0] - 36, 24, f'Page {doc.page}')
    canvas.restoreState()

doc.build(story, onFirstPage=draw_footer, onLaterPages=draw_footer)

Saving and restoring canvas state prevents the footer's font, color, or transforms from affecting the story content.

Register an application font embed-true-type-font

from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont

pdfmetrics.registerFont(TTFont('AppSans', 'fonts/AppSans-Regular.ttf'))
c.setFont('AppSans', 11)

Registration uses a process-global name. Ship the font file, verify its embedding license, and test the glyphs your documents require.

Fit an image without stretching it scale-image

from reportlab.lib.utils import ImageReader
from reportlab.platypus import Image

source = ImageReader('chart.png')
width, height = source.getSize()
max_width = 420
image = Image('chart.png', width=max_width, height=height * max_width / width)

ReportLab decodes the source image. Validate dimensions before construction so a compressed upload cannot expand beyond the worker's memory budget.

Start an appendix on a new page force-page-break

from reportlab.platypus import PageBreak, Paragraph

story.extend([
    PageBreak(),
    Paragraph('Appendix', styles['Heading1']),
])

Add `PageBreak` at the top story level. A break nested in another flowable does not necessarily act as a document-level page boundary.

Link to a named destination add-internal-link

c.bookmarkPage('terms')
c.drawString(72, 720, 'Terms')

c.linkAbsolute(
    'Jump to terms',
    'terms',
    Rect=(72, 680, 180, 700),
)

The link rectangle uses default page coordinates. The destination name must be created in the same document.

Place a Code 128 barcode draw-code128-barcode

from reportlab.graphics.barcode import code128

barcode = code128.Code128('ORDER-00042', barHeight=36, barWidth=0.8)
barcode.drawOn(c, 72, 640)

Render and scan a physical sample at the intended printer resolution; valid barcode data can still be too dense for the scanner.

Require a password to open the PDF encrypt-pdf

from reportlab.lib.pdfencrypt import StandardEncryption
from reportlab.pdfgen import canvas

encryption = StandardEncryption(
    userPassword,
    ownerPassword=ownerPassword,
    canPrint=0,
)
c = canvas.Canvas('private.pdf', encrypt=encryption)
c.drawString(72, 720, 'Private report')
c.save()

PDF permissions depend on reader enforcement. Password encryption does not replace authorization, secure delivery, or server-side access controls.

Alternatives

PackageRegistryPick it when
weasyprintPyPIChoose it when HTML and print CSS are the maintained document source and browser-like pagination is preferred.
fpdf2PyPIChoose it for a smaller imperative API covering ordinary text, images, tables, and page construction.
pypdfPyPIChoose it to read, combine, crop, rotate, encrypt, or otherwise transform PDFs that already exist.

More utils guides

lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.