Exporting Widgets to PDF and Controlling Position in PyQt5

How to render a QWidget to PDF and position it at the top of the page
Heads up! You've already completed this tutorial.

When you're building a PyQt5 application that displays information — student records, reports, invoices — you'll often want to export that content to PDF. PyQt5 makes this possible by using QPrinter and QPainter to render any QWidget directly onto a virtual "page." The tricky part is controlling where the widget appears on that page.

In this tutorial, we'll walk through how to render a widget to PDF, understand how the coordinate translation system works, and adjust the position so your widget appears at the top of the page (or anywhere else you want).

Rendering a QWidget to PDF

The basic approach is straightforward:

  1. Set up a QPrinter configured for PDF output.
  2. Create a QPainter that paints onto that printer.
  3. Use coordinate transforms (translate, scale) to position and size the widget on the page.
  4. Call widget.render(painter) to paint the widget's contents.

Here's a starting example that renders a widget centered on the page:

python
from PyQt5.QtPrintSupport import QPrinter
from PyQt5.QtGui import QPainter
from PyQt5.QtWidgets import QFileDialog
from PyQt5.QtCore import QFileInfo


def print_widget(self, widget, filename):
    printer = QPrinter(QPrinter.HighResolution)
    printer.setOutputFormat(QPrinter.PdfFormat)
    printer.setOutputFileName(filename)

    painter = QPainter(printer)

    # Calculate scale to fit the widget onto the page
    xscale = printer.pageRect().width() * 1.0 / widget.width()
    yscale = printer.pageRect().height() * 1.0 / widget.height()
    scale = min(xscale, yscale)

    # Move origin to the center of the paper
    painter.translate(printer.paperRect().center())
    painter.scale(scale, scale)
    # Shift back by half the widget size so it's centered
    painter.translate(-widget.width() / 2, -widget.height() / 2)

    widget.render(painter)
    painter.end()

This works well, and the widget ends up centered both horizontally and vertically on the page. But what if you want the widget at the top of the page instead?

To understand how to change the position, we need to look at what those translate calls are actually doing.

Understanding the Coordinate Transforms

The QPainter coordinate system starts with the origin (0, 0) at the top-left corner of the page. When you call widget.render(painter), the widget is drawn starting at whatever the current origin is. If you're new to QPainter and how its coordinate system works, our bitmap graphics tutorial covers these concepts in more detail.

The centering code uses three steps:

python
painter.translate(printer.paperRect().center())       # Step A
painter.scale(scale, scale)                            # Step B
painter.translate(-widget.width() / 2, -widget.height() / 2)  # Step C

Step A moves the origin to the exact center of the paper — both horizontally and vertically.

Step B applies a uniform scale so the widget fits on the page without being clipped.

Step C shifts the origin back by half the widget's width and half its height. Since the widget draws from the origin point, this adjustment means the center of the widget lines up with the center of the page.

The result: a perfectly centered widget.

Moving the Widget to the Top of the Page

To position the widget at the top of the page (still centered horizontally), we only want to shift the origin horizontally to the center — we don't want to move it down to the vertical center.

Here's the modified version:

python
# Move origin to the horizontal center only (no vertical shift)
xshift = printer.paperRect().width() / 2
painter.translate(xshift, 0)

painter.scale(scale, scale)

# Shift back horizontally by half the widget width (no vertical shift)
painter.translate(-widget.width() / 2, 0)

That's it. By passing 0 for the y-component in both translate calls, the widget stays at the top of the page. The horizontal centering still works exactly as before.

Complete Working Example

Here's a full, runnable example that creates a simple widget with some labels and exports it to PDF, positioned at the top of the page. The example uses a QMainWindow with a form layout — if you need a refresher on how layouts work in PyQt5, see our PyQt5 layouts tutorial.

python
import sys

