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 };