【问题标题】:Print items to pdf将项目打印为 pdf
【发布时间】:2020-02-23 08:30:30
【问题描述】:

我有一个带有 QGraphicsScene 作为画家的窗口,我想在按下按钮时将其元素呈现为 pdf 文件。

def generateReport(self):
        lineList = {}
        for i in self.circleList:
            for j,k in i.lineItems:
                if j not in lineList:
                    lineList[j] = [i, k]
        printed = QPdfWriter("Output.pdf")
        printed.setPageSize(QPagedPaintDevice.A4)
        printer = QPainter(printed)
        self.painter.render(printer)
        for i,j in enumerate(lineList):
            # j.scene().render(printer)
            # lineList[j][0].scene().render(printer)
            # lineList[j][1].scene().render(printer)
            printer.drawText(0, self.painter.height() + i*200, f'{j.nameItem.toPlainText()}: {lineList[j][0].m_items[4].toPlainText()}, {lineList[j][1].m_items[4].toPlainText()}')
        printer.end()

j 上的nameItem 是线的名称标签,m_items[4] 是每个圆的名称标签。

我的问题是我似乎无法获得渲染场景的确切高度,此外,如果内容不适合,我如何将文本溢出到下一页的线索为零。

如果我能以某种方式为每个连接分别渲染每一行及其对应的圆圈,那将是很可爱的,存储在 lineList 中

注意:这条线是每个圈子的孩子,每条线和圈子的名字都是他们的孩子,实现方式与我的previous question 的答案大致相同,这就是我最后的握把问题项目也在被渲染。

我发现我可以创建一个新场景,逐个移动每个项目并将其渲染到 pdf 中,但这会引发两个不同的问题

  1. 我无法添加换行符并避免在前一个渲染上过度绘制新渲染,并且
  2. 我无法定位文本,因为 addText 不接受位置参数。

MRE:

import random
from fbs_runtime.application_context.PyQt5 import ApplicationContext
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPainter, QPdfWriter, QBrush, QPagedPaintDevice
from PyQt5.QtWidgets import (QDialog, QGraphicsScene,
                             QGraphicsView, QGridLayout,
                             QPushButton, QGraphicsEllipseItem)

class gui(QDialog):
    def __init__(self, parent=None):
        super(gui, self).__init__(parent)
        self.resize(1280, 720)
        self.painter = QGraphicsScene(0, 0, self.width() - 50, self.height() - 70)
        self.painter.setBackgroundBrush(QBrush(Qt.white))
        self.canvas = QGraphicsView(self.painter)
        mainLayout = QGridLayout()
        mainLayout.addWidget(self.canvas, 0, 0, -1, -1)
        self.setLayout(mainLayout)

    @property
    def circleList(self):
        return [item for item in self.painter.items() if isinstance(item, QGraphicsEllipseItem)]

    def newCircle(self):
        self.painter.addEllipse( random.randint(100, 400), random.randint(100, 400), 50 + random.random() * 200, 50 + random.random() * 200)

    def generateReport(self):
        printed = QPdfWriter("Output.pdf")
        printed.setPageSize(QPagedPaintDevice.A4)
        printer = QPainter(printed)
        self.painter.render(printer)
        for i,j in enumerate(self.circleList):
            printer.drawText(0, printer.viewport().height() + i*200, 'test')
        printer.end()


if __name__ == "__main__":
    app = ApplicationContext()
    test = gui()
    test.newCircle()
    test.newCircle()
    test.newCircle()
    test.generateReport()
    test.show()
    exit(app.app.exec_())

如果可能的话,打印、测试然后圈出所有圈子的能力对我来说已经足够了。

不正确的输出示例:

【问题讨论】:

  • @eyllanesc 您能否看一下我在原始帖子中编辑的输出示例?在此期间,我将尝试提供 MRE
  • 这是我的代码,它与您的问题无关,它可能是您项目的一部分,但不是您当前的问题。我建议每次你问一个问题,你说:我想在我的项目中实现 X,尝试 Y,它对我不起作用,那么你必须提供一个 MRE。我理解这个问题,但不是答案取决于你的代码。
  • 1) 我知道您正在研究 MRE,但我的建议是针对下一个问题。 2)图像有助于理解问题。 3) 抓取物品的解决方案是微不足道的。 4) 如果您提供 MRE,则不需要图像,因为您可以直接看到问题,从一开始就提供的 MRE 可以为我们节省所有时间。 :-)
  • 现在您让我感到困惑,因为 MRE 不会生成您提供的图像,您可以放置​​一张您想要为 MRE 获取的图像,因为我不清楚。

