chore: add topic candidates for 2026-04-25
This commit is contained in:
212
gen-qr.mjs
Normal file
212
gen-qr.mjs
Normal file
@@ -0,0 +1,212 @@
|
||||
// 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 EC_LEVEL = 1; // L = 1, M = 0, Q = 3, H = 2
|
||||
|
||||
// Generator polynomials for Reed-Solomon
|
||||
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 arithmetic 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; // primitive polynomial
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
// Version 2 QR Code (25x25 modules, 4+4=8 data bytes for Version 1-L, 16+7=23 total)
|
||||
function generateQRData(text) {
|
||||
// Use Version 1-L: 21x21 modules, 19 data bytes + 7 ECC = 26 codewords total
|
||||
const dataBytes = [];
|
||||
|
||||
// Mode indicator for byte mode: 0100
|
||||
dataBytes.push(0x40 | (text.length >> 4));
|
||||
dataBytes.push((text.length << 4) | 0x00); // character count (9 bits)
|
||||
|
||||
// Character data
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
dataBytes.push(text.charCodeAt(i));
|
||||
}
|
||||
|
||||
// Terminator (0000)
|
||||
dataBytes.push(0x00);
|
||||
while (dataBytes.length < 19) dataBytes.push(0x00);
|
||||
|
||||
// Split into data and ECC
|
||||
const data = dataBytes.slice(0, 19);
|
||||
const ecc = rsEncode(data, 7);
|
||||
|
||||
return [...data, ...ecc]; // 26 codewords total
|
||||
}
|
||||
|
||||
function createQRMatrix(text) {
|
||||
const size = 21; // Version 1 QR code = 21x21 modules
|
||||
const matrix = Array.from({ length: size }, () => Array(size).fill(null));
|
||||
const reserved = Array.from({ length: size }, () => Array(size).fill(false));
|
||||
|
||||
// Finder patterns (3 corners)
|
||||
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;
|
||||
const val = isEdge ? 1 : (isInner ? 0 : (r === 0 && c === 0 ? 1 : 0));
|
||||
matrix[rr][cc] = val;
|
||||
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 for Version 1 (just center)
|
||||
const ctr = Math.floor(size / 2);
|
||||
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;
|
||||
const isInner = Math.abs(r) <= 1 && Math.abs(c) <= 1;
|
||||
matrix[rr][cc] = isEdge ? 1 : (isInner ? 0 : 1);
|
||||
reserved[rr][cc] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Format info around finder patterns
|
||||
// For simplicity, use fixed format pattern
|
||||
const formatBits = [1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0]; // mask 0, L level
|
||||
|
||||
// Version info (for Version 1, none needed)
|
||||
|
||||
// Encode data in the remaining areas
|
||||
const dataBits = generateQRData(text);
|
||||
let bitIdx = 0;
|
||||
let byteIdx = 0;
|
||||
let dataVal = dataBits[0];
|
||||
let bitsLeft = 8;
|
||||
|
||||
// Fill remaining modules column-by-column, boustrophedon style
|
||||
const directions = [
|
||||
{ dr: 1, dc: 1, cStart: size - 2, cEnd: 0, colFirst: true },
|
||||
{ dr: -1, dc: 1, cStart: size - 2, cEnd: 0, colFirst: false },
|
||||
{ dr: -1, dc: 1, cStart: size - 2, cEnd: 0, colFirst: true },
|
||||
{ dr: 1, dc: 1, cStart: size - 2, cEnd: 0, colFirst: false },
|
||||
{ dr: 1, dc: 1, cStart: size - 2, cEnd: 0, colFirst: true },
|
||||
{ dr: -1, dc: 1, cStart: size - 2, cEnd: 0, colFirst: false },
|
||||
{ dr: -1, dc: 1, cStart: size - 2, cEnd: 0, colFirst: true },
|
||||
];
|
||||
|
||||
// Simple fill: go through and fill non-reserved with data
|
||||
for (let r = size - 1; r >= 0; r--) {
|
||||
for (let c = size - 1; c >= 0; c--) {
|
||||
if (matrix[r][c] !== null) continue;
|
||||
// Skip timing pattern column
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply mask 0 (invert every cell where mask bit is 1)
|
||||
const mask0 = (r, c) => ((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 (mask0(r, c)) matrix[r][c] ^= 1;
|
||||
}
|
||||
}
|
||||
|
||||
return matrix;
|
||||
}
|
||||
|
||||
function renderQRToSVG(text, moduleSize = 10) {
|
||||
const size = 21;
|
||||
const matrix = createQRMatrix(text);
|
||||
const total = size * moduleSize;
|
||||
|
||||
let svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${total}" height="${total}" viewBox="0 0 ${total} ${total}">\n`;
|
||||
svg += `<rect width="${total}" height="${total}" fill="white"/>\n`;
|
||||
|
||||
for (let r = 0; r < size; r++) {
|
||||
for (let c = 0; c < size; c++) {
|
||||
if (matrix[r][c] === 1) {
|
||||
svg += `<rect x="${c * moduleSize}" y="${r * moduleSize}" width="${moduleSize}" height="${moduleSize}" fill="black"/>\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
svg += `</svg>`;
|
||||
return svg;
|
||||
}
|
||||
|
||||
// Main
|
||||
const text = process.argv[2] || 'https://github.com/login/device';
|
||||
const svg = renderQRToSVG(text, 10);
|
||||
console.log(svg);
|
||||
Reference in New Issue
Block a user