0

I'm writing a program that uses a generic template that was converted from PDF to HTML to create personalized reports for individuals. In order to convert the final HTML files back to PDF I am using PyQT5 and its printToPdf method. It works perfectly once, but the program hangs until I close the widget view that opens, at which point it segfaults and ends the entire python program. How can I close the program peacefully programmatically so that I can render all of the HTML in one sweep? Perhaps there is some way not to forfeit the thread over to the widget?

Here is my current code.

for htmlFileAsString in files:

   app = QtWidgets.QApplication(sys.argv)
   loader = QtWebEngineWidgets.QWebEngineView()
   loader.setZoomFactor(1)
   loader.setHtml(htmlFileAsString)
   loader.page().pdfPrintingFinished.connect(
     lambda *args: print('finished:', args))
   def emit_pdf(finished):
     loader.show()
     loader.page().printToPdf('output/' + name + '/1.pdf')

   loader.loadFinished.connect(emit_pdf)
   app.exec()
TJohn2017
  • 1
  • 1

2 Answers2

0

As stated in the ekhumoro answer, the problem is that you cannot create several QApplication (you must review the answer indicated for more details), so applying the same technique the solution is as follows:

import os
from PyQt5 import QtWidgets, QtWebEngineWidgets


class PdfPage(QtWebEngineWidgets.QWebEnginePage):
    def __init__(self):
        super().__init__()
        self._htmls_and_paths = []
        self._current_path = ""

        self.setZoomFactor(1)
        self.loadFinished.connect(self._handleLoadFinished)
        self.pdfPrintingFinished.connect(self._handlePrintingFinished)

    def convert(self, htmls, paths):
        self._htmls_and_paths = iter(zip(htmls, paths))
        self._fetchNext()

    def _fetchNext(self):
        try:
            self._current_path, path = next(self._htmls_and_paths)
        except StopIteration:
            return False
        else:
            self.setHtml(html)
        return True

    def _handleLoadFinished(self, ok):
        if ok:
            self.printToPdf(self._current_path)

    def _handlePrintingFinished(self, filePath, success):
        print("finished:", filePath, success)
        if not self._fetchNext():
            QtWidgets.QApplication.quit()


if __name__ == "__main__":

    current_dir = os.path.dirname(os.path.realpath(__file__))

    paths = []
    htmls = []
    for i in range(10):
        html = """<html>
    <header><title>This is title</title></header>
    <body>
    Hello world-{i}
    </body>
    </html>""".format(
            i=i
        )
        htmls.append(html)
        paths.append(os.path.join(current_dir, "{}.pdf".format(i)))

    app = QtWidgets.QApplication([])
    page = PdfPage()
    page.convert(htmls, paths)
    app.exec_()

    print("finished")
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • Thanks. With minor changes this mostly works. Do you know if there is any way not to emit a segfault when the app is done? I would like to continue working in the original python script if possible. Also, some pdfs (all from same template) are only showing up with background image (no text) and I'm not sure why. – TJohn2017 Oct 18 '19 at 10:28
  • TJohn2017 I'm having the same problem. My script crashes after I use pyqt5 to generate a pdf, did you solve this issue? – Jaime38130 Nov 17 '21 at 12:05
0

I don't have enough reputation on stack overflow to comment, so I'm posting a response.

The code @eyllanesc posted needs to be editet a bit to work:

import os
from PyQt5 import QtWidgets, QtWebEngineWidgets


class PdfPage(QtWebEngineWidgets.QWebEnginePage):
    def __init__(self):
        super().__init__()
        self._htmls_and_paths = []
        self._current_path = ""

        self.setZoomFactor(1)
        self.loadFinished.connect(self._handleLoadFinished)
        self.pdfPrintingFinished.connect(self._handlePrintingFinished)

    def convert(self, htmls, paths):
        self._htmls_and_paths = iter(zip(htmls, paths))
        self._fetchNext()

    def _fetchNext(self):
        try:
            self._current_html, self._current_path = next(self._htmls_and_paths)
        except StopIteration:
            return False
        else:
            self.setHtml(self._current_html)
        return True

    def _handleLoadFinished(self, ok):
        if ok:
            self.printToPdf(self._current_path)

    def _handlePrintingFinished(self, filePath, success):
        print("finished:", filePath, success)
        if not self._fetchNext():
            QtWidgets.QApplication.quit()


if __name__ == "__main__":

    current_dir = os.path.dirname(os.path.realpath(__file__))

    paths = []
    htmls = []
    for i in range(10):
        html = """<html>
    <header><title>This is title</title></header>
    <body>
    Hello world-{i}
    </body>
    </html>""".format(
            i=i
        )
        htmls.append(html)
        paths.append(os.path.join(current_dir, "{}.pdf".format(i)))

    app = QtWidgets.QApplication([])
    page = PdfPage()
    page.convert(htmls, paths)
    app.exec_()

    print("finished")

Concretely, the inputs to setHtml and printToPdf needed to be changed.

DavidBoja
  • 21
  • 1
  • 3