diQRcode provides interfaces for programming languages including C#, VB.NET, C, C++, VBA and VB6, plus a Windows console command-line interface (CLI).
Contents: .NET | C/C++ | VBA/VB6 | Command line interface | Example images | PDF output | Escaped character sequences | Embed image files in HTML using base64 | Reference files | New in this version | Summary of doc links | Contact us
The core native DLL diQRcode.dll
and its helper qrencodemap.dll
must be installed on your system for all of these programming interfaces.
These are installed automatically by the install program (licensed users should read the document DISTRIB.txt
).
Some example test code for each language is provided below: Show all code Hide all code
diQRcodeNet.dll
.
This file should be found in C:\Program Files (x86)\diQRcode\DotNet\
(where are the reference files?).
diQRcodeNet.dll
.using diQRcodeNet;or (for VB.NET)
Imports diQRcodeNet
using System; using System.Text; using System.IO; using System.Diagnostics; using diQRcodeNet; namespace TestQRcodeNet { class TestQRcodeNet { static void Main(string[] args) { int n; string fileName; string s; n = QRcode.Version(); Console.WriteLine("Version={0}", n); Console.WriteLine(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName); Console.WriteLine("DllInfo=[{0}]", QRcode.DllInfo()); fileName = "hello.gif"; n = QRcode.CreateGif(fileName, "hello world"); if (n != 0) Console.WriteLine("Error: {0}", QRcode.ErrorLookup(n)); Console.WriteLine("QRcode.CreateGif returns {0} (expected 0=>success)", n); Debug.Assert(0 == n, "QRcode.CreateGif failed"); Console.WriteLine("Created file '{0}'", fileName); fileName = "diqrcode-url.gif"; n = QRcode.CreateGif(fileName, @"https://cryptosys.net/qrcode/", level: Ecc.L, paramStr: "margin=2"); Console.WriteLine("QRcode.CreateGif returns {0} (expected 0=>success)", n); Debug.Assert(0 == n, "QRcode.CreateGif failed"); Console.WriteLine("Created file '{0}'", fileName); fileName = "diqrcode.gif"; n = QRcode.CreateGif(fileName, @"diQRcode", level: Ecc.L, paramStr: "margin=2"); Console.WriteLine("QRcode.CreateGif returns {0} (expected 0=>success)", n); Debug.Assert(0 == n, "QRcode.CreateGif failed"); Console.WriteLine("Created file '{0}'", fileName); fileName = "ola.gif"; n = QRcode.CreateGif(fileName, "Olá mundo"); // Character á will be Latin-1 encoded Console.WriteLine("QRcode.CreateGif returns {0} (expected 0=>success)", n); Debug.Assert(0 == n, "QRcode.CreateGif failed"); Console.WriteLine("Created file '{0}'", fileName); fileName = "ola1.gif"; // Same as above with explicit octet value for char n = QRcode.CreateGif(fileName, "Ol#xE1 mundo", options: Options.Escaped); Console.WriteLine("QRcode.CreateGif returns {0} (expected 0=>success)", n); Debug.Assert(0 == n, "QRcode.CreateGif failed"); Console.WriteLine("Created file '{0}'", fileName); fileName = "zhongguo.gif"; // #-escaped seqeunces in input string // U+4E2D (zhong) U+56FD (guo) n = QRcode.CreateGif(fileName, "#u4e2D#u56FD", 4, level: Ecc.H, options: Options.Escaped); Console.WriteLine("QRcode.CreateGif returns {0} (expected 0=>success)", n); Debug.Assert(0 == n, "QRcode.CreateGif failed"); Console.WriteLine("Created file '{0}'", fileName); fileName = "ola-utf8.gif"; // Character á (U+00E1) will be UTF-8 encoded n = QRcode.CreateGif(fileName, "Ol#u00E1 mundo", options: Options.Escaped); Console.WriteLine("QRcode.CreateGif returns {0} (expected 0=>success)", n); Debug.Assert(0 == n, "QRcode.CreateGif failed"); Console.WriteLine("Created file '{0}'", fileName); // QR Code Data Capacity Binary (8 Bits) ECC level L Max. 2953 characters // Read in a file of ~2950 characters s = File.ReadAllText("sonnets.txt"); s = s.Substring(0, 1000); // Theoretical limit does not work: try 1000 chars Console.WriteLine("String contains {0} chars", s.Length); fileName = "sonnets.gif"; n = QRcode.CreateGif(fileName, s, 4, Ecc.L); Console.WriteLine("QRcode.CreateGif returns {0} (expected 0=>success)", n); Debug.Assert(0 == n, "QRcode.CreateGif failed"); Console.WriteLine("Created file '{0}'", fileName); // THIS DOES NOT WORK... // (ANSI characters only for basic CreateGif input) n = QRcode.CreateGif("china.gif", "中国"); // zhong guo Console.WriteLine("QRcode.CreateGif returns {0} (expected nonzero ERROR)", n); if (n != 0) Console.WriteLine("Error: {0}.", QRcode.ErrorLookup(n)); // BUT THIS DOES... s = "中国"; // UTF-8 => E4 B8 AD E5 9C 8B n = QRcode.CreateGifInUtf8("zz-utf8.gif", s); Console.WriteLine("QRcode.CreateGifInUtf8 returns {0} (expected 0=>success)", n); Debug.Assert(0 == n, "QRcode.CreateGifInUtf8 failed"); // Polish (= To push a hedgehog or eight bins of figs in this boat) // Courtesy of Markus Kuhn <http://www.cl.cam.ac.uk/~mgk25/> s = "Pchnąć w tę łódź jeża lub ośm skrzyń fig"; n = QRcode.CreateGifInUtf8("polish.gif", s); Console.WriteLine("QRcode.CreateGifInUtf8 returns {0} (expected 0=>success)", n); Debug.Assert(0 == n, "QRcode.CreateGifInUtf8 failed"); // Create PDF files [new in v3.0] // PDF of exact same size as QRcode image fileName = "hello0.pdf"; n = QRcode.CreatePdf(fileName, "Hello world!"); Debug.Assert(0 == n, "QRcode.CreatePdf failed"); Console.WriteLine("Created file '{0}'", fileName); // PDF of A4 size with QRcode image offset from bottom-left corner. fileName = "helloA4.pdf"; n = QRcode.CreatePdf(fileName, "Hello world!", nPixelsPerModule: 6, nPageWidth: 595, nPageHeight: 842, nX: 230, nY: 380); Debug.Assert(0 == n, "QRcode.CreatePdf failed"); Console.WriteLine("Created file '{0}'", fileName); // PDF of size 6 x 4 in (432 x 288 px) with QRcode at default (0,0) bottom-left corner. // "Hello world" in Chinese using escaped character sequence. fileName = "hello-cn.pdf"; n = QRcode.CreatePdf(fileName, "#u4f60#u597d#u4e16#u754c", nPixelsPerModule: 8, nPageWidth: 432, nPageHeight: 288, options: Options.Escaped); Debug.Assert(0 == n, "QRcode.CreatePdf failed"); Console.WriteLine("Created file '{0}'", fileName); s = "中国"; // UTF-8 => E4 B8 AD E5 9C 8B fileName = "zz-utf8.pdf"; n = QRcode.CreatePdfInUtf8(fileName, s); Debug.Assert(0 == n, "QRcode.CreatePdfInUtf8 failed"); Console.WriteLine("Created file '{0}'", fileName); fileName = "polish.pdf"; s = "Pchnąć w tę łódź jeża lub ośm skrzyń fig"; n = QRcode.CreatePdfInUtf8(fileName, s, nPixelsPerModule: 8, nPageWidth: 432, nPageHeight: 288); Debug.Assert(0 == n, "QRcode.CreatePdf failed"); Console.WriteLine("Created file '{0}'", fileName); // Finally, demonstrate error code lookup... Console.WriteLine("Checking error code lookup..."); Console.WriteLine(QRcode.ErrorLookup(-39)); // Unable to encode } } }
Imports System Imports System.Text Imports diQRcodeNet Namespace TestQRcodeVB Class TestQRcodeVB <STAThread> _ Public Shared Sub Main(args As String()) Dim n As Integer n = QRcode.Version() Console.WriteLine("Version={0}", n) ' Console.WriteLine(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName) n = QRcode.CreateGif("hello.gif", "hello world") Console.WriteLine("QRcode.CreateGif returns {0} (expected 0=>success)", n) Console.WriteLine("DllInfo=[{0}]", QRcode.DllInfo()) End Sub End Class End Namespace
Version=30000 QRcode.CreateGif returns 0 (expected 0=>success) DllInfo=[Platform=Win64; Compiled=May 24 2021 13:20:36; Licence=T]
diQRcode.h
in your source code and link to diQRcode.lib
.
These two files should be found in C:\Program Files (x86)\diQRcode\C\
(where are the reference files?).
C/C++ documentation: diQRcode.h File Reference
#include <assert.h> #include <stdio.h> #include "diQRcode.h" int main(void) { char buf[512]; long nchars; long r; char *szText; char *szFileName; printf("QRCODE_Version=%ld\n", QRCODE_Version()); szText = "hello world"; szFileName = "hello1.gif"; // Defaults: 2 ppm, margin=4 modules, ECC level 'M' r = QRCODE_CreateGif(szFileName, szText, 0, "", 0); printf("QRCODE_CreateGif returns %ld (expecting 0)\n", r); assert(0 == r); printf("Created file '%s'\n", szFileName); szText = "hello world"; szFileName = "hello2.gif"; // With options: 4 ppm, margin=1 module, ECC level 'H' r = QRCODE_CreateGif(szFileName, szText, 4, "margin=1", QRCODE_ECC_H); printf("QRCODE_CreateGif returns %ld (expecting 0)\n", r); assert(0 == r); printf("Created file '%s'\n", szFileName); // Hardcode literal á character in string szText = "Olá mundo"; szFileName = "ola.gif"; // With options: 4 ppm, margin=1 module, ECC level 'L' r = QRCODE_CreateGif(szFileName, szText, 4, "margin=1", QRCODE_ECC_L); printf("QRCODE_CreateGif returns %ld (expecting 0)\n", r); assert(0 == r); printf("Created file '%s'\n", szFileName); // Use escaped sequence #u00E1 for character U+00E1 to be encoded in UTF-8 szText = "Ol#u00E1 mundo"; szFileName = "ola-utf8.gif"; // With options: 4 ppm, margin=1 module, ECC level 'L' r = QRCODE_CreateGif(szFileName, szText, 4, "margin=1", QRCODE_ECC_L | QRCODE_ESCAPED); printf("QRCODE_CreateGif returns %ld (expecting 0)\n", r); assert(0 == r); printf("Created file '%s'\n", szFileName); szText = "#u4e2D#u56FD"; // Chinese characters zhong guo (=China) szFileName = "zhongguo.gif"; r = QRCODE_CreateGif(szFileName, szText, 4, "margin=1", QRCODE_ECC_H | QRCODE_ESCAPED); printf("QRCODE_CreateGif returns %ld (expecting 0)\n", r); assert(0 == r); printf("Created file '%s'\n", szFileName); ///////////////////////////////////////// // Create PDF output files [new in v3.0] ///////////////////////////////////////// szText = "Hello world!"; szFileName = "hello0.pdf"; // Defaults: 2 ppm, ECC level 'M' r = QRCODE_CreatePdf(szFileName, szText, 0, 0, 0, 0, 0, 0); printf("QRCODE_CreatePdf returns %ld (expecting 0)\n", r); assert(0 == r); printf("Created file '%s'\n", szFileName); szText = "Hello world!"; szFileName = "helloA4.pdf"; r = QRCODE_CreatePdf(szFileName, szText, 6, 595, 842, 230, 380, 0); printf("QRCODE_CreatePdf returns %ld (expecting 0)\n", r); assert(0 == r); printf("Created file '%s'\n", szFileName); szText = "#u4f60#u597d#u4e16#u754c"; szFileName = "hello-cn.pdf"; r = QRCODE_CreatePdf(szFileName, szText, 8, 432, 288, 0, 0, QRCODE_ESCAPED); printf("QRCODE_CreatePdf returns %ld (expecting 0)\n", r); assert(0 == r); printf("Created file '%s'\n", szFileName); // Show DllInfo nchars = QRCODE_DllInfo(buf, sizeof(buf) - 1, 0); printf("DllInfo=[%s]\n", buf); return 0; }
basQRcode.bas
in your project.
This file should be found in C:\Program Files (x86)\diQRcode\VBA\
.
(where are the reference files?).
VBA/VB6 documentation: basQRcode.bas File Reference
Public Sub Test_QRCode() Dim n As Long Dim strText As String Dim strFileName As String n = QRCODE_Version() Debug.Print "QRCODE_Version = " & n strText = "hello world" strFileName = "hello1.gif" ' Defaults: 2 ppm, margin=4 modules, ECC level 'M' n = qrcodeCreateGif(strFileName, strText) Debug.Print "qrcodeCreateGif returns " & n & " (expected 0)" Debug.Assert 0 = n Debug.Print "Created file: " & strFileName strText = "hello world" strFileName = "hello2.gif" ' 3 ppm, margin=3, ECC level 'H' n = qrcodeCreateGif(strFileName, strText, 3, Ecc.H, "margin=1") Debug.Print "qrcodeCreateGif returns " & n & " (expected 0)" Debug.Assert 0 = n Debug.Print "Created file: " & strFileName ' Escaped characters strText = "#u4e2D#u56FD" ' Chinese characters zhong guo (=China) U+4E2D U+56FD strFileName = "zhongguo.gif" n = qrcodeCreateGif(strFileName, strText, 4, Ecc.H, nOptions:=QRCODE_ESCAPED) Debug.Print "qrcodeCreateGif returns " & n & " (expected 0)" Debug.Assert 0 = n Debug.Print "Created file: " & strFileName ' Hard-coded Spanish characters passed as UTF-16 string to be converted to UTF-8 strText = "El pingüino Wenceslao hizo kilómetros bajo exhaustiva lluvia y frío, añoraba a su querido cachorro." strFileName = "elpinguino.gif" n = qrcodeCreateGifInUtf8(strFileName, strText, 6, Ecc.Q) Debug.Print "qrcodeCreateGifInUtf8 returns " & n & " (expected 0)" Debug.Assert 0 = n Debug.Print "Created file: " & strFileName ' German strText = "Heizölrückstoßabdämpfung" strFileName = "heizoelrueck.gif" n = qrcodeCreateGifInUtf8(strFileName, strText, 6, Ecc.Q) Debug.Print "qrcodeCreateGifInUtf8 returns " & n & " (expected 0)" Debug.Assert 0 = n Debug.Print "Created file: " & strFileName ' Make a PDF file instead of a GIF strText = "Hello world!" strFileName = "hello0.pdf" ' Defaults: 2 ppm, ECC level 'M', PDF file exact size of QR code image (42 x 42 pixels) n = qrcodeCreatePdf(strFileName, strText) Debug.Print "qrcodeCreatePdf returns " & n & " (expected 0)" Debug.Assert 0 = n Debug.Print "Created file: " & strFileName strText = "Hello world!" strFileName = "helloA4.pdf" ' 6 ppm, ECC level 'M', PDF file of A4 size (210 x 297 mm, 595 x 842 px) ' with QR code image at position (230,380) pixels. n = qrcodeCreatePdf(strFileName, strText, 6, Ecc.M, 595, 842, 230, 380, 0) Debug.Print "qrcodeCreatePdf returns " & n & " (expected 0)" Debug.Assert 0 = n Debug.Print "Created file: " & strFileName ' Escaped characters (don't forget the QRCODE_ESCAPED flag!) strText = "#u4f60#u597d#u4e16#u754c" strFileName = "hello-cn.pdf" ' 8 ppm, ECC level 'Q', PDF file of size 6 x 4 in (432 x 288 px) ' with QR code image at default position (0,0) (bottom-left). n = qrcodeCreatePdf(strFileName, strText, 8, Ecc.Q, 432, 288, nOptions:=QRCODE_ESCAPED) Debug.Print "qrcodeCreatePdf returns " & n & " (expected 0)" Debug.Assert 0 = n Debug.Print "Created file: " & strFileName ' German as a PDF strText = "Heizölrückstoßabdämpfung" strFileName = "heizoelrueck.pdf" n = qrcodeCreatePdfInUtf8(strFileName, strText, 6, Ecc.Q) Debug.Print "qrcodeCreatePdfInUtf8 returns " & n & " (expected 0)" Debug.Assert 0 = n Debug.Print "Created file: " & strFileName End Sub
Use the executable diqrc.exe
, installed by default and accessible from the command line.
> diqrc --help Usage: diqrc [OPTION]... OUTFILE ["TEXT"] Generate a QRcode image file. OPTIONS: -f, --pdf create a PDF file [default=GIF] -s, --svg create an SVG file [default=GIF] -p, --ppm=N pixels per module [default=2 ppm] -e, --ecc-level=X error correction level X={L|M|Q|H} [default=M] -m, --margin=N margin of N modules [default=4] (GIF/SVG only) -6, --base64 encode output in base64 (GIF/SVG only) -i, --input=INFILE read input from file INFILE [default=TEXT] -@, --stdin read input from stdin [default=TEXT] -#, --escaped text contains escaped sequences #xHH #uHHHH #UHHHHHH -L, --libinfo print details of core library and exit -v, --version print program version and exit -h, --help print this help and exit -E, --examples print examples and exit Optional arguments for PDF only: --width=N width of PDF page in pixels [default=0] --height=N height of PDF page in pixels [default=0] --x=N X-coord in pixels of QRcode image [default=0] --y=N Y-coord in pixels of QRcode image [default=0] Exit status is 0 on success or 1 if error.
> diqrc --examples Examples: > diqrc hello.gif "Hello world!" create GIF file `hello.gif` for string `Hello world!` with default options. > diqrc -p 4 -m 1 -e H hello41h.gif "Hello world!" same as above with 4 pixels per module, margin 1 module and ecc level 'H'. > diqrc --ppm=4 --margin=1 --ecc-level=H hello41h.gif "Hello world!" same as above using 'long' options. > diqrc --input=input.txt myqr.gif create GIF file `myqr.gif` with input from file `input.txt`. > diqrc --stdin myqr1.gif < input.txt create GIF file `myqr.gif1` with input from stdin. > diqrc -# ola.gif "Ol#u00E1 mundo" create GIF file `ola.gif` for string `Olá mundo` with escaped char U+00E1. > diqrc --pdf hello.pdf "Hello world!" create PDF file `hello.pdf` for string `Hello world!` with default options. > diqrc --pdf --width=595 --height=842 --x=30 --y=40 helloA4.pdf "Hello world!" create PDF file `helloA4.pdf` for string `Hello world!` on A4 page at (30,40). > diqrc --svg hello.svg "Hello world!" create SVG image file `hello.svg` for string `Hello world!`. > diqrc --base64 -s hello.svg.txt "Hello world!" create SVG image as base64-encoded text file `hello.svg.txt`.
Some images produced from the code above.
If your QR code reader cannot read the above images correctly, get one that can properly interpret UTF-8-encoded characters. (We use the free Kaspersky QR scanner on our Android smart phone.)
PDF files are created with a DPI of 72 dots (pixels) per inch. The default output file is a PDF page of size exactly the size of the QR code image with no margin. The size of the QR code image is fixed by the number of pixels per module specified (default=2) and the number of modules required to store the input text (calculated automatically).
A larger PDF page can be specified and so can the position of the QR code image on the page. An A4 page (210 x 297 mm) is 595 x 842 pixels. A US Letter page (8.5 x 11.0 in) is 612 x 792 pixels. The origin (0,0) is at the bottom-left of the page and the QR code image is positioned with its bottom-left corner at (x, y).
diqrc.exe
with the --pdf
or -f
option.
> diqrc --pdf hello0.pdf "Hello world!"Output PDF file: hello0.pdf, a PDF file of default size (exactly the size of the QR code image) with the default 2 pixels per module. In this case, a QR code image of size 42 x 42 pixels, 14.8 x 14.8 mm.
> diqrc --pdf --width=595 --height=842 --x=230 --y=380 -p 6 helloA4.pdf "Hello world!"Output PDF file: helloA4.pdf, a PDF of A4 size (210 x 297 mm, 595 x 842 px) with the QR code image at position (230,380) pixels measured from the bottom-left of the page. The QR code image is 21 x 21 modules at 6 pixels per module = 126 x 126 px, 44.5 x 44.5 mm.
diqrc -f --width=432 --height=288 -p 8 -# hello-cn.pdf "#u4f60#u597d#u4e16#u754c"Output PDF file: hello-cn.pdf, a PDF of size 6 x 4 in (432 x 288 px) with the QR code image at the default (0,0) (bottom-left corner). The image represents the four chinese characters 你好世界 (ni hao shi jie, "Hello World") (
U+4F60 U+597D U+4E16 U+754C
) encoded in UTF-8.
Note the -#
option to indicate the input text contains an escaped sequence.
QRcode.CreatePdf Method
or
QRcode.CreatePdfInUtf8 Method
.
int n = QRcode.CreatePdf("helloA4.pdf", "Hello world!", nPixelsPerModule: 6, nPageWidth: 595, nPageHeight: 842, nX: 230, nY: 380); Debug.Assert(0 == n, "QRcode.CreatePdf failed");
qrcodeCreatePdf
or
qrcodeCreatePdfInUtf8
.
strText = "#u4f60#u597d#u4e16#u754c" strFileName = "hello-cn.pdf" n = qrcodeCreatePdf(strFileName, strText, 8, Ecc.Q, 432, 288, nOptions:=QRCODE_ESCAPED) Debug.Assert 0 = n
QRCODE_CreatePdf
.
// Defaults: 2 ppm, ECC level 'M' long r = QRCODE_CreatePdf("hello1.pdf", "hello world", 0, 0, 0, 0, 0, 0); assert(0 == r);
You can either hardcode non-ASCII international characters in your source code (if your IDE lets you) or you can use #-escaped character sequences.
#xHH
, #uHHHH
or #UHHHHHH
to represent bytes or Unicode characters in the input text, where H is a hexadecimal digit [0-9a-fA-F]. Options.Escaped
or command-line option --escaped
or -#
.#xHH
represents the byte whose numerical value is given by HH interpreted as a hexadecimal number.
The sequence will be replaced by the byte value.#uHHHH
and #UHHHHHH
represent the Unicode code points U+HHHH and U+HHHHHH, respectively.
The sequence will be replaced by the UTF-8 encoding of the code point.
The .NET and VBA interfaces offer a CreateImage
and CreateImageInUtf8
function.
These differ in the way they accept hard-coded characters in the szInput argument
(but #-escaped sequences are treated the same).
n = QRcode.CreateImage("latin1.gif", "áéíóñ"); // OK, will be encoded in ISO-8859-1 (Latin-1) encoding n = QRcode.CreateImage("china.gif", "中国"); // FAILS, OUT_OF_RANGE_ERROR n = QRcode.CreateImage("china1.gif", "#u4e2D#u56FD", options: Options.Escaped); // OK
n = QRcode.CreateImageInUtf("china.gif", "中国"); // OK, will be encoded in UTF-8 n = QRcode.CreateImageInUtf("china1.gif", "#u4e2D#u56FD", options: Options.Escaped); // OK, same result
The equivalent functions CreatePdf
and CreatePdfInUtf8
behave in the same manner.
Did you know that GIF and SVG images can be completely embedded in an HTML document without having to link to a separate image file?
<img src="hello.gif" alt="Hello">If you have the image file data encoded in base64, then you can do the following
<img src="data:image/gif;base64,R0lGODdhZABkAIAAAP///wAAAC...FAAA7">where
"R0lGODdhZABkAIAAAP///wAAAC...FAAA7"
is the base64 encoding of the GIF file. OK, it's long, but the big advantage is that there is no need to link to or store the actual image file
on your server.
For SVG files, the form is
<img src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0i...+Cjwvc3ZnPg==">Note it is a semicolon
';'
before the word "base64" but a comma ','
afterwards.
To generate the base64-encoded output, use the QRCODE_BASE64
/Options.Base64
option
This does not work for PDF files.
The reference files for programming interfaces for diQRcode are installed by default in the folder C:\Program Files (x86)\diQRcode\
, unless you changed it during installation (or in C:\Program Files\diQRcode\
on a 32-bit machine).
Alternatively from the Windows Start menu, follow Start > All Programs > diQRCode > diQRcode Reference Files.
Changes in Version 4.0.0 (June 2021):
QRcode.CreateGif
, QRCODE_CreateGif
and qrcodeCreateGif
in favour of CreateImage alternatives..gif
for a GIF file, .svg
for an SVG file, and .pdf
for a PDF, case-insensitive).
QRCODE_NO_NAMECHECK
/Options.NoNameCheck
option.To contact us or comment on this page, please send us a message.
This page last updated 4 July 2021