import QRCode from 'qrcode' const PNG_SIGNATURE = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a] const CRC_TABLE = makeCrcTable() const BASE64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' export function encodeQrPng (text: string, scale = 6, margin = 4) { const qr = QRCode.create(text, { errorCorrectionLevel: 'M' }) const moduleCount = qr.modules.size const dim = (moduleCount + margin * 2) * scale const raw = new Uint8Array((dim * 3 + 1) * dim) let offset = 0 for (let y = 0; y < dim; y += 1) { raw[offset] = 0 offset += 1 const moduleY = Math.floor(y / scale) - margin for (let x = 0; x < dim; x += 1) { const moduleX = Math.floor(x / scale) - margin const dark = moduleX >= 0 && moduleX < moduleCount && moduleY >= 0 && moduleY < moduleCount ? Boolean(qr.modules.get(moduleX, moduleY)) : false const value = dark ? 0 : 255 raw[offset] = value raw[offset + 1] = value raw[offset + 2] = value offset += 3 } } const ihdr = [ ...u32(dim), ...u32(dim), 8, 2, 0, 0, 0, ] const idat = zlibStore(raw) const bytes = new Uint8Array([ ...PNG_SIGNATURE, ...chunk('IHDR', ihdr), ...chunk('IDAT', idat), ...chunk('IEND', []), ]) return bytes } export function bytesToBase64 (bytes: Uint8Array) { let result = '' for (let i = 0; i < bytes.length; i += 3) { const a = bytes[i] const b = i + 1 < bytes.length ? bytes[i + 1] : 0 const c = i + 2 < bytes.length ? bytes[i + 2] : 0 const triple = (a << 16) | (b << 8) | c result += BASE64_CHARS[(triple >> 18) & 63] result += BASE64_CHARS[(triple >> 12) & 63] result += i + 1 < bytes.length ? BASE64_CHARS[(triple >> 6) & 63] : '=' result += i + 2 < bytes.length ? BASE64_CHARS[triple & 63] : '=' } return result } function zlibStore (raw: Uint8Array) { const blocks: number[] = [0x78, 0x01] const max = 65535 for (let i = 0; i < raw.length; i += max) { const end = Math.min(i + max, raw.length) const chunkData = raw.subarray(i, end) const last = end === raw.length ? 1 : 0 const len = chunkData.length blocks.push(last, len & 0xff, (len >> 8) & 0xff, (~len) & 0xff, ((~len) >> 8) & 0xff) for (let j = 0; j < chunkData.length; j += 1) { blocks.push(chunkData[j]) } } blocks.push(...u32(adler32(raw))) return blocks } function chunk (type: string, data: number[]) { const typeBytes = [type.charCodeAt(0), type.charCodeAt(1), type.charCodeAt(2), type.charCodeAt(3)] return [ ...u32(data.length), ...typeBytes, ...data, ...u32(crc32([...typeBytes, ...data])), ] } function u32 (value: number) { return [ (value >>> 24) & 0xff, (value >>> 16) & 0xff, (value >>> 8) & 0xff, value & 0xff, ] } function crc32 (bytes: number[]) { let crc = 0xffffffff for (let i = 0; i < bytes.length; i += 1) { crc = CRC_TABLE[(crc ^ bytes[i]) & 0xff] ^ (crc >>> 8) } return (crc ^ 0xffffffff) >>> 0 } function adler32 (bytes: Uint8Array) { let a = 1 let b = 0 for (let i = 0; i < bytes.length; i += 1) { a = (a + bytes[i]) % 65521 b = (b + a) % 65521 } return ((b << 16) | a) >>> 0 } function makeCrcTable () { const table = new Uint32Array(256) for (let n = 0; n < 256; n += 1) { let c = n for (let k = 0; k < 8; k += 1) { c = (c & 1) ? (0xedb88320 ^ (c >>> 1)) : (c >>> 1) } table[n] = c >>> 0 } return table }