601 lines
18 KiB
JavaScript
601 lines
18 KiB
JavaScript
const ECL = { L: 0, M: 1, Q: 2, H: 3 };
|
|
|
|
const MODE = {
|
|
Numeric: 0b0001,
|
|
Alphanumeric: 0b0010,
|
|
Byte: 0b0100,
|
|
Kanji: 0b1000
|
|
};
|
|
|
|
const ALPHANUM = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';
|
|
|
|
const CAPACITIES = [
|
|
[17, 14, 11, 7],
|
|
[32, 26, 20, 14],
|
|
[53, 42, 32, 24],
|
|
[78, 62, 46, 34],
|
|
[106, 84, 60, 44],
|
|
[134, 106, 74, 58],
|
|
[154, 122, 86, 64],
|
|
[192, 152, 108, 84],
|
|
[230, 180, 130, 98],
|
|
[271, 213, 151, 119],
|
|
[321, 251, 177, 137],
|
|
[367, 287, 203, 155],
|
|
[425, 331, 241, 177],
|
|
[458, 362, 258, 194],
|
|
[520, 412, 292, 220],
|
|
[586, 450, 322, 250],
|
|
[644, 504, 364, 280],
|
|
[718, 560, 394, 310],
|
|
[792, 624, 442, 338],
|
|
[858, 666, 482, 382],
|
|
[929, 711, 509, 403],
|
|
[1003, 779, 565, 439],
|
|
[1091, 857, 611, 461],
|
|
[1171, 911, 661, 511],
|
|
[1273, 997, 715, 535],
|
|
[1367, 1059, 751, 593],
|
|
[1465, 1125, 805, 625],
|
|
[1528, 1190, 868, 658],
|
|
[1628, 1264, 908, 698],
|
|
[1732, 1370, 982, 742],
|
|
[1840, 1452, 1030, 790],
|
|
[1952, 1538, 1112, 842],
|
|
[2068, 1628, 1168, 898],
|
|
[2188, 1722, 1228, 958],
|
|
[2303, 1809, 1283, 983],
|
|
[2431, 1911, 1351, 1051],
|
|
[2563, 1989, 1423, 1093],
|
|
[2699, 2099, 1499, 1139],
|
|
[2809, 2213, 1579, 1219],
|
|
[2953, 2331, 1663, 1273]
|
|
];
|
|
|
|
const EC_PARAMS = [
|
|
[[7, 19, 1, 0, 0], [10, 16, 1, 0, 0], [13, 13, 1, 0, 0], [17, 9, 1, 0, 0]],
|
|
[[10, 34, 1, 0, 0], [16, 28, 1, 0, 0], [22, 22, 1, 0, 0], [28, 16, 1, 0, 0]],
|
|
[[15, 55, 1, 0, 0], [26, 44, 1, 0, 0], [18, 17, 2, 0, 0], [22, 13, 2, 0, 0]],
|
|
[[20, 80, 1, 0, 0], [18, 32, 2, 0, 0], [26, 24, 2, 0, 0], [16, 9, 4, 0, 0]],
|
|
[[26, 108, 1, 0, 0], [24, 43, 2, 0, 0], [18, 15, 2, 16, 2], [22, 11, 2, 12, 2]],
|
|
[[18, 68, 2, 0, 0], [16, 27, 4, 0, 0], [24, 19, 4, 0, 0], [28, 15, 4, 0, 0]],
|
|
[[20, 78, 2, 0, 0], [18, 31, 4, 0, 0], [18, 14, 2, 15, 4], [26, 13, 4, 14, 1]],
|
|
[[24, 97, 2, 0, 0], [22, 38, 2, 39, 2], [22, 18, 4, 19, 2], [26, 14, 4, 15, 2]],
|
|
[[30, 116, 2, 0, 0], [22, 36, 3, 37, 2], [20, 16, 4, 17, 4], [24, 12, 4, 13, 4]],
|
|
[[18, 68, 2, 69, 2], [26, 43, 4, 44, 1], [24, 19, 6, 20, 2], [28, 15, 6, 16, 2]],
|
|
[[20, 81, 4, 0, 0], [30, 50, 1, 51, 4], [28, 22, 4, 23, 4], [24, 12, 3, 13, 8]],
|
|
[[24, 92, 2, 93, 2], [22, 36, 6, 37, 2], [26, 20, 4, 21, 6], [28, 14, 7, 15, 4]],
|
|
[[26, 107, 4, 0, 0], [22, 37, 8, 38, 1], [24, 20, 8, 21, 4], [22, 11, 12, 12, 4]],
|
|
[[30, 115, 3, 116, 1], [24, 40, 4, 41, 5], [20, 16, 11, 17, 5], [24, 12, 11, 13, 5]],
|
|
[[22, 87, 5, 88, 1], [24, 41, 5, 42, 5], [30, 24, 5, 25, 7], [24, 12, 11, 13, 7]],
|
|
[[24, 98, 5, 99, 1], [28, 45, 7, 46, 3], [24, 19, 15, 20, 2], [30, 15, 3, 16, 13]],
|
|
[[28, 107, 1, 108, 5], [28, 46, 10, 47, 1], [28, 22, 1, 23, 15], [28, 14, 2, 15, 17]],
|
|
[[30, 120, 5, 121, 1], [26, 43, 9, 44, 4], [28, 22, 17, 23, 1], [28, 14, 2, 15, 19]],
|
|
[[28, 113, 3, 114, 4], [26, 44, 3, 45, 11], [26, 21, 17, 22, 4], [26, 13, 9, 14, 16]],
|
|
[[28, 107, 3, 108, 5], [26, 41, 3, 42, 13], [30, 24, 15, 25, 5], [28, 15, 15, 16, 10]],
|
|
[[28, 116, 4, 117, 4], [26, 42, 17, 0, 0], [28, 22, 17, 23, 6], [30, 16, 19, 17, 6]],
|
|
[[28, 111, 2, 112, 7], [28, 46, 17, 0, 0], [30, 24, 7, 25, 16], [24, 13, 34, 0, 0]],
|
|
[[30, 121, 4, 122, 5], [28, 47, 4, 48, 14], [30, 24, 11, 25, 14], [30, 15, 16, 16, 14]],
|
|
[[30, 117, 6, 118, 4], [28, 45, 6, 46, 14], [30, 24, 11, 25, 16], [30, 16, 30, 17, 2]],
|
|
[[26, 106, 8, 107, 4], [28, 47, 8, 48, 13], [30, 24, 7, 25, 22], [30, 15, 22, 16, 13]],
|
|
[[28, 114, 10, 115, 2], [28, 46, 19, 47, 4], [28, 22, 28, 23, 6], [30, 16, 33, 17, 4]],
|
|
[[30, 122, 8, 123, 4], [28, 45, 22, 46, 3], [30, 23, 8, 24, 26], [30, 15, 12, 16, 28]],
|
|
[[30, 117, 3, 118, 10], [28, 45, 3, 46, 23], [30, 24, 4, 25, 31], [30, 15, 11, 16, 31]],
|
|
[[30, 116, 7, 117, 7], [28, 45, 21, 46, 7], [30, 23, 1, 24, 37], [30, 15, 19, 16, 26]],
|
|
[[30, 115, 5, 116, 10], [28, 47, 19, 48, 10], [30, 24, 15, 25, 25], [30, 15, 23, 16, 25]],
|
|
[[30, 115, 13, 116, 3], [28, 46, 2, 47, 29], [30, 24, 42, 25, 1], [30, 15, 23, 16, 28]],
|
|
[[30, 115, 17, 0, 0], [28, 46, 10, 47, 23], [30, 24, 10, 25, 35], [30, 15, 19, 16, 35]],
|
|
[[30, 115, 17, 116, 1], [28, 46, 14, 47, 21], [30, 24, 29, 25, 19], [30, 15, 11, 16, 46]],
|
|
[[30, 115, 13, 116, 6], [28, 46, 14, 47, 23], [30, 24, 44, 25, 7], [30, 16, 59, 17, 1]],
|
|
[[30, 121, 12, 122, 7], [28, 47, 12, 48, 26], [30, 24, 39, 25, 14], [30, 15, 22, 16, 41]],
|
|
[[30, 121, 6, 122, 14], [28, 47, 6, 48, 34], [30, 24, 46, 25, 10], [30, 15, 2, 16, 64]],
|
|
[[30, 122, 17, 123, 4], [28, 46, 29, 47, 14], [30, 24, 49, 25, 10], [30, 15, 24, 16, 46]],
|
|
[[30, 122, 4, 123, 18], [28, 46, 13, 47, 32], [30, 24, 48, 25, 14], [30, 15, 42, 16, 32]],
|
|
[[30, 117, 20, 118, 4], [28, 47, 40, 48, 7], [30, 24, 43, 25, 22], [30, 15, 10, 16, 67]],
|
|
[[30, 118, 19, 119, 6], [28, 47, 18, 48, 31], [30, 24, 34, 25, 34], [30, 15, 20, 16, 61]]
|
|
];
|
|
|
|
const ALIGN_POS = [
|
|
[],
|
|
[6, 18],
|
|
[6, 22],
|
|
[6, 26],
|
|
[6, 30],
|
|
[6, 34],
|
|
[6, 22, 38],
|
|
[6, 24, 42],
|
|
[6, 26, 46],
|
|
[6, 28, 50],
|
|
[6, 30, 54],
|
|
[6, 32, 58],
|
|
[6, 34, 62],
|
|
[6, 26, 46, 66],
|
|
[6, 26, 48, 70],
|
|
[6, 26, 50, 74],
|
|
[6, 30, 54, 78],
|
|
[6, 30, 56, 82],
|
|
[6, 30, 58, 86],
|
|
[6, 34, 62, 90],
|
|
[6, 28, 50, 72, 94],
|
|
[6, 26, 50, 74, 98],
|
|
[6, 30, 54, 78, 102],
|
|
[6, 28, 54, 80, 106],
|
|
[6, 32, 58, 84, 110],
|
|
[6, 30, 58, 86, 114],
|
|
[6, 34, 62, 90, 118],
|
|
[6, 26, 50, 74, 98, 122],
|
|
[6, 30, 54, 78, 102, 126],
|
|
[6, 26, 52, 78, 104, 130],
|
|
[6, 30, 56, 82, 108, 134],
|
|
[6, 34, 60, 86, 112, 138],
|
|
[6, 30, 58, 86, 114, 142],
|
|
[6, 34, 62, 90, 118, 146],
|
|
[6, 30, 54, 78, 102, 126, 150],
|
|
[6, 24, 50, 76, 102, 128, 154],
|
|
[6, 28, 54, 80, 106, 132, 158],
|
|
[6, 32, 58, 84, 110, 136, 162],
|
|
[6, 26, 54, 82, 110, 138, 166],
|
|
[6, 30, 58, 86, 114, 142, 170]
|
|
];
|
|
|
|
const FORMAT_BITS = [
|
|
0x77c4, 0x72f3, 0x7daa, 0x789d, 0x662f, 0x6318, 0x6c41, 0x6976,
|
|
0x5412, 0x5125, 0x5e7c, 0x5b4b, 0x45f9, 0x40ce, 0x4f97, 0x4aa0,
|
|
0x355f, 0x3068, 0x3f31, 0x3a06, 0x24b4, 0x2183, 0x2eda, 0x2bed,
|
|
0x1689, 0x13be, 0x1ce7, 0x19d0, 0x0762, 0x0255, 0x0d0c, 0x083b
|
|
];
|
|
|
|
const VERSION_BITS = [
|
|
0x07c94, 0x085bc, 0x09a99, 0x0a4d3, 0x0bbf6, 0x0c762, 0x0d847, 0x0e60d,
|
|
0x0f928, 0x10b78, 0x1145d, 0x12a17, 0x13532, 0x149a6, 0x15683, 0x168c9,
|
|
0x177ec, 0x18ec4, 0x191e1, 0x1afab, 0x1b08e, 0x1cc1a, 0x1d33f, 0x1ed75,
|
|
0x1f250, 0x209d5, 0x216f0, 0x228ba, 0x2379f, 0x24b0b, 0x2542e, 0x26a64,
|
|
0x27541, 0x28c69
|
|
];
|
|
|
|
const EXP = new Uint8Array(512);
|
|
const LOG = new Uint8Array(256);
|
|
(() => {
|
|
let x = 1;
|
|
for (let i = 0; i < 255; i++) {
|
|
EXP[i] = x;
|
|
LOG[x] = i;
|
|
x = (x << 1) ^ (x >= 128 ? 0x11d : 0);
|
|
}
|
|
for (let i = 255; i < 512; i++) EXP[i] = EXP[i - 255];
|
|
})();
|
|
|
|
function rsEncode(data, ecLen) {
|
|
const gen = new Uint8Array(ecLen + 1);
|
|
gen[0] = 1;
|
|
for (let i = 0; i < ecLen; i++) {
|
|
for (let j = i + 1; j >= 1; j--) {
|
|
gen[j] = gen[j] ? EXP[LOG[gen[j]] + i] ^ gen[j - 1] : gen[j - 1];
|
|
}
|
|
gen[0] = EXP[LOG[gen[0]] + i];
|
|
}
|
|
|
|
const result = new Uint8Array(ecLen);
|
|
for (let i = 0; i < data.length; i++) {
|
|
const coef = data[i] ^ result[0];
|
|
result.copyWithin(0, 1);
|
|
result[ecLen - 1] = 0;
|
|
if (coef) {
|
|
for (let j = 0; j < ecLen; j++) {
|
|
result[j] ^= EXP[LOG[gen[ecLen - 1 - j]] + LOG[coef]];
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function getMode(text) {
|
|
if (/^\d+$/.test(text)) return MODE.Numeric;
|
|
if (/^[0-9A-Z $%*+\-./:]+$/.test(text)) return MODE.Alphanumeric;
|
|
return MODE.Byte;
|
|
}
|
|
|
|
function getCharCountBits(ver, mode) {
|
|
const idx = ver < 10 ? 0 : ver < 27 ? 1 : 2;
|
|
return [[10, 9, 8, 8], [12, 11, 16, 10], [14, 13, 16, 12]][idx][[MODE.Numeric, MODE.Alphanumeric, MODE.Byte, MODE.Kanji].indexOf(mode)];
|
|
}
|
|
|
|
function toUtf8(str) {
|
|
const bytes = [];
|
|
for (let i = 0; i < str.length; i++) {
|
|
let c = str.charCodeAt(i);
|
|
if (c < 0x80) {
|
|
bytes.push(c);
|
|
} else if (c < 0x800) {
|
|
bytes.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f));
|
|
} else if (c >= 0xd800 && c < 0xdc00 && i + 1 < str.length) {
|
|
const c2 = str.charCodeAt(++i);
|
|
c = 0x10000 + ((c & 0x3ff) << 10) + (c2 & 0x3ff);
|
|
bytes.push(0xf0 | (c >> 18), 0x80 | ((c >> 12) & 0x3f), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f));
|
|
} else {
|
|
bytes.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f));
|
|
}
|
|
}
|
|
return bytes;
|
|
}
|
|
|
|
function getMinVersion(text, ecl) {
|
|
const mode = getMode(text);
|
|
const len = mode === MODE.Byte ? toUtf8(text).length : text.length;
|
|
for (let v = 1; v <= 40; v++) {
|
|
if (len <= CAPACITIES[v - 1][ecl]) return v;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
function encodeData(text, ver, ecl) {
|
|
const mode = getMode(text);
|
|
const bits = [];
|
|
|
|
const write = (val, len) => {
|
|
for (let i = len - 1; i >= 0; i--) bits.push((val >> i) & 1);
|
|
};
|
|
|
|
write(mode, 4);
|
|
|
|
const utf8 = mode === MODE.Byte ? toUtf8(text) : null;
|
|
const charCount = utf8 ? utf8.length : text.length;
|
|
write(charCount, getCharCountBits(ver, mode));
|
|
|
|
if (mode === MODE.Numeric) {
|
|
for (let i = 0; i < text.length; i += 3) {
|
|
const chunk = text.substr(i, 3);
|
|
write(parseInt(chunk, 10), chunk.length * 3 + 1);
|
|
}
|
|
} else if (mode === MODE.Alphanumeric) {
|
|
for (let i = 0; i < text.length; i += 2) {
|
|
if (i + 1 < text.length) {
|
|
write(ALPHANUM.indexOf(text[i]) * 45 + ALPHANUM.indexOf(text[i + 1]), 11);
|
|
} else {
|
|
write(ALPHANUM.indexOf(text[i]), 6);
|
|
}
|
|
}
|
|
} else {
|
|
for (const b of utf8) write(b, 8);
|
|
}
|
|
|
|
const params = EC_PARAMS[ver - 1][ecl];
|
|
const [ecPerBlock, dc1, bc1, dc2, bc2] = params;
|
|
const totalDC = dc1 * bc1 + dc2 * bc2;
|
|
const capacity = totalDC * 8;
|
|
|
|
const termLen = Math.min(4, capacity - bits.length);
|
|
for (let i = 0; i < termLen; i++) bits.push(0);
|
|
|
|
while (bits.length % 8) bits.push(0);
|
|
|
|
const pads = [0xec, 0x11];
|
|
let padIdx = 0;
|
|
while (bits.length < capacity) {
|
|
write(pads[padIdx++ % 2], 8);
|
|
}
|
|
|
|
const bytes = [];
|
|
for (let i = 0; i < bits.length; i += 8) {
|
|
let b = 0;
|
|
for (let j = 0; j < 8; j++) b = (b << 1) | bits[i + j];
|
|
bytes.push(b);
|
|
}
|
|
|
|
const blocks = [];
|
|
const ecBlocks = [];
|
|
let offset = 0;
|
|
|
|
for (let i = 0; i < bc1; i++) {
|
|
const block = bytes.slice(offset, offset + dc1);
|
|
blocks.push(block);
|
|
ecBlocks.push(rsEncode(new Uint8Array(block), ecPerBlock));
|
|
offset += dc1;
|
|
}
|
|
for (let i = 0; i < bc2; i++) {
|
|
const block = bytes.slice(offset, offset + dc2);
|
|
blocks.push(block);
|
|
ecBlocks.push(rsEncode(new Uint8Array(block), ecPerBlock));
|
|
offset += dc2;
|
|
}
|
|
|
|
const result = [];
|
|
const maxDC = Math.max(dc1, dc2);
|
|
for (let i = 0; i < maxDC; i++) {
|
|
for (const block of blocks) {
|
|
if (i < block.length) result.push(block[i]);
|
|
}
|
|
}
|
|
for (let i = 0; i < ecPerBlock; i++) {
|
|
for (const ec of ecBlocks) {
|
|
result.push(ec[i]);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function createMatrix(ver) {
|
|
const size = ver * 4 + 17;
|
|
const matrix = [];
|
|
const reserved = [];
|
|
for (let i = 0; i < size; i++) {
|
|
matrix.push(new Array(size).fill(0));
|
|
reserved.push(new Array(size).fill(false));
|
|
}
|
|
|
|
const mark = (r, c) => {
|
|
if (r >= 0 && r < size && c >= 0 && c < size) reserved[r][c] = true;
|
|
};
|
|
|
|
const placeFinder = (r, c) => {
|
|
for (let dr = -1; dr <= 7; dr++) {
|
|
for (let dc = -1; dc <= 7; dc++) {
|
|
const nr = r + dr;
|
|
const nc = c + dc;
|
|
if (nr < 0 || nr >= size || nc < 0 || nc >= size) continue;
|
|
mark(nr, nc);
|
|
if (dr >= 0 && dr <= 6 && dc >= 0 && dc <= 6) {
|
|
const isBlack = dr === 0 || dr === 6 || dc === 0 || dc === 6 || (dr >= 2 && dr <= 4 && dc >= 2 && dc <= 4);
|
|
matrix[nr][nc] = isBlack ? 1 : 0;
|
|
} else {
|
|
matrix[nr][nc] = 0;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
placeFinder(0, 0);
|
|
placeFinder(0, size - 7);
|
|
placeFinder(size - 7, 0);
|
|
|
|
if (ver >= 2) {
|
|
const positions = ALIGN_POS[ver - 1];
|
|
for (const r of positions) {
|
|
for (const c of positions) {
|
|
if (reserved[r][c]) continue;
|
|
for (let dr = -2; dr <= 2; dr++) {
|
|
for (let dc = -2; dc <= 2; dc++) {
|
|
mark(r + dr, c + dc);
|
|
const isBlack = Math.abs(dr) === 2 || Math.abs(dc) === 2 || (dr === 0 && dc === 0);
|
|
matrix[r + dr][c + dc] = isBlack ? 1 : 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (let i = 8; i < size - 8; i++) {
|
|
const v = i % 2 === 0 ? 1 : 0;
|
|
if (!reserved[6][i]) {
|
|
matrix[6][i] = v;
|
|
mark(6, i);
|
|
}
|
|
if (!reserved[i][6]) {
|
|
matrix[i][6] = v;
|
|
mark(i, 6);
|
|
}
|
|
}
|
|
|
|
matrix[size - 8][8] = 1;
|
|
mark(size - 8, 8);
|
|
|
|
for (let i = 0; i < 9; i++) {
|
|
mark(8, i);
|
|
mark(i, 8);
|
|
}
|
|
for (let i = 0; i < 8; i++) {
|
|
mark(8, size - 1 - i);
|
|
mark(size - 1 - i, 8);
|
|
}
|
|
|
|
if (ver >= 7) {
|
|
for (let i = 0; i < 6; i++) {
|
|
for (let j = 0; j < 3; j++) {
|
|
mark(i, size - 11 + j);
|
|
mark(size - 11 + j, i);
|
|
}
|
|
}
|
|
}
|
|
|
|
return { matrix, reserved, size };
|
|
}
|
|
|
|
function placeData(matrix, reserved, data) {
|
|
const size = matrix.length;
|
|
let bitIdx = 0;
|
|
let upward = true;
|
|
|
|
for (let col = size - 1; col >= 1; col -= 2) {
|
|
if (col === 6) col = 5;
|
|
|
|
for (let i = 0; i < size; i++) {
|
|
const row = upward ? size - 1 - i : i;
|
|
|
|
for (let dc = 0; dc < 2; dc++) {
|
|
const c = col - dc;
|
|
if (!reserved[row][c]) {
|
|
const bit = bitIdx < data.length * 8 ? (data[Math.floor(bitIdx / 8)] >> (7 - bitIdx % 8)) & 1 : 0;
|
|
matrix[row][c] = bit;
|
|
bitIdx++;
|
|
}
|
|
}
|
|
}
|
|
upward = !upward;
|
|
}
|
|
}
|
|
|
|
function applyMask(matrix, reserved, mask) {
|
|
const size = matrix.length;
|
|
const result = matrix.map((row) => [...row]);
|
|
|
|
const masks = [
|
|
(r, c) => (r + c) % 2 === 0,
|
|
(r, c) => r % 2 === 0,
|
|
(r, c) => c % 3 === 0,
|
|
(r, c) => (r + c) % 3 === 0,
|
|
(r, c) => (Math.floor(r / 2) + Math.floor(c / 3)) % 2 === 0,
|
|
(r, c) => (r * c) % 2 + (r * c) % 3 === 0,
|
|
(r, c) => ((r * c) % 2 + (r * c) % 3) % 2 === 0,
|
|
(r, c) => ((r + c) % 2 + (r * c) % 3) % 2 === 0
|
|
];
|
|
|
|
for (let r = 0; r < size; r++) {
|
|
for (let c = 0; c < size; c++) {
|
|
if (!reserved[r][c] && masks[mask](r, c)) {
|
|
result[r][c] ^= 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function placeFormatInfo(matrix, ecl, mask) {
|
|
const size = matrix.length;
|
|
const bits = FORMAT_BITS[ecl * 8 + mask];
|
|
|
|
for (let i = 0; i <= 5; i++) matrix[8][i] = (bits >> (14 - i)) & 1;
|
|
matrix[8][7] = (bits >> 8) & 1;
|
|
matrix[8][8] = (bits >> 7) & 1;
|
|
matrix[7][8] = (bits >> 6) & 1;
|
|
for (let i = 0; i <= 5; i++) matrix[i][8] = (bits >> i) & 1;
|
|
|
|
for (let i = 0; i <= 7; i++) matrix[8][size - 1 - i] = (bits >> i) & 1;
|
|
for (let i = 0; i <= 6; i++) matrix[size - 1 - i][8] = (bits >> (14 - i)) & 1;
|
|
}
|
|
|
|
function placeVersionInfo(matrix, ver) {
|
|
if (ver < 7) return;
|
|
const size = matrix.length;
|
|
const bits = VERSION_BITS[ver - 7];
|
|
|
|
for (let i = 0; i < 6; i++) {
|
|
for (let j = 0; j < 3; j++) {
|
|
const bit = (bits >> (i * 3 + j)) & 1;
|
|
matrix[i][size - 11 + j] = bit;
|
|
matrix[size - 11 + j][i] = bit;
|
|
}
|
|
}
|
|
}
|
|
|
|
function calcPenalty(matrix) {
|
|
const size = matrix.length;
|
|
let penalty = 0;
|
|
|
|
for (let r = 0; r < size; r++) {
|
|
let cnt = 1;
|
|
for (let c = 1; c < size; c++) {
|
|
if (matrix[r][c] === matrix[r][c - 1]) cnt++;
|
|
else {
|
|
if (cnt >= 5) penalty += cnt - 2;
|
|
cnt = 1;
|
|
}
|
|
}
|
|
if (cnt >= 5) penalty += cnt - 2;
|
|
}
|
|
for (let c = 0; c < size; c++) {
|
|
let cnt = 1;
|
|
for (let r = 1; r < size; r++) {
|
|
if (matrix[r][c] === matrix[r - 1][c]) cnt++;
|
|
else {
|
|
if (cnt >= 5) penalty += cnt - 2;
|
|
cnt = 1;
|
|
}
|
|
}
|
|
if (cnt >= 5) penalty += cnt - 2;
|
|
}
|
|
|
|
for (let r = 0; r < size - 1; r++) {
|
|
for (let c = 0; c < size - 1; c++) {
|
|
const v = matrix[r][c];
|
|
if (v === matrix[r][c + 1] && v === matrix[r + 1][c] && v === matrix[r + 1][c + 1]) {
|
|
penalty += 3;
|
|
}
|
|
}
|
|
}
|
|
|
|
const p1 = [1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0];
|
|
const p2 = [0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1];
|
|
for (let r = 0; r < size; r++) {
|
|
for (let c = 0; c <= size - 11; c++) {
|
|
let m1 = true;
|
|
let m2 = true;
|
|
for (let i = 0; i < 11; i++) {
|
|
if (matrix[r][c + i] !== p1[i]) m1 = false;
|
|
if (matrix[r][c + i] !== p2[i]) m2 = false;
|
|
}
|
|
if (m1 || m2) penalty += 40;
|
|
}
|
|
}
|
|
for (let c = 0; c < size; c++) {
|
|
for (let r = 0; r <= size - 11; r++) {
|
|
let m1 = true;
|
|
let m2 = true;
|
|
for (let i = 0; i < 11; i++) {
|
|
if (matrix[r + i][c] !== p1[i]) m1 = false;
|
|
if (matrix[r + i][c] !== p2[i]) m2 = false;
|
|
}
|
|
if (m1 || m2) penalty += 40;
|
|
}
|
|
}
|
|
|
|
let dark = 0;
|
|
for (let r = 0; r < size; r++) {
|
|
for (let c = 0; c < size; c++) {
|
|
if (matrix[r][c]) dark++;
|
|
}
|
|
}
|
|
const ratio = dark / (size * size);
|
|
penalty += Math.floor(Math.abs(ratio - 0.5) / 0.05) * 10;
|
|
|
|
return penalty;
|
|
}
|
|
|
|
function selectMask(matrix, reserved, ecl, ver) {
|
|
let bestMask = 0;
|
|
let bestPenalty = Infinity;
|
|
|
|
for (let mask = 0; mask < 8; mask++) {
|
|
const masked = applyMask(matrix, reserved, mask);
|
|
placeFormatInfo(masked, ecl, mask);
|
|
placeVersionInfo(masked, ver);
|
|
const p = calcPenalty(masked);
|
|
if (p < bestPenalty) {
|
|
bestPenalty = p;
|
|
bestMask = mask;
|
|
}
|
|
}
|
|
|
|
return bestMask;
|
|
}
|
|
|
|
function generate(text, options = {}) {
|
|
const eclName = String(options.errorCorrectionLevel || 'M').toUpperCase();
|
|
const ecl = ECL[eclName] !== undefined ? ECL[eclName] : ECL.M;
|
|
|
|
let ver = options.version || getMinVersion(text, ecl);
|
|
if (ver < 1) throw new Error('数据过长');
|
|
if (ver > 40) ver = 40;
|
|
|
|
const data = encodeData(text, ver, ecl);
|
|
const { matrix, reserved, size } = createMatrix(ver);
|
|
|
|
placeData(matrix, reserved, data);
|
|
|
|
const mask = selectMask(matrix, reserved, ecl, ver);
|
|
const final = applyMask(matrix, reserved, mask);
|
|
placeFormatInfo(final, ecl, mask);
|
|
placeVersionInfo(final, ver);
|
|
|
|
return {
|
|
version: ver,
|
|
size,
|
|
modules: final,
|
|
errorCorrectionLevel: eclName
|
|
};
|
|
}
|
|
|
|
export default { generate, ECL, MODE };
|
|
export { generate, ECL, MODE };
|