All files / qr-code/src/encode interleave.ts

100% Statements 28/28
100% Branches 6/6
100% Functions 1/1
100% Lines 23/23

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54                                          430x 430x 430x 430x 430x 430x       430x 430x 430x 3987x 3987x 3987x   3987x 3987x 3987x 3987x     430x 430x 430x 21907x 256550x 254311x     430x    
import type { ErrorCorrectionLevel } from "../types";
import { ecCodewordsPerBlock, numEcBlocks, totalCodewords } from "./capacity";
import { rsEncode } from "./reedSolomon";
 
/**
 * Block splitting and codeword interleaving (ISO/IEC 18004 §7.6).
 *
 * Data is split into blocks — some "short", some one codeword longer — each
 * gets its own Reed–Solomon parity, and the blocks are then interleaved so a
 * physical smudge damages a few codewords in *every* block rather than
 * destroying one block entirely.
 *
 * Short blocks are padded with a placeholder byte during assembly purely so
 * every block's parity section lines up at the same index; the placeholder is
 * skipped when emitting, never written to the symbol.
 */
export function interleave(
  data: Readonly<Uint8Array>,
  version: number,
  level: ErrorCorrectionLevel,
): Uint8Array {
  const blockCount = numEcBlocks(version, level);
  const ecLength = ecCodewordsPerBlock(version, level);
  const rawCodewords = totalCodewords(version);
  const shortBlockLength = Math.floor(rawCodewords / blockCount);
  const shortBlockCount = blockCount - (rawCodewords % blockCount);
  const shortDataLength = shortBlockLength - ecLength;
 
  // Every assembled block is shortBlockLength + 1 long: short blocks reach it
  // with a placeholder at `shortDataLength`, long blocks with a real codeword.
  const stride = shortBlockLength + 1;
  const blocks: Uint8Array[] = [];
  for (let i = 0, offset = 0; i < blockCount; i++) {
    const dataLength = shortDataLength + (i < shortBlockCount ? 0 : 1);
    const blockData = data.slice(offset, offset + dataLength);
    offset += dataLength;
 
    const block = new Uint8Array(stride);
    block.set(blockData);
    block.set(rsEncode(blockData, ecLength), stride - ecLength);
    blocks.push(block);
  }
 
  const result = new Uint8Array(rawCodewords);
  let out = 0;
  for (let i = 0; i < stride; i++) {
    for (let j = 0; j < blockCount; j++) {
      if (i === shortDataLength && j < shortBlockCount) continue; // the placeholder
      result[out++] = blocks[j]![i]!;
    }
  }
  return result;
}