Search code examples
python-3.xpyqt5qpainterqprinterqtextdocument

How to set painter of printer correctly?


I'm printing a set of tables, each table should get its own page and could be long. The basics are working, but I don't get the footer painted. The problem is the footer will be painted in an extra document(s).

According to the docs I must set the painter to the device. The device is painter, that's correct, but how do I set the painter to the correct Block? Or is it wrong to act this way?

The goal is to use this document twice. 1st attempt is to print, the second a QTextDocument where I can pic up the QTextTable's and to compile it with another document elements.

Working example

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtPrintSupport import *

content = [['section 1', [1,2,3,4]],['section2', [5,6,7,8]]]

app = QApplication(sys.argv)

document = QTextDocument ()
printer = QPrinter()
painter = QPainter(printer)
pageRect = printer.pageRect ()

tableFormat = QTextTableFormat ()
cellBlockFormat = QTextBlockFormat ()
cellCharFormat = QTextCharFormat ()
cellCharFormat.setFont (QFont ("Arial", 10))

for rownr, line in enumerate(content):
    cursor = QTextCursor (document)
    mainFrame = cursor.currentFrame ()
    # header
    cursor.setPosition (mainFrame.firstPosition ())
    cursor.insertHtml ("This is the table for  %s"%line[0])

    # table
    table = cursor.insertTable (3, 4, tableFormat)
    for colnr, col in enumerate(line[1]):
        print("col:", col)
        cellCursor = table.cellAt (rownr + 1, colnr).firstCursorPosition ()
        cellCursor.setBlockFormat (cellBlockFormat)
        cellCursor.insertText (str (col))

    #footer
    painter.begin(printer)
    painter.drawText (0, pageRect.bottom(), "I may be the footer")
    painter.end()
    # section finished
    cursor.setPosition (mainFrame.lastPosition ())
    tableFormat.setPageBreakPolicy (QTextFormat.PageBreak_AlwaysAfter)
    cursor.insertBlock (cellBlockFormat, cellCharFormat)
document.print_(printer)

Solution

  • Premise: this is more a hack than a solution, as it's a dirty workaround.

    The idea is to subclass QPrinter, override the newPage method and draw the footer accordingly. This requires that the printer instance is manually updated with footers.

    Unfortunately, there's another important catch: I've not been able to print the footer as long as there's only one page.

    In the next days I'll try to look into it again, and find if there's a solution for it.

    import sys
    from PyQt5.QtCore import *
    from PyQt5.QtWidgets import *
    from PyQt5.QtGui import *
    from PyQt5.QtPrintSupport import *
    
    
    class FooterPrinter(QPrinter):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.footers = {}
    
        def paintEngine(self, *args):
            engine = super().paintEngine(*args)
            self.currentPage = 0
            return engine
    
        def drawFooter(self):
            text = self.footers.get(self.currentPage)
            if not text:
                return
            painter = super().paintEngine().painter()
            dpiy = painter.device().logicalDpiY()
            margin = dpiy * (2/2.54)
            rect = QRectF(margin, margin, self.width() - margin * 2, self.height() - margin * 2)
            fm = QFontMetrics(painter.font(), painter.device())
            size = fm.size(0, text)
            painter.drawText(rect.left(), rect.bottom() - size.height(), 
                size.width(), size.height(), Qt.AlignLeft|Qt.AlignTop, text)
    
        def newPage(self):
            self.drawFooter()
            newPage = super().newPage()
            if newPage:
                self.currentPage += 1
                self.drawFooter()
            return newPage
    
    content = [['section 1', [1,2,3,4]],['section2', [5,6,7,8]]]
    
    app = QApplication(sys.argv)
    
    document = QTextDocument()
    printer = FooterPrinter()
    printer.setOutputFileName('/tmp/test.pdf')
    pageRect = printer.pageRect ()
    
    tableFormat = QTextTableFormat ()
    cellBlockFormat = QTextBlockFormat ()
    cellCharFormat = QTextCharFormat ()
    cellCharFormat.setFont (QFont ("Arial", 10))
    
    for rownr, line in enumerate(content):
        cursor = QTextCursor (document)
        mainFrame = cursor.currentFrame ()
        # header
        cursor.setPosition (mainFrame.firstPosition ())
        cursor.insertHtml ("This is the table for  %s"%line[0])
    
        # table
        table = cursor.insertTable (3, 4, tableFormat)
        for colnr, col in enumerate(line[1]):
            cellCursor = table.cellAt (rownr + 1, colnr).firstCursorPosition ()
            cellCursor.setBlockFormat (cellBlockFormat)
            cellCursor.insertText (str (col))
        cursor.setPosition (mainFrame.lastPosition ())
        tableFormat.setPageBreakPolicy (QTextFormat.PageBreak_AlwaysAfter)
        cursor.insertBlock (cellBlockFormat, cellCharFormat)
    
        printer.footers[rownr] = 'Note for page {}: some text.\nNew line\nAnother new line'.format(rownr + 1)
    
    document.print_(printer)