2

I've tested at least 20 different workarounds for this.

I want to print a widget.

My widget is an Invoice and it have inside a lot of other widgets like QLineEdits, QLabels with text, QLabels that act as images, QDropDown menus, etc. This is a Screenshot (all data inside like the N.I.F are temporary placeholders for a real business):

My invoice

So it should be easy, right? It does not seem so.

  1. First I tried to sent it to Printers, and that alone was a very hard job dealing with lot of different printing specific options. After all my research I found QPrintDialog deals with most of the stuff
  2. After testing that for a while I noticed it grabs the widget quite fine, but it prints it to a very tiny section, so after much research I found a way to scale that, this is what I ended up with.

Code:

def print_as_pdf(widgettoprint):
    # Printer
    printer = QPrinter()
    setupdialog = QPrintDialog(printer, widgettoprint)
    if setupdialog.exec() == QPrintDialog.Accepted:
        # Renderer
        painter = QPainter(printer)
        painter.begin(printer)
        # Scale it
        xscale = printer.pageRect().width() / widgettoprint.width()
        yscale = printer.pageRect().height() / widgettoprint.height()
        scale = min(xscale, yscale)
        painter.translate(printer.paperRect().x() + printer.pageRect().width() / 2,
                          printer.paperRect().y() + printer.pageRect().height() / 2)
        painter.scale(scale, scale)
        painter.translate(-1 * widgettoprint.width() / 2, -1 * widgettoprint.height() / 2)
        widgettoprint.render(painter)
        painter.end()
        return True
    else:
        return False

It does the trick, it prints an entire A4 page as intended, but the quality of the texts is quite bad. I found out it just takes an screenshot of it instead of saving the data as text or vector data.

So that wouldn't work for me, but I had to try. I tried to print as PDF with the same method (choosing PDF as a printer) and it just generates a PDF with an screenshot on it. I can't select or copy the text data.

  1. So I researched for alternatives. I found about Reportlab but couldn't make it work. I also tried PyFPDF, but especifically creating a PDF without choosing a printer would break the ability to actually print the invoice to a different printer.
  2. Okay, days of frustration spent trying to find alternatives without rebuilding from scratch my 7000 lines of code program. I found this blog about jinja2 and qt workaround and decided to give it a try. Re-creating the entire invoice as an HTML template would be a fair amount of work (as there is a lot more different invoices, not just this one). But a new problem arises, PyQt5 doesn't have QtWebKit or QWebView, and as far as I could get was to print a white empty PDF with this code and QtWebEngine:

Code:

import os
from jinja2 import Environment, FileSystemLoader
from PyQt5 import QtWebEngineWidgets
from PyQt5.QtPrintSupport import QPrinter, QPrintDialog
from PyQt5.QtGui import QPainter

PATH = os.path.dirname(os.path.abspath(__file__))
TEMPLATE_ENVIRONMENT = Environment(
    autoescape=False,
    loader=FileSystemLoader(os.path.join(PATH, 'userfiles/templates')),
    trim_blocks=False)

def render_template(template_filename, context):
    return TEMPLATE_ENVIRONMENT.get_template(template_filename).render(context)

def test():
    something = ['something1', 'something2', 'something3']
    context = {
        'something': something,
        'invoicenumber': "17012"
    }
    # Generate the HTML data with the template without creating a file, on-the-fly
    html = render_template('invoices.html', context)

    # add html to a webengine/widget
    web = QtWebEngineWidgets.QWebEngineView()
    web.page().setHtml(html)

    # Print that widget
    printer = QPrinter()
    setupdialog = QPrintDialog(printer)
    if setupdialog.exec() == QPrintDialog.Accepted:
        web.render(printer)
        print("done")

test()

Where the HTML template is:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <title>Factura {{ invoicenumber }}</title>
</head>
<body>
<center>
    <h1>Factura {{ invoicenumber }}</h1>
    <p>{{ something|length }} something</p>
</center>
<ol align="left">
{{ something[0] }}
{{ something[1] }}
{{ something[2] }}
</ol>
</body>
</html>

I will probably spend more weeks and months trying to make that works. Printing is something that I though it should had been really easy, as every random program in the market seems to be able to print or create PDFs, but it's now for me the worst part of my programming learning experience.

The goal: This question aims to find a permanent solution to my problem: how to print to a selectable (windows platform) printer my invoice widget? And then, if PDF printer is chosen, print it with endless quality, using vectors or anything that is just don't create an screenshot.

I'd also appreciate explanations of what I am doing wrong as I'm still learning (both programming, Python and Qt).

halfer
  • 19,824
  • 17
  • 99
  • 186
