PyQt6: QSvgWidget does not render when loading a QByteArray instead of a file
-
I'm using PyQt6 with python 3.12
I want to display a diagram with live values inside a GroupBox widget to show a process values.
For that, i want to use a SVG that is not loaded from a file but generated at each values changes.QSvgWidget allow to load a QByteArray that contains an svg but it does not work. The viewbox space is reserved but nothing is drown.
My test svg is :
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg version="1.1" viewBox="0 0 100 100" width="100px" height="100px" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> <defs> <style type="text/css"> .shape-other{fill:#0852f8;stroke:none;} .text-channel{fill:#fefefe;text-anchor: middle; font-size:28px;} </style> </defs> <rect class="shape-other" x="0" y="0" width="100" height="100" rx="2" ry="2" /> <text class="text-channel" x="50" y="50">TEXT</text> </svg>
Loading the svg from a file works well:
OpDiagram = QSvgWidget("./images/TestDiagram.svg")
But loading from inline bytes string does not: no error reported by Qt
RawSvg= """ <?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg version="1.1" viewBox="0 0 100 100" width="100px" height="100px" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> <defs> <style type="text/css"> .shape-other{fill:#0852f8;stroke:none;} .text-channel{fill:#fefefe;text-anchor: middle; font-size:28px;} </style> </defs> <rect class="shape-other" x="0" y="0" width="100" height="100" rx="2" ry="2" /> <text class="text-channel" x="50" y="50">TEXT</text> </svg> """ OpDiagram = QSvgWidget() # can't load direct content at init ; need to call load to do so in a second stage OpDiagram.load(QByteArray(bytes(RawSvg, 'utf-8')))
Can someone explain why the loading of QByteArray stored svg screwed up ?
-
I'm using PyQt6 with python 3.12
I want to display a diagram with live values inside a GroupBox widget to show a process values.
For that, i want to use a SVG that is not loaded from a file but generated at each values changes.QSvgWidget allow to load a QByteArray that contains an svg but it does not work. The viewbox space is reserved but nothing is drown.
My test svg is :
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg version="1.1" viewBox="0 0 100 100" width="100px" height="100px" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> <defs> <style type="text/css"> .shape-other{fill:#0852f8;stroke:none;} .text-channel{fill:#fefefe;text-anchor: middle; font-size:28px;} </style> </defs> <rect class="shape-other" x="0" y="0" width="100" height="100" rx="2" ry="2" /> <text class="text-channel" x="50" y="50">TEXT</text> </svg>
Loading the svg from a file works well:
OpDiagram = QSvgWidget("./images/TestDiagram.svg")
But loading from inline bytes string does not: no error reported by Qt
RawSvg= """ <?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg version="1.1" viewBox="0 0 100 100" width="100px" height="100px" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> <defs> <style type="text/css"> .shape-other{fill:#0852f8;stroke:none;} .text-channel{fill:#fefefe;text-anchor: middle; font-size:28px;} </style> </defs> <rect class="shape-other" x="0" y="0" width="100" height="100" rx="2" ry="2" /> <text class="text-channel" x="50" y="50">TEXT</text> </svg> """ OpDiagram = QSvgWidget() # can't load direct content at init ; need to call load to do so in a second stage OpDiagram.load(QByteArray(bytes(RawSvg, 'utf-8')))
Can someone explain why the loading of QByteArray stored svg screwed up ?
@ErwanM
To my knowledge both PySide and PyQt treatbytes
asQByteArray
internally (just like they treatstr
asQString
). Did you try just passing thebytes(...)
toload()
without theQByteArray
conversion? Just a thought. I do not know whether theutf-8
is right or not (probably is), never have understood! You might also verify that the return result of thebytes()
and/or theQByteArray()
have about the same size/number of bytes as the original file content/string.Having said that, I see from https://stackoverflow.com/questions/77792905/pyside2-load-svg-from-variable-rather-than-from-file (also https://stackoverflow.com/a/52838067/489865) that they seem to do much as you do. Try exactly that example and verify whether that works for you? I note they use
svgWidget.renderer().load(svg_bytes)
rather than callingload()
directly on theQSvgWidget
. Does that make any difference? It might be that in older PySide/PyQts the latter was not available, I don't know, but maybe worth a try? -
I've found the problem: it's a pure python syntax problem.
Adding a carriage return in the first line of the string put the xml header in line 2 so the entire content is ignored at load.RawSvg= """<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg version="1.1" viewBox="0 0 100 100" width="100px" height="100px" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> <defs> <style type="text/css"> .shape-other{fill:#0852f8;stroke:none;} .text-channel{fill:#fefefe;text-anchor: middle; font-size:28px;} </style> </defs> <rect class="shape-other" x="0" y="0" width="100" height="100" rx="2" ry="2" /> <text class="text-channel" x="50" y="50">TEXT</text> </svg> """
-
I've found the problem: it's a pure python syntax problem.
Adding a carriage return in the first line of the string put the xml header in line 2 so the entire content is ignored at load.RawSvg= """<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg version="1.1" viewBox="0 0 100 100" width="100px" height="100px" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> <defs> <style type="text/css"> .shape-other{fill:#0852f8;stroke:none;} .text-channel{fill:#fefefe;text-anchor: middle; font-size:28px;} </style> </defs> <rect class="shape-other" x="0" y="0" width="100" height="100" rx="2" ry="2" /> <text class="text-channel" x="50" y="50">TEXT</text> </svg> """
@ErwanM
Ah, I did not realise that the<?xml ...
must appear starting from the very first byte/line of the input, not just after any whitespace (such as a CR-LF). Good spot.https://en.wikipedia.org/wiki/XML only states of
<?xml ...
XML declaration
XML documents may begin with an XML declaration that describes some information about themselves. An example is
<?xml version="1.0" encoding="UTF-8"?>
.It does not define precisely what "begin with" constitutes. I don't know whether this behaviour is a Qt thing. The answer at https://superuser.com/a/1509336/479430 may hint at this exact behaviour, I'm not sure.
-
@JonB many thanks for your links.
My own search leaded me to similar answers that where written for Pyside2 or PyQt5 but none of them was really relevant for my case.I can now go further and make my SVG produced dynamically by a refresh function. I will see if loading another svg will trigger the rendering. Yous links could be interesting if rendering does not occur in that case.
Concerning utf-8, no one should use any other encoding now for python scripts, svg or any text file unless having a very good reason to use another encoding. With windows API, utf16-le is also common but it's a niche.
-
@JonB many thanks for your links.
My own search leaded me to similar answers that where written for Pyside2 or PyQt5 but none of them was really relevant for my case.I can now go further and make my SVG produced dynamically by a refresh function. I will see if loading another svg will trigger the rendering. Yous links could be interesting if rendering does not occur in that case.
Concerning utf-8, no one should use any other encoding now for python scripts, svg or any text file unless having a very good reason to use another encoding. With windows API, utf16-le is also common but it's a niche.
-
@ErwanM
Technically this has nothing to do with PySide, PyQt or Python. I imagine you would get same problem if you sent it from C++ or wherever with preceding whitespace, or if you edited your external file and put a blank line at the very start.@JonB the link to python comes from the here string syntax: the string start just after the triple quote and not at the next line.
In others language like perl, the here string start at the next line to preserve the first line indent.C++ 11 introduced a here string like construction and, again, the raw string start at the next line.
So this error is in part due to the language syntax for here string.
-
Well if you don't know whether your language happens to put in or not put a linefeed in whatever string literal construct you choose to use that is a problem. But the error here is the due to the parsing of the XML requiring there be no leading whitespace, that's all I'm saying.
@ErwanM said in PyQt6: QSvgWidget does not render when loading a QByteArray instead of a file:
C++ 11 introduced a here string like construction and, again, the raw string start at the next line.
Pardon/really? Not to the best of my knowledge, but I may be at fault. Which construct are you talking about (example please) and where do you claim " the raw string start at the next line" is the case? The C++ literals I can think of (e.g.
R"(...)"
) would not only "start at the next line", the initial line where the string literal opener is placed would "count" towards the string content. Perl or Linux shells have the "here document", and that indeed does not count the start line, only the following line below, but that's not C++.