@@ -94,6 +94,20 @@ 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+ }
110+
97111export interface RMQROptions {
98112 ecLevel ?: "M" | "H" ;
99113 version ?: number ; // index into RMQR_SIZES (0-31)
@@ -211,70 +225,154 @@ export function encodeRMQR(text: string, options: RMQROptions = {}): boolean[][]
211225 const ecBytes = generateECCodewords ( dataBytes , ecCW ) ;
212226 const allBytes = [ ...dataBytes , ...ecBytes ] ;
213227
214- // Build matrix
228+ // Build matrix (null = data area, boolean = function pattern)
215229 const matrix : ( boolean | null ) [ ] [ ] = Array . from ( { length : rows } , ( ) =>
216230 Array . from < boolean | null > ( { length : cols } ) . fill ( null ) ,
217231 ) ;
218232
219- // Finder pattern (7×7 at top-left)
220- for ( let r = 0 ; r < 7 && r < rows ; r ++ ) {
221- for ( let c = 0 ; c < 7 && c < cols ; c ++ ) {
233+ // 1. Finder pattern (7×7 at top-left)
234+ for ( let r = 0 ; r < 7 ; r ++ ) {
235+ for ( let c = 0 ; c < 7 ; c ++ ) {
222236 const isOuter = r === 0 || r === 6 || c === 0 || c === 6 ;
223237 const isInner = r >= 2 && r <= 4 && c >= 2 && c <= 4 ;
224238 matrix [ r ] ! [ c ] = isOuter || isInner ;
225239 }
226240 }
241+ // Separator around finder
242+ if ( rows > 7 ) for ( let c = 0 ; c < 8 && c < cols ; c ++ ) matrix [ 7 ] ! [ c ] = false ;
243+ for ( let r = 0 ; r < 7 && 7 < cols ; r ++ ) matrix [ r ] ! [ 7 ] = false ;
227244
228- // Separator
229- for ( let i = 0 ; i < 8 && i < cols ; i ++ ) {
230- if ( 7 < rows && matrix [ 7 ] ! [ i ] === null ) matrix [ 7 ] ! [ i ] = false ;
231- }
232- for ( let i = 0 ; i < 8 && i < rows ; i ++ ) {
233- if ( 7 < cols && matrix [ i ] ! [ 7 ] === null ) matrix [ i ] ! [ 7 ] = false ;
245+ // 2. Bottom-right alignment pattern (5×5)
246+ const arx = cols - 5 ;
247+ const ary = rows - 5 ;
248+ const AP = [ 0x1f , 0x11 , 0x15 , 0x11 , 0x1f ] ; // 5x5 alignment
249+ for ( let r = 0 ; r < 5 ; r ++ ) {
250+ for ( let c = 0 ; c < 5 ; c ++ ) {
251+ matrix [ ary + r ] ! [ arx + c ] = ( ( AP [ r ] ! >> ( 4 - c ) ) & 1 ) === 1 ;
252+ }
234253 }
235254
236- // Timing patterns
237- for ( let c = 8 ; c < cols ; c ++ ) {
255+ // 3. Corner markers (2×2 at bottom-left and top-right)
256+ // Bottom-left
257+ matrix [ rows - 2 ] ! [ 0 ] = true ;
258+ matrix [ rows - 2 ] ! [ 1 ] = true ;
259+ matrix [ rows - 1 ] ! [ 0 ] = true ;
260+ matrix [ rows - 1 ] ! [ 1 ] = false ;
261+ // Top-right
262+ matrix [ 0 ] ! [ cols - 2 ] = true ;
263+ matrix [ 0 ] ! [ cols - 1 ] = true ;
264+ matrix [ 1 ] ! [ cols - 2 ] = false ;
265+ matrix [ 1 ] ! [ cols - 1 ] = true ;
266+
267+ // 4. Timing patterns on all 4 edges
268+ for ( let c = 7 ; c < cols - 1 ; c ++ ) {
238269 if ( matrix [ 0 ] ! [ c ] === null ) matrix [ 0 ] ! [ c ] = c % 2 === 0 ;
270+ if ( matrix [ rows - 1 ] ! [ c ] === null ) matrix [ rows - 1 ] ! [ c ] = ( c + rows + 1 ) % 2 === 0 ;
239271 }
240- for ( let r = 8 ; r < rows ; r ++ ) {
272+ for ( let r = 1 ; r < rows - 1 ; r ++ ) {
241273 if ( matrix [ r ] ! [ 0 ] === null ) matrix [ r ] ! [ 0 ] = r % 2 === 0 ;
274+ if ( matrix [ r ] ! [ cols - 1 ] === null ) matrix [ r ] ! [ cols - 1 ] = ( r + 1 ) % 2 === 0 ;
242275 }
243276
244- // Corner finder (bottom-right 5×5)
245- const cr = rows - 1 ;
246- const cc = cols - 1 ;
247- for ( let r = - 2 ; r <= 2 ; r ++ ) {
248- for ( let c = - 2 ; c <= 2 ; c ++ ) {
249- const rr = cr + r ;
250- const ccc = cc + c ;
251- if ( rr >= 0 && rr < rows && ccc >= 0 && ccc < cols ) {
252- const isOuter = Math . abs ( r ) === 2 || Math . abs ( c ) === 2 ;
253- const isCenter = r === 0 && c === 0 ;
254- if ( matrix [ rr ] ! [ ccc ] === null ) {
255- matrix [ rr ] ! [ ccc ] = isOuter || isCenter ;
256- }
257- }
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 ) ;
284+
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 ;
310+ }
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 ;
258336 }
259337 }
260338
261- // Place data
339+ // 6. Place data bits (column-pair zigzag, skip timing columns)
262340 const allBits : number [ ] = [ ] ;
263341 for ( const byte of allBytes ) {
264342 pushBits ( allBits , byte , 8 ) ;
265343 }
266344
267345 let bitIdx = 0 ;
268- for ( let c = cols - 1 ; c >= 1 ; c -= 2 ) {
269- for ( let r = 0 ; r < rows ; r ++ ) {
270- for ( const cc2 of [ c , c - 1 ] ) {
271- if ( cc2 >= 0 && matrix [ r ] ! [ cc2 ] === null ) {
272- matrix [ r ] ! [ cc2 ] = bitIdx < allBits . length ? allBits [ bitIdx ] ! === 1 : false ;
346+ let upward = true ;
347+ for ( let col = cols - 2 ; col >= 1 ; col -= 2 ) {
348+ // Skip timing column (not applicable for rMQR — no column 6 timing)
349+ const rowOrder = upward
350+ ? Array . from ( { length : rows } , ( _ , i ) => rows - 1 - i )
351+ : Array . from ( { length : rows } , ( _ , i ) => i ) ;
352+
353+ for ( const r of rowOrder ) {
354+ for ( const c of [ col , col - 1 ] ) {
355+ if ( c >= 0 && c < cols && matrix [ r ] ! [ c ] === null ) {
356+ matrix [ r ] ! [ c ] = bitIdx < allBits . length ? allBits [ bitIdx ] ! === 1 : false ;
273357 bitIdx ++ ;
274358 }
275359 }
276360 }
361+ upward = ! upward ;
362+ }
363+
364+ // 7. Apply mask: (row/2 + col/3) % 2 == 0 (fixed mask per ISO/IEC 23941)
365+ const result = matrix . map ( ( row ) => row . map ( ( cell ) => cell === true ) ) ;
366+ for ( let r = 0 ; r < rows ; r ++ ) {
367+ for ( let c = 0 ; c < cols ; c ++ ) {
368+ // Only mask data modules (null in original matrix)
369+ if ( matrix [ r ] ! [ c ] === null ) {
370+ if ( ( Math . floor ( r / 2 ) + Math . floor ( c / 3 ) ) % 2 === 0 ) {
371+ result [ r ] ! [ c ] = ! result [ r ] ! [ c ] ;
372+ }
373+ }
374+ }
277375 }
278376
279- return matrix . map ( ( row ) => row . map ( ( cell ) => cell === true ) ) ;
377+ return result ;
280378}
0 commit comments