// Real QR code generator using pure JavaScript (QR code algorithm) const fs = require('fs'); // QR Code Generator - Minimal implementation // Based on QR Code standard (ISO/IEC 18004) const GEN_POLYNOMIALS = { 7: [87, 229, 146, 149, 238, 102, 21], 10: [66, 83, 10, 220, 108, 180, 110, 36, 221, 40], 13: [120, 104, 107, 109, 102, 161, 76, 3, 37, 40, 213, 199, 162, 12], 15: [104, 70, 127, 54, 67, 81, 61, 97, 197, 192, 6, 151, 77, 56, 217], 16: [120, 112, 45, 6, 236, 124, 228, 185, 66, 39, 52, 228, 198, 180, 50, 28], 17: [136, 92, 228, 226, 220, 222, 224, 140, 120, 40, 54, 179, 176, 163, 22, 237, 79], 18: [94, 50, 103, 89, 81, 196, 21, 1, 4, 69, 120, 104, 62, 220, 80, 54, 82, 119], 20: [152, 185, 240, 5, 111, 99, 6, 220, 112, 150, 69, 36, 187, 22, 228, 198, 100, 101, 68, 205], 22: [89, 179, 131, 176, 182, 244, 19, 189, 69, 40, 194, 232, 93, 149, 147, 92, 239, 242, 63, 130, 194, 120], 24: [122, 118, 169, 70, 178, 237, 216, 85, 230, 105, 165, 42, 229, 175, 68, 255, 212, 251, 245, 198, 221, 180, 52, 99], 26: [246, 51, 50, 103, 174, 74, 65, 238, 185, 192, 249, 52, 220, 86, 65, 231, 47, 163, 248, 19, 50, 126, 194, 190, 12, 192], 28: [252, 85, 144, 50, 104, 230, 24, 201, 84, 37, 172, 58, 150, 33, 24, 147, 149, 206, 77, 220, 101, 225, 40, 198, 77, 56], 30: [212, 246, 77, 173, 80, 158, 98, 139, 52, 135, 214, 207, 229, 116, 253, 121, 195, 242, 237, 88, 14, 109, 221, 53, 200, 74, 8, 172, 98, 80], }; // Galois Field for Reed-Solomon const GF_EXP = new Array(512); const GF_LOG = new Array(256); (function initGF() { let x = 1; for (let i = 0; i < 255; i++) { GF_EXP[i] = x; GF_LOG[x] = i; x <<= 1; if (x & 0x100) x ^= 0x11d; } 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]]; } function rsEncode(data, eccLen) { const gen = GEN_POLYNOMIALS[eccLen]; if (!gen) return new Array(eccLen).fill(0); const result = new Array(eccLen).fill(0); for (let i = 0; i < data.length; i++) { const coef = data[i] ^ result[0]; result.shift(); result.push(0); if (coef !== 0) { for (let j = 0; j < eccLen; j++) { result[j] ^= gfMul(gen[j], coef); } } } return result; } function generateQRData(text) { // Version 1-L: 21x21 modules, 19 data bytes + 7 ECC const dataBytes = []; const len = text.length; // Mode indicator (0100 = byte mode) dataBytes.push(0x40 | (len >> 4)); dataBytes.push((len << 4)); // Character data for (let i = 0; i < text.length; i++) { dataBytes.push(text.charCodeAt(i)); } // Terminator dataBytes.push(0x00); while (dataBytes.length < 19) dataBytes.push(0x00); const data = dataBytes.slice(0, 19); const ecc = rsEncode(data, 7); return [...data, ...ecc]; } function createQRMatrix(text) { const size = 21; const matrix = Array.from({ length: size }, () => Array(size).fill(null)); const reserved = Array.from({ length: size }, () => Array(size).fill(false)); function addFinderPattern(row, col) { for (let r = -3; r <= 3; r++) { for (let c = -3; c <= 3; c++) { const rr = row + r, cc = col + c; if (rr < 0 || rr >= size || cc < 0 || cc >= size) continue; const isEdge = Math.abs(r) === 3 || Math.abs(c) === 3; const isInner = Math.abs(r) <= 1 && Math.abs(c) <= 1; matrix[rr][cc] = isEdge ? 1 : (isInner ? 0 : (r === 0 && c === 0 ? 1 : 0)); reserved[rr][cc] = true; } } } addFinderPattern(3, 3); addFinderPattern(3, size - 4); addFinderPattern(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 (center only for Version 1) const ctr = 10; for (let r = -2; r <= 2; r++) { for (let c = -2; c <= 2; c++) { const rr = ctr + r, cc = ctr + c; if (rr < 0 || rr >= size || cc < 0 || cc >= size) continue; if (matrix[rr][cc] !== null) continue; const isEdge = Math.abs(r) === 2 || Math.abs(c) === 2; matrix[rr][cc] = isEdge ? 1 : 0; reserved[rr][cc] = true; } } // Format info const formatBits = [1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0]; // Fill data bits const dataBits = generateQRData(text); let byteIdx = 0; let dataVal = dataBits[0]; let bitsLeft = 8; for (let r = size - 1; r >= 0; r--) { const down = (size - 1 - r) % 2 === 0; if (!down) { // Upward for (let c = size - 1; c >= 0; c--) { if (matrix[r][c] !== null) continue; if (c === 6 && r >= 8 && r <= size - 9) continue; const bit = (dataVal >> (bitsLeft - 1)) & 1; matrix[r][c] = bit; bitsLeft--; if (bitsLeft === 0) { byteIdx++; if (byteIdx < dataBits.length) dataVal = dataBits[byteIdx]; bitsLeft = 8; } } } else { // Downward for (let c = size - 1; c >= 0; c--) { if (matrix[r][c] !== null) continue; if (c === 6 && r >= 8 && r <= size - 9) continue; const bit = (dataVal >> (bitsLeft - 1)) & 1; matrix[r][c] = bit; bitsLeft--; if (bitsLeft === 0) { byteIdx++; if (byteIdx < dataBits.length) dataVal = dataBits[byteIdx]; bitsLeft = 8; } } } } // Mask 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 matrix; } function renderQRToSVG(text, moduleSize) { const size = 21; const matrix = createQRMatrix(text); const total = size * moduleSize; 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; } // Convert SVG to PNG using the browserless chrome async function svgToPng(svgString, outputPath) { // Browserless has been flaky. Let me use a different approach: generate SVG and convert with rsvg-convert or inkscape // But first check what tools are available const { execSync } = require('child_process'); try { execSync('which inkscape && which rsvg-convert 2>/dev/null', { encoding: 'utf8' }); console.log('Conversion tools available'); } catch(e) { console.log('No conversion tools'); } } const text = process.argv[2] || 'https://github.com/login/device'; const svg = renderQRToSVG(text, 1); console.log(svg);