Saelyth
  • 1,694
  • 2
  • 25
  • 42
  • You need to `show()` the web-view in order to get some visible output. However, when I tried this on linux, the results weren't all that impressive. If you're on windows, you might want to try creating the printer like this: `QPrinter(QPrinter.HighResolution)`. – ekhumoro Sep 09 '17 at 20:13
  • PS: AFAICS, rendering via a web-view will just create a pdf that contains an image. So it doesn't really seem to get you anywhere, if I undertand your requirements correctly. – ekhumoro Sep 09 '17 at 20:19
  • Hm then indeed it won't work either. What should I do? – Saelyth Sep 09 '17 at 20:20
  • Have you read [Handling PDF](https://wiki.qt.io/Handling_PDF) on the Qt Wiki? This has quite a few useful suggestions. – ekhumoro Sep 09 '17 at 20:29
  • 2
    Actually, I think you can achieve what you want using `QTextDocument` - and I have even already provided examples of how to do it on SO! See [this answer](https://stackoverflow.com/a/12100960/984421) and [this answer](https://stackoverflow.com/a/22335115/984421). I just tested the first one, and can confirm that it is possible to select and copy individual text elements of the resulting pdf. – ekhumoro Sep 09 '17 at 20:39
  • Finallyyyyyyyyyyyyyy. Yes, your first answer works like a charm (at least for html code generated with jinja, which is enough for my program to work. I'll try to make it works with normal widgets and not html, but so far it WORKS!). Thank you. – Saelyth Sep 10 '17 at 11:01
  • Hm... or not. Tested the first answer for 24 hours and found out that it won't work for stuff like Div float properties, images, and @font-face at CSS. I need those 3 so... stuck again. – Saelyth Sep 12 '17 at 11:22
  • Well, yes, of course it is limited to the [HTML Subset](https://doc.qt.io/qt-5/richtext-html-subset.html) that is supported by Qt's rich-text engine. If you want premium-quality PDF, you will obviously have to use a dedicated third-party solution that has all the features. However, the form shown in your screenshot looks well within the capabilities of Qt's rich-text engine. Certainly images and the float css property are supported (to some extent) - not @font-face, though. – ekhumoro Sep 12 '17 at 15:42
  • You are right. I'm playing with it for a while and almost works as intended, my only issue now is a big Margin at the printed PDF both at left side and top side. All my efforts so far trying to remove that margin are useless. – Saelyth Sep 14 '17 at 15:50
  • Screenshot: http://prntscr.com/gl5422 (I want to remove the red borders). – Saelyth Sep 14 '17 at 15:56
  • Try: `printer.setPageMargins(0, 0, 0, 0, QPrinter.Point)`. – ekhumoro Sep 14 '17 at 16:55
  • Nope. I tried all this: http://www.qtcentre.org/threads/68741-Remove-the-Margins-of-a-QtextEdit-or-QtextDocument-when-printing – Saelyth Sep 14 '17 at 20:56
  • Works fine with Qt4 (I only tried printing to PDF, though). So apparently something has changed in Qt5. – ekhumoro Sep 14 '17 at 22:23
  • Setting margins is buggy in Qt5. See [QtBUG-32987](https://bugreports.qt.io/browse/QTBUG-32987). – ekhumoro Sep 15 '17 at 00:00
  • Found a workaround randomly googling examples. This seems to work: `self.textEdit.document().setPageSize(QSizeF(printer.pageRect().size()))` as it remove those borders. I don't get why yet but I'll continue researching. – Saelyth Sep 15 '17 at 01:33

1 Answers1

1

Although I myself program in python, I have little knowledge about programming GUI in it. However, I found several questions and sites that can be a starting point to your own implementation. I know some of these are QT4 but please, have a look at them, I believe that things didn't change so much (but I can be wrong):
Pyqt take screenshot of certain screen area
Screenshot of a window using python
QT documentation Screenshot example
https://github.com/Python-Devs-Brasil/PyQt5-Tutorials/blob/master/Post-2(QtCore)/screenshot.py
https://github.com/baoboa/pyqt5/blob/master/examples/desktop/screenshot.py
How to grab a desktop screenshot with PyQt?

EDIT
Have you tried saving to a pdf file via matplotlib?
https://pythonspot.com/en/matplotlib-save-figure-to-image-file/
saving images in python at a very high quality

EDIT 2
Also, have a look at this
Create a pdf with fillable fields python

EDIT 3
You can also have a look at documentations and examples of libraries mentioned in this question
Python PDF library

Colonder
  • 1,556
  • 3
  • 20
  • 40
  • I appreciate your answer, but I think you missed the goal :(. All those links are aimed to capture screenshots, which I'm already able to do. My goal is not that. My goal is to print a PDF as "not" an screenshot because otherwise you can't use your mouse to Select data on the PDF. The solution wanted needs to render a PDF as a layout (think on that conversion as an example of PDF to HTML. If the result is just an screenshot, the point of it being an HTML is useless. A PNG would work. But you need it HTML to select data/capture/edit data). I need my invoice as PDF, but not an screenshot. – Saelyth Sep 09 '17 at 20:10
  • That 2nd Edit miiight be what I need. I'm checking it. – Saelyth Sep 09 '17 at 20:21
  • Please, let me know whether the question in the 2nd edit was helpful. – Colonder Sep 09 '17 at 20:34