@nuintun/qrcode
Version:
A pure JavaScript QRCode encode and decode library.
165 lines (160 loc) • 4.75 kB
JavaScript
/**
* @module QRCode
* @package @nuintun/qrcode
* @license MIT
* @version 5.0.2
* @author nuintun <nuintun@qq.com>
* @description A pure JavaScript QRCode encode and decode library.
* @see https://github.com/nuintun/qrcode#readme
*/
;
const utils = require('../common/utils.cjs');
const mask = require('../common/mask.cjs');
const FormatInfo = require('./FormatInfo.cjs');
const Version = require('../common/Version.cjs');
/**
* @module BitMatrixParser
*/
function copyBit(matrix, x, y, bits) {
return matrix.get(x, y) ? (bits << 1) | 0x01 : bits << 1;
}
class BitMatrixParser {
#size;
#matrix;
constructor(matrix) {
const { width, height } = matrix;
this.#matrix = matrix.clone();
this.#size = Math.min(width, height);
}
readVersion() {
const size = this.#size;
const versionNumber = utils.toInt32((size - 17) / 4);
if (versionNumber < 1) {
throw new Error('illegal version');
}
if (versionNumber <= 6) {
return Version.VERSIONS[versionNumber - 1];
}
// Hmm, failed. Try bottom left: 6 wide by 3 tall.
let version1 = 0;
let version2 = 0;
const min = size - 11;
const matrix = this.#matrix;
for (let y = 5; y >= 0; y--) {
for (let x = size - 9; x >= min; x--) {
version1 = copyBit(matrix, x, y, version1);
}
}
for (let x = 5; x >= 0; x--) {
for (let y = size - 9; y >= min; y--) {
version2 = copyBit(matrix, x, y, version2);
}
}
const version = Version.decodeVersion(version1, version2);
if (version.size > size) {
throw new Error('matrix size too small for version');
}
return version;
}
readFormatInfo() {
let formatInfo1 = 0;
let formatInfo2 = 0;
const matrix = this.#matrix;
const size = this.#size;
const max = size - 7;
// Read top-left format info bits.
for (let x = 0; x <= 8; x++) {
if (x !== 6) {
// Skip timing pattern bit.
formatInfo1 = copyBit(matrix, x, 8, formatInfo1);
}
}
for (let y = 7; y >= 0; y--) {
if (y !== 6) {
// Skip timing pattern bit.
formatInfo1 = copyBit(matrix, 8, y, formatInfo1);
}
}
for (let y = size - 1; y >= max; y--) {
formatInfo2 = copyBit(matrix, 8, y, formatInfo2);
}
for (let x = size - 8; x < size; x++) {
formatInfo2 = copyBit(matrix, x, 8, formatInfo2);
}
return FormatInfo.decodeFormatInfo(formatInfo1, formatInfo2);
}
readCodewords(version, ecLevel) {
let bitsRead = 0;
let byteOffset = 0;
let currentByte = 0;
let readingUp = true;
const size = this.#size;
const matrix = this.#matrix;
const ecBlocks = version.getECBlocks(ecLevel);
const functionPattern = Version.buildFunctionPattern(version);
const codewords = new Uint8Array(ecBlocks.numTotalCodewords);
// Read columns in pairs, from right to left.
for (let x = size - 1; x > 0; x -= 2) {
if (x === 6) {
// Skip whole column with vertical alignment pattern
// saves time and makes the other code proceed more cleanly.
x--;
}
// Read alternatingly from bottom to top then top to bottom.
for (let count = 0; count < size; count++) {
const y = readingUp ? size - 1 - count : count;
for (let col = 0; col < 2; col++) {
const offsetX = x - col;
// Ignore bits covered by the function pattern.
if (!functionPattern.get(offsetX, y)) {
// Read a bit
bitsRead++;
currentByte <<= 1;
if (matrix.get(offsetX, y)) {
currentByte |= 1;
}
// If we've made a whole byte, save it off.
if (bitsRead === 8) {
codewords[byteOffset++] = currentByte;
bitsRead = 0;
currentByte = 0;
}
}
}
}
// Switch directions.
readingUp = !readingUp;
}
if (byteOffset !== ecBlocks.numTotalCodewords) {
throw new Error('illegal codewords length');
}
return codewords;
}
unmask(mask$1) {
const size = this.#size;
const matrix = this.#matrix;
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
if (mask.isApplyMask(mask$1, x, y)) {
matrix.flip(x, y);
}
}
}
}
remask(mask) {
this.unmask(mask);
}
mirror() {
const size = this.#size;
const matrix = this.#matrix;
for (let x = 0; x < size; x++) {
for (let y = x + 1; y < size; y++) {
if (matrix.get(x, y) !== matrix.get(y, x)) {
matrix.flip(x, y);
matrix.flip(y, x);
}
}
}
}
}
exports.BitMatrixParser = BitMatrixParser;