chore: add topic candidates for 2026-04-25
This commit is contained in:
238
gen-qr-full.cjs
Normal file
238
gen-qr-full.cjs
Normal file
@@ -0,0 +1,238 @@
|
||||
// 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 xmlns="http://www.w3.org/2000/svg" width="${total}" height="${total}" viewBox="0 0 ${size} ${size}">`;
|
||||
svg += `<rect width="${size}" height="${size}" fill="white"/>`;
|
||||
for (let r = 0; r < size; r++) {
|
||||
for (let c = 0; c < size; c++) {
|
||||
if (matrix[r][c] === 1) {
|
||||
svg += `<rect x="${c}" y="${r}" width="1" height="1" fill="black"/>`;
|
||||
}
|
||||
}
|
||||
}
|
||||
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}`);
|
||||
Reference in New Issue
Block a user