Skip to content

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 😅

About KDAB

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.

FacebookTwitterLinkedInEmail

Categories: Android blogs / KDAB Blogs / KDAB Labs / KDAB on Qt / QML / Qt

Tags: / /

2 thoughts on “Incredibly Simple QR Generation in QML”

  1. 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.

    1. 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

Leave a Reply

Your email address will not be published. Required fields are marked *