chore: add topic candidates for 2026-04-25

This commit is contained in:
小橙
2026-04-25 01:38:03 +00:00
parent fbe1d70c32
commit c2981264c6
72 changed files with 2143 additions and 21 deletions

238
gen-qr-full.cjs Normal file
View 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}`);