Disclaimer: This was written while working on my very first QT and C++ project. Take with a very large grain of salt.
Oh boy. Whenever you think something is going to take 5 seconds and be super simple, that is when it takes all day and makes you want to smash your keyboard into bits. Enter the world of trying to use external resources in QT, relative paths, QRC files, and .PRO files. Not fun.
I wanted to use FontAwesome (an icon set) with QML, but I didn’t want to add the FontAwesome files into my actual QT workspace, for several reasons. One of which is that this is something I would prefer to use a package manager, like NPM, for.
So, in this case, I set up a simple package.json a level up from my QT project directory, npm installed FontAwesome, and then attempted to use the files in QML. My file structure looked something like this:
- package.json
- node_modules
- lib
- Actual_QT_Workspace
- my-project.pro
- main.cpp
- qml.qrc
- …
However, I quickly ran into an issue…
Issue 1: How to include files outside the Source directory in resources, so you can use “import” with them?
The very first thing I tried (man can you imagine if this simply worked) is using a typical relative path syntax in my QML file pointing to the path of the file in node_modules, like this:
import "../node_modules/@fortawesome/fontawesome-free/js/all.js" as FontAwesome
I thought it might work at first, since I am new to QT and didn’t know any better, and also that the editor autocompleted the path, showing that it recognized the file as existing. However, trying to build resulted in this kind of error:
qrc:/main.qml:5 Script qrc:/node_modules/@fortawesome/fontawesome-free/js/all.js unavailable
qrc:/node_modules/@fortawesome/fontawesome-free/js/all.js:-1 No such file or directory
This is because, and I’m grossly over-simplifying here, but QT uses QRC (QT Resource Collection) files to bundle resources together that get passed to the compiler. In order for my application to use a file off the disk, the easiest way to access it is by specifying the file in a QRC that is included as part of the build process.
Using a relative path in a .qrc file
So the next thing I tried was adding this local file to the default QRC file, qml.qrc. I tried using the built in QRC editor panel in QT Creator (right click *.qrc, hit “Open in Editor”, then use the “Add > Add Files”), but when trying to add the file, I got this error:
The file C:\Users… is not in a subdirectory of the resource file. You now have the option to copy this file to a valid location.
No! I don’t want to copy the file – that is the whole point of this – I want it left in node_modules. This actually seems like a bug, because you can easily get around this by right clicking on the *.qrc and instead of using the editor, hit “Add existing files…”, then select the file you want to add. This actually worked! However, it was a little silly, because all it does is use the relative path in the <file></file> element in the QRC doc – I could have easily have tried that by hand instead of using the menu. The resulting QRC ended up looking something like this:
<RCC>
<qresource prefix="/">
<file>main.qml</file>
<file>../node_modules/@fortawesome/fontawesome-free/js/all.js</file>
</qresource>
</RCC>
Another option is to create a brand new QRC file, leave it in the parent directory / outside directory, and include the QRC in your .PRO file as part of RESOURCES. Then in your new QRC file, you don’t always have to keep traversing up a directory (or multiple, depending on your structure). For example:
.pro file:
# ...
RESOURCES += qml.qrc
RESOURCES += ../external-assets.qrc
# ...
And then in external-assets.qrc :
<RCC>
<qresource prefix="/">
<file>node_modules/@fortawesome/fontawesome-free/js/all.js</file>
</qresource>
</RCC>
Issue 2: You can’t use FontAwesome like that Joshua!
Bah! I should have known that would be too easy to work! Although QT ships with a JS engine, and you can definitely use JS with it, you have to remember that there are many many caveats to that. In short, you can’t just import FontAwesome’s all.js into your QML and have it load everything; for one, all.js loads a bunch of CSS files, of which QML does not natively parse / apply. In addition, you’ll get errors about setTimeout, and other stuff like that where all.js is trying to use normal window JS globals, but of course QML!==window DOM.
So… how do we get around this? Well, unfortunately it is a little complicated. Lucky for me, I found this great in-depth guide by Martin Höher that lays out how to use FontAwesome files with QML, step by step. Although Font-Awesome recently bumped up versions from 4.x to 5.x, his Python script for generating the Icons.qml Singleton still works perfectly! However, currently Font-Awesome is excluding icons.json from their NPM package, which is what his script uses as the input. You can still find the icons.json if you manually go to their website and download the zip, which is what I am doing in the meantime to avoid the headache of having to parse their icons without a .json index.
I’m still keeping the font-awesome files outside my QT directory, although there is not much reason to at this point while I currently can’t really use the NPM version of FA. I just extract the four files I need from the downloaded zip and place them in one flat directory under /lib/.
Here is close to what my files look like now:
qmldir:
singleton CustomFonts 1.0 CustomFonts.qml
singleton Icons 1.0 Icons.qml
CustomFonts.qml:
pragma Singleton
import QtQuick 2.0
Item {
id : customFonts
readonly property FontLoader fontAwesomeRegular: FontLoader {
source: "../lib/fontawesome-free-5.7.2-web/fa-regular-400.ttf"
}
readonly property FontLoader fontAwesomeSolid: FontLoader {
source: "../lib/fontawesome-free-5.7.2-web/fa-solid-900.ttf"
}
readonly property FontLoader fontAwesomeBrands: FontLoader {
source: "../lib/fontawesome-free-5.7.2-web/fa-brands-400.ttf"
}
readonly property var names : {
"fontAwesomeRegular" : customFonts.fontAwesomeRegular.name,
"fontAwesomeSolid" : customFonts.fontAwesomeSolid.name,
"fontAwesomeBrands" : customFonts.fontAwesomeBrands.name
}
}
main.qml
import QtQuick 2.7
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.0
import QtQuick.Controls.Material 2.3
// Singleton import
import "."
ApplicationWindow {
id : root
visible: true
// ...
// Chrome icon
Text {
color: "black"
font.pixelSize: 40
text: Icons.faChrome
font.family: CustomFonts.names.fontAwesomeRegular
}
// ...
}
qml.qrc
<RCC>
<qresource prefix="/">
<!-- ... -->
<file>../lib/fontawesome-free-5.7.2-web/fa-regular-400.ttf</file>
<file>../lib/fontawesome-free-5.7.2-web/fa-solid-900.ttf</file>
<file>../lib/fontawesome-free-5.7.2-web/fa-brands-400.ttf</file>
<file>../lib/fontawesome-free-5.7.2-web/icons.json</file>
<!-- ... -->
</qresource>
</RCC>
To-Do:
There are lots of ways I could optimize how this is loaded, especially if I want to be considerate of how others might have different build systems. If I find time, I might revisit this post, especially as I learn more about QT and its internals.