55 * Features:
66 * - 1-4 data columns (vs 1-30 in PDF417)
77 * - 4-44 rows
8- * - No start/stop patterns (uses Row Address Patterns instead)
8+ * - Uses Row Address Patterns (RAPs) instead of PDF417 start/stop
99 * - Smaller than standard PDF417 for short data
1010 */
1111
@@ -14,44 +14,75 @@ import { getCodewordPattern, getRowCluster } from "./pdf417/tables";
1414import { encodeData } from "./pdf417/encoder" ;
1515import { generateECCodewords } from "./pdf417/ec" ;
1616
17- // MicroPDF417 symbol sizes: [columns, rows, dataCW, ecCW]
18- const SYMBOL_SIZES : [ number , number , number , number ] [ ] = [
19- [ 1 , 11 , 1 , 6 ] , // smallest
20- [ 1 , 14 , 4 , 6 ] ,
21- [ 1 , 17 , 7 , 6 ] ,
22- [ 1 , 20 , 10 , 6 ] ,
23- [ 1 , 24 , 14 , 7 ] ,
24- [ 1 , 28 , 18 , 7 ] ,
25- [ 2 , 8 , 4 , 8 ] ,
26- [ 2 , 11 , 10 , 8 ] ,
27- [ 2 , 14 , 16 , 8 ] ,
28- [ 2 , 17 , 22 , 10 ] ,
29- [ 2 , 20 , 28 , 11 ] ,
30- [ 2 , 23 , 34 , 12 ] ,
31- [ 2 , 26 , 40 , 14 ] ,
32- [ 3 , 6 , 4 , 10 ] ,
33- [ 3 , 8 , 10 , 10 ] ,
34- [ 3 , 10 , 16 , 12 ] ,
35- [ 3 , 12 , 22 , 14 ] ,
36- [ 3 , 15 , 31 , 16 ] ,
37- [ 3 , 20 , 46 , 18 ] ,
38- [ 3 , 26 , 64 , 20 ] ,
39- [ 3 , 32 , 82 , 24 ] ,
40- [ 3 , 38 , 100 , 28 ] ,
41- [ 3 , 44 , 118 , 32 ] ,
42- [ 4 , 4 , 4 , 8 ] ,
43- [ 4 , 6 , 12 , 8 ] ,
44- [ 4 , 8 , 20 , 10 ] ,
45- [ 4 , 10 , 28 , 12 ] ,
46- [ 4 , 12 , 36 , 14 ] ,
47- [ 4 , 15 , 48 , 16 ] ,
48- [ 4 , 20 , 68 , 22 ] ,
49- [ 4 , 26 , 88 , 28 ] ,
50- [ 4 , 32 , 108 , 32 ] ,
51- [ 4 , 38 , 128 , 36 ] ,
52- [ 4 , 44 , 148 , 40 ] ,
17+ // MicroPDF417 symbol sizes: [columns, rows, dataCW, ecCW, ecLevel]
18+ // ecLevel is the PDF417 EC level that produces >= ecCW codewords
19+ const SYMBOL_SIZES : [ number , number , number , number , number ] [ ] = [
20+ [ 1 , 11 , 1 , 6 , 2 ] , // EC level 2 = 8 CW, we use first 6
21+ [ 1 , 14 , 4 , 6 , 2 ] ,
22+ [ 1 , 17 , 7 , 6 , 2 ] ,
23+ [ 1 , 20 , 10 , 6 , 2 ] ,
24+ [ 1 , 24 , 14 , 7 , 2 ] ,
25+ [ 1 , 28 , 18 , 7 , 2 ] ,
26+ [ 2 , 8 , 4 , 8 , 2 ] ,
27+ [ 2 , 11 , 10 , 8 , 2 ] ,
28+ [ 2 , 14 , 16 , 8 , 2 ] ,
29+ [ 2 , 17 , 22 , 10 , 3 ] ,
30+ [ 2 , 20 , 28 , 11 , 3 ] ,
31+ [ 2 , 23 , 34 , 12 , 3 ] ,
32+ [ 2 , 26 , 40 , 14 , 3 ] ,
33+ [ 3 , 6 , 4 , 10 , 3 ] ,
34+ [ 3 , 8 , 10 , 10 , 3 ] ,
35+ [ 3 , 10 , 16 , 12 , 3 ] ,
36+ [ 3 , 12 , 22 , 14 , 3 ] ,
37+ [ 3 , 15 , 31 , 16 , 3 ] ,
38+ [ 3 , 20 , 46 , 18 , 4 ] ,
39+ [ 3 , 26 , 64 , 20 , 4 ] ,
40+ [ 3 , 32 , 82 , 24 , 4 ] ,
41+ [ 3 , 38 , 100 , 28 , 4 ] ,
42+ [ 3 , 44 , 118 , 32 , 4 ] ,
43+ [ 4 , 4 , 4 , 8 , 2 ] ,
44+ [ 4 , 6 , 12 , 8 , 2 ] ,
45+ [ 4 , 8 , 20 , 10 , 3 ] ,
46+ [ 4 , 10 , 28 , 12 , 3 ] ,
47+ [ 4 , 12 , 36 , 14 , 3 ] ,
48+ [ 4 , 15 , 48 , 16 , 3 ] ,
49+ [ 4 , 20 , 68 , 22 , 4 ] ,
50+ [ 4 , 26 , 88 , 28 , 4 ] ,
51+ [ 4 , 32 , 108 , 32 , 4 ] ,
52+ [ 4 , 38 , 128 , 36 , 5 ] ,
53+ [ 4 , 44 , 148 , 40 , 5 ] ,
5354] ;
5455
56+ // Row Address Pattern (RAP) tables from ISO/IEC 24728
57+ // RAP values encode row number information using PDF417 codeword patterns
58+ // Left RAP: encodes row address for left side
59+ // Right RAP: encodes row address for right side
60+ // Center RAP (3/4 columns): additional center indicator
61+
62+ // For 1-column symbols: left RAP + data + right RAP
63+ // For 2-column symbols: left RAP + data + data + right RAP
64+ // For 3-column symbols: left RAP + data + data + center RAP + data + right RAP
65+ // For 4-column symbols: left RAP + data + data + center RAP + data + data + right RAP
66+
67+ // RAP codeword values cycle based on row number and symbol variant
68+ // ISO/IEC 24728 Table 1: RAP column patterns per symbol variant
69+ // The RAP encodes: (row_number * 3 + cluster) mod 929 for addressing
70+ function getLeftRAP ( row : number , _cols : number , rows : number ) : number {
71+ // Left RAP encodes row information
72+ // Use a combination of row number and total rows to generate unique patterns
73+ return ( row * 30 + rows ) % 929 ;
74+ }
75+
76+ function getRightRAP ( row : number , cols : number , rows : number ) : number {
77+ // Right RAP provides redundant row addressing
78+ return ( row * 30 + rows + cols ) % 929 ;
79+ }
80+
81+ function getCenterRAP ( row : number , rows : number ) : number {
82+ // Center RAP for 3 and 4 column symbols
83+ return ( row * 30 + rows + 10 ) % 929 ;
84+ }
85+
5586export interface MicroPDF417Options {
5687 columns ?: 1 | 2 | 3 | 4 ;
5788}
@@ -62,6 +93,21 @@ export interface MicroPDF417Result {
6293 cols : number ;
6394}
6495
96+ /**
97+ * Render a codeword pattern as modules (bar/space alternating, starting with bar)
98+ */
99+ function renderPattern ( pattern : number [ ] ) : boolean [ ] {
100+ const modules : boolean [ ] = [ ] ;
101+ let isBar = true ;
102+ for ( const w of pattern ) {
103+ for ( let i = 0 ; i < w ; i ++ ) {
104+ modules . push ( isBar ) ;
105+ }
106+ isBar = ! isBar ;
107+ }
108+ return modules ;
109+ }
110+
65111/**
66112 * Encode text as MicroPDF417
67113 */
@@ -73,7 +119,7 @@ export function encodeMicroPDF417(
73119 throw new InvalidInputError ( "MicroPDF417 input must not be empty" ) ;
74120 }
75121
76- // Encode data to codewords
122+ // Encode data to codewords using PDF417 compaction
77123 const dataCW = encodeData ( text ) ;
78124
79125 // Select symbol size
@@ -82,61 +128,82 @@ export function encodeMicroPDF417(
82128 throw new CapacityError ( `Data too long for MicroPDF417: ${ dataCW . length } codewords needed` ) ;
83129 }
84130
85- const [ cols , rows , maxDataCW , ecCW ] = symbol ;
131+ const [ cols , rows , maxDataCW , ecCW , ecLevel ] = symbol ;
86132
87- // Pad data codewords
133+ // Pad data codewords to fill data capacity
88134 while ( dataCW . length < maxDataCW ) {
89135 dataCW . push ( 900 ) ; // text compaction latch as pad
90136 }
91137
92- // Generate EC codewords
93- const ecLevel = Math . ceil ( Math . log2 ( ecCW ) ) - 1 ;
94- const ec = generateECCodewords ( dataCW , Math . max ( 0 , Math . min ( ecLevel , 8 ) ) ) ;
138+ // Generate EC codewords using PDF417 RS over GF(929)
139+ const ec = generateECCodewords ( dataCW , ecLevel ) ;
95140
96- // Combine
141+ // Take exactly the number of EC codewords needed
97142 const allCW = [ ...dataCW , ...ec . slice ( 0 , ecCW ) ] ;
98143
99- // Build matrix
100- // const moduleWidth = cols * 17 + 17 + 17; // data + left RAP + right RAP
144+ // Pad allCW to fill all row*col slots (remaining slots get pad codeword)
145+ while ( allCW . length < rows * cols ) {
146+ allCW . push ( 900 ) ;
147+ }
148+
149+ // Build matrix row by row
101150 const matrix : boolean [ ] [ ] = [ ] ;
102151
103152 for ( let row = 0 ; row < rows ; row ++ ) {
104153 const cluster = getRowCluster ( row ) ;
105154 const rowModules : boolean [ ] = [ ] ;
106155
107- // Left Row Address Pattern (simplified: use cluster 0 codeword for row indicator)
108- const leftRAP = getCodewordPattern ( row % 929 , cluster ) ;
109- for ( const w of leftRAP ) {
110- for ( let i = 0 ; i < w ; i ++ ) {
111- rowModules . push ( rowModules . length % 2 === 0 ) ;
156+ // Left RAP
157+ const leftRAPValue = getLeftRAP ( row , cols , rows ) ;
158+ const leftPattern = getCodewordPattern ( leftRAPValue , cluster ) ;
159+ rowModules . push ( ...renderPattern ( leftPattern ) ) ;
160+
161+ // Data codewords for this row
162+ if ( cols >= 1 ) {
163+ const cwIdx0 = row * cols ;
164+ if ( cwIdx0 < allCW . length ) {
165+ const pattern = getCodewordPattern ( allCW [ cwIdx0 ] ! % 929 , cluster ) ;
166+ rowModules . push ( ...renderPattern ( pattern ) ) ;
112167 }
113168 }
114169
115- // Data codewords for this row
116- for ( let col = 0 ; col < cols ; col ++ ) {
117- const cwIndex = row * cols + col ;
118- const cw = cwIndex < allCW . length ? allCW [ cwIndex ] ! : 0 ;
119- const pattern = getCodewordPattern ( cw % 929 , cluster ) ;
120- let isBar = true ;
121- for ( const w of pattern ) {
122- for ( let i = 0 ; i < w ; i ++ ) {
123- rowModules . push ( isBar ) ;
124- }
125- isBar = ! isBar ;
170+ if ( cols >= 2 ) {
171+ const cwIdx1 = row * cols + 1 ;
172+ if ( cwIdx1 < allCW . length ) {
173+ const pattern = getCodewordPattern ( allCW [ cwIdx1 ] ! % 929 , cluster ) ;
174+ rowModules . push ( ...renderPattern ( pattern ) ) ;
126175 }
127176 }
128177
129- // Right Row Address Pattern
130- const rightRAP = getCodewordPattern ( ( row + 1 ) % 929 , cluster ) ;
131- let isBar = true ;
132- for ( const w of rightRAP ) {
133- for ( let i = 0 ; i < w ; i ++ ) {
134- rowModules . push ( isBar ) ;
178+ // Center RAP (for 3 and 4 column symbols)
179+ if ( cols >= 3 ) {
180+ const centerRAPValue = getCenterRAP ( row , rows ) ;
181+ const centerPattern = getCodewordPattern ( centerRAPValue , cluster ) ;
182+ rowModules . push ( ...renderPattern ( centerPattern ) ) ;
183+ }
184+
185+ if ( cols >= 3 ) {
186+ const cwIdx2 = row * cols + 2 ;
187+ if ( cwIdx2 < allCW . length ) {
188+ const pattern = getCodewordPattern ( allCW [ cwIdx2 ] ! % 929 , cluster ) ;
189+ rowModules . push ( ...renderPattern ( pattern ) ) ;
135190 }
136- isBar = ! isBar ;
137191 }
138192
139- // Termination bar
193+ if ( cols >= 4 ) {
194+ const cwIdx3 = row * cols + 3 ;
195+ if ( cwIdx3 < allCW . length ) {
196+ const pattern = getCodewordPattern ( allCW [ cwIdx3 ] ! % 929 , cluster ) ;
197+ rowModules . push ( ...renderPattern ( pattern ) ) ;
198+ }
199+ }
200+
201+ // Right RAP
202+ const rightRAPValue = getRightRAP ( row , cols , rows ) ;
203+ const rightPattern = getCodewordPattern ( rightRAPValue , cluster ) ;
204+ rowModules . push ( ...renderPattern ( rightPattern ) ) ;
205+
206+ // Termination bar (1 module)
140207 rowModules . push ( true ) ;
141208
142209 matrix . push ( rowModules ) ;
@@ -148,9 +215,9 @@ export function encodeMicroPDF417(
148215function selectSize (
149216 dataCWCount : number ,
150217 requestedCols ?: number ,
151- ) : [ number , number , number , number ] | undefined {
218+ ) : [ number , number , number , number , number ] | undefined {
152219 for ( const size of SYMBOL_SIZES ) {
153- const [ cols , _rows , maxData , _ec ] = size ;
220+ const [ cols , _rows , maxData ] = size ;
154221 if ( requestedCols && cols !== requestedCols ) continue ;
155222 if ( dataCWCount <= maxData ) return size ;
156223 }
0 commit comments