图书馆小程序
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

619 lines
20 KiB

1 month ago
  1. /**
  2. * QRCode Generator - 纯JavaScript实现
  3. * 基于 ISO/IEC 18004 标准
  4. * @version 2.0.0
  5. */
  6. // 纠错级别 - 使用标准索引
  7. const ECL = { L: 0, M: 1, Q: 2, H: 3 };
  8. // 模式指示符
  9. const MODE = {
  10. Numeric: 0b0001,
  11. Alphanumeric: 0b0010,
  12. Byte: 0b0100,
  13. Kanji: 0b1000
  14. };
  15. // 字母数字字符映射
  16. const ALPHANUM = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';
  17. // 各版本各纠错级别的数据容量(字节模式)
  18. const CAPACITIES = [
  19. [17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],
  20. [134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],
  21. [321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],
  22. [586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],
  23. [929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],
  24. [1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],
  25. [1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],
  26. [2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]
  27. ];
  28. // RS块信息表 [总码字, [各纠错级别的纠错码字数], [各纠错级别的块数(组1,组2)]]
  29. const RS_BLOCKS = [
  30. [[26,[7,10,13,17],[[1,0],[1,0],[1,0],[1,0]]]],
  31. [[44,[10,16,22,28],[[1,0],[1,0],[1,0],[1,0]]]],
  32. [[70,[15,26,18,22],[[1,0],[1,0],[2,0],[2,0]]]],
  33. [[100,[20,18,26,16],[[1,0],[2,0],[2,0],[4,0]]]],
  34. [[134,[26,24,18,22],[[1,0],[2,0],[4,0],[4,0]]]],
  35. [[172,[18,16,24,28],[[2,0],[4,0],[4,0],[4,0]]]],
  36. [[196,[20,18,18,26],[[2,0],[4,0],[6,0],[5,0]]]],
  37. [[242,[24,22,22,26],[[2,0],[4,0],[6,0],[6,0]]]],
  38. [[292,[30,22,20,24],[[2,0],[5,0],[8,0],[8,0]]]],
  39. [[346,[18,26,24,28],[[4,0],[5,0],[8,0],[8,0]]]],
  40. [[404,[20,30,28,24],[[4,0],[5,0],[8,0],[11,0]]]],
  41. [[466,[24,22,26,28],[[4,0],[8,0],[10,0],[11,0]]]],
  42. [[532,[26,22,24,22],[[4,0],[9,0],[12,0],[16,0]]]],
  43. [[581,[30,24,20,24],[[4,0],[9,0],[16,0],[16,0]]]],
  44. [[655,[22,24,30,24],[[6,0],[10,0],[12,0],[18,0]]]],
  45. [[733,[24,28,24,30],[[6,0],[10,0],[17,0],[16,0]]]],
  46. [[815,[28,28,28,28],[[6,0],[11,0],[16,0],[19,0]]]],
  47. [[901,[30,26,28,28],[[6,0],[13,0],[18,0],[21,0]]]],
  48. [[991,[28,26,26,26],[[7,0],[14,0],[21,0],[25,0]]]],
  49. [[1085,[28,26,30,28],[[8,0],[16,0],[20,0],[25,0]]]],
  50. [[1156,[28,26,28,30],[[8,0],[17,0],[23,0],[25,0]]]],
  51. [[1258,[28,28,30,24],[[9,0],[17,0],[23,0],[34,0]]]],
  52. [[1364,[30,28,30,30],[[9,0],[18,0],[25,0],[30,0]]]],
  53. [[1474,[30,28,30,30],[[10,0],[20,0],[27,0],[32,0]]]],
  54. [[1588,[26,28,30,30],[[12,0],[21,0],[29,0],[35,0]]]],
  55. [[1706,[28,28,28,30],[[12,0],[23,0],[34,0],[37,0]]]],
  56. [[1828,[30,28,30,30],[[12,0],[25,0],[34,0],[40,0]]]],
  57. [[1921,[30,28,30,30],[[13,0],[26,0],[35,0],[42,0]]]],
  58. [[2051,[30,28,30,30],[[14,0],[28,0],[38,0],[45,0]]]],
  59. [[2185,[30,28,30,30],[[15,0],[29,0],[40,0],[48,0]]]],
  60. [[2323,[30,28,30,30],[[16,0],[31,0],[43,0],[51,0]]]],
  61. [[2465,[30,28,30,30],[[17,0],[33,0],[45,0],[54,0]]]],
  62. [[2611,[30,28,30,30],[[18,0],[35,0],[48,0],[57,0]]]],
  63. [[2761,[30,28,30,30],[[19,0],[37,0],[51,0],[60,0]]]],
  64. [[2876,[30,28,30,30],[[19,0],[38,0],[53,0],[63,0]]]],
  65. [[3034,[30,28,30,30],[[20,0],[40,0],[56,0],[66,0]]]],
  66. [[3196,[30,28,30,30],[[21,0],[43,0],[59,0],[70,0]]]],
  67. [[3362,[30,28,30,30],[[22,0],[45,0],[62,0],[74,0]]]],
  68. [[3532,[30,28,30,30],[[24,0],[47,0],[65,0],[77,0]]]],
  69. [[3706,[30,28,30,30],[[25,0],[49,0],[68,0],[81,0]]]]
  70. ];
  71. // 更完整的RS块信息
  72. const EC_PARAMS = [
  73. // [纠错码字每块, 数据码字每块组1, 块数组1, 数据码字每块组2, 块数组2]
  74. // L, M, Q, H for each version
  75. [[7,19,1,0,0],[10,16,1,0,0],[13,13,1,0,0],[17,9,1,0,0]], // v1
  76. [[10,34,1,0,0],[16,28,1,0,0],[22,22,1,0,0],[28,16,1,0,0]], // v2
  77. [[15,55,1,0,0],[26,44,1,0,0],[18,17,2,0,0],[22,13,2,0,0]], // v3
  78. [[20,80,1,0,0],[18,32,2,0,0],[26,24,2,0,0],[16,9,4,0,0]], // v4
  79. [[26,108,1,0,0],[24,43,2,0,0],[18,15,2,16,2],[22,11,2,12,2]], // v5
  80. [[18,68,2,0,0],[16,27,4,0,0],[24,19,4,0,0],[28,15,4,0,0]], // v6
  81. [[20,78,2,0,0],[18,31,4,0,0],[18,14,2,15,4],[26,13,4,14,1]], // v7
  82. [[24,97,2,0,0],[22,38,2,39,2],[22,18,4,19,2],[26,14,4,15,2]], // v8
  83. [[30,116,2,0,0],[22,36,3,37,2],[20,16,4,17,4],[24,12,4,13,4]], // v9
  84. [[18,68,2,69,2],[26,43,4,44,1],[24,19,6,20,2],[28,15,6,16,2]], // v10
  85. [[20,81,4,0,0],[30,50,1,51,4],[28,22,4,23,4],[24,12,3,13,8]], // v11
  86. [[24,92,2,93,2],[22,36,6,37,2],[26,20,4,21,6],[28,14,7,15,4]], // v12
  87. [[26,107,4,0,0],[22,37,8,38,1],[24,20,8,21,4],[22,11,12,12,4]], // v13
  88. [[30,115,3,116,1],[24,40,4,41,5],[20,16,11,17,5],[24,12,11,13,5]], // v14
  89. [[22,87,5,88,1],[24,41,5,42,5],[30,24,5,25,7],[24,12,11,13,7]], // v15
  90. [[24,98,5,99,1],[28,45,7,46,3],[24,19,15,20,2],[30,15,3,16,13]], // v16
  91. [[28,107,1,108,5],[28,46,10,47,1],[28,22,1,23,15],[28,14,2,15,17]], // v17
  92. [[30,120,5,121,1],[26,43,9,44,4],[28,22,17,23,1],[28,14,2,15,19]], // v18
  93. [[28,113,3,114,4],[26,44,3,45,11],[26,21,17,22,4],[26,13,9,14,16]], // v19
  94. [[28,107,3,108,5],[26,41,3,42,13],[30,24,15,25,5],[28,15,15,16,10]], // v20
  95. [[28,116,4,117,4],[26,42,17,0,0],[28,22,17,23,6],[30,16,19,17,6]], // v21
  96. [[28,111,2,112,7],[28,46,17,0,0],[30,24,7,25,16],[24,13,34,0,0]], // v22
  97. [[30,121,4,122,5],[28,47,4,48,14],[30,24,11,25,14],[30,15,16,16,14]], // v23
  98. [[30,117,6,118,4],[28,45,6,46,14],[30,24,11,25,16],[30,16,30,17,2]], // v24
  99. [[26,106,8,107,4],[28,47,8,48,13],[30,24,7,25,22],[30,15,22,16,13]], // v25
  100. [[28,114,10,115,2],[28,46,19,47,4],[28,22,28,23,6],[30,16,33,17,4]], // v26
  101. [[30,122,8,123,4],[28,45,22,46,3],[30,23,8,24,26],[30,15,12,16,28]], // v27
  102. [[30,117,3,118,10],[28,45,3,46,23],[30,24,4,25,31],[30,15,11,16,31]], // v28
  103. [[30,116,7,117,7],[28,45,21,46,7],[30,23,1,24,37],[30,15,19,16,26]], // v29
  104. [[30,115,5,116,10],[28,47,19,48,10],[30,24,15,25,25],[30,15,23,16,25]], // v30
  105. [[30,115,13,116,3],[28,46,2,47,29],[30,24,42,25,1],[30,15,23,16,28]], // v31
  106. [[30,115,17,0,0],[28,46,10,47,23],[30,24,10,25,35],[30,15,19,16,35]], // v32
  107. [[30,115,17,116,1],[28,46,14,47,21],[30,24,29,25,19],[30,15,11,16,46]], // v33
  108. [[30,115,13,116,6],[28,46,14,47,23],[30,24,44,25,7],[30,16,59,17,1]], // v34
  109. [[30,121,12,122,7],[28,47,12,48,26],[30,24,39,25,14],[30,15,22,16,41]], // v35
  110. [[30,121,6,122,14],[28,47,6,48,34],[30,24,46,25,10],[30,15,2,16,64]], // v36
  111. [[30,122,17,123,4],[28,46,29,47,14],[30,24,49,25,10],[30,15,24,16,46]], // v37
  112. [[30,122,4,123,18],[28,46,13,47,32],[30,24,48,25,14],[30,15,42,16,32]], // v38
  113. [[30,117,20,118,4],[28,47,40,48,7],[30,24,43,25,22],[30,15,10,16,67]], // v39
  114. [[30,118,19,119,6],[28,47,18,48,31],[30,24,34,25,34],[30,15,20,16,61]] // v40
  115. ];
  116. // 对齐图案位置
  117. const ALIGN_POS = [
  118. [], [6,18], [6,22], [6,26], [6,30], [6,34],
  119. [6,22,38], [6,24,42], [6,26,46], [6,28,50], [6,30,54], [6,32,58], [6,34,62],
  120. [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],
  121. [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],
  122. [6,30,58,86,114], [6,34,62,90,118], [6,26,50,74,98,122], [6,30,54,78,102,126],
  123. [6,26,52,78,104,130], [6,30,56,82,108,134], [6,34,60,86,112,138], [6,30,58,86,114,142],
  124. [6,34,62,90,118,146], [6,30,54,78,102,126,150], [6,24,50,76,102,128,154],
  125. [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]
  126. ];
  127. // 格式信息预计算 (ecl*8+mask) -> formatBits
  128. const FORMAT_BITS = [
  129. 0x77c4, 0x72f3, 0x7daa, 0x789d, 0x662f, 0x6318, 0x6c41, 0x6976,
  130. 0x5412, 0x5125, 0x5e7c, 0x5b4b, 0x45f9, 0x40ce, 0x4f97, 0x4aa0,
  131. 0x355f, 0x3068, 0x3f31, 0x3a06, 0x24b4, 0x2183, 0x2eda, 0x2bed,
  132. 0x1689, 0x13be, 0x1ce7, 0x19d0, 0x0762, 0x0255, 0x0d0c, 0x083b
  133. ];
  134. // 版本信息 (版本7+)
  135. const VERSION_BITS = [
  136. 0x07c94, 0x085bc, 0x09a99, 0x0a4d3, 0x0bbf6, 0x0c762, 0x0d847, 0x0e60d,
  137. 0x0f928, 0x10b78, 0x1145d, 0x12a17, 0x13532, 0x149a6, 0x15683, 0x168c9,
  138. 0x177ec, 0x18ec4, 0x191e1, 0x1afab, 0x1b08e, 0x1cc1a, 0x1d33f, 0x1ed75,
  139. 0x1f250, 0x209d5, 0x216f0, 0x228ba, 0x2379f, 0x24b0b, 0x2542e, 0x26a64,
  140. 0x27541, 0x28c69
  141. ];
  142. // GF(2^8) 运算表
  143. const EXP = new Uint8Array(512);
  144. const LOG = new Uint8Array(256);
  145. (() => {
  146. let x = 1;
  147. for (let i = 0; i < 255; i++) {
  148. EXP[i] = x;
  149. LOG[x] = i;
  150. x = (x << 1) ^ (x >= 128 ? 0x11d : 0);
  151. }
  152. for (let i = 255; i < 512; i++) EXP[i] = EXP[i - 255];
  153. })();
  154. // RS编码
  155. function rsEncode(data, ecLen) {
  156. const gen = new Uint8Array(ecLen + 1);
  157. gen[0] = 1;
  158. for (let i = 0; i < ecLen; i++) {
  159. for (let j = i + 1; j >= 1; j--) {
  160. gen[j] = gen[j] ? EXP[LOG[gen[j]] + i] ^ gen[j-1] : gen[j-1];
  161. }
  162. gen[0] = EXP[LOG[gen[0]] + i];
  163. }
  164. const result = new Uint8Array(ecLen);
  165. for (let i = 0; i < data.length; i++) {
  166. const coef = data[i] ^ result[0];
  167. result.copyWithin(0, 1);
  168. result[ecLen - 1] = 0;
  169. if (coef) {
  170. for (let j = 0; j < ecLen; j++) {
  171. result[j] ^= EXP[LOG[gen[ecLen - 1 - j]] + LOG[coef]];
  172. }
  173. }
  174. }
  175. return result;
  176. }
  177. // 获取数据模式
  178. function getMode(text) {
  179. if (/^\d+$/.test(text)) return MODE.Numeric;
  180. if (/^[0-9A-Z $%*+\-./:]+$/.test(text)) return MODE.Alphanumeric;
  181. return MODE.Byte;
  182. }
  183. // 获取字符计数位数
  184. function getCharCountBits(ver, mode) {
  185. const idx = ver < 10 ? 0 : ver < 27 ? 1 : 2;
  186. return [[10,9,8,8],[12,11,16,10],[14,13,16,12]][idx][[MODE.Numeric,MODE.Alphanumeric,MODE.Byte,MODE.Kanji].indexOf(mode)];
  187. }
  188. // UTF-8编码
  189. function toUtf8(str) {
  190. const bytes = [];
  191. for (let i = 0; i < str.length; i++) {
  192. let c = str.charCodeAt(i);
  193. if (c < 0x80) {
  194. bytes.push(c);
  195. } else if (c < 0x800) {
  196. bytes.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f));
  197. } else if (c >= 0xd800 && c < 0xdc00 && i + 1 < str.length) {
  198. const c2 = str.charCodeAt(++i);
  199. c = 0x10000 + ((c & 0x3ff) << 10) + (c2 & 0x3ff);
  200. bytes.push(0xf0 | (c >> 18), 0x80 | ((c >> 12) & 0x3f), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f));
  201. } else {
  202. bytes.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f));
  203. }
  204. }
  205. return bytes;
  206. }
  207. // 获取最小版本
  208. function getMinVersion(text, ecl) {
  209. const mode = getMode(text);
  210. const len = mode === MODE.Byte ? toUtf8(text).length : text.length;
  211. for (let v = 1; v <= 40; v++) {
  212. if (len <= CAPACITIES[v-1][ecl]) return v;
  213. }
  214. return -1;
  215. }
  216. // 编码数据
  217. function encodeData(text, ver, ecl) {
  218. const mode = getMode(text);
  219. const bits = [];
  220. // 写入位
  221. const write = (val, len) => {
  222. for (let i = len - 1; i >= 0; i--) bits.push((val >> i) & 1);
  223. };
  224. // 模式指示符
  225. write(mode, 4);
  226. // 字符计数
  227. const utf8 = mode === MODE.Byte ? toUtf8(text) : null;
  228. const charCount = utf8 ? utf8.length : text.length;
  229. write(charCount, getCharCountBits(ver, mode));
  230. // 数据编码
  231. if (mode === MODE.Numeric) {
  232. for (let i = 0; i < text.length; i += 3) {
  233. const chunk = text.substr(i, 3);
  234. write(parseInt(chunk, 10), chunk.length * 3 + 1);
  235. }
  236. } else if (mode === MODE.Alphanumeric) {
  237. for (let i = 0; i < text.length; i += 2) {
  238. if (i + 1 < text.length) {
  239. write(ALPHANUM.indexOf(text[i]) * 45 + ALPHANUM.indexOf(text[i+1]), 11);
  240. } else {
  241. write(ALPHANUM.indexOf(text[i]), 6);
  242. }
  243. }
  244. } else {
  245. for (const b of utf8) write(b, 8);
  246. }
  247. // 获取数据容量
  248. const params = EC_PARAMS[ver - 1][ecl];
  249. const [ecPerBlock, dc1, bc1, dc2, bc2] = params;
  250. const totalDC = dc1 * bc1 + dc2 * bc2;
  251. const capacity = totalDC * 8;
  252. // 终止符
  253. const termLen = Math.min(4, capacity - bits.length);
  254. for (let i = 0; i < termLen; i++) bits.push(0);
  255. // 对齐到字节
  256. while (bits.length % 8) bits.push(0);
  257. // 填充
  258. const pads = [0xec, 0x11];
  259. let padIdx = 0;
  260. while (bits.length < capacity) {
  261. write(pads[padIdx++ % 2], 8);
  262. }
  263. // 转换为字节
  264. const bytes = [];
  265. for (let i = 0; i < bits.length; i += 8) {
  266. let b = 0;
  267. for (let j = 0; j < 8; j++) b = (b << 1) | bits[i + j];
  268. bytes.push(b);
  269. }
  270. // 分块并添加纠错码
  271. const blocks = [];
  272. const ecBlocks = [];
  273. let offset = 0;
  274. for (let i = 0; i < bc1; i++) {
  275. const block = bytes.slice(offset, offset + dc1);
  276. blocks.push(block);
  277. ecBlocks.push(rsEncode(new Uint8Array(block), ecPerBlock));
  278. offset += dc1;
  279. }
  280. for (let i = 0; i < bc2; i++) {
  281. const block = bytes.slice(offset, offset + dc2);
  282. blocks.push(block);
  283. ecBlocks.push(rsEncode(new Uint8Array(block), ecPerBlock));
  284. offset += dc2;
  285. }
  286. // 交织
  287. const result = [];
  288. const maxDC = Math.max(dc1, dc2);
  289. for (let i = 0; i < maxDC; i++) {
  290. for (const block of blocks) {
  291. if (i < block.length) result.push(block[i]);
  292. }
  293. }
  294. for (let i = 0; i < ecPerBlock; i++) {
  295. for (const ec of ecBlocks) {
  296. result.push(ec[i]);
  297. }
  298. }
  299. return result;
  300. }
  301. // 创建二维码矩阵
  302. function createMatrix(ver) {
  303. const size = ver * 4 + 17;
  304. const matrix = [];
  305. const reserved = [];
  306. for (let i = 0; i < size; i++) {
  307. matrix.push(new Array(size).fill(0));
  308. reserved.push(new Array(size).fill(false));
  309. }
  310. // 标记保留区域
  311. const mark = (r, c) => {
  312. if (r >= 0 && r < size && c >= 0 && c < size) reserved[r][c] = true;
  313. };
  314. // 查找图案
  315. const placeFinder = (r, c) => {
  316. for (let dr = -1; dr <= 7; dr++) {
  317. for (let dc = -1; dc <= 7; dc++) {
  318. const nr = r + dr, nc = c + dc;
  319. if (nr < 0 || nr >= size || nc < 0 || nc >= size) continue;
  320. mark(nr, nc);
  321. if (dr >= 0 && dr <= 6 && dc >= 0 && dc <= 6) {
  322. const isBlack = dr === 0 || dr === 6 || dc === 0 || dc === 6 ||
  323. (dr >= 2 && dr <= 4 && dc >= 2 && dc <= 4);
  324. matrix[nr][nc] = isBlack ? 1 : 0;
  325. } else {
  326. matrix[nr][nc] = 0;
  327. }
  328. }
  329. }
  330. };
  331. placeFinder(0, 0);
  332. placeFinder(0, size - 7);
  333. placeFinder(size - 7, 0);
  334. // 对齐图案
  335. if (ver >= 2) {
  336. const positions = ALIGN_POS[ver - 1];
  337. for (const r of positions) {
  338. for (const c of positions) {
  339. if (reserved[r][c]) continue;
  340. for (let dr = -2; dr <= 2; dr++) {
  341. for (let dc = -2; dc <= 2; dc++) {
  342. mark(r + dr, c + dc);
  343. const isBlack = Math.abs(dr) === 2 || Math.abs(dc) === 2 || (dr === 0 && dc === 0);
  344. matrix[r + dr][c + dc] = isBlack ? 1 : 0;
  345. }
  346. }
  347. }
  348. }
  349. }
  350. // 时序图案
  351. for (let i = 8; i < size - 8; i++) {
  352. const v = i % 2 === 0 ? 1 : 0;
  353. if (!reserved[6][i]) { matrix[6][i] = v; mark(6, i); }
  354. if (!reserved[i][6]) { matrix[i][6] = v; mark(i, 6); }
  355. }
  356. // 暗模块
  357. matrix[size - 8][8] = 1;
  358. mark(size - 8, 8);
  359. // 格式信息区域
  360. for (let i = 0; i < 9; i++) { mark(8, i); mark(i, 8); }
  361. for (let i = 0; i < 8; i++) { mark(8, size - 1 - i); mark(size - 1 - i, 8); }
  362. // 版本信息区域
  363. if (ver >= 7) {
  364. for (let i = 0; i < 6; i++) {
  365. for (let j = 0; j < 3; j++) {
  366. mark(i, size - 11 + j);
  367. mark(size - 11 + j, i);
  368. }
  369. }
  370. }
  371. return { matrix, reserved, size };
  372. }
  373. // 放置数据
  374. function placeData(matrix, reserved, data) {
  375. const size = matrix.length;
  376. let bitIdx = 0;
  377. let upward = true;
  378. for (let col = size - 1; col >= 1; col -= 2) {
  379. if (col === 6) col = 5;
  380. for (let i = 0; i < size; i++) {
  381. const row = upward ? size - 1 - i : i;
  382. for (let dc = 0; dc < 2; dc++) {
  383. const c = col - dc;
  384. if (!reserved[row][c]) {
  385. const bit = bitIdx < data.length * 8
  386. ? (data[Math.floor(bitIdx / 8)] >> (7 - bitIdx % 8)) & 1
  387. : 0;
  388. matrix[row][c] = bit;
  389. bitIdx++;
  390. }
  391. }
  392. }
  393. upward = !upward;
  394. }
  395. }
  396. // 应用掩码
  397. function applyMask(matrix, reserved, mask) {
  398. const size = matrix.length;
  399. const result = matrix.map(row => [...row]);
  400. const masks = [
  401. (r, c) => (r + c) % 2 === 0,
  402. (r, c) => r % 2 === 0,
  403. (r, c) => c % 3 === 0,
  404. (r, c) => (r + c) % 3 === 0,
  405. (r, c) => (Math.floor(r / 2) + Math.floor(c / 3)) % 2 === 0,
  406. (r, c) => (r * c) % 2 + (r * c) % 3 === 0,
  407. (r, c) => ((r * c) % 2 + (r * c) % 3) % 2 === 0,
  408. (r, c) => ((r + c) % 2 + (r * c) % 3) % 2 === 0
  409. ];
  410. for (let r = 0; r < size; r++) {
  411. for (let c = 0; c < size; c++) {
  412. if (!reserved[r][c] && masks[mask](r, c)) {
  413. result[r][c] ^= 1;
  414. }
  415. }
  416. }
  417. return result;
  418. }
  419. // 放置格式信息
  420. function placeFormatInfo(matrix, ecl, mask) {
  421. const size = matrix.length;
  422. const bits = FORMAT_BITS[ecl * 8 + mask];
  423. // 左上水平
  424. for (let i = 0; i <= 5; i++) matrix[8][i] = (bits >> (14 - i)) & 1;
  425. matrix[8][7] = (bits >> 8) & 1;
  426. matrix[8][8] = (bits >> 7) & 1;
  427. matrix[7][8] = (bits >> 6) & 1;
  428. for (let i = 0; i <= 5; i++) matrix[i][8] = (bits >> i) & 1;
  429. // 右上和左下
  430. for (let i = 0; i <= 7; i++) matrix[8][size - 1 - i] = (bits >> i) & 1;
  431. for (let i = 0; i <= 6; i++) matrix[size - 1 - i][8] = (bits >> (14 - i)) & 1;
  432. }
  433. // 放置版本信息
  434. function placeVersionInfo(matrix, ver) {
  435. if (ver < 7) return;
  436. const size = matrix.length;
  437. const bits = VERSION_BITS[ver - 7];
  438. for (let i = 0; i < 6; i++) {
  439. for (let j = 0; j < 3; j++) {
  440. const bit = (bits >> (i * 3 + j)) & 1;
  441. matrix[i][size - 11 + j] = bit;
  442. matrix[size - 11 + j][i] = bit;
  443. }
  444. }
  445. }
  446. // 计算惩罚分数
  447. function calcPenalty(matrix) {
  448. const size = matrix.length;
  449. let penalty = 0;
  450. // 规则1: 连续同色
  451. for (let r = 0; r < size; r++) {
  452. let cnt = 1;
  453. for (let c = 1; c < size; c++) {
  454. if (matrix[r][c] === matrix[r][c-1]) cnt++;
  455. else { if (cnt >= 5) penalty += cnt - 2; cnt = 1; }
  456. }
  457. if (cnt >= 5) penalty += cnt - 2;
  458. }
  459. for (let c = 0; c < size; c++) {
  460. let cnt = 1;
  461. for (let r = 1; r < size; r++) {
  462. if (matrix[r][c] === matrix[r-1][c]) cnt++;
  463. else { if (cnt >= 5) penalty += cnt - 2; cnt = 1; }
  464. }
  465. if (cnt >= 5) penalty += cnt - 2;
  466. }
  467. // 规则2: 2x2块
  468. for (let r = 0; r < size - 1; r++) {
  469. for (let c = 0; c < size - 1; c++) {
  470. const v = matrix[r][c];
  471. if (v === matrix[r][c+1] && v === matrix[r+1][c] && v === matrix[r+1][c+1]) {
  472. penalty += 3;
  473. }
  474. }
  475. }
  476. // 规则3: 特定图案
  477. const p1 = [1,0,1,1,1,0,1,0,0,0,0];
  478. const p2 = [0,0,0,0,1,0,1,1,1,0,1];
  479. for (let r = 0; r < size; r++) {
  480. for (let c = 0; c <= size - 11; c++) {
  481. let m1 = true, m2 = true;
  482. for (let i = 0; i < 11; i++) {
  483. if (matrix[r][c+i] !== p1[i]) m1 = false;
  484. if (matrix[r][c+i] !== p2[i]) m2 = false;
  485. }
  486. if (m1 || m2) penalty += 40;
  487. }
  488. }
  489. for (let c = 0; c < size; c++) {
  490. for (let r = 0; r <= size - 11; r++) {
  491. let m1 = true, m2 = true;
  492. for (let i = 0; i < 11; i++) {
  493. if (matrix[r+i][c] !== p1[i]) m1 = false;
  494. if (matrix[r+i][c] !== p2[i]) m2 = false;
  495. }
  496. if (m1 || m2) penalty += 40;
  497. }
  498. }
  499. // 规则4: 黑白比例
  500. let dark = 0;
  501. for (let r = 0; r < size; r++) {
  502. for (let c = 0; c < size; c++) {
  503. if (matrix[r][c]) dark++;
  504. }
  505. }
  506. const ratio = dark / (size * size);
  507. penalty += Math.floor(Math.abs(ratio - 0.5) / 0.05) * 10;
  508. return penalty;
  509. }
  510. // 选择最佳掩码
  511. function selectMask(matrix, reserved, ecl, ver) {
  512. let bestMask = 0;
  513. let bestPenalty = Infinity;
  514. for (let mask = 0; mask < 8; mask++) {
  515. const masked = applyMask(matrix, reserved, mask);
  516. placeFormatInfo(masked, ecl, mask);
  517. placeVersionInfo(masked, ver);
  518. const p = calcPenalty(masked);
  519. if (p < bestPenalty) {
  520. bestPenalty = p;
  521. bestMask = mask;
  522. }
  523. }
  524. return bestMask;
  525. }
  526. /**
  527. * 生成二维码
  528. */
  529. function generate(text, options = {}) {
  530. const eclName = (options.errorCorrectionLevel || 'M').toUpperCase();
  531. const ecl = ECL[eclName] !== undefined ? ECL[eclName] : ECL.M;
  532. let ver = options.version || getMinVersion(text, ecl);
  533. if (ver < 1) throw new Error('数据过长');
  534. if (ver > 40) ver = 40;
  535. const data = encodeData(text, ver, ecl);
  536. const { matrix, reserved, size } = createMatrix(ver);
  537. placeData(matrix, reserved, data);
  538. const mask = selectMask(matrix, reserved, ecl, ver);
  539. const final = applyMask(matrix, reserved, mask);
  540. placeFormatInfo(final, ecl, mask);
  541. placeVersionInfo(final, ver);
  542. return {
  543. version: ver,
  544. size,
  545. modules: final,
  546. errorCorrectionLevel: eclName
  547. };
  548. }
  549. export default { generate, ECL, MODE };
  550. export { generate, ECL, MODE };