corp-mp/src/lib/qrPng.ts

130 lines
3.4 KiB
TypeScript

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
}