pythonhtmlprintingpyqt6qtextdocument

Printing nested HTML tables in PyQt6


I have an issue when trying to print the contents of a QTableWidget in a PyQt6 application.

It actually works, but there is a small problem: I have tables embedded in the main table and I'd like those tables to completely fill the parent cells (100% of their widths), but the child tables don't expand as expected.

This is my code:

import sys
from PyQt6 import QtWidgets, QtPrintSupport
from PyQt6.QtGui import QTextDocument


class MyWidget(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.table_widget = QtWidgets.QTableWidget()
        self.button = QtWidgets.QPushButton('Print TableWidget')
        self.layout = QtWidgets.QVBoxLayout(self)
        self.layout.addWidget(self.table_widget)
        self.layout.addWidget(self.button)
        self.button.clicked.connect(self.print_table)

    def print_table(self):
        html_table = '''
            <table cellpadding="0">
                <tr><th>header1</th><th>header2</th><th>header3</th></tr>
                <tr>
                    <td>data1</td>
                    <td>data2</td>
                    <td><table>
                        <tr>
                            <th>header1</th><th>header2</th><th>header3</th>
                        </tr>
                        <tr>
                            <td>data3</td><td>data3</td><td>data3</td>
                        </tr>
                    </table></td>
                </tr>
                <tr>
                    <td>data1</td>
                    <td>data2</td>
                    <td><table>
                        <tr>
                            <th>hr1</th><th>hr2</th><th>hr3</th><th>hr4</th>
                        </tr>
                        <tr>
                            <td>d3</td><td>d3</td><td>d3</td><td>d3</td>
                        </tr>
                        <tr>
                            <td>d3</td><td>d3</td><td>d3</td><td>d3</td>
                        </tr>
                    </table></td>
                </tr>
            </table>
        '''

        style_sheet = '''
            table {
                border-collapse: collapse; 
                width: 100%;
            }
            th {
                background-color: lightblue; 
                border: 1px solid gray; 
                height: 1em;
            }
            td {
                border: 1px solid gray; 
                padding: 0; 
                vertical-align: top;
            }
        '''
        text_doc = QTextDocument()
        text_doc.setDefaultStyleSheet(style_sheet)
        text_doc.setHtml(html_table)
        prev_dialog = QtPrintSupport.QPrintPreviewDialog()
        prev_dialog.paintRequested.connect(text_doc.print)
        prev_dialog.exec()

if __name__ == '__main__':
    app = QtWidgets.QApplication([])
    widget = MyWidget()
    widget.resize(640,480)
    widget.show()
    sys.exit(app.exec())

And this is what i get:

Screenshot of the result

But this is what i want:

Expected result

I would appreciate any suggestions about this problem, as I have no idea about how to fix it.


Solution

  • The rich text in Qt only provides a limited subset of HTML and CSS. More specifically, it only provides what QTextDocument allows, and the HTML parsing is therefore completely based on the QTextDocument capabilities.

    Complex layouts that may be easier to achieve with standard HTML and CSS in a common web browser, may be difficult if not impossible to get in Qt, and the documentation has to be carefully checked.

    Specifically, the width property is not listed in the CSS Properties list.
    Nonetheless, it is supported as an HTML attribute for many tags, including table and their cells.

    Note: there may be occurrences of tags/CSS behavior that works "as expected" even if not documented, but you shall not rely on those, unless it's been found consistent in multiple and previous Qt versions, and possibly upon careful checking of consistency in the sources, both for the parser, and the document layout structure.

    Just remove that property from the CSS, and use it as an attribute, for example:

    <table width="100%">
        ...
        <tr><td width="75%">
            <table width="100%">
                ...
            </table>
        </td></tr>
    </table>
    

    The above will make the parent table occupy the full viewport (or page) width, with the column containing the nested table being 75% of that width, and that table occupying the cell in full, horizontally.

    UPDATE

    As properly noted by ekhumoro's comment, though, there is a bug that actually prevents setting any numerical value of width, with the result that, no matter what, the table will always occupy almost the full width of the cell.

    In case you want the nested table to occupy just a percentage of the "parent cell", the work around for that is by setting percentages for each of its columns.

    Consider the following header syntax for the second nested table:

    <tr>
        <th width=20%>hr1</th>
        <th width=20%>hr2</th>
        <th width=20%>hr3</th>
        <th width=20%>hr4</th>
    </tr>
    

    Which, along with the first snippet above, will result in a table that properly occupies 80% of the cell width:

    enter image description here

    You'll probably notice a small margin on the left and top of the tables, which is probably caused by the border-collapse property, and another demonstration of the limitations of Qt's rich text engine, which was not intended for pixel-perfect rendering and initially implemented in a more "flexible" (not design-oriented) intention of hyper text display.

    A possible work around may be to completely avoid border collapse, and use the table attribute cellspacing=0 instead; it may not be always appropriate, but it works in your specific case, and can be eventually fixed with a more carefully written CSS based on class selectors.
    For instance by setting a class for the table and explicitly declaring the rules for cells (eg: table.nested td { ... }).

    As already noted, if you want more control on the layout/appearance and more compliant support for modern HTML and CSS standards, the only option is to use the QtWebEngine related classes. Be aware, though, that that module alone is more than 150MB in size, so consider that requirement carefully.