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 | 304x 304x 885x 10347x 2x 10345x 82850x 82850x 82850x 82850x 82850x 300x 331x 331x 32x 32x 361x 361x 32x 32x 24x 8x 7x 32x 142x 142x 1243x 142x 142x 7345x 157x 293x 293x 293x 4x 4x 293x 325x 325x 1x 324x 324x 324x 292x 1x 291x 291x 291x 291x 291x 291x 25731x 291x | import type { ErrorCorrectionLevel, QRSegment } from "../types";
import { QRCapacityError } from "../errors";
import { MODE_INDICATOR, charCountBits, dataCapacityBits } from "./capacity";
import { ECI_UTF8, alphanumericValue } from "./segment";
/**
* Bit-level assembly of the data codewords (ISO/IEC 18004 §7.4).
*
* Segments are written most-significant-bit first, then the stream is closed
* with a terminator, padded to a byte boundary, and filled to the version's
* exact data capacity with the alternating 0xEC / 0x11 pad codewords the spec
* mandates.
*/
/** An MSB-first bit accumulator. */
export class BitWriter {
private readonly bytes: number[] = [];
private bits = 0;
/** Bits written so far. */
get length(): number {
return this.bits;
}
/** Append the low `count` bits of `value`, most-significant first. */
push(value: number, count: number): void {
if (count < 0 || count > 32) {
throw new RangeError(`Cannot write ${count} bits at once`);
}
for (let i = count - 1; i >= 0; i--) {
const bit = (value >>> i) & 1;
const byteIndex = this.bits >>> 3;
if (byteIndex === this.bytes.length) this.bytes.push(0);
this.bytes[byteIndex]! |= bit << (7 - (this.bits & 7));
this.bits++;
}
}
/** The written bits as bytes, zero-padded to the next byte boundary. */
toUint8Array(): Uint8Array {
return Uint8Array.from(this.bytes);
}
}
/** Write one segment's payload (no mode indicator, no character count). */
export function writeSegmentPayload(writer: BitWriter, segment: QRSegment): void {
const { data } = segment;
switch (segment.mode) {
case "numeric": {
let i = 0;
for (; i + 3 <= data.length; i += 3) {
const value =
(data[i]! - 0x30) * 100 + (data[i + 1]! - 0x30) * 10 + (data[i + 2]! - 0x30);
writer.push(value, 10);
}
const rest = data.length - i;
if (rest === 2) {
writer.push((data[i]! - 0x30) * 10 + (data[i + 1]! - 0x30), 7);
} else if (rest === 1) {
writer.push(data[i]! - 0x30, 4);
}
break;
}
case "alphanumeric": {
let i = 0;
for (; i + 2 <= data.length; i += 2) {
writer.push(alphanumericValue(data[i]!) * 45 + alphanumericValue(data[i + 1]!), 11);
}
if (i < data.length) writer.push(alphanumericValue(data[i]!), 6);
break;
}
case "byte": {
for (let i = 0; i < data.length; i++) writer.push(data[i]!, 8);
break;
}
}
}
/**
* Segments → the full data-codeword block for a version and level.
*
* @param segments - Mode-split payload.
* @param version - Symbol version 1–40.
* @param level - Error-correction level.
* @param eci - Prefix an ECI 26 (UTF-8) header.
* @throws {QRCapacityError} when the segments exceed the version's capacity.
*/
export function buildBitStream(
segments: readonly QRSegment[],
version: number,
level: ErrorCorrectionLevel,
eci: boolean,
): Uint8Array {
const capacityBits = dataCapacityBits(version, level);
const writer = new BitWriter();
if (eci) {
writer.push(0b0111, 4);
writer.push(ECI_UTF8, 8);
}
for (const segment of segments) {
const countBits = charCountBits(segment.mode, version);
if (segment.charCount >= 1 << countBits) {
throw new RangeError(
`Segment of ${segment.charCount} ${segment.mode} characters exceeds the ` +
`${countBits}-bit character count field at version ${version}`,
);
}
writer.push(MODE_INDICATOR[segment.mode], 4);
writer.push(segment.charCount, countBits);
writeSegmentPayload(writer, segment);
}
if (writer.length > capacityBits) {
throw new QRCapacityError({
needed: writer.length,
capacity: capacityBits,
level,
maxVersion: version,
});
}
// Terminator (up to four zero bits), then pad to a whole byte.
writer.push(0, Math.min(4, capacityBits - writer.length));
writer.push(0, (8 - (writer.length % 8)) % 8);
const written = writer.toUint8Array();
const codewords = new Uint8Array(capacityBits / 8);
codewords.set(written);
for (let i = written.length, pad = 0xec; i < codewords.length; i++, pad ^= 0xec ^ 0x11) {
codewords[i] = pad;
}
return codewords;
}
|