@@ -334,73 +334,52 @@ function renderPattern(pattern: number[]): boolean[] {
334334
335335const 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