Incredibly Simple QR Generation in QML Wrapping tiny JS libraries in QML to do quick and simple things effortlessly and elegantly.
The Need for Simple & Modular QR Generation in QML
Recently, our designer Nuno Pinheiro needed to generate QR codes for an Android app in QML and started asking around about a simple way to do this. The best existing QML solution was QZXing, a Qt/QML wrapper for the 1D/2D barcode image processing library ZXing. He felt this was too much.
QZXing is quite a large and feature-rich library and is a great choice for something that requires a lot more rigorous work with encoding and decoding barcodes and QR. However, this application wasn’t focused around barcode processing. It just needed to display a few QR codes below its other content; it didn’t need something so heavy-duty. It seemed like too much work to build and link this library and register QML types in C++ if there was something simpler available.
Finding A JavaScript Library to Wrap in QML
There are plenty of minimal QR Code libraries in JS, and JS files can be imported natively in QML. Why not just slap a minified JS file into our Qt resources and expose its functionality through a QML object? No compiling libraries, no CMake, simple setup for a simple task.
My colleague, the one and only Javier O. Cordero Pérez attempted to tackle this first using QRCode.js. He found a few issues, which I’ll let him explain. This is what Javier contributed:
Why Most Browser Libraries Don’t Work With QML
Not all ECMAScript or JavaScript environments are created equal. QML, for example, doesn’t have a DOM (Document Object Model) that represents the contents on screen. That feature comes from HTML, so when a JS library designed for use in the browser attempts to access the DOM from QML, it can’t find these APIs. This limits the use of JS libraries in QML to business logic. Frontend JS libaries would have to be ported to QML in order to work.
Note to those concerned with performance: At the time of writing, JS data structures, and many JS and C++ design patterns don’t optimize well in QML code when using QML compilers. You should use C++ for backend code if you work in embedded or performance is a concern for you. Even JavaScript libraries have started a trend of moving away from pure JS in favor of Rust and WASM for backend code. Having said that, we cannot understate the convenience of having JS or QML modules or libraries you can simply plug and play. This is why we did this in the first place.
In my first approach to using qrcodejs
, I tried using the library from within a QML slot (Component.onCompleted
) and found that QRCode.js
calls document.documentElement
, document.getElementById
, document.documentElement
, and document.createElement
, which are undefined, because document
is typically an HTMLDocument
, part of the HTML DOM API.
I then began attempting to refactor the code, but quickly realized there was no easy way to get the library to use QtQuick’s Canvas element. I knew from past experiences that Canvas performs very poorly on Android, so, being pressed for time and Android being our target platform, I came up with a different solution.
Embedding and Communicating With A Browser View
My second approach was to give QRCode.js
a browser to work with. I chose to use QtWebView
, because on mobile, it displays web content using the operating system’s web view.
To keep things simple, I sent the QRCode’s data to the web page by encoding it to a safe character space using Base64 encoding and passing the result as a URL attribute. This attribute is then decoded inside the page and then sent to the library to generate a QR code on the Canvas. The WebView dimensions are also passed as attributes so the image can be produced at the width of the shortest side.
This is what my solution looked like at this point:
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
id: document
QRCode {
id: qr
text: "https://kdab.com/"
anchors.centerIn: parent
// The smallest dimension determines and fixes QR code size
width: 400
height: 600
}
width: 640
height: 480
visible: true
title: qsTr("Web based embedded QR Code")
}
// QRCode.qml
import QtQuick 2.15
import QtWebView 1.15
Item {
required property string text
// Due to platform limitations, overlapping the WebView with other QML components is not supported.
// Doing this will have unpredictable results which may differ from platform to platform.
WebView {
id: document
// String is encoded using base64 and transfered through page URL
url: "qrc:///qr-loader.html?w=" + width + "&t=" + Qt.btoa(text)
// Keep view dimensions to a minimum
width: parent.width < parent.height ? parent.width : parent.height
height: parent.height < parent.width ? parent.height : parent.width
anchors.centerIn: parent
// Note: To update the contents after the page has loaded, we could expand upon this
// by calling runJavaScript(script: string, callback: var) from the WebView component.
// Any method attributes, such as dimensions or the QR Code’s contents would have to
// be concatenated inside the script parameter.
}
}
// qr-loader.html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<style>
body {
margin: 0;
padding: 0;
}
#qr {
width: 100%;
height: 100%;
margin: auto;
}
</style>
</head>
<body>
<h1>u</h1>
<div id="q"></div>
<script src="jquery.min.js"></script>
<script src="qrcode.min.js"></script>
<script>
function generateQRCode() {
const s = new URLSearchParams(document.location.search);
const w = Number(s.get("w"));
const t = atob(s.get("t"));
new QRCode(document.getElementById("q"), {
text: t,
width: w,
height: w
});
}
generateQRCode();
</script>
</body>
Why You Should Avoid QtWebView On Mobile
If you read the comments in the code, you’ll notice that “due to platform limitations, overlapping the WebView with other QML components is not supported. Doing this will have unpredictable results which may differ from platform to platform.” Additionally, there is so much overhead in loading an embedded site that you can see the exact moment the QR code appears on screen.
Unfortunately for Nuno, QtWebView
is unable to load pages embedded as Qt resources on mobile systems. This is because by default, Qt resources become a part of the app’s binary, which can’t be read by the embedded browser. If the site was stored online or the app hosted its own web server, we could load our resources from there. Since this app didn’t do either of those things, all resources had to be copied into a temporary folder and accessed via the file://
protocol. Even then, the embedded browser would fail to locate or load the resources, making it necessary to inline all of our resources into the HTML for this to work.
As you can see, what started as a simple way to use a JS library on the desktop, quickly became cumbersome and difficult to maintain for mobile devices. Given more time, I would’ve chosen to instead re-implement QRCode.js
's algorithm using QtQuick’s Shapes API. The Shapes API would allow the QR code to be rendered in a single pass of the scene graph.
Fortunately, there’s a better, simpler and more practical solution. I will defer back to Matt here, who figured it out:
Proper JS in QML Solution
I decided to expand on Javier’s idea and try qrcode-svg. This library uses a modified version of QRCode.js and enables creation of an SVG string from the QR Code data.
Here’s an example snipped from the project’s README:
var qrcode = new QRCode({
content: "Hello World!",
container: "svg-viewbox", // Responsive use
join: true // Crisp rendering and 4-5x reduced file size
});
var svg = qrcode.svg();
Since the data is SVG, it can be used with QML’s Image
item natively by transforming it into a data URI and using that as the source for the image. There’s no need to write or read anything to disk, just append the string to "data:image/svg+xml;utf8,"
and use that as the source file.
Starting Our Wrapper
We can just wrap the function call up in a QML type, called QR, and use that wherever we need a QR code. Let’s make a ridiculously basic QtObject
that takes a content string and uses the library to produce an SVG:
// QR.qml
import QtQuick
import "qrcode.min.js" as QrSvg
QtObject {
id: root
required property string content
property string svgString: ""
Component.onCompleted: {
root.svgString = new QrSvg.QRCode({
content: root.content
}).svg()
}
}
So, whenever we make a QR
object, the string bound to content
is used to make the SVG and store it in svgString
. Then we can render it in an Image
item:
// example.qml
import QtQuick
import QtQuick.Window
Window {
visible: true
QR {
id: qrObj
content: "hello QR!"
}
Image {
source: "data:image/svg+xml;utf8," + qrObj.svgString
}
}
This is basically effortless and works like a charm.
Finishing Up The Wrapper
Now let’s completely wrap the QRCode constructor, so all the options from qrcode-svg are exposed by our QML object. We just need to set all options in the constructor through QML properties and give all the unrequired properties default values.
While we’re at it, let’s go ahead and connect to onContentChanged
, so we can refresh the SVG automatically when the content changes.
// QR.qml
import QtQuick
import "qrcode.min.js" as QrSvg
QtObject {
id: root
required property string content
property int padding: 4
property int width: 256
property int height: 256
property string color: "black"
property string background: "white"
property string ecl: "M"
property bool join: false
property bool predefined: false
property bool pretty: true
property bool swap: false
property bool xmlDeclaration: true
property string container: "svg"
property string svgString: ""
function createSvgString() {
root.svgString = new QrSvg.QRCode({
content: root.content,
padding: root.padding,
width: root.width,
height: root.height,
color: root.color,
background: root.background,
ecl: root.ecl,
join: root.join,
predefined: root.predefined,
pretty: root.pretty,
swap: root.swap,
xmlDeclaration: root.xmlDeclaration,
container: root.container
}).svg()
}
onContentChanged: createSvgString()
Component.onCompleted: createSvgString()
}
Nice and Easy
With these 45 lines of QML and the minified JS file, we have a QML wrapper for the library. Now any arbitrary QML project can include these two files and generate any QR Code that qrcode-svg can make.
Here I use it to re-generate a QR code as you type the content into a TextInput:
// example.qml
import QtQuick
import QtQuick.Window
import QtQuick.Controls
Window {
id: root
visible: true
QR {
id: qrObj
content: txtField.text
join: true
}
TextField {
id: txtField
width: parent.width
}
Image {
anchors.top: txtField.bottom
source: (qrObj.svgString === "")
? ""
: ("data:image/svg+xml;utf8," + qrObj.svgString)
}
}
This runs well when deployed on Android, and the image re-renders on content change in under 30 milliseconds, sometimes as low as 7.
Hopefully this code will be useful to those looking for the simplest no-frills method to generate a QR code in QML, and maybe the post can inspire other QML developers who feel like they’re overcomplicating something really simple.
The solution associated with this post is available in a GitHub repo linked here, so it can be used for your projects and tweaked if needed. There is also a branch that contains the code for Javier's alternate solution, available here.
Note: Nuno settled on QZXing before we got a chance to show him this solution, and was so frustrated about not having it earlier that he made us write this blog post 😅
If you like this article and want to read similar material, consider subscribing via our RSS feed.
Subscribe to KDAB TV for similar informative short video content.
KDAB provides market leading software consulting and development services and training in Qt, C++ and 3D/OpenGL. Contact us.
There is a more proper solution available KDE Prison: https://invent.kde.org/frameworks/prison
It’s LGPLv2, has proper QML bindings, has a stable API/ABI, is maintained by two KDE long timers (Sune Vuorela and Volker Krause) and doesn’t introduce the usage of a vendored JS lib which might break with the coming QML type compiler.
Hi Carl,
Thank you for this insight. I had heard of KDE Prison a while ago. Unfortunately, Prison wouldn’t have met Nuno’s requirement, being written in C++. Remember, it was not about the bindings, he was looking something he wouldn’t have to compile. Third party C++ libraries can be tricky when targeting Android, with cross-compilation and static linking being involved.
As for QMLTC, you’re absolutely right in that it wouldn’t work. To optimize JS code, the developer would have to use the QML Script Compiler (QMLSC), which is only available under a Qt Commercial license.
Thanks,
Javier