The Qt Less Traveled Fixing Bugs in Qt's Lesser-used Features, for Profit (and fun!)
Qt is a high quality library, but it is also massive in number of features. This massive number results in a higher likelihood that there are bugs that go unnoticed lying around in its less frequently used features or combination of features.
Our friends at qgis.org hired us to solve some of those issues that had been affecting them for a while. QGIS is an open source geospatial information system (an application which allows users to design maps, analyze spatial data, and perform various ETL tasks) that makes use of the Qt library for its GUI components and relies on the QPainter framework for all of its map rendering and exporting functionality. Instead of trying to workaround these issues in their downstream project, the QGIS leadership instead invested some project funds to contract KDAB to fix these rare issues which were specifically affecting the QGIS application. Keep reading to follow our adventures through those less often traveled paths.
SVG and Length Definitions
SVG, Scalable Vector Graphics, is commonly used to share graphics that need to be drawn at arbitrary resolutions. We fixed two issues in that area regarding the parsing of elements:
SVG has several specifications, 2.0, 1.1, and Tiny 1.2, each supporting different features. The Qt documentation is relatively clear that it supports a subset of features of Tiny 1.2: https://doc.qt.io/qt-5/svgrendering.html. That’s fine when you control the SVG that your application renders; your designers are a bit sad but can usually adapt to it. It gets much more complicated, however, when your application can use SVG files provided by the users. It’s relatively harder to explain the distinctions of the several SVG specifications to a normal user.
You can choose multiple units to represent SVG elements. Take, for example, the x,y properties of a text element. The definition in the specification (https://www.w3.org/TR/SVGTiny12/text.html#TextElement) is a bit long. But, after a few clicks, you end up with something that says:
A length is a distance measurement. The format of a <length> is a <number> optionally followed by a unit identifier. If the <length> is expressed as a value without a unit identifier (e.g., '48'), then the <length> represents a distance in the current user coordinate system. SVG Tiny 1.2 only supports optional units on the 'width' and 'height' attributes on the 'svg' element. These can specify values in any of the following units: in, cm, mm, pt, pc, px and %.
If we do the same for the SVG 1.1 spec, it doesn’t have that restriction of optional units only being valid for the width and height properties.
Here, we colored a bit outside the lines because the Qt code was fine. It said that it supports SVG Tiny 1.2 and, as such, misrendered files that use unsupported features (like saying x=”15pt”), but given that supporting that feature was less than 10 lines. See https://codereview.qt-project.org/c/qt/qtsvg/+/376065 and https://codereview.qt-project.org/c/qt/qtsvg/+/376066. We applied the Robustness principle and allowed ourselves to be a bit more flexible in what we accept.
QFont Ignored Stretch Value When Used Together with styleName
Now, let’s talk about one of those combinations of two features that are probably not often used together.
You can tell QFont to stretch the font to a certain width:
You can also tell QFont to change the style of a given font, in this example to bold:
However, if you tell it to do both things, Qt forgets to do the stretching. After some digging, the fix ended up being a single line of code: https://codereview.qt-project.org/c/qt/qtbase/+/373727.
QPainterPath Did Not Respect SmallCaps in Some Situations
This bug was 10 years old! Let’s compare the old rendering (first below) and the fixed one (second below)
SmallCaps is that feature that says, “render lowercase characters using (smaller) capital letters.”
The old rendering was half right, the letters positions were correct, but they where not “small”. This means that the letters ended up overlapping each other.
Like the previous fix, this was also a one liner: https://codereview.qt-project.org/c/qt/qtbase/+/373741 (The commit log is much longer than the actual fix). We also added some tests, in hopes of preventing regression: https://codereview.qt-project.org/c/qt/qtbase/+/374874.
QFontComboBox and Broken Fonts
QFontComboBox is a class used to select fonts. It shows the name of the font rendered in the font itself so you can see how it looks. Rendering the name of the font in its font is not always possible since there are some “Symbol” fonts that don’t have all the characters. In such cases, Qt renders the name of the font in your normal font, followed by adding a few characters of the font itself.
For that feature to work, the font has to correctly identify itself as a Symbol font. Sadly, that’s not always the case, as it’s not the case with the D050000L font that comes with most Linux distributions, for example. Here’s how that font name is rendered:
It’s definitely not easy to figure out what all those pencils are.
Our first attempt at fixing that was saying, “Well, the type of font is not being properly detected, so let’s add a function so we can override the detected type.” We suggested a function called QFontDatabase::replaceWritingSystems to override the detected font type and get us this:
After some back and forth with the reviewers, we ended up discarding that in favor of a more generic solution. That consisted of adding two new functions, QFontComboBox::setDisplayFont and QFontComboBox::setSampleText, that allow you to set which font and text will be used to display the preview of the font in question. Thus, we are able to workaround the initial issue while also adding extra functionality — a win for everyone! 🙂
Getting Others to Fix Bugs for You
Sometimes when you try to fix a bug, your fix is not good enough. Fortunately, this doesn’t mean that all hope is lost. Your analysis of the situation may help the reviewers come up with a better solution.
This has happened twice in this series of bug fixes; let’s look at them.
Brush Transformations When Printing to a PDF File
Since brush transformations were not supported, I added a flag to the PDF printing engine that says so: https://codereview.qt-project.org/c/qt/qtbase/+/373978. Unfortunately, that flag regressed other features (the flags that mention which features are supported are not super detailed), so it had to be abandoned. But Eirik Aavitsland from The Qt Company came to the rescue and actually implemented support of brush transformations when printing to a PDF file with a one liner (as seen already a few times in this blog): https://codereview.qt-project.org/c/qt/qtbase/+/374484.
QDockArea Sizing When Starting an Application
This one took a while to investigate. At the end, the problem was identified as:
QMainWindow remembers a few things so it can restore its state when the application restarts. Among them are:
- the size of the window when it was in “normal” state
- the size of the window when it was in “maximized” state
- whether the last state was maximized or normal
- the sizes and positions of the dock widgets
Now when starting the application, something like this happens:
- Creation of Window
- Window is shown
- Window says, “Oh, it’s the first time I’m being shown; let’s restore my previous state.”
- If it was maximized when the application was closed and upon restoring the previous state, it will say “I was maximized; I want to be maximized again, please.”
- After that, it will restore the sizes and positions of the dock widgets.
The problem is that “I want to be maximized” is not always a synchronous function (e.g. Linux/X11). Because of this, you sometimes still have a small-ish window when you get to the “restore dock widgets sizes and positions” phase and the dock widget sizes don’t make sense. The window says, “Oh, you silly. I can’t give you 500 pixels of width for this dock widget; I’m only 400 pixels wide myself.” So, the dock widget gets a smaller size. Eventually, the window will get maximized, but your original dock widget size is already lost.
I tried a simple approach (https://codereview.qt-project.org/c/qt/qtbase/+/374272), but it was clearly not enough. The Qt Company’s Volker Hilsheimer ended up adding https://codereview.qt-project.org/c/qt/qtbase/+/375521. Despite its having a timer to workaround the asynchronous resizing, it seems to work reliably well.