git / moonbite-network / static/vendor/qr.js · raw
/* Minimal, correct QR Code generator (byte mode, ECC level M). * Compact ES-module port of the public-domain qrcode-generator algorithm * (Kazuhiko Arase). Produces a real, scannable matrix — no decorative fakes. * Exports qrMatrix(text) -> boolean[][] (true = dark module). * Verified against OpenCV and ZXing decoders (format info had been written * transposed, which made every code unreadable; v7+ lacked version info). */const EXP = new Array(256), LOG = new Array(256);for (let i = 0; i < 8; i++) EXP[i] = 1 << i;for (let i = 8; i < 256; i++) EXP[i] = EXP[i-4] ^ EXP[i-5] ^ EXP[i-6] ^ EXP[i-8];for (let i = 0; i < 255; i++) LOG[EXP[i]] = i;const gexp = n => { while (n < 0) n += 255; while (n >= 255) n -= 255; return EXP[n]; };const glog = n => LOG[n];function polyMul(a, b){ const r = new Array(a.length + b.length - 1).fill(0); for (let i = 0; i < a.length; i++) for (let j = 0; j < b.length; j++) r[i+j] ^= gexp(glog(a[i]) + glog(b[j])); return r;}function rsGenerator(n){ let g = [1]; for (let i = 0; i < n; i++) g = polyMul(g, [1, gexp(i)]); return g;}function rsEncode(data, ecCount){ const gen = rsGenerator(ecCount); const res = data.concat(new Array(ecCount).fill(0)); for (let i = 0; i < data.length; i++){ const coef = res[i]; if (coef !== 0) for (let j = 0; j < gen.length; j++) res[i+j] ^= gexp(glog(gen[j]) + glog(coef)); } return res.slice(data.length);}/* Per-version capacity + RS block layout for ECC level M (versions 1..10). */const M = { // totalDataCodewords, ecPerBlock, [ [numBlocks, dataPerBlock], ... ] 1: [16, 10, [[1,16]]], 2: [28, 16, [[1,28]]], 3: [44, 26, [[1,44]]], 4: [64, 18, [[2,32]]], 5: [86, 24, [[2,43]]], 6: [108, 16, [[4,27]]], 7: [124, 18, [[4,31]]], 8: [154, 22, [[2,38],[2,39]]], 9: [182, 22, [[3,36],[2,37]]], 10: [216, 26, [[4,43],[1,44]]],};const ALIGN = {1:[],2:[6,18],3:[6,22],4:[6,26],5:[6,30],6:[6,34], 7:[6,22,38],8:[6,24,42],9:[6,26,46],10:[6,28,50]};function pickVersion(len){ for (let v = 1; v <= 10; v++){ const cap = M[v][0] - 2 - ((v >= 10) ? 2 : 1); // minus mode(4b)+count; count is 1 byte for v<10 if (len <= cap) return v; } throw new Error('data too long for QR v1..10');}function buildData(bytes, version){ const [totalData] = M[version]; const bits = []; const push = (val, n) => { for (let i = n-1; i >= 0; i--) bits.push((val >> i) & 1); }; push(0b0100, 4); // byte mode push(bytes.length, version >= 10 ? 16 : 8); for (const b of bytes) push(b, 8); push(0, Math.min(4, totalData*8 - bits.length)); // terminator while (bits.length % 8) bits.push(0); const codewords = []; for (let i = 0; i < bits.length; i += 8){ let c = 0; for (let j = 0; j < 8; j++) c = (c << 1) | bits[i+j]; codewords.push(c); } const pads = [0xEC, 0x11]; let p = 0; while (codewords.length < totalData) codewords.push(pads[p++ % 2]); return codewords;}function interleave(codewords, version){ const [, ecCount, layout] = M[version]; const blocks = []; let idx = 0; for (const [num, dataLen] of layout) for (let b = 0; b < num; b++){ const data = codewords.slice(idx, idx + dataLen); idx += dataLen; blocks.push({ data, ec: rsEncode(data, ecCount) }); } const maxData = Math.max(...blocks.map(b => b.data.length)); const out = []; for (let i = 0; i < maxData; i++) for (const b of blocks) if (i < b.data.length) out.push(b.data[i]); for (let i = 0; i < ecCount; i++) for (const b of blocks) out.push(b.ec[i]); return out;}function makeMatrix(version){ const size = version * 4 + 17; const m = Array.from({length:size}, () => new Array(size).fill(null)); const finder = (r, c) => { for (let dr = -1; dr <= 7; dr++) for (let dc = -1; dc <= 7; dc++){ const rr = r+dr, cc = c+dc; if (rr < 0 || rr >= size || cc < 0 || cc >= size) continue; const on = (dr >= 0 && dr <= 6 && (dc === 0 || dc === 6)) || (dc >= 0 && dc <= 6 && (dr === 0 || dr === 6)) || (dr >= 2 && dr <= 4 && dc >= 2 && dc <= 4); m[rr][cc] = on; } }; finder(0,0); finder(0,size-7); finder(size-7,0); for (let i = 8; i < size-8; i++){ const b = i % 2 === 0; m[6][i] = b; m[i][6] = b; } const al = ALIGN[version]; for (const r of al) for (const c of al){ if ((r <= 8 && c <= 8) || (r <= 8 && c >= size-9) || (r >= size-9 && c <= 8)) continue; for (let dr = -2; dr <= 2; dr++) for (let dc = -2; dc <= 2; dc++) m[r+dr][c+dc] = Math.max(Math.abs(dr), Math.abs(dc)) !== 1; } m[size-8][8] = true; // dark module if (version >= 7){ // version information blocks, required from v7 up let d = version << 12; for (let i = 5; i >= 0; i--) if ((d >> (i+12)) & 1) d ^= 0x1F25 << i; const bits = (version << 12) | d; for (let i = 0; i < 18; i++){ const on = ((bits >> i) & 1) === 1; m[Math.floor(i/3)][i%3 + size-11] = on; m[i%3 + size-11][Math.floor(i/3)] = on; } } return m;}function reserveFormat(m, size){ const r = []; for (let i = 0; i < 9; i++){ if (i !== 6){ r.push([8,i]); r.push([i,8]); } } for (let i = size-8; i < size; i++){ r.push([8,i]); r.push([size-1-(i-(size-8)),8]); } return r;}function placeData(m, data, version){ const size = m.length; const reserved = new Set(reserveFormat(m, size).map(([r,c]) => r+','+c)); const isFree = (r,c) => m[r][c] === null && !reserved.has(r+','+c); let bitIdx = 0; const total = data.length * 8; const bitAt = k => (data[k >> 3] >> (7 - (k & 7))) & 1; let up = true; for (let col = size-1; col > 0; col -= 2){ if (col === 6) col--; for (let i = 0; i < size; i++){ const row = up ? size-1-i : i; for (const c of [col, col-1]){ if (isFree(row, c)){ m[row][c] = bitIdx < total ? bitAt(bitIdx) === 1 : false; bitIdx++; } } } up = !up; }}function applyMask(m, mask, version){ const size = m.length; const reserved = new Set(reserveFormat(m, size).map(([r,c]) => r+','+c)); // rebuild a structural map to know which cells are function patterns const fn = makeMatrix(version); const cond = (r,c) => { switch(mask){ case 0: return (r+c)%2===0; case 1: return r%2===0; case 2: return c%3===0; case 3: return (r+c)%3===0; case 4: return (Math.floor(r/2)+Math.floor(c/3))%2===0; case 5: return (r*c)%2 + (r*c)%3 === 0; case 6: return ((r*c)%2 + (r*c)%3)%2===0; case 7: return ((r+c)%2 + (r*c)%3)%2===0; } }; for (let r = 0; r < size; r++) for (let c = 0; c < size; c++){ if (fn[r][c] !== null || reserved.has(r+','+c)) continue; if (cond(r,c)) m[r][c] = !m[r][c]; }}const FORMAT_BITS = mask => { // ECC level M = 0b00, plus 3-bit mask. BCH(15,5) + XOR mask 0x5412. const data = (0b00 << 3) | mask; let d = data << 10; const g = 0b10100110111; for (let i = 4; i >= 0; i--) if ((d >> (i+10)) & 1) d ^= g << i; return ((data << 10) | d) ^ 0b101010000010010;};function placeFormat(m, mask){ // ISO 18004 layout (bit 0 = LSB). The first copy runs down column 8 and // along row 8 next to the top-left finder; the second is split between the // bottom-left (column 8) and top-right (row 8) finders. const size = m.length, bits = FORMAT_BITS(mask); for (let i = 0; i < 15; i++){ const on = ((bits >> i) & 1) === 1; if (i < 6) m[i][8] = on; else if (i < 8) m[i+1][8] = on; else m[size-15+i][8] = on; if (i < 8) m[8][size-1-i] = on; else if (i < 9) m[8][15-i] = on; else m[8][14-i] = on; } m[size-8][8] = true; // dark module}function penalty(m){ const size = m.length; let p = 0; // simple run-based penalty (rule 1) — enough to pick a decent mask for (let r = 0; r < size; r++){ let run = 1; for (let c = 1; c < size; c++){ if (m[r][c] === m[r][c-1]) { run++; if (run === 5) p += 3; else if (run > 5) p++; } else run = 1; } } for (let c = 0; c < size; c++){ let run = 1; for (let r = 1; r < size; r++){ if (m[r][c] === m[r-1][c]) { run++; if (run === 5) p += 3; else if (run > 5) p++; } else run = 1; } } return p;}export function qrMatrix(text){ const bytes = Array.from(new TextEncoder().encode(String(text))); const version = pickVersion(bytes.length); const codewords = buildData(bytes, version); const finalData = interleave(codewords, version); let best = null, bestScore = Infinity; for (let mask = 0; mask < 8; mask++){ const m = makeMatrix(version); placeData(m, finalData, version); applyMask(m, mask, version); placeFormat(m, mask); // normalize nulls to false for (let r = 0; r < m.length; r++) for (let c = 0; c < m.length; c++) if (m[r][c] === null) m[r][c] = false; const s = penalty(m); if (s < bestScore){ bestScore = s; best = m; } } return best;}