@akamfoad/qr
Version:
Fully typed QRCode encoding implementation in JavaScript with no dependencies!
300 lines (261 loc) • 7.32 kB
text/typescript
import Mode from './mode';
import Polynomial from './Polynomial';
import math from './math';
import type QRCode from './QRCode';
const QRMaskPattern = {
PATTERN000: 0,
PATTERN001: 1,
PATTERN010: 2,
PATTERN011: 3,
PATTERN100: 4,
PATTERN101: 5,
PATTERN110: 6,
PATTERN111: 7,
};
const QRUtil = {
PATTERN_POSITION_TABLE: [
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
],
G15:
(1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0),
G18:
(1 << 12) |
(1 << 11) |
(1 << 10) |
(1 << 9) |
(1 << 8) |
(1 << 5) |
(1 << 2) |
(1 << 0),
G15_MASK: (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1),
getBCHTypeInfo: function (data: number) {
let d = data << 10;
while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15) >= 0) {
d ^=
QRUtil.G15 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15));
}
return ((data << 10) | d) ^ QRUtil.G15_MASK;
},
getBCHTypeNumber: function (data: number) {
let d = data << 12;
while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18) >= 0) {
d ^=
QRUtil.G18 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18));
}
return (data << 12) | d;
},
getBCHDigit: function (data: number) {
let digit = 0;
while (data != 0) {
digit++;
data >>>= 1;
}
return digit;
},
getPatternPosition: function (typeNumber: number) {
return QRUtil.PATTERN_POSITION_TABLE[typeNumber - 1];
},
getMask: function (maskPattern: number, i: number, j: number) {
switch (maskPattern) {
case QRMaskPattern.PATTERN000:
return (i + j) % 2 == 0;
case QRMaskPattern.PATTERN001:
return i % 2 == 0;
case QRMaskPattern.PATTERN010:
return j % 3 == 0;
case QRMaskPattern.PATTERN011:
return (i + j) % 3 == 0;
case QRMaskPattern.PATTERN100:
return (Math.floor(i / 2) + Math.floor(j / 3)) % 2 == 0;
case QRMaskPattern.PATTERN101:
return ((i * j) % 2) + ((i * j) % 3) == 0;
case QRMaskPattern.PATTERN110:
return (((i * j) % 2) + ((i * j) % 3)) % 2 == 0;
case QRMaskPattern.PATTERN111:
return (((i * j) % 3) + ((i + j) % 2)) % 2 == 0;
default:
throw new Error('bad maskPattern:' + maskPattern);
}
},
getErrorCorrectPolynomial: function (errorCorrectLength: number) {
let a = new Polynomial([1], 0);
for (let i = 0; i < errorCorrectLength; i++) {
a = a.multiply(new Polynomial([1, math.gexp(i)], 0));
}
return a;
},
getLengthInBits: function (mode: number, type: number) {
if (1 <= type && type < 10) {
// 1 - 9
switch (mode) {
case Mode.MODE_NUMBER:
return 10;
case Mode.MODE_ALPHA_NUM:
return 9;
case Mode.MODE_8BIT_BYTE:
return 8;
case Mode.MODE_KANJI:
return 8;
default:
throw new Error('mode:' + mode);
}
} else if (type < 27) {
// 10 - 26
switch (mode) {
case Mode.MODE_NUMBER:
return 12;
case Mode.MODE_ALPHA_NUM:
return 11;
case Mode.MODE_8BIT_BYTE:
return 16;
case Mode.MODE_KANJI:
return 10;
default:
throw new Error('mode:' + mode);
}
} else if (type < 41) {
// 27 - 40
switch (mode) {
case Mode.MODE_NUMBER:
return 14;
case Mode.MODE_ALPHA_NUM:
return 13;
case Mode.MODE_8BIT_BYTE:
return 16;
case Mode.MODE_KANJI:
return 12;
default:
throw new Error('mode:' + mode);
}
} else {
throw new Error('type:' + type);
}
},
getLostPoint: function (qrCode: QRCode) {
const moduleCount = qrCode.getModuleCount();
let lostPoint = 0;
// LEVEL1
for (let row = 0; row < moduleCount; row++) {
for (let col = 0; col < moduleCount; col++) {
let sameCount = 0;
const dark = qrCode.isDark(row, col);
for (let r = -1; r <= 1; r++) {
if (row + r < 0 || moduleCount <= row + r) {
continue;
}
for (let c = -1; c <= 1; c++) {
if (col + c < 0 || moduleCount <= col + c) {
continue;
}
if (r == 0 && c == 0) {
continue;
}
if (dark == qrCode.isDark(row + r, col + c)) {
sameCount++;
}
}
}
if (sameCount > 5) {
lostPoint += 3 + sameCount - 5;
}
}
}
// LEVEL2
for (let row = 0; row < moduleCount - 1; row++) {
for (let col = 0; col < moduleCount - 1; col++) {
let count = 0;
if (qrCode.isDark(row, col)) count++;
if (qrCode.isDark(row + 1, col)) count++;
if (qrCode.isDark(row, col + 1)) count++;
if (qrCode.isDark(row + 1, col + 1)) count++;
if (count == 0 || count == 4) {
lostPoint += 3;
}
}
}
// LEVEL3
for (let row = 0; row < moduleCount; row++) {
for (let col = 0; col < moduleCount - 6; col++) {
if (
qrCode.isDark(row, col) &&
!qrCode.isDark(row, col + 1) &&
qrCode.isDark(row, col + 2) &&
qrCode.isDark(row, col + 3) &&
qrCode.isDark(row, col + 4) &&
!qrCode.isDark(row, col + 5) &&
qrCode.isDark(row, col + 6)
) {
lostPoint += 40;
}
}
}
for (let col = 0; col < moduleCount; col++) {
for (let row = 0; row < moduleCount - 6; row++) {
if (
qrCode.isDark(row, col) &&
!qrCode.isDark(row + 1, col) &&
qrCode.isDark(row + 2, col) &&
qrCode.isDark(row + 3, col) &&
qrCode.isDark(row + 4, col) &&
!qrCode.isDark(row + 5, col) &&
qrCode.isDark(row + 6, col)
) {
lostPoint += 40;
}
}
}
// LEVEL4
let darkCount = 0;
for (let col = 0; col < moduleCount; col++) {
for (let row = 0; row < moduleCount; row++) {
if (qrCode.isDark(row, col)) {
darkCount++;
}
}
}
const ratio =
Math.abs((100 * darkCount) / moduleCount / moduleCount - 50) / 5;
lostPoint += ratio * 10;
return lostPoint;
},
};
export default QRUtil;