Skip to content

Commit b4d6d91

Browse files
fix: rMQR format info now uses Zint pre-computed lookup tables
Replace BCH computation with exact Zint format info tables (64 entries for left and right sides). Match improved to 80.1% vs Zint reference. Remaining diffs are in data placement and sub-alignment patterns. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent b7fe879 commit b4d6d91

1 file changed

Lines changed: 44 additions & 70 deletions

File tree

src/encoders/rmqr.ts

Lines changed: 44 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -94,19 +94,30 @@ const RMQR_CCI_LENGTHS: [number, number, number, number][] = [
9494
[9, 8, 8, 7], // 31: R17x139
9595
];
9696

97-
/** Generate 18-bit rMQR format info with BCH error correction */
98-
function rmqrFormatInfo(formatData: number): number {
99-
// BCH(18,6) encoding for rMQR format info
100-
// Generator polynomial for BCH(18,6)
101-
let bch = formatData << 12;
102-
const gen = 0x1f25; // x^12 + x^11 + x^10 + x^9 + x^8 + x^5 + x^2 + 1
103-
for (let i = 5; i >= 0; i--) {
104-
if (bch & (1 << (i + 12))) {
105-
bch ^= gen << i;
106-
}
107-
}
108-
return ((formatData << 12) | bch) ^ 0x1faf2; // XOR mask
109-
}
97+
// Pre-computed rMQR format info tables from Zint (ISO/IEC 23941)
98+
// Index = version_index + (ecLevel == "H" ? 32 : 0)
99+
// prettier-ignore
100+
const RMQR_FORMAT_LEFT: number[] = [
101+
0x1fab2,0x1e597,0x1dbdd,0x1c4f8,0x1b86c,0x1a749,0x19903,0x18626,
102+
0x17f0e,0x1602b,0x15e61,0x14144,0x13dd0,0x122f5,0x11cbf,0x1039a,
103+
0x0f1ca,0x0eeef,0x0d0a5,0x0cf80,0x0b314,0x0ac31,0x0927b,0x08d5e,
104+
0x07476,0x06b53,0x05519,0x04a3c,0x036a8,0x0298d,0x017c7,0x008e2,
105+
0x3f367,0x3ec42,0x3d208,0x3cd2d,0x3b1b9,0x3ae9c,0x390d6,0x38ff3,
106+
0x376db,0x369fe,0x357b4,0x34891,0x33405,0x32b20,0x3156a,0x30a4f,
107+
0x2f81f,0x2e73a,0x2d970,0x2c655,0x2bac1,0x2a5e4,0x29bae,0x2848b,
108+
0x27da3,0x26286,0x25ccc,0x243e9,0x23f7d,0x22058,0x21e12,0x20137,
109+
];
110+
// prettier-ignore
111+
const RMQR_FORMAT_RIGHT: number[] = [
112+
0x20a7b,0x2155e,0x22b14,0x23431,0x248a5,0x25780,0x269ca,0x276ef,
113+
0x28fc7,0x290e2,0x2aea8,0x2b18d,0x2cd19,0x2d23c,0x2ec76,0x2f353,
114+
0x30103,0x31e26,0x3206c,0x33f49,0x343dd,0x35cf8,0x362b2,0x37d97,
115+
0x384bf,0x39b9a,0x3a5d0,0x3baf5,0x3c661,0x3d944,0x3e70e,0x3f82b,
116+
0x003ae,0x01c8b,0x022c1,0x03de4,0x04170,0x05e55,0x0601f,0x07f3a,
117+
0x08612,0x09937,0x0a77d,0x0b858,0x0c4cc,0x0dbe9,0x0e5a3,0x0fa86,
118+
0x108d6,0x117f3,0x129b9,0x1369c,0x14a08,0x1552d,0x16b67,0x17442,
119+
0x18d6a,0x1924f,0x1ac05,0x1b320,0x1cfb4,0x1d091,0x1eedb,0x1f1fe,
120+
];
110121

111122
export interface RMQROptions {
112123
ecLevel?: "M" | "H";
@@ -274,67 +285,30 @@ export function encodeRMQR(text: string, options: RMQROptions = {}): boolean[][]
274285
if (matrix[r]![cols - 1] === null) matrix[r]![cols - 1] = (r + 1) % 2 === 0;
275286
}
276287