from PyQt5.QtCore import QFileInfo
from PyQt5.QtGui import QPainter, QFont
from PyQt5.QtPrintSupport import QPrinter
from PyQt5.QtWidgets import (
    QApplication,
    QFileDialog,
    QFormLayout,
    QLabel,
    QMainWindow,
    QPushButton,
    QVBoxLayout,
    QWidget,
)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Export Widget to PDF")

        # Main layout
        central = QWidget()
        layout = QVBoxLayout(central)
        self.setCentralWidget(central)

        # The widget we want to export
        self.student_card = QWidget()
        self.student_card.setStyleSheet(
            "background-color: white; padding: 10px;"
        )
        form_layout = QFormLayout(self.student_card)
        form_layout.addRow("Name:", QLabel("Jane Doe"))
        form_layout.addRow("Student ID:", QLabel("2024-00142"))
        form_layout.addRow("Program:", QLabel("Computer Science"))
        form_layout.addRow("Year:", QLabel("3rd Year"))
        form_layout.addRow("Status:", QLabel("Active"))

        layout.addWidget(self.student_card)

        # Export button
        export_btn = QPushButton("Export to PDF")
        export_btn.clicked.connect(self.pdf_export)
        layout.addWidget(export_btn)

    def print_widget(self, widget, filename):
        """Render a widget to a PDF file, positioned at the top center."""
        printer = QPrinter(QPrinter.HighResolution)
        printer.setOutputFormat(QPrinter.PdfFormat)
        printer.setOutputFileName(filename)

        painter = QPainter(printer)

        # Calculate scale factor to fit widget width on the page
        xscale = printer.pageRect().width() * 1.0 / widget.width()
        yscale = printer.pageRect().height() * 1.0 / widget.height()
        scale = min(xscale, yscale)

        # Center horizontally, keep at top vertically
        xshift = printer.paperRect().width() / 2
        painter.translate(xshift, 0)
        painter.scale(scale, scale)
        painter.translate(-widget.width() / 2, 0)

        widget.render(painter)
        painter.end()

    def pdf_export(self):
        fn, _ = QFileDialog.getSaveFileName(
            self, "Export PDF", None, "PDF files (*.pdf);;All Files(*)"
        )
        if fn:
            if QFileInfo(fn).suffix() == "":
                fn += ".pdf"
            self.print_widget(self.student_card, fn)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

Run this, click Export to PDF, and open the resulting file. The student card content will appear at the top of the page, centered horizontally.

Positioning the Widget Anywhere

Now that you understand the pattern, you can position the widget wherever you like by adjusting the translate values. Here are a few common positions:

Top-left corner (no translation at all):

python
painter.scale(scale, scale)
# Widget draws from (0, 0) — top-left

Top-center (what we used above):

python
xshift = printer.paperRect().width() / 2
painter.translate(xshift, 0)
painter.scale(scale, scale)
painter.translate(-widget.width() / 2, 0)

Center of the page (the original code):

python
painter.translate(printer.paperRect().center())
painter.scale(scale, scale)
painter.translate(-widget.width() / 2, -widget.height() / 2)

Custom position — for example, 100 pixels from the top and left, in printer coordinates:

python
painter.translate(100, 100)
painter.scale(scale, scale)

The translate-scale-translate pattern gives you full control. The first translate sets where the widget's anchor point will be on the page, the scale adjusts the size, and the second translate offsets the widget relative to that anchor.

Common Pitfalls

There are a couple of easy mistakes to watch out for when working with this code.

Using QPrinter instead of QPainter. When creating the painter, make sure you write QPainter(printer) and not QPrinter(printer). They look very similar but are completely different classes. The printer defines the output device; the painter is what draws onto it.

Forgetting to call painter.end(). If you don't end the painter, the PDF file may not be written correctly — or at all. Always call painter.end() when you're done rendering.

Mixing up pageRect() and paperRect(). pageRect() is the printable area (inside the margins), while paperRect() is the full physical page. For centering calculations, think about which one makes sense for your layout — usually paperRect() for centering on the physical page and pageRect() for staying inside the margins.

With this approach, you can export any widget in your PyQt5 application to a cleanly positioned PDF — whether it's a student profile, a report, or any other visual layout you've built with widgets. If you want to distribute your finished application, take a look at our guide to packaging PyQt5 applications with PyInstaller.

Well done, you've finished this tutorial! Mark As Complete
[[ user.completed.length ]] completed [[ user.streak+1 ]] day streak

Packaging Python Applications with PyInstaller by Martin Fitzpatrick

This step-by-step guide walks you through packaging your own Python applications from simple examples to complete installers and signed executables.

More info Get the book

Martin Fitzpatrick

Exporting Widgets to PDF and Controlling Position in PyQt5 was written by Martin Fitzpatrick.

Martin Fitzpatrick has been developing Python/Qt apps for 8 years. Building desktop applications to make data-analysis tools more user-friendly, Python was the obvious choice. Starting with Tk, later moving to wxWidgets and finally adopting PyQt. Martin founded PythonGUIs to provide easy to follow GUI programming tutorials to the Python community. He has written a number of popular Python books on the subject.