xralf
xralf

Reputation: 3642

Print pdf document in PyQt5

I'd like to print pdf document to a printer. So far, I have this code.

def printDialog(self):
        filePath, filter = QFileDialog.getOpenFileName(self, 'Open file', '', 'Text (*.txt);;PDF (*.pdf)')
        if not filePath:
          return
        file_extension = os.path.splitext(filePath)[1]
        if file_extension == ".txt":
            doc = QtGui.QTextDocument()
            try:
                with open(filePath, 'r') as txtFile:
                    doc.setPlainText(txtFile.read())
                printer = QPrinter(QPrinter.HighResolution)
                if not QPrintDialog(printer, self).exec_():
                    return
                doc.print_(printer)
            except Exception as e:
                print('Error trying to print: {}'.format(e))
        elif file_extension == ".pdf":
            printer = QPrintDialog(QPrinter.HighResolution)
            printer.setOutputFormat(QPrinter.PdfFormat)
            printer.setOutputFileName(filePath)
            # TODO
        else:
            pass

I don't know how to continue in the TODO section.

Upvotes: 3

Views: 5518

Answers (1)

Stephan Schlecht
Stephan Schlecht

Reputation: 27106

One possibility would be to use pdf2image which is

A python (3.5+) module that wraps pdftoppm and pdftocairo to convert PDF to a PIL Image object

see https://pypi.org/project/pdf2image/

Under the link above you will find installation instructions for poppler, which is a dependency of the module.

Steps

  • PDF file is readed
  • it is converted into images with help of pdf2image
  • each image is painted using QPaint

A simple version of the code would look like this:

images = convert_from_path(filePath, dpi=300, output_folder=path)
painter = QPainter()
painter.begin(printer)
for i, image in enumerate(images):
    if i > 0:
        printer.newPage()
    rect = painter.viewport()
    qtImage = ImageQt(image)
    qtImageScaled = qtImage.scaled(rect.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
    painter.drawImage(rect, qtImageScaled)
painter.end()

The second parameter of convert_from_path describes the output resolution in dpi. The target rectangle in the device coordinate system can be determined by calling viewport on the QPainter instance. Finally, the image can then be smoothly scaled to the target rectangle using a bilinear filter algorithm.

Self-contained Sample Program

I have slightly modified your code and created a completely self-contained example.

Topics such as paper sizes, resolutions, optimization regarding memory requirements and error handling etc. are not handled. The intention is rather to give a minimal example.

import os
import sys

from PIL.ImageQt import ImageQt
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSize, Qt
from PyQt5.QtGui import QPainter
from PyQt5.QtPrintSupport import QPrintDialog, QPrinter
from PyQt5.QtWidgets import QMainWindow, QFileDialog, QPushButton
import tempfile

from pdf2image import convert_from_path


class PrintDemo(QMainWindow):

    def __init__(self):
        QMainWindow.__init__(self)

        self.setMinimumSize(QSize(192, 128))
        self.setWindowTitle("Print Demo")

        printButton = QPushButton('Print', self)
        printButton.clicked.connect(self.onPrint)
        printButton.resize(128, 32)
        printButton.move(32, 48)

    def onPrint(self):
        self.printDialog()

    def printDialog(self):
        filePath, filter = QFileDialog.getOpenFileName(self, 'Open file', '', 'PDF (*.pdf)')
        if not filePath:
            return
        file_extension = os.path.splitext(filePath)[1]

        if file_extension == ".pdf":
            printer = QPrinter(QPrinter.HighResolution)
            dialog = QPrintDialog(printer, self)
            if dialog.exec_() == QPrintDialog.Accepted:
                with tempfile.TemporaryDirectory() as path:
                    images = convert_from_path(filePath, dpi=300, output_folder=path)
                    painter = QPainter()
                    painter.begin(printer)
                    for i, image in enumerate(images):
                        if i > 0:
                            printer.newPage()
                        rect = painter.viewport()
                        qtImage = ImageQt(image)
                        qtImageScaled = qtImage.scaled(rect.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
                        painter.drawImage(rect, qtImageScaled)
                    painter.end()
        else:
            pass


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    mainWin = PrintDemo()
    mainWin.show()
    sys.exit(app.exec_())

Upvotes: 2

Related Questions