277-
// 5. Format info (18 bits total: version + EC level encoded with BCH)
278-
// Reserve format info positions (around finder and alignment)
279-
// Format info left: 3 rows (1,3,5) x 3 cols (8,9,10) + extra positions
280-
// Format info right: near bottom-right alignment
281-
// For now: use Zint's format lookup tables
282-
const formatData = sizeIdx + (ecLevel === "H" ? 32 : 0);
283-
const formatInfo = rmqrFormatInfo(formatData);
288+
// 5. Format info from pre-computed Zint tables (18 bits each side)
289+
const fmtIdx = sizeIdx + (ecLevel === "H" ? 32 : 0);
290+
const leftFmt = RMQR_FORMAT_LEFT[fmtIdx]!;
291+
const rightFmt = RMQR_FORMAT_RIGHT[fmtIdx]!;
284292

285-
// Place format info: 18 bits split between left and right sides
286-
// Left side: around top-left finder (rows 1-5, cols 8-10)
287-
const leftPos: [number, number][] = [
288-
[1, 8],
289-
[2, 8],
290-
[3, 8],
291-
[4, 8],
292-
[5, 8],
293-
[1, 9],
294-
[2, 9],
295-
[3, 9],
296-
[4, 9],
297-
[5, 9],
298-
[1, 10],
299-
[2, 10],
300-
[3, 10],
301-
[4, 10],
302-
[5, 10],
303-
[1, 11],
304-
[2, 11],
305-
[3, 11],
306-
];
307-
for (let i = 0; i < 18 && i < leftPos.length; i++) {
308-
const [r, c] = leftPos[i]!;
309-
if (r < rows && c < cols) matrix[r]![c] = ((formatInfo >> i) & 1) === 1;
293+
// Left format info: rows 1-5, cols 8-10 (bit = j*5+i), rows 1-3 col 11 (bits 15-17)
294+
for (let i = 0; i < 5; i++) {
295+
for (let j = 0; j < 3; j++) {
296+
matrix[i + 1]![j + 8] = ((leftFmt >> (j * 5 + i)) & 1) === 1;
297+
}
310298
}
311-
// Right side: near bottom-right alignment
312-
const rightPos: [number, number][] = [
313-
[rows - 6, arx - 1],
314-
[rows - 5, arx - 1],
315-
[rows - 4, arx - 1],
316-
[rows - 3, arx - 1],
317-
[rows - 2, arx - 1],
318-
[rows - 6, arx - 2],
319-
[rows - 5, arx - 2],
320-
[rows - 4, arx - 2],
321-
[rows - 3, arx - 2],
322-
[rows - 2, arx - 2],
323-
[rows - 6, arx - 3],
324-
[rows - 5, arx - 3],
325-
[rows - 4, arx - 3],
326-
[rows - 3, arx - 3],
327-
[rows - 2, arx - 3],
328-
[rows - 6, arx - 4],
329-
[rows - 5, arx - 4],
330-
[rows - 4, arx - 4],
331-
];
332-
for (let i = 0; i < 18 && i < rightPos.length; i++) {
333-
const [r, c] = rightPos[i]!;
334-
if (r >= 0 && r < rows && c >= 0 && c < cols) {
335-
matrix[r]![c] = ((formatInfo >> i) & 1) === 1;
299+
matrix[1]![11] = ((leftFmt >> 15) & 1) === 1;
300+
matrix[2]![11] = ((leftFmt >> 16) & 1) === 1;
301+
matrix[3]![11] = ((leftFmt >> 17) & 1) === 1;
302+
303+
// Right format info: rows (rows-6)-(rows-2), cols (cols-8)-(cols-6), + 3 extra
304+
for (let i = 0; i < 5; i++) {
305+
for (let j = 0; j < 3; j++) {
306+
matrix[i + rows - 6]![j + cols - 8] = ((rightFmt >> (j * 5 + i)) & 1) === 1;
336307
}
337308
}
309+
matrix[rows - 6]![cols - 5] = ((rightFmt >> 15) & 1) === 1;
310+
matrix[rows - 6]![cols - 4] = ((rightFmt >> 16) & 1) === 1;
311+
matrix[rows - 6]![cols - 3] = ((rightFmt >> 17) & 1) === 1;
338312

339313
// 6. Place data bits (column-pair zigzag, skip timing columns)
340314
const allBits: number[] = [];

0 commit comments

Comments
 (0)