pythonqtsvgpyqt5qtwebengine

QWebEngineView cannot load large SVG/HTML using setHtml


I tried two different ways to load and show an SVG file in PyQt:

import sys
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import QUrl

app = QApplication(sys.argv)
webView = QWebEngineView()

# Variant 1: Reasonably fast
webView.load(QUrl('file:///Test.svg'))

# Variant 2: Slow for small files, not working for big ones
webView.setHtml('<svg>........')

webView.show()
sys.exit(app.exec_())

The first way works okay but requires a file as input. I would like to generate the SVGs dynamically though, so this is not really an option. Does anyone have an idea why the second approach is so darn slow or failing completely for more complex vector images?


Solution

  • UPDATE:

    A work-around for this issue is to use a custom url scheme handler, which bypasses the need to pass in the file contents via a data-url.

    Below is a basic demo which shows how implement this. This works with both PyQt5 and PyQt6 (just amend the imports):

    from PyQt5.QtCore import (
        QUrl, QByteArray, QBuffer, QIODevice,
    )
    from PyQt5.QtWidgets import (
        QApplication, QWidget, QPushButton, QCheckBox, QGridLayout,
    )
    from PyQt5.QtWebEngineCore import (
        QWebEngineUrlScheme, QWebEngineUrlSchemeHandler, QWebEngineUrlRequestJob,
        )
    from PyQt5.QtWebEngineWidgets import QWebEngineView
    
    
    HTML_DATA = {}
    URL_SCHEME = 'local'
    TEST_FILE = 'test.data'
    
    
    class UrlSchemeHandler(QWebEngineUrlSchemeHandler):
        def requestStarted(self, job):
            href = job.requestUrl().path()
            if (data := HTML_DATA.get(href)) is not None:
                if not isinstance(data, bytes):
                    data = str(data).encode()
                mime = QByteArray(b'text/html')
                buffer = QBuffer(job)
                buffer.setData(data)
                buffer.open(QIODevice.OpenModeFlag.ReadOnly)
                job.reply(mime, buffer)
            else:
                print(f'ERROR: request job failed: {href!r}')
                job.fail(QWebEngineUrlRequestJob.Error.UrlNotFound)
    
    
    class Window(QWidget):
        def __init__(self):
            super().__init__()
            self.button = QPushButton('Load Source')
            self.button.clicked.connect(self.handleButtton)
            self.option = QCheckBox('Use url scheme handler')
            self.view = QWebEngineView()
            self.view.page().profile().installUrlSchemeHandler(
                bytes(URL_SCHEME, 'ascii'), UrlSchemeHandler(self))
            self.view.loadFinished.connect(self.handleLoaded)
            layout = QGridLayout(self)
            layout.addWidget(self.view, 0, 0, 1, 2)
            layout.addWidget(self.button, 1, 0)
            layout.addWidget(self.option, 1, 1)
    
        def handleButtton(self):
            if self.option.isChecked():
                url = QUrl(TEST_FILE)
                url.setScheme(URL_SCHEME)
                self.view.setUrl(url)
            else:
                self.view.setHtml(HTML_DATA[TEST_FILE])
    
        def handleLoaded(self, ok):
            if not ok:
                self.view.setHtml('<h3>414: URI Too Long</h3>')
    
    
    if __name__ == '__main__':
    
        try:
            HTML_DATA[TEST_FILE] = open('large.svg').read()
        except OSError:
            HTML_DATA[TEST_FILE] = (html := f"""
                <html><head></head><body>
                <h3>Test Page ({{}} MB)</h3>{'''<p>
                Lorem ipsum dolor sit amet, consectetur adipiscing
                elit, sed do eiusmod tempor incididunt ut labore et
                dolore magna aliqua. Ut enim ad minim veniam, quis
                nostrud exercitation ullamco laboris nisi ut aliquip
                ex ea commodo consequat. Duis aute irure dolor in
                reprehenderit in voluptate velit esse cillum dolore
                eu fugiat nulla pariatur. Excepteur sint occaecat
                cupidatat non proident, sunt in culpa qui officia
                deserunt mollit anim id est laborum.
                </p>''' * 4000}</body></html>"""
                ).format(round(len(html) / 1e6, 1))
    
        scheme = QWebEngineUrlScheme(bytes(URL_SCHEME, 'ascii'))
        scheme.setFlags(QWebEngineUrlScheme.Flag.SecureScheme |
                        QWebEngineUrlScheme.Flag.LocalScheme |
                        QWebEngineUrlScheme.Flag.LocalAccessAllowed)
        QWebEngineUrlScheme.registerScheme(scheme)
    
        app = QApplication(['Test'])
        window = Window()
        window.setGeometry(600, 50, 800, 600)
        window.show()
        app.exec()
    

    PREVIOUS ANSWER:

    The setHtml function cannot load any content (not just svg) that is greater than 2MB in size. This is because Chromium uses a data: scheme url to load the content (which obvioulsy limits the size to the maximum length for a url). So it would seem that the only option is to load the svg from a local file.