Skip to content

Commit c099057

Browse files
fix: MicroPDF417 use spec-specific EC coefficients + prepend padding
Major improvements: - Replace custom RS with Zint's pre-computed Microcoeffs table (k=7..50) - Prepend padding (900) before data, not after (matches Zint/bwip-js) - Match improved 78% → 90%, 5/11 rows now bit-perfect Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 0282bba commit c099057

1 file changed

Lines changed: 45 additions & 64 deletions

File tree

src/encoders/micropdf417.ts

Lines changed: 45 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -334,73 +334,52 @@ function renderPattern(pattern: number[]): boolean[] {
334334

335335
const GF_MOD = 929;
336336

337-
// Build log/antilog tables for GF(929) with primitive element 3
338-
const RSALOG: number[] = Array.from({ length: GF_MOD });
339-
RSALOG[0] = 1;
340-
for (let _i = 1; _i < GF_MOD; _i++) {
341-
RSALOG[_i] = (RSALOG[_i - 1]! * 3) % GF_MOD;
342-
}
343-
const RSLOG: number[] = Array.from({ length: GF_MOD });
344-
for (let _i = 0; _i < GF_MOD - 1; _i++) {
345-
RSLOG[RSALOG[_i]!] = _i;
346-
}
347-
348-
/** Multiply two GF(929) elements */
349-
function rsProd(a: number, b: number): number {
350-
if (a === 0 || b === 0) return 0;
351-
return RSALOG[(RSLOG[a]! + RSLOG[b]!) % (GF_MOD - 1)]!;
352-
}
353-
354-
/**
355-
* Generate RS generator polynomial coefficients for k EC codewords.
356-
* g(x) = (x - 3^1)(x - 3^2)...(x - 3^k)
357-
* Returns k coefficients with alternating sign adjustment per BWIPP.
358-
*/
359-
function genECCoeffs(k: number): number[] {
360-
const coeffs: number[] = Array.from({ length: k + 1 }, () => 0);
361-
coeffs[0] = 1;
362-
for (let i = 1; i <= k; i++) {
363-
coeffs[i] = coeffs[i - 1]!;
364-
for (let j = i - 1; j >= 1; j--) {
365-
coeffs[j] = (coeffs[j - 1]! + rsProd(coeffs[j]!, RSALOG[i]!)) % GF_MOD;
366-
}
367-
coeffs[0] = rsProd(coeffs[0]!, RSALOG[i]!);
368-
}
369-
// Remove leading coefficient (x^k term), keep g_0..g_{k-1}
370-
const result = coeffs.slice(0, k);
371-
// Negate alternate coefficients (per BWIPP convention)
372-
for (let i = k - 1; i >= 0; i -= 2) {
373-
result[i] = (GF_MOD - result[i]!) % GF_MOD;
374-
}
375-
return result;
376-
}
337+
// MicroPDF417 EC coefficients from ISO/IEC 24728 / Zint zint_pdf_Microcoeffs
338+
// Indexed by EC count (k), offset into the flat array
339+
// prettier-ignore
340+
const MICRO_EC_COEFFS: Record<number, number[]> = {
341+
7: [76,925,537,597,784,691,437],
342+
8: [237,308,436,284,646,653,428,379],
343+
9: [567,527,622,257,289,362,501,441,205],
344+
10: [377,457,64,244,826,841,818,691,266,612],
345+
11: [462,45,565,708,825,213,15,68,327,602,904],
346+
12: [597,864,757,201,646,684,347,127,388,7,69,851],
347+
13: [764,713,342,384,606,583,322,592,678,204,184,394,692],
348+
14: [669,677,154,187,241,286,274,354,478,915,691,833,105,215],
349+
15: [460,829,476,109,904,664,230,5,80,74,550,575,147,868,642],
350+
16: [274,562,232,755,599,524,801,132,295,116,442,428,295,42,176,65],
351+
18: [279,577,315,624,37,855,275,739,120,297,312,202,560,321,233,756,760,573],
352+
21: [108,519,781,534,129,425,681,553,422,716,763,693,624,610,310,691,347,165,193,259,568],
353+
26: [443,284,887,544,788,93,477,760,331,608,269,121,159,830,446,893,699,245,441,454,325,858,131,847,764,169],
354+
32: [361,575,922,525,176,586,640,321,536,742,677,742,687,284,193,517,273,494,263,147,593,800,571,320,803,133,231,390,685,330,63,410],
355+
38: [234,228,438,848,133,703,529,721,788,322,280,159,738,586,388,684,445,680,245,595,614,233,812,32,284,658,745,229,95,689,920,771,554,289,231,125,117,518],
356+
44: [476,36,659,848,678,64,764,840,157,915,470,876,109,25,632,405,417,436,714,60,376,97,413,706,446,21,3,773,569,267,272,213,31,560,231,758,103,271,572,436,339,730,82,285],
357+
50: [923,797,576,875,156,706,63,81,257,874,411,416,778,50,205,303,188,535,909,155,637,230,534,96,575,102,264,233,919,593,865,26,579,623,766,146,10,739,246,127,71,244,211,477,920,876,427,820,718,435],
358+
};
377359

378360
/**
379-
* Generate RS error correction codewords over GF(929) for MicroPDF417.
380-
* Supports arbitrary EC codeword count (not limited to powers of 2).
361+
* MicroPDF417 RS error correction using spec-specific coefficients.
362+
* Algorithm from Zint pdf417.c — uses pre-computed Microcoeffs table.
381363
*/
382-
function generateMicroPDF417EC(dataCodewords: number[], ecCount: number): number[] {
383-
const n = dataCodewords.length;
384-
const coeffs = genECCoeffs(ecCount);
385-
386-
// Working array: data codewords followed by EC slots + 1 sentinel
387-
const cws: number[] = Array.from({ length: n + ecCount + 1 }, () => 0);
388-
for (let i = 0; i < n; i++) {
389-
cws[i] = dataCodewords[i]!;
390-
}
391-
392-
// Polynomial long division
393-
for (let i = 0; i < n; i++) {
394-
const t = (cws[i]! + cws[n]!) % GF_MOD;
395-
for (let j = 0; j < ecCount; j++) {
396-
cws[n + j] = (cws[n + j + 1]! + GF_MOD - ((t * coeffs[ecCount - j - 1]!) % GF_MOD)) % GF_MOD;
364+
function microPDF417RS(data: number[], ecCW: number): number[] {
365+
const coeffs = MICRO_EC_COEFFS[ecCW];
366+
if (!coeffs) throw new Error(`No MicroPDF417 EC coefficients for k=${ecCW}`);
367+
368+
const ec = Array.from<number>({ length: ecCW }).fill(0);
369+
for (const cw of data) {
370+
const total = (cw + ec[ecCW - 1]!) % GF_MOD;
371+
for (let j = ecCW - 1; j >= 0; j--) {
372+
if (j === 0) {
373+
ec[j] = (GF_MOD - ((total * coeffs[j]!) % GF_MOD)) % GF_MOD;
374+
} else {
375+
ec[j] = (ec[j - 1]! + GF_MOD - ((total * coeffs[j]!) % GF_MOD)) % GF_MOD;
376+
}
397377
}
398378
}
399379

400-
// Negate non-zero EC codewords
401-
const ec: number[] = [];
402-
for (let i = n; i < n + ecCount; i++) {
403-
ec.push(cws[i] !== 0 ? (GF_MOD - cws[i]!) % GF_MOD : 0);
380+
// Negate non-zero values
381+
for (let j = 0; j < ecCW; j++) {
382+
if (ec[j] !== 0) ec[j] = GF_MOD - ec[j]!;
404383
}
405384
return ec;
406385
}
@@ -442,13 +421,15 @@ export function encodeMicroPDF417(
442421
const [cols, rows, ecCW, rapl, rapc, rapr] = metric;
443422
const maxDataCW = rows * cols - ecCW;
444423

445-
// Pad data codewords to fill data capacity
424+
// Pad data codewords: MicroPDF417 prepends 900 (text latch) as padding
425+
// This matches Zint/bwip-js behavior where padding goes BEFORE data
446426
while (dataCW.length < maxDataCW) {
447-
dataCW.push(900); // text compaction latch as pad
427+
dataCW.unshift(900);
448428
}
449429

450430
// Generate EC codewords using RS over GF(929)
451-
const ec = generateMicroPDF417EC(dataCW, ecCW);
431+
// Generate EC using RS over GF(929) with roots 3^1..3^ecCW
432+
const ec = microPDF417RS(dataCW, ecCW);
452433

453434
// Combine data + EC codewords
454435
const allCW = [...dataCW, ...ec];

0 commit comments

Comments
 (0)