import QRCode from "qrcode";
|
|
function u32(buf: Uint8Array, offset: number, value: number) {
|
buf[offset] = (value >>> 24) & 255;
|
buf[offset + 1] = (value >>> 16) & 255;
|
buf[offset + 2] = (value >>> 8) & 255;
|
buf[offset + 3] = value & 255;
|
}
|
|
function crc32(data: Uint8Array) {
|
let c = 0xffffffff;
|
for (let i = 0; i < data.length; i++) {
|
c ^= data[i];
|
for (let k = 0; k < 8; k++) {
|
c = (c >>> 1) ^ (c & 1 ? 0xedb88320 : 0);
|
}
|
}
|
return (c ^ 0xffffffff) >>> 0;
|
}
|
|
function adler32(data: Uint8Array) {
|
let a = 1;
|
let b = 0;
|
for (let i = 0; i < data.length; i++) {
|
a = (a + data[i]) % 65521;
|
b = (b + a) % 65521;
|
}
|
return ((b << 16) | a) >>> 0;
|
}
|
|
function chunk(type: string, data: Uint8Array) {
|
const out = new Uint8Array(12 + data.length);
|
u32(out, 0, data.length);
|
out[4] = type.charCodeAt(0);
|
out[5] = type.charCodeAt(1);
|
out[6] = type.charCodeAt(2);
|
out[7] = type.charCodeAt(3);
|
out.set(data, 8);
|
u32(out, 8 + data.length, crc32(out.subarray(4, 8 + data.length)));
|
return out;
|
}
|
|
function concat(parts: Uint8Array[]) {
|
const len = parts.reduce((sum, part) => sum + part.length, 0);
|
const out = new Uint8Array(len);
|
let offset = 0;
|
for (const part of parts) {
|
out.set(part, offset);
|
offset += part.length;
|
}
|
return out;
|
}
|
|
function zlibStore(raw: Uint8Array) {
|
const blocks: Uint8Array[] = [];
|
let offset = 0;
|
while (offset < raw.length) {
|
const len = Math.min(65535, raw.length - offset);
|
const last = offset + len >= raw.length;
|
const block = new Uint8Array(5 + len);
|
block[0] = last ? 1 : 0;
|
block[1] = len & 255;
|
block[2] = (len >>> 8) & 255;
|
const nlen = (~len) & 0xffff;
|
block[3] = nlen & 255;
|
block[4] = (nlen >>> 8) & 255;
|
block.set(raw.subarray(offset, offset + len), 5);
|
blocks.push(block);
|
offset += len;
|
}
|
const bodyLen = blocks.reduce((sum, block) => sum + block.length, 0);
|
const out = new Uint8Array(2 + bodyLen + 4);
|
out[0] = 0x78;
|
out[1] = 0x01;
|
let cursor = 2;
|
for (const block of blocks) {
|
out.set(block, cursor);
|
cursor += block.length;
|
}
|
u32(out, cursor, adler32(raw));
|
return out;
|
}
|
|
function bytesToBase64(bytes: Uint8Array) {
|
const api = typeof uni === "undefined" ? undefined : uni;
|
if (api && typeof api.arrayBufferToBase64 === "function") {
|
const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
return api.arrayBufferToBase64(buffer as ArrayBuffer);
|
}
|
const table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
let out = "";
|
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 n = (a << 16) | (b << 8) | c;
|
out += table[(n >> 18) & 63];
|
out += table[(n >> 12) & 63];
|
out += i + 1 < bytes.length ? table[(n >> 6) & 63] : "=";
|
out += i + 2 < bytes.length ? table[n & 63] : "=";
|
}
|
return out;
|
}
|
|
/** 不依赖浏览器 canvas,直接画出可在小程序 image 中显示的 PNG。 */
|
export function createQrPng(text: string) {
|
const qr = QRCode.create(text, { errorCorrectionLevel: "M" });
|
const modules = qr.modules;
|
const count = modules.size;
|
const margin = 4;
|
const scale = 8;
|
const size = (count + margin * 2) * scale;
|
const raw = new Uint8Array(size * (size + 1));
|
let cursor = 0;
|
for (let y = 0; y < size; y++) {
|
raw[cursor++] = 0;
|
const row = Math.floor(y / scale) - margin;
|
for (let x = 0; x < size; x++) {
|
const col = Math.floor(x / scale) - margin;
|
const dark =
|
row >= 0 && col >= 0 && row < count && col < count && !!modules.get(row, col);
|
raw[cursor++] = dark ? 0 : 255;
|
}
|
}
|
const ihdr = new Uint8Array(13);
|
u32(ihdr, 0, size);
|
u32(ihdr, 4, size);
|
ihdr[8] = 8;
|
const signature = Uint8Array.from([137, 80, 78, 71, 13, 10, 26, 10]);
|
return concat([
|
signature,
|
chunk("IHDR", ihdr),
|
chunk("IDAT", zlibStore(raw)),
|
chunk("IEND", new Uint8Array(0)),
|
]);
|
}
|
|
function fileName(text: string) {
|
let hash = 0;
|
for (let i = 0; i < text.length; i++) {
|
hash = (hash * 31 + text.charCodeAt(i)) >>> 0;
|
}
|
return hash.toString(16);
|
}
|
|
function savePng(bytes: Uint8Array, text: string) {
|
const base64 = bytesToBase64(bytes);
|
const dataUrl = `data:image/png;base64,${base64}`;
|
const root = (globalThis as any).wx?.env?.USER_DATA_PATH;
|
if (!root || typeof uni === "undefined" || typeof uni.getFileSystemManager !== "function") {
|
return Promise.resolve(dataUrl);
|
}
|
const filePath = `${root}/recycle-qr-${fileName(text)}.png`;
|
return new Promise<string>((resolve) => {
|
uni.getFileSystemManager().writeFile({
|
filePath,
|
data: base64,
|
encoding: "base64",
|
success: () => resolve(filePath),
|
fail: () => resolve(dataUrl),
|
});
|
});
|
}
|
|
export function generateQrcode(text: string) {
|
return savePng(createQrPng(text), text);
|
}
|
|
export function generateRecycleQrcode(text: string) {
|
return generateQrcode(text);
|
}
|
|
/**
|
* 清除指定二维码缓存
|
* @param key
|
*/
|
export function clearQrcodeCache(key: string) {
|
uni.removeStorageSync(key);
|
}
|