I'd like to print a file selected with a file picker (or somehow) with
certain extension, so that PyQt
or printer will automatically recognize
the format (e.g. pdf
, ms word
, excel
, txt
, html
, jpg
etc.)
So far, I have found here how to print the content of the TextEdit, but I'd like to print files with various formats.
Is it possible with PyQt5
or should I search elsewhere?
Printing a plain text document does not require a viewer, since the print_()
function actually calls the internal QDocument's print_()
function:
filePath, filter = QFileDialog.getOpenFileName(self, 'Open file', '', 'Text (*.txt)')
if not filePath:
return
doc = QtGui.QTextDocument()
try:
with open(filePath, 'r') as txtFile:
doc.setPlainText(txtFile.read())
printer = QtPrintSupport.QPrinter(QtPrintSupport.QPrinter.HighResolution)
if not QtPrintSupport.QPrintDialog(printer, self).exec_():
return
doc.print_(printer)
except Exception as e:
print('Error trying to print: {}'.format(e))
You might want to add some functionalities to set the page size, document margins, font sizes etc, before actually printing (just read the QTextDocument docs), I'll leave that to you.
Printing from html files is almost similar, but you'll need to use the QWebEnginePage class from QtWebEngineWidgets. See this answer.
Do not use QTextDocument.setHtml()
, as Qt has limited support for html tags.
The same is valid for PDF files too, the difference is that the file has to be loaded through setUrl()
and the QWebEngineSettings.PluginsEnabled setting has to be enabled through page.settings().setAttribute(setting, bool)
in case it's not.
Read the documentation about PDF File Viewing.
Printing images could be done through two approaches.
The first and simpler, is to create a temporary html file that embeds the image and load to a webengine page as above (you could add controls for zoom/scaling).
Alternatively, you could directly print using QPainter, but you'll have to relate to the printer resolution and image size, so you'd probably want to have a preview dialog before actually printing the image, otherwise it might be too small (or too big).
While more complex than a plain <html><img src=""></html>
, this allows a better control on the positioning and sizing of the image[s].
class ImagePrintPreview(QtWidgets.QDialog):
def __init__(self, parent, printer, pixmap):
super().__init__(parent)
self.printer = printer
self.pixmap = pixmap
layout = QtWidgets.QGridLayout(self)
self.viewer = QtWidgets.QLabel()
layout.addWidget(self.viewer, 0, 0, 1, 2)
self.resoCombo = QtWidgets.QComboBox()
layout.addWidget(self.resoCombo, 1, 0)
self.zoom = QtWidgets.QSpinBox(minimum=50, maximum=200, suffix='%')
self.zoom.setValue(100)
self.zoom.setAccelerated(True)
layout.addWidget(self.zoom, 1, 1)
self.zoom.valueChanged.connect(self.updatePreview)
self.buttonBox = QtWidgets.QDialogButtonBox(
QtWidgets.QDialogButtonBox.Ok|QtWidgets.QDialogButtonBox.Cancel)
layout.addWidget(self.buttonBox)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
default = printer.resolution()
self.resoCombo.addItem(str(default), default)
for dpi in (150, 300, 600, 1200):
if dpi == default:
continue
self.resoCombo.addItem(str(dpi), dpi)
self.resoCombo.currentIndexChanged.connect(self.updatePreview)
self.updatePreview()
def updatePreview(self):
# create a preview to show how the image will be printed
self.printer.setResolution(self.resoCombo.currentData())
paperRect = self.printer.paperRect(self.printer.DevicePixel)
printRect = self.printer.pageRect(self.printer.DevicePixel)
# a temporary pixmap that will use the printer's page size
# note that page/paper are QRectF, they have a QSizeF which has to
# be converted to a QSize
pm = QtGui.QPixmap(paperRect.size().toSize())
# new pixmap have allocated memory for their contents, which usually
# result in some random pixels, just fill it with white
pm.fill(QtCore.Qt.white)
# start a qpainter on the pixmap
qp = QtGui.QPainter(pm)
# scale the pixmap to the wanted zoom value
zoom = self.zoom.value() * .01
scaled = self.pixmap.scaledToWidth(int(self.pixmap.width() * zoom), QtCore.Qt.SmoothTransformation)
# paint the pixmap aligned to the printing margins
qp.drawPixmap(printRect.topLeft(), scaled)
# other possible alternatives:
# Center the image:
# qp.translate(printRect.center())
# delta = QtCore.QPointF(scaled.rect().center())
# qp.drawPixmap(-delta, scaled)
# To also rotate 90° clockwise, add this to the above:
# qp.rotate(90)
# *after* qp.translate() and before qp.drawPixmap()
# when painting to a non QWidget device, you always have to end the
# painter before being able to use it
qp.end()
# scale the temporary pixmap to a fixed width
self.viewer.setPixmap(pm.scaledToWidth(300, QtCore.Qt.SmoothTransformation))
def exec_(self):
if super().exec_():
self.printer.setResolution(self.resoCombo.currentData())
# do the same as above, but paint directly on the printer device
printRect = self.printer.pageRect(self.printer.DevicePixel)
qp = QtGui.QPainter(self.printer)
zoom = self.zoom.value() * .01
scaled = self.pixmap.scaledToWidth(int(self.pixmap.width() * zoom), QtCore.Qt.SmoothTransformation)
qp.drawPixmap(printRect.topLeft(), scaled)
# as above, that's important!
qp.end()
class ImagePrinter(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QVBoxLayout(self)
selBtn = QtWidgets.QPushButton('Open image')
layout.addWidget(selBtn)
selBtn.clicked.connect(self.selectFile)
def selectFile(self):
filePath, filter = QtWidgets.QFileDialog.getOpenFileName(self, 'Open file', '/tmp', 'Images (*.jpg *.png)')
if not filePath:
return
pixmap = QtGui.QPixmap(filePath)
if pixmap.isNull():
return
printer = QtPrintSupport.QPrinter(QtPrintSupport.QPrinter.HighResolution)
if QtPrintSupport.QPrintDialog(printer, self).exec_():
ImagePrintPreview(self, printer, pixmap).exec_()
Note that I couldn't test this under windows, so it might be necessary to change things related to the resolution (possibly by using printer.supportedResolutions()
).
As already explained in the comments, printing to other (and possibly proprietary) formats requires external modules.