// ========================================== // PIXELDRIVE - QR.JS : générateur QR minimal // Byte mode, ECC L, masque 0, versions 1-6 // window.makeQRMatrix(text) -> { size, isDark(r,c) } // ========================================== window.makeQRMatrix = (function () { // Tables GF(256) const EXP = new Uint8Array(512), LOG = new Uint8Array(256); (function () { let x = 1; for (let i = 0; i < 255; i++) { EXP[i] = x; LOG[x] = i; x <<= 1; if (x & 0x100) x ^= 0x11d; } for (let i = 255; i < 512; i++) EXP[i] = EXP[i - 255]; })(); const gfMul = (a, b) => (a && b) ? EXP[LOG[a] + LOG[b]] : 0; function rsGenerator(degree) { const result = new Uint8Array(degree); result[degree - 1] = 1; let root = 1; for (let i = 0; i < degree; i++) { for (let j = 0; j < result.length; j++) { result[j] = gfMul(result[j], root); if (j + 1 < result.length) result[j] ^= result[j + 1]; } root = gfMul(root, 0x02); } return result; } function rsRemainder(data, gen) { const result = new Uint8Array(gen.length); for (const b of data) { const factor = b ^ result[0]; result.copyWithin(0, 1); result[result.length - 1] = 0; for (let i = 0; i < result.length; i++) result[i] ^= gfMul(gen[i], factor); } return result; } // Versions 1-6, ECC L : [size, dataCodewords, ecPerBlock, nBlocks, dataPerBlock] const VERS = [null, { size: 21, ec: 7, blocks: [[1, 19]] }, { size: 25, ec: 10, blocks: [[1, 34]] }, { size: 29, ec: 15, blocks: [[1, 55]] }, { size: 33, ec: 20, blocks: [[1, 80]] }, { size: 37, ec: 26, blocks: [[1, 108]] }, { size: 41, ec: 18, blocks: [[2, 68]] }, ]; const ALIGN = [null, [], [18], [22], [26], [30], [34]]; function formatBits(mask) { const data = (1 << 3) | mask; // ECC L = 0b01 let rem = data; for (let i = 0; i < 10; i++) rem = (rem << 1) ^ ((rem >>> 9) * 0x537); return ((data << 10) | rem) ^ 0x5412; } return function makeQRMatrix(text) { const bytes = new TextEncoder().encode(text); // Choix de version let ver = 0; for (let v = 1; v <= 6; v++) { const dataCW = VERS[v].blocks.reduce((s, b) => s + b[0] * b[1], 0); const bitsNeeded = 4 + 8 + bytes.length * 8; if (dataCW * 8 >= bitsNeeded + 4 || dataCW * 8 >= bitsNeeded) { ver = v; break; } } if (!ver) throw new Error('QR: données trop longues'); const V = VERS[ver], size = V.size; // Flux de bits const bits = []; const push = (val, n) => { for (let i = n - 1; i >= 0; i--) bits.push((val >>> i) & 1); }; push(4, 4); // mode byte push(bytes.length, 8); // compteur for (const b of bytes) push(b, 8); const dataCW = V.blocks.reduce((s, b) => s + b[0] * b[1], 0); push(0, Math.min(4, dataCW * 8 - bits.length)); // terminator while (bits.length % 8) bits.push(0); const pads = [0xEC, 0x11]; for (let i = 0; bits.length < dataCW * 8; i++) push(pads[i % 2], 8); // Octets de données const data = new Uint8Array(dataCW); for (let i = 0; i < dataCW; i++) { let b = 0; for (let j = 0; j < 8; j++) b = (b << 1) | bits[i * 8 + j]; data[i] = b; } // Blocs + Reed-Solomon + entrelacement const gen = rsGenerator(V.ec); const blocksData = [], blocksEC = []; let off = 0; for (const [n, len] of V.blocks) { for (let k = 0; k < n; k++) { const d = data.slice(off, off + len); off += len; blocksData.push(d); blocksEC.push(rsRemainder(d, gen)); } } const all = []; const maxD = Math.max(...blocksData.map(b => b.length)); for (let i = 0; i < maxD; i++) for (const b of blocksData) if (i < b.length) all.push(b[i]); for (let i = 0; i < V.ec; i++) for (const b of blocksEC) all.push(b[i]); // Matrice const M = Array.from({ length: size }, () => new Array(size).fill(null)); const setF = (r, c, v) => { M[r][c] = v; }; // Finders + séparateurs function finder(r0, c0) { for (let r = -1; r <= 7; r++) for (let c = -1; c <= 7; c++) { const rr = r0 + r, cc = c0 + c; if (rr < 0 || rr >= size || cc < 0 || cc >= size) continue; const on = (r >= 0 && r <= 6 && (c === 0 || c === 6)) || (c >= 0 && c <= 6 && (r === 0 || r === 6)) || (r >= 2 && r <= 4 && c >= 2 && c <= 4); setF(rr, cc, on); } } finder(0, 0); finder(0, size - 7); finder(size - 7, 0); // Timings for (let i = 8; i < size - 8; i++) { const v = i % 2 === 0; if (M[6][i] === null) setF(6, i, v); if (M[i][6] === null) setF(i, 6, v); } // Alignment (v2+) if (ver >= 2) { const a = ALIGN[ver][0]; for (let r = -2; r <= 2; r++) for (let c = -2; c <= 2; c++) { const on = Math.max(Math.abs(r), Math.abs(c)) !== 1; setF(a + r, a + c, on); } } // Réservation format for (let i = 0; i < 9; i++) { if (M[8][i] === null) setF(8, i, false); if (M[i][8] === null) setF(i, 8, false); } for (let i = 0; i < 8; i++) { if (M[size - 1 - i][8] === null) setF(size - 1 - i, 8, false); if (M[8][size - 8 + i] === null) setF(8, size - 8 + i, false); } setF(size - 8, 8, true); // module sombre // Placement des données (zigzag) let bitIdx = 0; const totalBits = all.length * 8; for (let right = size - 1; right >= 1; right -= 2) { if (right === 6) right = 5; for (let vert = 0; vert < size; vert++) { for (let j = 0; j < 2; j++) { const c = right - j; const upward = ((right + 1) & 2) === 0; const r = upward ? size - 1 - vert : vert; if (M[r][c] === null) { let bit = false; if (bitIdx < totalBits) bit = ((all[bitIdx >>> 3] >>> (7 - (bitIdx & 7))) & 1) === 1; bitIdx++; // Masque 0 : (r + c) pair → inversion if ((r + c) % 2 === 0) bit = !bit; M[r][c] = bit; } } } } // Bits de format (ECC L, masque 0) const fb = formatBits(0); const getBit = i => ((fb >>> i) & 1) === 1; for (let i = 0; i <= 5; i++) M[8][i] = getBit(i); M[8][7] = getBit(6); M[8][8] = getBit(7); M[7][8] = getBit(8); for (let i = 9; i < 15; i++) M[14 - i][8] = getBit(i); for (let i = 0; i < 8; i++) M[size - 1 - i][8] = getBit(i); for (let i = 8; i < 15; i++) M[8][size - 15 + i] = getBit(i); M[size - 8][8] = true; return { size, isDark: (r, c) => !!M[r][c] }; }; })(); // Rendu canvas simple window.drawQR = function (canvas, text, scale = 4, quiet = 4) { const qr = window.makeQRMatrix(text); const dim = (qr.size + quiet * 2) * scale; canvas.width = dim; canvas.height = dim; const ctx = canvas.getContext('2d'); ctx.fillStyle = '#ffffff'; ctx.fillRect(0, 0, dim, dim); ctx.fillStyle = '#000000'; for (let r = 0; r < qr.size; r++) for (let c = 0; c < qr.size; c++) { if (qr.isDark(r, c)) ctx.fillRect((c + quiet) * scale, (r + quiet) * scale, scale, scale); } };