1

I would like to render SVG files with PyQt5.

The simplest way to do that is to use QSvgGenerator applied on a QPainter object.

However, for some reason I have to render text in the output SVG file. To do that, having a QApplication running is compulsory because some components initialized during the execution of QApplication are needed. Otherwise, QPainter.drawText() methods end in a SEGFAULT.

I'm now able to generate text in my SVG file by creating a QSvgWidget object with handles the painting through the paintEvent method.

If I just run the application with the exec_ method, everything works fine. However I'm only interested in generating the SVG, so I don't want to be forced to close the main window with my mouse (I'd like to run my program on a headless server). Here is my base code:

app = QApplication(sys.argv)
drawer = MyDrawerClass()
drawer.show()
app.exec_()

and MyDrawerClass inheritates from QSvgWidget and implements printEvent method which is successfully called when executing the app.

So my question is: Is there a way to run the app a headless way and quit it after everything is rendered? I read a few things on QTimer but I can't find any example which suits my usage.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Benjamin Barrois
  • 2,566
  • 13
  • 30

1 Answers1

1

I assume you're referring to the approach used in your previous question.

Some painter capabilities require that a QGuiApplication is constructed before using them, and drawing text is amongst them, since it depends on the system's own GUI management (default fonts, DPI, etc). Usually one would use a standard QApplication, but, as reported in the documentation:

For "non-QWidget based Qt applications, use QGuiApplication instead, as it does not depend on the QtWidgets library".

This will make the creation of a QGuiApplication faster and lighter.

Unfortunately, a running display is mandatory for Q[Gui]Applications, so you will not be able to run it on a headless server, unless at least a minimal virtual X server is active. If you can manage to do that, the following example should work fine.

def createImage(width=400, height=400):
    rect = QRect(0, 0, width, height)
    generator = QSvgGenerator()
    generator.setFileName("test.svg")
    generator.setSize(rect.size())
    generator.setViewBox(rect)
    painter = QPainter(generator)
    painter.fillRect(rect, Qt.black)
    textRect = QRect(0, 0, 200, 200)
    textRect.moveCenter(rect.center())
    painter.setPen(Qt.white)
    painter.setBrush(Qt.darkGray)
    painter.drawRect(textRect)
    painter.drawText(textRect, Qt.AlignCenter, 'test')
    painter.end()

if __name__ == "__main__":
    app = QGuiApplication(sys.argv)
    createImage()

Since there's no need for an event loop (because there's no GUI interaction), you don't have to actually exec_(), and the program will automatically exit as soon as the paint function is returned.

musicamante
  • 41,230
  • 6
  • 33
  • 58
  • Thanks a lot, I'll definitely try this way with a Docker running a small X server. – Benjamin Barrois Aug 12 '19 at 21:27
  • I actually had already tried your solution, but I thought it didn't work because I didn't store QGuiApplication object in a variable (`app`) in your case. I had not thought Python would never create it, which obviously it didn't. – Benjamin Barrois Aug 13 '19 at 07:51
  • @BenjaminBarrois It *is* created, but since you're not assigning the instance to a variable within the scope, it's instance garbage collected as soon as it's initialization returns. – musicamante Aug 13 '19 at 15:32
  • Yes that's what I meant ;-) – Benjamin Barrois Aug 13 '19 at 17:09