const maxAreaObstructionByIconRatio = 0.06; //!< The maximum amount of area in percentage allowed for the icon to cover the QR code const iconPaddingRatio = 0.3; //!< The padding in percentage with respect of the circle (i.e. logo 30% smaller than the white circle) //! Convert a QR code into an SVG string function toSvgString(qr, logoBase64) { let parts = []; console.log("size",qr.size); for (let y = 0; y < qr.size; y++) { for (let x = 0; x < qr.size; x++) { if (qr.getModule(x, y)) parts.push(`M${x},${y}h1v1h-1z`); } } var circleElementString = ""; var iconSvgElementString = "" if (logoBase64 != undefined) { const circleElement = document.createElementNS("http://www.w3.org/2000/svg", "circle"); const qrArea = qr.size * qr.size; const iconArea = maxAreaObstructionByIconRatio * qrArea; const circleRadius = Math.sqrt(iconArea / Math.PI); circleElement.setAttribute("cx", "50%"); circleElement.setAttribute("cy", "50%"); circleElement.setAttribute("r", circleRadius); circleElement.setAttribute("fill", "white"); circleElementString = circleElement.outerHTML; // Add logo as svg const [imgMetadata, imgBase64Content] = logoBase64.split(','); const svgImage = atob(imgBase64Content); const parser = new DOMParser(); const iconSvg = parser.parseFromString(svgImage, 'image/svg+xml').documentElement; const circleDiam = 2 * circleRadius; const logoSize = circleDiam * (1 - iconPaddingRatio); iconSvg.setAttribute("width", logoSize); iconSvg.setAttribute("height", logoSize); iconSvg.setAttribute("x",qr.size / 2 - logoSize / 2); iconSvg.setAttribute("y", qr.size / 2 - logoSize / 2); iconSvgElementString = iconSvg.outerHTML; } return ` ${circleElementString} ${iconSvgElementString} `; } //! Encode a list of bytes into an alphanumeric string function alphanumericEncode(data) { const ALPHANUMERIC_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"; let rs = ""; for(let i = 0; i < data.length; i += 2) { let len, ab; if (i + 1 === data.length) { ab = data[i]; len = 2; } else { ab = ((data[i] << 8) + data[i + 1]); len = 3; } let res = ""; while (ab > 0) { let div = Math.floor(ab / ALPHANUMERIC_CHARS.length); let alphIdx = ab % ALPHANUMERIC_CHARS.length; res = ALPHANUMERIC_CHARS.charAt(alphIdx) + res; ab = div; } rs += res.padStart(len, "0"); } return rs; } //! Create the CRC32 table function makeCRCTable() { let c; let crcTable = []; for(let n = 0; n < 256; n++){ c = n; for(let k = 0; k < 8; k++){ c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); } crcTable[n] = c; } return crcTable; } function crc32(data) { let crcTable = window.crcTable || (window.crcTable = makeCRCTable()); let crc = 0 ^ (-1); for (let i = 0; i < data.length; i++ ) { crc = (crc >>> 8) ^ crcTable[(crc ^ data[i]) & 0xFF]; } return (crc ^ (-1)) >>> 0; } //! Convert an integer into an array of bytes function intToBytes(value, nBytes, signed) { const buffer = new ArrayBuffer(nBytes); const view = new DataView(buffer); if (nBytes == 1) { if (signed) view.setInt8(0, value); else view.setUint8(0, value); } else if (nBytes == 2) { if (signed) view.setInt16(0, value, false); else view.setUint16(0, value, false); } else if (nBytes == 4) { if (signed) view.setInt32(0, value, false); else view.setUint32(0, value, false); } else if (nBytes == 8) { if (signed) view.setBigInt64(0, BigInt(value), false); else view.setBigUint64(0, BigInt(value), false); } let bytes = [] for (let i = 0; i < nBytes; i++) bytes.push(view.getUint8(i)); return bytes; } //! Show a Qr code into the given canvas function toCanvas(qr, canvas, logoSrc) { // Scale up the QR code by an 8x factor const scale = 8; const border = 1; const size = (qr.size + 2 * border) * scale; const qrScaledSize = qr.size * scale; const qrArea = qrScaledSize * qrScaledSize; // Not considering border in the Qr area const iconArea = maxAreaObstructionByIconRatio * qrArea; const circleRadius = Math.sqrt(iconArea / Math.PI); const circleDiam = 2 * circleRadius; const logoSize = circleDiam * (1 - iconPaddingRatio); canvas.width = size; canvas.height = size; let ctx = canvas.getContext("2d"); for (let y = -border; y < qr.size + border; y++) { for (let x = -border; x < qr.size + border; x++) { ctx.fillStyle = qr.getModule(x, y) ? "#000" : "#FFF"; ctx.fillRect((x + border) * scale, (y + border) * scale, scale, scale); } } if (logoSrc != undefined) { var img = new Image(); img.src = logoSrc img.onload = function() { const imgRatio = img.naturalWidth / img.naturalHeight; var imgWidth = logoSize; var imgHeight = logoSize / imgRatio; if (imgHeight > logoSize) { imgWidth = logoSize * imgRatio; imgHeight = logoSize; } var canvasCenterX = size / 2 - imgWidth / 2; var canvasCenterY = size / 2 - imgHeight / 2; ctx.beginPath(); ctx.fillStyle = 'white'; ctx.arc(size / 2, size / 2, circleRadius, 0, 2 * Math.PI); ctx.fill(); ctx.drawImage(img, canvasCenterX, canvasCenterY, imgWidth, imgHeight); }; } }