标签: python pyqt pyqt5


【解决方案1】:

要了解绘画是什么样的,你必须了解QGraphicsScene::render()方法的工作原理:

void QGraphicsScene::render(QPainter *painter, const QRectF &target = QRectF(), const QRectF &source = QRectF(), Qt::AspectRatioMode aspectRatioMode = Qt::KeepAspectRatio)

使用painter将源矩形从场景渲染到目标。这个 功能对于将场景的内容捕捉到 绘制设备,例如 QImage(例如,截屏),或用于 使用 QPrinter 打印。例如:

QGraphicsScene scene;
scene.addItem(...
...
QPrinter printer(QPrinter::HighResolution);
printer.setPaperSize(QPrinter::A4);

QPainter painter(&printer);
scene.render(&painter);

如果source是一个空矩形,这个函数将使用sceneRect() 确定要渲染的内容。如果目标是空矩形,则 将使用画家的油漆设备。

源矩形内容将根据 aspectRatioMode 以适应目标矩形。默认情况下,方面 比率保持不变,源被缩放以适应目标。

参见 QGraphicsView::render()。

在你的情况下,如果源不传递,整个sceneRect(0,0,1230,650)将被复制并绘制在pdf页面上,如果尺寸不匹配,尺寸将被缩放。所以从上面可以看出,如果你想打印一个项目,那么你必须将它在场景中占据的空间作为源传递并隐藏其他项目,目标是你要绘制的地方,这涉及到计算新的位置基于上一个项目的打印位置。

考虑到上述情况,一个可能的解决方案如下:

import random

from PyQt5 import QtCore, QtGui, QtWidgets


class Gui(QtWidgets.QDialog):
    def __init__(self, parent=None):
        super(Gui, self).__init__(parent)
        self.resize(1280, 720)
        self.scene = QtWidgets.QGraphicsScene(
            0, 0, self.width() - 50, self.height() - 70
        )
        self.scene.setBackgroundBrush(QtGui.QBrush(QtCore.Qt.white))
        self.canvas = QtWidgets.QGraphicsView(self.scene)
        mainLayout = QtWidgets.QGridLayout(self)
        mainLayout.addWidget(self.canvas)

    @property
    def circleList(self):
        return [
            item
            for item in self.scene.items()
            if isinstance(item, QtWidgets.QGraphicsEllipseItem)
        ]

    def newCircle(self):
        self.scene.addEllipse(
            random.randint(100, 400),
            random.randint(100, 400),
            50 + random.random() * 200,
            50 + random.random() * 200,
        )

    def generateReport(self):
        printer = QtGui.QPdfWriter("Output.pdf")
        printer.setPageSize(QtGui.QPagedPaintDevice.A4)
        printer.setResolution(100)
        painter = QtGui.QPainter(printer)
        delta = 20
        f = painter.font()
        f.setPixelSize(delta)
        painter.setFont(f)
        # hide all items
        last_states = []
        for item in self.scene.items():
            last_states.append(item.isVisible())
            item.setVisible(False)

        target = QtCore.QRectF(0, 0, printer.width(), 0)

        for i, item in enumerate(self.circleList):
            item.setVisible(True)
            source = item.mapToScene(item.boundingRect()).boundingRect()
            target.setHeight(source.height())
            if target.bottom() > printer.height():
                printer.newPage()
                target.moveTop(0)
            self.scene.render(painter, target, source)
            f = painter.font()
            f.setPixelSize(delta)
            painter.drawText(
                QtCore.QRectF(
                    target.bottomLeft(), QtCore.QSizeF(printer.width(), delta + 5)
                ),
                "test",
            )
            item.setVisible(False)
            target.setTop(target.bottom() + delta + 20)
        # restore visibility
        for item, state in zip(self.scene.items(), last_states):
            item.setVisible(state)
        painter.end()


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)
    w = Gui()
    for _ in range(200):
        w.newCircle()
    w.generateReport()
    w.show()
    sys.exit(app.exec_())

【讨论】:

    猜你喜欢
    • 2015-10-02
    • 1970-01-01
    • 2015-08-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-21
    • 2014-04-04
    相关资源
    最近更新 更多