在 QPdfWriter 上结合 QPainter 和 QTextDocument

Combining QPainter and QTextDocument on QPdfWriter

在另一个问题中,我了解到 QTextDocument,我 可以在 PDF 的同一页上使用 QPainterQTextDocument。但是,当我尝试这样做时,他们各自重新启动了文档,抹去了对方的内容。

from PySide6.QtGui import QPdfWriter, QPainter, QPageSize, QTextDocument, Qt
from PySide6.QtWidgets import QApplication


def main():
    app = QApplication()
    pdf = QPdfWriter('example.pdf')
    pdf.setPageSize(QPageSize.Letter)

    # Whichever of these goes second, overwrites the first.
    draw_diagram(pdf)
    print_document(pdf)

    app.exit(0)


def draw_diagram(pdf: QPdfWriter):
    painter = QPainter(pdf)
    painter.drawArc(painter.window().width()//4,
                    painter.window().height()//2 - painter.window().width()//4,
                    painter.window().width()//2,
                    painter.window().width()//2,
                    0,
                    5760)
    painter.drawText(0,
                     painter.window().height()//2,
                     painter.window().width(),
                     painter.window().height()//10,
                     Qt.AlignHCenter | Qt.AlignTop,
                     'https://donkirkby.github.io')
    print(pdf.newPage())
    painter.drawText(painter.window().width()//2,
                     painter.window().height()//2,
                     'Bar')
    print(pdf.newPage())
    painter.end()


def print_document(pdf: QPdfWriter):
    html = "<a href='https://donkirkby.github.io'>donkirkby.github.io</a>"

    document = QTextDocument()
    document.setHtml(html)
    document.print_(pdf)


main()

理想情况下,我希望在同一页上显示文本和绘图,但此代码试图将它们分开,以免它们相互覆盖。这两种方式都行不通。

如何将绘图与文本文档结合起来? QTextCursor 有帮助吗?

问题是每次设置 QPainter 时都会重置 QPdfWriter。一个可能的解决方案是使用相同的 QPainter 而不是打印方法,您应该使用 drawContents,您还必须手动处理分页。

from PySide6.QtGui import QPdfWriter, QPainter, QPageSize, QTextDocument, Qt
from PySide6.QtWidgets import QApplication


def main():
    app = QApplication()
    pdf = QPdfWriter("example.pdf")
    pdf.setPageSize(QPageSize.Letter)

    painter = QPainter(pdf)

    draw_diagram(painter, pdf)
    print_document(painter, pdf)

    painter.end()


def draw_diagram(painter: QPainter, pdf: QPdfWriter):
    painter.drawArc(
        painter.window().width() // 4,
        painter.window().height() // 2 - painter.window().width() // 4,
        painter.window().width() // 2,
        painter.window().width() // 2,
        0,
        5760,
    )
    painter.drawText(
        0,
        painter.window().height() // 2,
        painter.window().width(),
        painter.window().height() // 10,
        Qt.AlignHCenter | Qt.AlignTop,
        "https://donkirkby.github.io",
    )
    print(pdf.newPage())
    painter.drawText(
        painter.window().width() // 2, painter.window().height() // 2, "Bar"
    )
    print(pdf.newPage())


def print_document(painter: QPainter, pdf: QPdfWriter):
    document = QTextDocument()
    document.documentLayout().setPaintDevice(pdf)

    html = "<a href='https://donkirkby.github.io'>donkirkby.github.io</a>"
    document.setHtml(html)

    document.drawContents(painter)


if __name__ == "__main__":
    main()

虽然我最后用了 eyllanesc's answer of switching from document.print_() to document.drawContents(), I also experimented with QTextCursor and QPyTextObject. This TextObject example 介绍的很好。我想,如果我需要分布在多个页面上,它会很有用。

这是我从问题转换为使用 QPyTextObject:

的示例
from PySide6.QtCore import QSizeF, QRectF
from PySide6.QtGui import (QPdfWriter, QPainter, QPageSize, QTextDocument, Qt, QPyTextObject, QTextFormat, QTextCursor,
                           QTextCharFormat)
from PySide6.QtWidgets import QApplication

DIAGRAM_TEXT_FORMAT = QTextFormat.UserObject + 1
DIAGRAM_DATA = 1
OBJECT_REPLACEMENT = chr(0xfffc)


def main():
    app = QApplication()
    pdf = QPdfWriter('example.pdf')
    pdf.setPageSize(QPageSize.Letter)

    print_document(pdf)

    app.exit(0)


class Diagram(QPyTextObject):
    def __init__(self, parent=None):
        super().__init__(parent)

    # noinspection PyPep8Naming,PyShadowingBuiltins
    def intrinsicSize(self,
                      doc: QTextDocument,
                      posInDocument: int,
                      format: QTextFormat) -> QSizeF:
        diameter = doc.textWidth()/4
        return QSizeF(doc.textWidth(), diameter)

    # noinspection PyPep8Naming,PyShadowingBuiltins
    def drawObject(self,
                   painter: QPainter,
                   rect: QRectF,
                   doc: QTextDocument,
                   posInDocument: int,
                   format: QTextFormat):
        diameter = rect.height()
        message = format.property(DIAGRAM_DATA)
        painter.drawArc((rect.width() - diameter) // 2,
                        rect.y(),
                        diameter,
                        diameter,
                        0,
                        5760)
        painter.drawText(rect,
                         Qt.AlignHCenter | Qt.AlignVCenter,
                         message)


def print_document(pdf: QPdfWriter):
    html = """\
<h1>Title</h1>
<p>Lorem ipsum
<a href='https://donkirkby.github.io'>donkirkby.github.io</a>
dolores sit amet.</p>
"""

    document = QTextDocument()
    document.setPageSize(QSizeF(pdf.width(), pdf.height()))
    font = document.defaultFont()
    font.setPixelSize(pdf.height()//60)
    document.setDefaultFont(font)
    diagram_handler = Diagram()
    doc_layout = document.documentLayout()
    doc_layout.registerHandler(DIAGRAM_TEXT_FORMAT, diagram_handler)

    document.setHtml(html)
    cursor = QTextCursor(document)
    cursor.movePosition(cursor.End)
    cursor.insertText('\n')
    
    diagram_format = QTextCharFormat()
    diagram_format.setObjectType(DIAGRAM_TEXT_FORMAT)

    for i in range(1, 11):
        cursor.insertHtml(f'<h3>Heading {i}</h3>')
        cursor.insertText('\n')
        diagram_format.setProperty(DIAGRAM_DATA, f'Message {i} in a circle')
        cursor.insertText(OBJECT_REPLACEMENT, diagram_format)

    document.print_(pdf)


main()

我仍然不喜欢这种方法的一点是可以在标题和图表之间添加分页符。我会问一个跟进的问题。