How to Build an Online QR Code Generator

In the previous article, we talked about what is QR code, its modes for encoding the data, and how to generate QR codes in Python.

In this article, we are going to build an online QR code generator to make it convenient to generate QR codes in the browser.

We will continue to use the Python library segno as the generation library. In order to run it in the browser, we will use pyodide to provide a Python environment for the browser.

Online Demo

New HTML File

Create a new HTML file with the following template:

<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Online QR Code Generator</title>
    <style></style>
  </head>
  <body>
    <h2>QR Code Generator</h2>
    <script type="text/javascript"></script>
  </body>
</html>

Setup a Python Environment

  1. Include pyodide in the page.

    <script type="text/javascript" src="https://cdn.jsdelivr.net/pyodide/v0.25.0/full/pyodide.js"></script>
    
  2. Load pyodide.

    let pyodide = await loadPyodide();
    
  3. Load micropip and use it to install the segno library.

    await pyodide.loadPackage("micropip");
    const micropip = pyodide.pyimport("micropip");
    await micropip.install("segno");
    
  4. We can test generating a QR code as SVG with the following code.

    let code = `
    import io
    import segno
    qrcode = segno.make_qr("Text")
    buff = io.BytesIO()
    qrcode.save(buff, kind='svg')
    buff.getvalue().decode("utf-8")
    `;
    let result = await pyodide.runPython(code);
    

Read Data from TextArea or File Input

Next, let’s read the data to encode from a TextArea or a File Input.

HTML elements:

<label>
  Content:
  <select id="contentSelect">
    <option>Text</option>
    <option>Bytes</option>
  </select>
</label>
<br/>
<textarea>Hello!</textarea>
<input style="display:none" type="file" id="fileInput"/>

If the content mode is selected as Bytes, load the file as base64 to pass it for segno to encode in byte mode. Otherwise, simply pass the text value in the TextArea.

JavaScript:

async function generateQRCodes(){
  let contentIsBytes = document.getElementById("contentSelect").selectedIndex;
  let base64_string = "";
  if (contentIsBytes == 1) {
    base64_string = await readFileAsBase64();
  }
  let content = document.querySelector("textarea").value;
  try {
    let code = `
import base64
import segno

content = '''`+content+`'''
base64_string = '''`+base64_string+`'''
if base64_string != '':
    content = base64.b64decode(base64_string)
qrcode = segno.make_qr(content)
import io
buff = io.BytesIO()
qrcode.save(buff, kind='svg')
buff.getvalue().decode("utf-8")
`;
    let result = await pyodide.runPython(code);
  }catch(error){
    alert(error);
  }
}

function readFileAsBase64() {
  return new Promise((resolve, reject) => {
    let fileInput = document.getElementById("fileInput");
    if (fileInput.files.length>0) {
      let file = fileInput.files[0];
      let fileReader = new FileReader();
      fileReader.onload = function(e){
        let dataURL = e.target.result;
        let base64 = dataURL.substring(dataURL.indexOf(",")+1,dataURL.length);
        resolve(base64);
      };
      fileReader.onerror = function () {
        console.warn('oops, something went wrong.');
        reject();
      };
      fileReader.readAsDataURL(file);
    }else{
      reject();
    }
  })
}

Specify the Mode of the QR Code

A QR code has the following modes to encode the data: numeric, alphanumeric, kanji, byte, and structured append.

We can store data in several QR codes using the structured append mode. It will add some metadata describing the total page number and the page index of the QR code. If the content we need to store is larger than one QR code’s capacity (2953 bytes) or the version of the QR code will be too high if we store it in one QR code which makes it difficult to print and read, we can use this mode.

