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 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | 586x 5x 5x 5x 5x 5x 140x 5x 5x 5x 5x 5x 5x 5x 5x 1968x 1968x 1968x 1968x 1968x 5x 1235x 1235x 825477x 825477x 825477x 825477x 131x 131x 131x 219619x 131x 2800x 2800x 2800x 2800x 2800x 131x 120x 120x 120x 15505x 15505x 14400x 14400x 120x | import {
codewordModuleOrder,
dataCodewords,
ecCodewordsPerBlock,
encodeQR,
maskAt,
numEcBlocks,
rsEncode,
type ErrorCorrectionLevel,
type QRMatrix,
} from "@usefy/qr-code/headless";
import { BitMatrix } from "../image/bitmatrix";
/**
* Test-only helpers that turn `@usefy/qr-code`'s encoder into this package's
* oracle (SPEC §7).
*
* The counterpart encoder produces both the symbol *and* the payload it should
* decode to, so every assertion here is exact rather than "looks plausible".
* Degradation is seeded and reproducible: a failure can be replayed from its
* seed instead of being a flaky "sometimes decodes".
*/
/** A `QRMatrix` from the encoder, as the module grid the decoder consumes. */
export function gridFromMatrix(matrix: QRMatrix): BitMatrix {
return BitMatrix.square(matrix.size, Uint8Array.from(matrix.modules));
}
/**
* Build a real, scannable symbol carrying an **arbitrary** bit stream.
*
* `@usefy/qr-code` only emits the modes it encodes, so Kanji, Structured
* Append and FNC1 have no oracle. This takes a genuine symbol of the requested
* version/level/mask — keeping its function patterns, format word and mask
* exactly as the encoder produced them — and rewrites only the data modules
* with codewords built here. The result travels the entire decode path, so
* those modes are tested against the real reader rather than a shortcut.
*
* Restricted to single-block versions so no interleaving has to be restated:
* at levels L and M, versions 1–4 qualify.
*/
export function symbolWithBitStream(
bits: Readonly<Uint8Array>,
bitLength: number,
version: number,
level: ErrorCorrectionLevel,
mask = 0,
): BitMatrix {
Iif (numEcBlocks(version, level) !== 1) {
throw new RangeError(
`symbolWithBitStream needs a single-block symbol; v${version}-${level} has ` +
`${numEcBlocks(version, level)}`,
);
}
const dataLength = dataCodewords(version, level);
const data = new Uint8Array(dataLength);
data.set(bits.subarray(0, Math.min(bits.length, dataLength)));
// Pad exactly as the specification does, so the parser meets a realistic
// stream: terminator (already implied by the zero fill), then 0xEC / 0x11.
for (let i = Math.ceil(bitLength / 8); i < dataLength; i++) {
data[i] = i % 2 === Math.ceil(bitLength / 8) % 2 ? 0xec : 0x11;
}
const ecLength = ecCodewordsPerBlock(version, level);
const codewords = new Uint8Array(dataLength + ecLength);
codewords.set(data);
codewords.set(rsEncode(data, ecLength), dataLength);
// A throwaway symbol of the same shape supplies the function patterns and the
// format word; only the data modules are replaced.
const base = encodeQR("x", { version, level, mask });
const grid = gridFromMatrix(base);
const order = codewordModuleOrder(version);
for (let bit = 0; bit < codewords.length * 8; bit++) {
const index = order[bit]!;
const x = index % grid.size;
const y = (index / grid.size) | 0;
const value = (codewords[bit >>> 3]! >>> (7 - (bit & 7))) & 1;
grid.data[index] = value ^ (maskAt(mask, x, y) ? 1 : 0);
}
return grid;
}
/** Mulberry32 — small, fast, and identical across runs and platforms. */
export function seededRandom(seed: number): () => number {
let state = seed >>> 0;
return () => {
state = (state + 0x6d2b79f5) >>> 0;
let t = Math.imul(state ^ (state >>> 15), 1 | state);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
/**
* Flip `count` distinct modules of a grid, avoiding the function patterns.
*
* Damaging only data modules is what makes "recovers up to the EC budget" a
* meaningful claim: flipping a finder pattern tests detection, not error
* correction, and mixing the two would blur which layer a failure came from.
*/
export function damageDataModules(
grid: BitMatrix,
reserved: Readonly<Uint8Array>,
count: number,
random: () => number,
): BitMatrix {
const damaged = new BitMatrix(grid.width, grid.height, Uint8Array.from(grid.data));
const candidates: number[] = [];
for (let i = 0; i < reserved.length; i++) {
if (reserved[i] === 0) candidates.push(i);
}
// Partial Fisher–Yates: only the prefix we consume gets shuffled.
for (let i = 0; i < count && i < candidates.length; i++) {
const j = i + Math.floor(random() * (candidates.length - i));
const temp = candidates[i]!;
candidates[i] = candidates[j]!;
candidates[j] = temp;
damaged.data[candidates[i]!] ^= 1;
}
return damaged;
}
/** Flip `count` distinct modules anywhere in the grid, function patterns included. */
export function damageAnyModules(
grid: BitMatrix,
count: number,
random: () => number,
): BitMatrix {
const damaged = new BitMatrix(grid.width, grid.height, Uint8Array.from(grid.data));
const seen = new Set<number>();
while (seen.size < count && seen.size < damaged.data.length) {
const index = Math.floor(random() * damaged.data.length);
if (seen.has(index)) continue;
seen.add(index);
damaged.data[index] ^= 1;
}
return damaged;
}
|