// Complete QR Code Generator - Version 1, Byte mode // Based on ISO/IEC 18004 const fs = require('fs'); // GF setup const GF_EXP = new Uint8Array(512); const GF_LOG = new Uint8Array(256); { let x = 1; for (let i = 0; i < 255; i++) { GF_EXP[i] = x; GF_LOG[x] = i; x = (x << 1) ^ (x & 0x100 ? 0x11d : 0); } for (let i = 255; i < 512; i++) GF_EXP[i] = GF_EXP[i - 255]; } function gfMul(a, b) { if (a === 0 || b === 0) return 0; return GF_EXP[GF_LOG[a] + GF_LOG[b]]; } // Reed-Solomon generator polynomial for ECLevel 1 (L), 7 ECC for version 1 // g(x) = (x - α^0)(x - α^1)...(x - α^6) = x^7 + 87x^6 + 229x^5 + 146x^4 + 149x^3 + 238x^2 + 102x + 21 function rsGeneratorPolynomial(eccLen) { let poly = [1]; for (let i = 0; i < eccLen; i++) { poly = [...poly, 0]; for (let j = 0; j < poly.length; j++) { poly[j] = gfMul(poly[j], GF_EXP[i]); } // Multiply by x+α^i const newPoly = [1]; for (let j = 1; j < poly.length; j++) { const a = poly[j - 1]; const b = poly[j]; newPoly[j] = b ^ (a !== 0 ? GF_EXP[(GF_LOG[a] + i) % 255] : 0); } poly = newPoly; } return poly.slice(1); // return coefficient array } function rsEncode(data, eccLen) { const gen = rsGeneratorPolynomial(eccLen); const result = new Array(data.length + eccLen).fill(0); for (let i = 0; i < data.length; i++) { result[i] = data[i]; } for (let i = 0; i < data.length; i++) { const coef = result[i]; if (coef !== 0) { for (let j = 0; j < eccLen; j++) { result[data.length + j] ^= gfMul(gen[j], coef); } } } return result.slice(data.length); } // Mode: byte = 0100 const MODE_BYTE = 0b0100; // Version 1: 21x21 modules, capacity: 19 bytes L, 16 bytes M, 13 bytes Q, 9 bytes H // For "https://github.com/login/device" (29 chars) - need Version 2 // But let's try with a shorter URL that fits in Version 1 function encodeData(text) { const capacityTable = { 1: { L: 19, M: 16, Q: 13, H: 9 }, 2: { L: 34, M: 28, Q: 22, H: 16 }, }; // Find minimum version that fits let version = 1; while (version <= 2) { const cap = capacityTable[version]; const byteCount = text.length + 3; // mode (1) + char count (1) + data + terminator if (byteCount <= cap.L) break; version++; } if (version > 2) version = 2; // For Version 1-L, 19 data bytes const dataBytes = []; // Mode indicator (4 bits) + character count (9 bits for version 1-9) const modeBits = (MODE_BYTE << 12) | (text.length << 4) | (0b0000); dataBytes.push(modeBits >> 8); dataBytes.push(modeBits & 0xff); // Character data for (let i = 0; i < text.length; i++) { dataBytes.push(text.charCodeAt(i)); } // Terminator (4 zeros) dataBytes.push(0x00); // Pad to fill capacity while (dataBytes.length < 19) { dataBytes.push(0xec); if (dataBytes.length < 19) dataBytes.push(0x11); } const data = dataBytes.slice(0, 19); const ecc = rsEncode(data, 7); return { version, data: [...data, ...ecc], modules: version === 1 ? 21 : 25 }; } function createQRMatrix(text) { const result = encodeData(text); const size = result.modules; const matrix = Array.from({ length: size }, () => Array(size).fill(null)); const reserved = Array.from({ length: size }, () => Array(size).fill(false)); // Add finder pattern function addFinder(mrow, mcol) { for (let r = -3; r <= 3; r++) { for (let c = -3; c <= 3; c++) { const rr = mrow + r, cc = mcol + c; if (rr < 0 || rr >= size || cc < 0 || cc >= size) continue; const isOuter = Math.abs(r) === 3 || Math.abs(c) === 3; const isInner = Math.abs(r) <= 1 && Math.abs(c) <= 1; matrix[rr][cc] = isOuter ? 1 : (isInner ? 0 : 1); reserved[rr][cc] = true; } } } addFinder(3, 3); addFinder(3, size - 4); addFinder(size - 4, 3); // Timing patterns for (let i = 8; i < size - 8; i++) { if (matrix[6][i] === null) { matrix[6][i] = i % 2 === 0 ? 1 : 0; reserved[6][i] = true; } if (matrix[i][6] === null) { matrix[i][6] = i % 2 === 0 ? 1 : 0; reserved[i][6] = true; } } // Alignment pattern (Version 2 = center + one) if (size === 25) { const pos = [22]; for (const r of pos) { for (const c of pos) { if (matrix[r][c] !== null) continue; for (let dr = -2; dr <= 2; dr++) { for (let dc = -2; dc <= 2; dc++) { const rr = r + dr, cc = c + dc; if (matrix[rr][cc] !== null) continue; const isEdge = Math.abs(dr) === 2 || Math.abs(dc) === 2; matrix[rr][cc] = isEdge ? 1 : 0; reserved[rr][cc] = true; } } } } } // Format info (mask 0, L=01) // Format string: 15 bits: 5 data + 10 ECC for L level // Simplified: just use correct bits for format const FORMAT_BITS = [ 1,0,1,0,1,0,0,0,0,0,1,0,0,1,0 // mask 0, L ]; // Place format info for (let i = 0; i < 15; i++) { const bit = FORMAT_BITS[i]; // Above top finder if (i < 6) { matrix[i][8] = bit; reserved[i][8] = true; } else if (i < 8) { matrix[i + 1][8] = bit; reserved[i + 1][8] = true; } else { matrix[8][14 - i] = bit; reserved[8][14 - i] = true; } // Left of left finder if (i < 8) { matrix[8][size - 1 - i] = bit; reserved[8][size - 1 - i] = true; } } matrix[8][7] = 1; reserved[8][7] = true; // dark module // Fill data (boustrophedon) const bits = []; for (const b of result.data) { for (let i = 7; i >= 0; i--) bits.push((b >> i) & 1); } let bitIdx = 0; for (let col = size - 1; col >= 1; col--) { if (col === 6) continue; if (col % 2 === 0) { for (let row = size - 1; row >= 0; row--) { if (matrix[row][col] === null) { matrix[row][col] = bits[bitIdx++]; if (bitIdx >= bits.length) break; } } } else { for (let row = 0; row < size; row++) { if (matrix[row][col] === null) { matrix[row][col] = bits[bitIdx++]; if (bitIdx >= bits.length) break; } } } if (bitIdx >= bits.length) break; } // Mask 0: (r+c) % 2 == 0 for (let r = 0; r < size; r++) { for (let c = 0; c < size; c++) { if (reserved[r][c] || matrix[r][c] === null) continue; if ((r + c) % 2 === 0) matrix[r][c] ^= 1; } } return { size, matrix, version: result.version }; } function matrixToSVG(matrix, size) { const MOD = 10; const total = size * MOD; let svg = ``; svg += ``; for (let r = 0; r < size; r++) { for (let c = 0; c < size; c++) { if (matrix[r][c] === 1) { svg += ``; } } } svg += ``; return svg; } const text = process.argv[2] || 'TEST'; const { size, matrix } = createQRMatrix(text); const svg = matrixToSVG(matrix, size); const outFile = process.argv[3] || '/home/node/.openclaw/workspace/assets/qr-output.svg'; fs.writeFileSync(outFile, svg); console.log(`QR for "${text}": ${size}x${size} Version ${text.length} chars, saved to ${outFile}`);