The library can decide which mode to use based on the input. But sometimes, we may need to specify the mode.

  1. Add some HTML elements to specify the mode and the number of QR codes in structured append mode.

    <label>
      Mode:
      <select id="modeSelect">
        <option value="auto">Auto</option>
        <option value="numeric">Numeric</option>
        <option value="alphanumeric">Alphanumeric</option>
        <option value="kanji">Kanji</option>
        <option value="byte">Byte</option>
        <option value="structuredappend">Structrued Append</option>
      </select>
    </label>
    <label>
      Expected QR Code count for Structured Append mode:
      <input style="width:50px" type="number" value="2" id="countInput"/>
    </label>
    
  2. Pass the mode value in the Python code. Use segno.make_sequence to make QR codes in structured append mode and return the QR codes in a list.

    let count = document.getElementById("countInput").value; 
    let mode = document.getElementById("modeSelect").selectedOptions[0].value;
    try {
      let code = `
    import base64
    import segno
    
    mode = '`+mode+`'
    
    if mode == "auto":
        mode = None
    content = '''`+content+`'''
    
    base64_string = '''`+base64_string+`'''
    if base64_string != '':
        content = base64.b64decode(base64_string)
    
    qrcodes = []
    qrcode_svgs = []
    if mode == "structuredappend":
        qrcode_seq = segno.make_sequence(content,symbol_count=`+count+`)
        for qrcode in qrcode_seq:
            qrcodes.append(qrcode)
    else:
        qrcode = segno.make_qr(content,mode=mode)
        qrcodes.append(qrcode)
    import io
    for qrcode in qrcodes:
        buff = io.BytesIO()
        qrcode.save(buff, kind='svg')
        svg = buff.getvalue().decode("utf-8")
        qrcode_svgs.append(svg)
    qrcode_svgs
    `;
    let result = await pyodide.runPython(code);
    let svgs = result.toJs(); //convert the Python list to a JavaScript array
    
  3. We can then append the QR codes into the document as img elements.

    let resultContainer = document.getElementById("result");
    resultContainer.innerHTML = "";
    for (let index = 0; index < svgs.length; index++) {
      const svg = svgs[index];
      let decoded = unescape(encodeURIComponent(svg));
      // Now we can use btoa to convert the svg to base64
      let base64 = btoa(decoded);
      let imgSource = `data:image/svg+xml;base64,${base64}`;
      let img = document.createElement("img");
      img.src = imgSource;
      img.className = "qrcode"
      img.style.width = document.getElementById("widthInput").value + "px";
      resultContainer.appendChild(img);
    }
    

Specify the Error Correction Level

QR Code uses Reed–Solomon error correction so that even if the image is damaged, we can still read the data.

There are four levels of error correction. The higher the level, the more redundant data will be added to a QR code to make the QR code more resistant to damage. The highest level is H, whose percentage of data bytes that can be restored is 30%.

  • L: 7%
  • M: 15%
  • Q: 25%
  • H: 30%
  1. Add some HTML elements for specifying the error correction level.

    <label>
      Error Correction Level:
      <select id="errorCorrectionLevelSelect">
        <option value="L">L(7%)</option>
        <option value="M">M(15%)</option>
        <option value="Q">Q(25%)</option>
        <option value="H">H(30%)</option>
      </select>
    </label>
    
  2. In the Python code, set the error correction level.

    if mode == "structuredappend":
      qrcode_seq = segno.make_sequence(content,error=errorCorrectionLevel, symbol_count=`+count+`)
    else:
      qrcode = segno.make_qr(content,error=errorCorrectionLevel,mode=mode)
    

Specify the Version

The symbol versions of QR Code range from Version 1 to Version 40. Each version has a different module configuration or number of modules. The higher the version, the more number of modules can a QR code has so that it can store more data.

  1. Add some HTML elements for specifying the version.

    <label>
      Version:
      <select id="versionSelect">
        <option value="0">Auto</option>
      </select>
    </label>
    <script>
    loadVersions();
    function loadVersions(){
      const versionSelect = document.getElementById("versionSelect");
      for (let index = 1; index <= 40; index++) {
        const option = new Option(index,index);
        versionSelect.appendChild(option);
      }
    }
    </script>
    
  2. In the Python code, set the version.

    if mode == "structuredappend":
      qrcode_seq = segno.make_sequence(content,error=errorCorrectionLevel, version=version, symbol_count=`+count+`)
    else:
      qrcode = segno.make_qr(content,error=errorCorrectionLevel, version=version, mode=mode)
    

Specify the Text Encoding in Byte Mode

We can specify the text encoding in byte mode to store more characters. For example, UTF-8 needs three bytes to encode a Chinese Hanzi while GBK only needs two bytes.

  1. Add some HTML elements to select the text encoding. The list of encodings is from Python’s docs as we will encode the text in Python.

    <label>
      Text Encoding in Byte Mode:
      <select id="encodingSelect">
      </select>
    </label>
    <script>
    loadEncodings();
    function loadEncodings(){
      const encodingSelect = document.getElementById("encodingSelect");
      const encodings = ["ascii","big5","big5hkscs","cp037","cp273","cp424","cp437","cp500","cp720","cp737","cp775","cp850","cp852","cp855","cp856","cp857","cp858","cp860","cp861","cp862","cp863","cp864","cp865","cp866","cp869","cp874","cp875","cp932","cp949","cp950","cp1006","cp1026","cp1125","cp1140","cp1250","cp1251","cp1252","cp1253","cp1254","cp1255","cp1256","cp1257","cp1258","euc_jp","euc_jis_2004","euc_jisx0213","euc_kr","gb2312","gbk","gb18030","hz","iso2022_jp","iso2022_jp_1","iso2022_jp_2","iso2022_jp_2004","iso2022_jp_3","iso2022_jp_ext","iso2022_kr","latin_1","iso8859_2","iso8859_3","iso8859_4","iso8859_5","iso8859_6","iso8859_7","iso8859_8","iso8859_9","iso8859_10","iso8859_11","iso8859_13","iso8859_14","iso8859_15","iso8859_16","johab","koi8_r","koi8_t","koi8_u","kz1048","mac_cyrillic","mac_greek","mac_iceland","mac_latin2","mac_roman","mac_turkish","ptcp154","shift_jis","shift_jis_2004","shift_jisx0213","utf_32","utf_32_be","utf_32_le","utf_16","utf_16_be","utf_16_le","utf_7","utf_8","utf_8_sig"];
      let utf8Index = encodings.indexOf("utf_8");
      for (let index = 0; index < encodings.length; index++) {
        const encoding = encodings[index];
        let option = new Option(encoding,encoding);
        encodingSelect.appendChild(option);
      }
      encodingSelect.selectedIndex = utf8Index;
    }
    </script>
    
  2. Encode the text using the selected encoding in Python.

    if mode == "byte":
        content = content.encode(encoding)
    

Download the QR Codes

The QR codes we generated are appended as img elements in the document. We can then download the QR codes as SVG or JPEG.

function downloadAsJPEG(){
  downloadQRCodeImages(false);
}

function downloadAsSVG(){
  downloadQRCodeImages(true);
}

function convertSVGAsJPEG(svg){
  const canvas = document.createElement("canvas");
  canvas.width = svg.width;
  canvas.height = svg.height;
  const context = canvas.getContext("2d");
  context.fillStyle = "white";
  context.fillRect(0, 0, canvas.width, canvas.height);
  context.drawImage(svg, 0, 0, svg.naturalWidth, svg.naturalHeight, 0, 0, canvas.width, canvas.height);
  return canvas.toDataURL("image/jpeg")
}

function downloadQRCodeImages(isSVG){
  let resultContainer = document.getElementById("result");
  let images = resultContainer.getElementsByTagName("img");
  for (let index = 0; index < images.length; index++) {
    const image = images[index];
    let a = document.createElement("a");
    if (isSVG) {
      a.href = image.src;
      a.download = (index+1)+".svg";
    }else{
      a.href = convertSVGAsJPEG(image);
      a.download = (index+1)+".jpg";
    }
    a.style.display = "none";
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
  }
}

Read the QR Codes

You can use this Web QR Code Scanner based on Dynamsoft Barcode Reader to read the QR Codes we generated in this article.

Source Code

Get the source code to have a try:

https://github.com/tony-xlh/online-qr-code-generator