1 | (function (global, factory) {
|
2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
3 | typeof define === 'function' && define.amd ? define(factory) :
|
4 | (global.qrCodeGenerator = factory());
|
5 | }(this, (function () { 'use strict';
|
6 |
|
7 |
|
8 |
|
9 | var qrcodegen = new function () {
|
10 |
|
11 |
|
12 | this.qrBorder = 7;
|
13 | this.tileSize = 96;
|
14 | this.incTileSize = 96;
|
15 | this.minImageTiles = 5;
|
16 | |
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 | this.QrCode = function (version, errCorLvl, dataCodewords, mask) {
|
38 |
|
39 |
|
40 |
|
41 |
|
42 | if (version < MIN_VERSION || version > MAX_VERSION) throw "Version value out of range";
|
43 | if (mask < -1 || mask > 7) throw "Mask value out of range";
|
44 | if (!(errCorLvl instanceof Ecc)) throw "QrCode.Ecc expected";
|
45 | var size = version * 4 + 17;
|
46 |
|
47 |
|
48 | var row = [];
|
49 | for (var i = 0; i < size; i++) {
|
50 | row.push(false);
|
51 | }var modules = [];
|
52 | var isFunction = [];
|
53 | for (var i = 0; i < size; i++) {
|
54 | modules.push(row.slice());
|
55 | isFunction.push(row.slice());
|
56 | }
|
57 |
|
58 |
|
59 | drawFunctionPatterns();
|
60 | var allCodewords = addEccAndInterleave(dataCodewords);
|
61 | drawCodewords(allCodewords);
|
62 |
|
63 |
|
64 | if (mask == -1) {
|
65 |
|
66 | var minPenalty = Infinity;
|
67 | for (var i = 0; i < 8; i++) {
|
68 | applyMask(i);
|
69 | drawFormatBits(i);
|
70 | var penalty = getPenaltyScore();
|
71 | if (penalty < minPenalty) {
|
72 | mask = i;
|
73 | minPenalty = penalty;
|
74 | }
|
75 | applyMask(i);
|
76 | }
|
77 | }
|
78 | if (mask < 0 || mask > 7) throw "Assertion error";
|
79 | applyMask(mask);
|
80 | drawFormatBits(mask);
|
81 |
|
82 | isFunction = null;
|
83 |
|
84 |
|
85 |
|
86 |
|
87 |
|
88 | Object.defineProperty(this, "version", { value: version });
|
89 |
|
90 |
|
91 |
|
92 | Object.defineProperty(this, "size", { value: size });
|
93 |
|
94 |
|
95 | Object.defineProperty(this, "errorCorrectionLevel", { value: errCorLvl });
|
96 |
|
97 |
|
98 |
|
99 |
|
100 | Object.defineProperty(this, "mask", { value: mask });
|
101 |
|
102 |
|
103 |
|
104 |
|
105 |
|
106 |
|
107 | this.getPixel = function (x, y) {
|
108 |
|
109 | var imageTiles = (size - 13) / 2;
|
110 | if (imageTiles < qrcodegen.minImageTiles) {
|
111 | imageTiles = qrcodegen.minImageTiles;
|
112 | }
|
113 |
|
114 | var padding = size - qrcodegen.qrBorder * 2 - imageTiles;
|
115 | if (padding % 2 === 0) {
|
116 | padding++;
|
117 | }
|
118 | padding /= 2;
|
119 | padding--;
|
120 |
|
121 | if (x > qrcodegen.qrBorder + padding && y > qrcodegen.qrBorder + padding && x < size - padding - qrcodegen.qrBorder - 1 && y < size - padding - qrcodegen.qrBorder - 1) {
|
122 | return false;
|
123 | }
|
124 |
|
125 | return 0 <= x && x < size && 0 <= y && y < size && modules[y][x];
|
126 | };
|
127 |
|
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 | this.drawCanvas = function (scale, border, canvas) {
|
135 | if (scale <= 0 || border < 0) throw "Value out of range";
|
136 | var width = (size + border * 2) * scale;
|
137 | canvas.width = width;
|
138 | canvas.height = width;
|
139 | var ctx = canvas.getContext("2d");
|
140 | for (var y = -border; y < size + border; y++) {
|
141 | for (var x = -border; x < size + border; x++) {
|
142 | ctx.fillStyle = this.getPixel(x, y) ? "#000000" : "#FFFFFF";
|
143 | ctx.fillRect((x + border) * scale, (y + border) * scale, scale, scale);
|
144 | }
|
145 | }
|
146 | };
|
147 |
|
148 | this.getNeighbors = function (x, y) {
|
149 | return {
|
150 | l: this.getPixel(x - 1, y),
|
151 | r: this.getPixel(x + 1, y),
|
152 | t: this.getPixel(x, y - 1),
|
153 | b: this.getPixel(x, y + 1)
|
154 | };
|
155 | };
|
156 |
|
157 | this.toSvgString = function () {
|
158 | var parts = [];
|
159 | var leftPadding = 0;
|
160 | var topPadding = 0;
|
161 | var xCoord = 0;
|
162 | var yCoord = 0;
|
163 | for (var y = 0; y < size; y++) {
|
164 | leftPadding = 0;
|
165 | for (var x = 0; x < size; x++) {
|
166 | xCoord = x + leftPadding;
|
167 | leftPadding += qrcodegen.tileSize;
|
168 | yCoord = y + topPadding;
|
169 | var neighbors = this.getNeighbors(x, y);
|
170 | var path = '';
|
171 | if (this.getPixel(x, y)) {
|
172 | path = '';
|
173 | if (!neighbors.l && !neighbors.r && !neighbors.t && !neighbors.b) {
|
174 | path = '<path d="M0,28.6v42.9C0,87.3,12.8,100,28.6,100h42.9c15.9,0,28.6-12.8,28.6-28.6V28.6C100,12.7,87.2,0,71.4,0H28.6 C12.8,0,0,12.8,0,28.6z"/>';
|
175 | } else if (!neighbors.l && !neighbors.r && !neighbors.t && neighbors.b) {
|
176 | path = '<path d="M100,100V28.6C100,12.7,87.2,0,71.4,0H28.6C12.7,0,0,12.8,0,28.6V100H100z"/>';
|
177 | } else if (!neighbors.l && neighbors.r && !neighbors.t && !neighbors.b) {
|
178 | path = '<path d="M100,0H28.6C12.7,0,0,12.8,0,28.6v42.9C0,87.3,12.8,100,28.6,100H100V0z"/>';
|
179 | } else if (neighbors.l && !neighbors.r && !neighbors.t && !neighbors.b) {
|
180 | path = '<path d="M0,100h71.4c15.9,0,28.6-12.8,28.6-28.6V28.6C100,12.7,87.2,0,71.4,0H0V100z"/>';
|
181 | } else if (!neighbors.l && !neighbors.r && neighbors.t && !neighbors.b) {
|
182 | path = '<path d="M0,0v71.4C0,87.3,12.8,100,28.6,100h42.9c15.9,0,28.6-12.8,28.6-28.6V0H0z"/>';
|
183 | } else if (neighbors.l && !neighbors.r && !neighbors.t && neighbors.b) {
|
184 | path = '<path d="m0 100h100v-71.5c0-15.8-12.8-28.5-28.5-28.5h-71.5v100z"/>';
|
185 | } else if (neighbors.l && !neighbors.r && neighbors.t && !neighbors.b) {
|
186 | path = '<path d="m0 0v100h71.5c15.8 0 28.5-12.8 28.5-28.5v-71.5h-100z"/>';
|
187 | } else if (!neighbors.l && neighbors.r && !neighbors.t && neighbors.b) {
|
188 | path = '<path d="m100 100v-100h-71.5c-15.8 0-28.5 12.8-28.5 28.5v71.5h100z"/>';
|
189 | } else if (!neighbors.l && neighbors.r && neighbors.t && !neighbors.b) {
|
190 | path = '<path d="m100 0h-100v71.5c0 15.8 12.8 28.5 28.5 28.5h71.5v-100z"/>';
|
191 | } else {
|
192 | path = '<rect width="100" height="100"/>';
|
193 | }
|
194 |
|
195 | parts.push('<g transform="translate(' + xCoord + (yCoord !== 0 ? ',' + yCoord : '') + ')">' + path + '</g>');
|
196 | } else {
|
197 | if (neighbors.l && neighbors.t && this.getPixel(x - 1, y - 1)) {
|
198 | parts.push('<g transform="translate(' + xCoord + (yCoord !== 0 ? ',' + yCoord : '') + ')"><path d="M30.5,2V0H0v30.5h2C2,14.7,14.8,2,30.5,2z"/></g>');
|
199 | }
|
200 | if (neighbors.l && neighbors.b && this.getPixel(x - 1, y + 1)) {
|
201 | parts.push('<g transform="translate(' + xCoord + (yCoord !== 0 ? ',' + yCoord : '') + ')"><path d="M2,69.5H0V100h30.5v-2C14.7,98,2,85.2,2,69.5z"/></g>');
|
202 | }
|
203 | if (neighbors.r && neighbors.t && this.getPixel(x + 1, y - 1)) {
|
204 | parts.push('<g transform="translate(' + xCoord + (yCoord !== 0 ? ',' + yCoord : '') + ')"><path d="M98,30.5h2V0H69.5v2C85.3,2,98,14.8,98,30.5z"/></g>');
|
205 | }
|
206 | if (neighbors.r && neighbors.b && this.getPixel(x + 1, y + 1)) {
|
207 | parts.push('<g transform="translate(' + xCoord + (yCoord !== 0 ? ',' + yCoord : '') + ')"><path d="M69.5,98v2H100V69.5h-2C98,85.3,85.2,98,69.5,98z"/></g>');
|
208 | }
|
209 | }
|
210 | }
|
211 | topPadding += qrcodegen.tileSize;
|
212 | }
|
213 |
|
214 | var imgWidthInTiles = (size - (qrcodegen.qrBorder * 2 - 1)) / 2;
|
215 | var width = imgWidthInTiles * qrcodegen.incTileSize;
|
216 | var position = (size - imgWidthInTiles) / 2 * qrcodegen.incTileSize + qrcodegen.incTileSize / 4;
|
217 | parts.push('<image transform="translate(' + position + ',' + position + ')" width="' + width + '" height="' + width + '" xlink:href="https://upload.wikimedia.org/wikipedia/commons/2/21/VK.com-logo.svg"/>');
|
218 |
|
219 | return '<?xml version="1.0" encoding="UTF-8"?>\n' + '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' + '<svg version="1.1" viewBox="0 0 ' + (size * qrcodegen.incTileSize + qrcodegen.incTileSize) + ' ' + (size * qrcodegen.incTileSize + qrcodegen.incTileSize) + '" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">\n' + '<g transform="translate(0,0)">\n' + parts.join("\n") + '</g>\n' + '</svg>\n';
|
220 | };
|
221 |
|
222 |
|
223 |
|
224 |
|
225 | function drawFunctionPatterns() {
|
226 |
|
227 | for (var i = 0; i < size; i++) {
|
228 | setFunctionModule(6, i, i % 2 == 0);
|
229 | setFunctionModule(i, 6, i % 2 == 0);
|
230 | }
|
231 |
|
232 |
|
233 | drawFinderPattern(3, 3);
|
234 | drawFinderPattern(size - 4, 3);
|
235 | drawFinderPattern(3, size - 4);
|
236 |
|
237 |
|
238 | var alignPatPos = getAlignmentPatternPositions();
|
239 | var numAlign = alignPatPos.length;
|
240 | for (var i = 0; i < numAlign; i++) {
|
241 | for (var j = 0; j < numAlign; j++) {
|
242 |
|
243 | if (!(i == 0 && j == 0 || i == 0 && j == numAlign - 1 || i == numAlign - 1 && j == 0)) drawAlignmentPattern(alignPatPos[i], alignPatPos[j]);
|
244 | }
|
245 | }
|
246 |
|
247 |
|
248 | drawFormatBits(0);
|
249 | drawVersion();
|
250 | }
|
251 |
|
252 |
|
253 |
|
254 | function drawFormatBits(mask) {
|
255 |
|
256 | var data = errCorLvl.formatBits << 3 | mask;
|
257 | var rem = data;
|
258 | for (var i = 0; i < 10; i++) {
|
259 | rem = rem << 1 ^ (rem >>> 9) * 0x537;
|
260 | }var bits = (data << 10 | rem) ^ 0x5412;
|
261 | if (bits >>> 15 != 0) throw "Assertion error";
|
262 |
|
263 |
|
264 | for (var i = 0; i <= 5; i++) {
|
265 | setFunctionModule(8, i, getBit(bits, i));
|
266 | }setFunctionModule(8, 7, getBit(bits, 6));
|
267 | setFunctionModule(8, 8, getBit(bits, 7));
|
268 | setFunctionModule(7, 8, getBit(bits, 8));
|
269 | for (var i = 9; i < 15; i++) {
|
270 | setFunctionModule(14 - i, 8, getBit(bits, i));
|
271 | }
|
272 | for (var i = 0; i < 8; i++) {
|
273 | setFunctionModule(size - 1 - i, 8, getBit(bits, i));
|
274 | }for (var i = 8; i < 15; i++) {
|
275 | setFunctionModule(8, size - 15 + i, getBit(bits, i));
|
276 | }setFunctionModule(8, size - 8, true);
|
277 | }
|
278 |
|
279 |
|
280 |
|
281 | function drawVersion() {
|
282 | if (version < 7) return;
|
283 |
|
284 |
|
285 | var rem = version;
|
286 | for (var i = 0; i < 12; i++) {
|
287 | rem = rem << 1 ^ (rem >>> 11) * 0x1F25;
|
288 | }var bits = version << 12 | rem;
|
289 | if (bits >>> 18 != 0) throw "Assertion error";
|
290 |
|
291 |
|
292 | for (var i = 0; i < 18; i++) {
|
293 | var bit = getBit(bits, i);
|
294 | var a = size - 11 + i % 3;
|
295 | var b = Math.floor(i / 3);
|
296 | setFunctionModule(a, b, bit);
|
297 | setFunctionModule(b, a, bit);
|
298 | }
|
299 | }
|
300 |
|
301 |
|
302 |
|
303 | function drawFinderPattern(x, y) {
|
304 | for (var dy = -4; dy <= 4; dy++) {
|
305 | for (var dx = -4; dx <= 4; dx++) {
|
306 | var dist = Math.max(Math.abs(dx), Math.abs(dy));
|
307 | var xx = x + dx,
|
308 | yy = y + dy;
|
309 | if (0 <= xx && xx < size && 0 <= yy && yy < size) setFunctionModule(xx, yy, dist != 2 && dist != 4);
|
310 | }
|
311 | }
|
312 | }
|
313 |
|
314 |
|
315 |
|
316 | function drawAlignmentPattern(x, y) {
|
317 | for (var dy = -2; dy <= 2; dy++) {
|
318 | for (var dx = -2; dx <= 2; dx++) {
|
319 | setFunctionModule(x + dx, y + dy, Math.max(Math.abs(dx), Math.abs(dy)) != 1);
|
320 | }
|
321 | }
|
322 | }
|
323 |
|
324 |
|
325 |
|
326 | function setFunctionModule(x, y, isBlack) {
|
327 | modules[y][x] = isBlack;
|
328 | isFunction[y][x] = true;
|
329 | }
|
330 |
|
331 |
|
332 |
|
333 |
|
334 |
|
335 | function addEccAndInterleave(data) {
|
336 | if (data.length != QrCode.getNumDataCodewords(version, errCorLvl)) throw "Invalid argument";
|
337 |
|
338 |
|
339 | var numBlocks = QrCode.NUM_ERROR_CORRECTION_BLOCKS[errCorLvl.ordinal][version];
|
340 | var blockEccLen = QrCode.ECC_CODEWORDS_PER_BLOCK[errCorLvl.ordinal][version];
|
341 | var rawCodewords = Math.floor(QrCode.getNumRawDataModules(version) / 8);
|
342 | var numShortBlocks = numBlocks - rawCodewords % numBlocks;
|
343 | var shortBlockLen = Math.floor(rawCodewords / numBlocks);
|
344 |
|
345 |
|
346 | var blocks = [];
|
347 | var rs = new ReedSolomonGenerator(blockEccLen);
|
348 | for (var i = 0, k = 0; i < numBlocks; i++) {
|
349 | var dat = data.slice(k, k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1));
|
350 | k += dat.length;
|
351 | var ecc = rs.getRemainder(dat);
|
352 | if (i < numShortBlocks) dat.push(0);
|
353 | blocks.push(dat.concat(ecc));
|
354 | }
|
355 |
|
356 |
|
357 | var result = [];
|
358 | for (var i = 0; i < blocks[0].length; i++) {
|
359 | for (var j = 0; j < blocks.length; j++) {
|
360 |
|
361 | if (i != shortBlockLen - blockEccLen || j >= numShortBlocks) result.push(blocks[j][i]);
|
362 | }
|
363 | }
|
364 | if (result.length != rawCodewords) throw "Assertion error";
|
365 | return result;
|
366 | }
|
367 |
|
368 |
|
369 |
|
370 | function drawCodewords(data) {
|
371 | if (data.length != Math.floor(QrCode.getNumRawDataModules(version) / 8)) throw "Invalid argument";
|
372 | var i = 0;
|
373 |
|
374 | for (var right = size - 1; right >= 1; right -= 2) {
|
375 |
|
376 | if (right == 6) right = 5;
|
377 | for (var vert = 0; vert < size; vert++) {
|
378 |
|
379 | for (var j = 0; j < 2; j++) {
|
380 | var x = right - j;
|
381 | var upward = (right + 1 & 2) == 0;
|
382 | var y = upward ? size - 1 - vert : vert;
|
383 | if (!isFunction[y][x] && i < data.length * 8) {
|
384 | modules[y][x] = getBit(data[i >>> 3], 7 - (i & 7));
|
385 | i++;
|
386 | }
|
387 |
|
388 |
|
389 | }
|
390 | }
|
391 | }
|
392 | if (i != data.length * 8) throw "Assertion error";
|
393 | }
|
394 |
|
395 |
|
396 |
|
397 |
|
398 |
|
399 |
|
400 | function applyMask(mask) {
|
401 | if (mask < 0 || mask > 7) throw "Mask value out of range";
|
402 | for (var y = 0; y < size; y++) {
|
403 | for (var x = 0; x < size; x++) {
|
404 | var invert;
|
405 | switch (mask) {
|
406 | case 0:
|
407 | invert = (x + y) % 2 == 0;
|
408 | break;
|
409 | case 1:
|
410 | invert = y % 2 == 0;
|
411 | break;
|
412 | case 2:
|
413 | invert = x % 3 == 0;
|
414 | break;
|
415 | case 3:
|
416 | invert = (x + y) % 3 == 0;
|
417 | break;
|
418 | case 4:
|
419 | invert = (Math.floor(x / 3) + Math.floor(y / 2)) % 2 == 0;
|
420 | break;
|
421 | case 5:
|
422 | invert = x * y % 2 + x * y % 3 == 0;
|
423 | break;
|
424 | case 6:
|
425 | invert = (x * y % 2 + x * y % 3) % 2 == 0;
|
426 | break;
|
427 | case 7:
|
428 | invert = ((x + y) % 2 + x * y % 3) % 2 == 0;
|
429 | break;
|
430 | default:
|
431 | throw "Assertion error";
|
432 | }
|
433 | if (!isFunction[y][x] && invert) modules[y][x] = !modules[y][x];
|
434 | }
|
435 | }
|
436 | }
|
437 |
|
438 |
|
439 |
|
440 | function getPenaltyScore() {
|
441 | var result = 0;
|
442 |
|
443 |
|
444 | for (var y = 0; y < size; y++) {
|
445 | var runHistory = [0, 0, 0, 0, 0, 0, 0];
|
446 | var color = false;
|
447 | var runX = 0;
|
448 | for (var x = 0; x < size; x++) {
|
449 | if (modules[y][x] == color) {
|
450 | runX++;
|
451 | if (runX == 5) result += QrCode.PENALTY_N1;else if (runX > 5) result++;
|
452 | } else {
|
453 | QrCode.addRunToHistory(runX, runHistory);
|
454 | if (!color && QrCode.hasFinderLikePattern(runHistory)) result += QrCode.PENALTY_N3;
|
455 | color = modules[y][x];
|
456 | runX = 1;
|
457 | }
|
458 | }
|
459 | QrCode.addRunToHistory(runX, runHistory);
|
460 | if (color) QrCode.addRunToHistory(0, runHistory);
|
461 | if (QrCode.hasFinderLikePattern(runHistory)) result += QrCode.PENALTY_N3;
|
462 | }
|
463 |
|
464 | for (var x = 0; x < size; x++) {
|
465 | var runHistory = [0, 0, 0, 0, 0, 0, 0];
|
466 | var color = false;
|
467 | var runY = 0;
|
468 | for (var y = 0; y < size; y++) {
|
469 | if (modules[y][x] == color) {
|
470 | runY++;
|
471 | if (runY == 5) result += QrCode.PENALTY_N1;else if (runY > 5) result++;
|
472 | } else {
|
473 | QrCode.addRunToHistory(runY, runHistory);
|
474 | if (!color && QrCode.hasFinderLikePattern(runHistory)) result += QrCode.PENALTY_N3;
|
475 | color = modules[y][x];
|
476 | runY = 1;
|
477 | }
|
478 | }
|
479 | QrCode.addRunToHistory(runY, runHistory);
|
480 | if (color) QrCode.addRunToHistory(0, runHistory);
|
481 | if (QrCode.hasFinderLikePattern(runHistory)) result += QrCode.PENALTY_N3;
|
482 | }
|
483 |
|
484 |
|
485 | for (var y = 0; y < size - 1; y++) {
|
486 | for (var x = 0; x < size - 1; x++) {
|
487 | var color = modules[y][x];
|
488 | if (color == modules[y][x + 1] && color == modules[y + 1][x] && color == modules[y + 1][x + 1]) result += QrCode.PENALTY_N2;
|
489 | }
|
490 | }
|
491 |
|
492 |
|
493 | var black = 0;
|
494 | modules.forEach(function (row) {
|
495 | row.forEach(function (color) {
|
496 | if (color) black++;
|
497 | });
|
498 | });
|
499 | var total = size * size;
|
500 |
|
501 | var k = Math.ceil(Math.abs(black * 20 - total * 10) / total) - 1;
|
502 | result += k * QrCode.PENALTY_N4;
|
503 | return result;
|
504 | }
|
505 |
|
506 |
|
507 |
|
508 |
|
509 | function getAlignmentPatternPositions() {
|
510 | if (version == 1) return [];else {
|
511 | var numAlign = Math.floor(version / 7) + 2;
|
512 | var step = version == 32 ? 26 : Math.ceil((size - 13) / (numAlign * 2 - 2)) * 2;
|
513 | var result = [6];
|
514 | for (var pos = size - 7; result.length < numAlign; pos -= step) {
|
515 | result.splice(1, 0, pos);
|
516 | }return result;
|
517 | }
|
518 | }
|
519 |
|
520 |
|
521 | function getBit(x, i) {
|
522 | return (x >>> i & 1) != 0;
|
523 | }
|
524 | };
|
525 |
|
526 |
|
527 |
|
528 | |
529 |
|
530 |
|
531 |
|
532 |
|
533 |
|
534 |
|
535 | this.QrCode.encodeText = function (text, ecl) {
|
536 | var segs = qrcodegen.QrSegment.makeSegments(text);
|
537 | return this.encodeSegments(segs, ecl);
|
538 | };
|
539 |
|
540 | |
541 |
|
542 |
|
543 |
|
544 |
|
545 |
|
546 | this.QrCode.encodeBinary = function (data, ecl) {
|
547 | var seg = qrcodegen.QrSegment.makeBytes(data);
|
548 | return this.encodeSegments([seg], ecl);
|
549 | };
|
550 |
|
551 |
|
552 |
|
553 | |
554 |
|
555 |
|
556 |
|
557 |
|
558 |
|
559 |
|
560 |
|
561 |
|
562 |
|
563 |
|
564 | this.QrCode.encodeSegments = function (segs, ecl, minVersion, maxVersion, mask, boostEcl) {
|
565 | if (minVersion == undefined) minVersion = MIN_VERSION;
|
566 | if (maxVersion == undefined) maxVersion = MAX_VERSION;
|
567 | if (mask == undefined) mask = -1;
|
568 | if (boostEcl == undefined) boostEcl = true;
|
569 | if (!(MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= MAX_VERSION) || mask < -1 || mask > 7) throw "Invalid value";
|
570 |
|
571 |
|
572 | var version, dataUsedBits;
|
573 | for (version = minVersion;; version++) {
|
574 | var dataCapacityBits = QrCode.getNumDataCodewords(version, ecl) * 8;
|
575 | dataUsedBits = qrcodegen.QrSegment.getTotalBits(segs, version);
|
576 | if (dataUsedBits <= dataCapacityBits) break;
|
577 | if (version >= maxVersion)
|
578 | throw "Data too long";
|
579 | }
|
580 |
|
581 |
|
582 | [this.Ecc.MEDIUM, this.Ecc.QUARTILE, this.Ecc.HIGH].forEach(function (newEcl) {
|
583 |
|
584 | if (boostEcl && dataUsedBits <= QrCode.getNumDataCodewords(version, newEcl) * 8) ecl = newEcl;
|
585 | });
|
586 |
|
587 |
|
588 | var bb = new BitBuffer();
|
589 | segs.forEach(function (seg) {
|
590 | bb.appendBits(seg.mode.modeBits, 4);
|
591 | bb.appendBits(seg.numChars, seg.mode.numCharCountBits(version));
|
592 | seg.getData().forEach(function (bit) {
|
593 | bb.push(bit);
|
594 | });
|
595 | });
|
596 | if (bb.length != dataUsedBits) throw "Assertion error";
|
597 |
|
598 |
|
599 | var dataCapacityBits = QrCode.getNumDataCodewords(version, ecl) * 8;
|
600 | if (bb.length > dataCapacityBits) throw "Assertion error";
|
601 | bb.appendBits(0, Math.min(4, dataCapacityBits - bb.length));
|
602 | bb.appendBits(0, (8 - bb.length % 8) % 8);
|
603 | if (bb.length % 8 != 0) throw "Assertion error";
|
604 |
|
605 |
|
606 | for (var padByte = 0xEC; bb.length < dataCapacityBits; padByte ^= 0xEC ^ 0x11) {
|
607 | bb.appendBits(padByte, 8);
|
608 | }
|
609 | var dataCodewords = [];
|
610 | while (dataCodewords.length * 8 < bb.length) {
|
611 | dataCodewords.push(0);
|
612 | }bb.forEach(function (bit, i) {
|
613 | dataCodewords[i >>> 3] |= bit << 7 - (i & 7);
|
614 | });
|
615 |
|
616 |
|
617 | return new this(version, ecl, dataCodewords, mask);
|
618 | };
|
619 |
|
620 |
|
621 |
|
622 | var QrCode = {};
|
623 |
|
624 |
|
625 |
|
626 |
|
627 |
|
628 | QrCode.getNumRawDataModules = function (ver) {
|
629 | if (ver < MIN_VERSION || ver > MAX_VERSION) throw "Version number out of range";
|
630 | var result = (16 * ver + 128) * ver + 64;
|
631 | if (ver >= 2) {
|
632 | var numAlign = Math.floor(ver / 7) + 2;
|
633 | result -= (25 * numAlign - 10) * numAlign - 55;
|
634 | if (ver >= 7) result -= 36;
|
635 | }
|
636 | return result;
|
637 | };
|
638 |
|
639 |
|
640 |
|
641 |
|
642 | QrCode.getNumDataCodewords = function (ver, ecl) {
|
643 | return Math.floor(QrCode.getNumRawDataModules(ver) / 8) - QrCode.ECC_CODEWORDS_PER_BLOCK[ecl.ordinal][ver] * QrCode.NUM_ERROR_CORRECTION_BLOCKS[ecl.ordinal][ver];
|
644 | };
|
645 |
|
646 |
|
647 |
|
648 | QrCode.addRunToHistory = function (run, history) {
|
649 | history.pop();
|
650 | history.unshift(run);
|
651 | };
|
652 |
|
653 |
|
654 |
|
655 |
|
656 | QrCode.hasFinderLikePattern = function (runHistory) {
|
657 | var n = runHistory[1];
|
658 | return n > 0 && runHistory[2] == n && runHistory[4] == n && runHistory[5] == n && runHistory[3] == n * 3 && Math.max(runHistory[0], runHistory[6]) >= n * 4;
|
659 | };
|
660 |
|
661 |
|
662 |
|
663 | var MIN_VERSION = 1;
|
664 | var MAX_VERSION = 40;
|
665 | Object.defineProperty(this.QrCode, "MIN_VERSION", { value: MIN_VERSION });
|
666 | Object.defineProperty(this.QrCode, "MAX_VERSION", { value: MAX_VERSION });
|
667 |
|
668 |
|
669 | QrCode.PENALTY_N1 = 3;
|
670 | QrCode.PENALTY_N2 = 3;
|
671 | QrCode.PENALTY_N3 = 40;
|
672 | QrCode.PENALTY_N4 = 10;
|
673 |
|
674 | QrCode.ECC_CODEWORDS_PER_BLOCK = [
|
675 |
|
676 |
|
677 | [null, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30],
|
678 | [null, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28],
|
679 | [null, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30],
|
680 | [null, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30]];
|
681 |
|
682 | QrCode.NUM_ERROR_CORRECTION_BLOCKS = [
|
683 |
|
684 |
|
685 | [null, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25],
|
686 | [null, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49],
|
687 | [null, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68],
|
688 | [null, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81]];
|
689 |
|
690 |
|
691 |
|
692 | |
693 |
|
694 |
|
695 | this.QrCode.Ecc = {
|
696 | LOW: new Ecc(0, 1),
|
697 | MEDIUM: new Ecc(1, 0),
|
698 | QUARTILE: new Ecc(2, 3),
|
699 | HIGH: new Ecc(3, 2)
|
700 | };
|
701 |
|
702 |
|
703 | function Ecc(ord, fb) {
|
704 |
|
705 | Object.defineProperty(this, "ordinal", { value: ord });
|
706 |
|
707 |
|
708 | Object.defineProperty(this, "formatBits", { value: fb });
|
709 | }
|
710 |
|
711 |
|
712 |
|
713 | |
714 |
|
715 |
|
716 |
|
717 |
|
718 |
|
719 |
|
720 |
|
721 |
|
722 |
|
723 |
|
724 |
|
725 |
|
726 |
|
727 | this.QrSegment = function (mode, numChars, bitData) {
|
728 |
|
729 | if (numChars < 0 || !(mode instanceof Mode)) throw "Invalid argument";
|
730 |
|
731 |
|
732 | bitData = bitData.slice();
|
733 |
|
734 |
|
735 | Object.defineProperty(this, "mode", { value: mode });
|
736 |
|
737 |
|
738 |
|
739 |
|
740 | Object.defineProperty(this, "numChars", { value: numChars });
|
741 |
|
742 |
|
743 | this.getData = function () {
|
744 | return bitData.slice();
|
745 | };
|
746 | };
|
747 |
|
748 |
|
749 |
|
750 | |
751 |
|
752 |
|
753 |
|
754 |
|
755 | this.QrSegment.makeBytes = function (data) {
|
756 | var bb = new BitBuffer();
|
757 | data.forEach(function (b) {
|
758 | bb.appendBits(b, 8);
|
759 | });
|
760 | return new this(this.Mode.BYTE, data.length, bb);
|
761 | };
|
762 |
|
763 | |
764 |
|
765 |
|
766 | this.QrSegment.makeNumeric = function (digits) {
|
767 | if (!this.NUMERIC_REGEX.test(digits)) throw "String contains non-numeric characters";
|
768 | var bb = new BitBuffer();
|
769 | for (var i = 0; i < digits.length;) {
|
770 |
|
771 | var n = Math.min(digits.length - i, 3);
|
772 | bb.appendBits(parseInt(digits.substring(i, i + n), 10), n * 3 + 1);
|
773 | i += n;
|
774 | }
|
775 | return new this(this.Mode.NUMERIC, digits.length, bb);
|
776 | };
|
777 |
|
778 | |
779 |
|
780 |
|
781 |
|
782 |
|
783 | this.QrSegment.makeAlphanumeric = function (text) {
|
784 | if (!this.ALPHANUMERIC_REGEX.test(text)) throw "String contains unencodable characters in alphanumeric mode";
|
785 | var bb = new BitBuffer();
|
786 | var i;
|
787 | for (i = 0; i + 2 <= text.length; i += 2) {
|
788 |
|
789 | var temp = QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)) * 45;
|
790 | temp += QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i + 1));
|
791 | bb.appendBits(temp, 11);
|
792 | }
|
793 | if (i < text.length)
|
794 | bb.appendBits(QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)), 6);
|
795 | return new this(this.Mode.ALPHANUMERIC, text.length, bb);
|
796 | };
|
797 |
|
798 | |
799 |
|
800 |
|
801 |
|
802 | this.QrSegment.makeSegments = function (text) {
|
803 |
|
804 | if (text == "") return [];else if (this.NUMERIC_REGEX.test(text)) return [this.makeNumeric(text)];else if (this.ALPHANUMERIC_REGEX.test(text)) return [this.makeAlphanumeric(text)];else return [this.makeBytes(toUtf8ByteArray(text))];
|
805 | };
|
806 |
|
807 | |
808 |
|
809 |
|
810 |
|
811 | this.QrSegment.makeEci = function (assignVal) {
|
812 | var bb = new BitBuffer();
|
813 | if (assignVal < 0) throw "ECI assignment value out of range";else if (assignVal < 1 << 7) bb.appendBits(assignVal, 8);else if (assignVal < 1 << 14) {
|
814 | bb.appendBits(2, 2);
|
815 | bb.appendBits(assignVal, 14);
|
816 | } else if (assignVal < 1000000) {
|
817 | bb.appendBits(6, 3);
|
818 | bb.appendBits(assignVal, 21);
|
819 | } else throw "ECI assignment value out of range";
|
820 | return new this(this.Mode.ECI, 0, bb);
|
821 | };
|
822 |
|
823 |
|
824 |
|
825 | this.QrSegment.getTotalBits = function (segs, version) {
|
826 | var result = 0;
|
827 | for (var i = 0; i < segs.length; i++) {
|
828 | var seg = segs[i];
|
829 | var ccbits = seg.mode.numCharCountBits(version);
|
830 | if (seg.numChars >= 1 << ccbits) return Infinity;
|
831 | result += 4 + ccbits + seg.getData().length;
|
832 | }
|
833 | return result;
|
834 | };
|
835 |
|
836 |
|
837 |
|
838 | var QrSegment = {};
|
839 |
|
840 |
|
841 |
|
842 |
|
843 | this.QrSegment.NUMERIC_REGEX = /^[0-9]*$/;
|
844 |
|
845 |
|
846 |
|
847 |
|
848 |
|
849 | this.QrSegment.ALPHANUMERIC_REGEX = /^[A-Z0-9 $%*+.\/:-]*$/;
|
850 |
|
851 |
|
852 |
|
853 | QrSegment.ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:";
|
854 |
|
855 |
|
856 |
|
857 | |
858 |
|
859 |
|
860 | this.QrSegment.Mode = {
|
861 | NUMERIC: new Mode(0x1, [10, 12, 14]),
|
862 | ALPHANUMERIC: new Mode(0x2, [9, 11, 13]),
|
863 | BYTE: new Mode(0x4, [8, 16, 16]),
|
864 | KANJI: new Mode(0x8, [8, 10, 12]),
|
865 | ECI: new Mode(0x7, [0, 0, 0])
|
866 | };
|
867 |
|
868 |
|
869 | function Mode(mode, ccbits) {
|
870 |
|
871 | Object.defineProperty(this, "modeBits", { value: mode });
|
872 |
|
873 |
|
874 |
|
875 | this.numCharCountBits = function (ver) {
|
876 | return ccbits[Math.floor((ver + 7) / 17)];
|
877 | };
|
878 | }
|
879 |
|
880 |
|
881 |
|
882 |
|
883 | function toUtf8ByteArray(str) {
|
884 | str = encodeURI(str);
|
885 | var result = [];
|
886 | for (var i = 0; i < str.length; i++) {
|
887 | if (str.charAt(i) != "%") result.push(str.charCodeAt(i));else {
|
888 | result.push(parseInt(str.substring(i + 1, i + 3), 16));
|
889 | i += 2;
|
890 | }
|
891 | }
|
892 | return result;
|
893 | }
|
894 |
|
895 | |
896 |
|
897 |
|
898 |
|
899 |
|
900 |
|
901 |
|
902 | function ReedSolomonGenerator(degree) {
|
903 | if (degree < 1 || degree > 255) throw "Degree out of range";
|
904 |
|
905 |
|
906 |
|
907 | var coefficients = [];
|
908 |
|
909 |
|
910 | for (var i = 0; i < degree - 1; i++) {
|
911 | coefficients.push(0);
|
912 | }coefficients.push(1);
|
913 |
|
914 |
|
915 |
|
916 |
|
917 | var root = 1;
|
918 | for (var i = 0; i < degree; i++) {
|
919 |
|
920 | for (var j = 0; j < coefficients.length; j++) {
|
921 | coefficients[j] = ReedSolomonGenerator.multiply(coefficients[j], root);
|
922 | if (j + 1 < coefficients.length) coefficients[j] ^= coefficients[j + 1];
|
923 | }
|
924 | root = ReedSolomonGenerator.multiply(root, 0x02);
|
925 | }
|
926 |
|
927 |
|
928 |
|
929 |
|
930 | this.getRemainder = function (data) {
|
931 |
|
932 | var result = coefficients.map(function () {
|
933 | return 0;
|
934 | });
|
935 | data.forEach(function (b) {
|
936 | var factor = b ^ result.shift();
|
937 | result.push(0);
|
938 | coefficients.forEach(function (coef, i) {
|
939 | result[i] ^= ReedSolomonGenerator.multiply(coef, factor);
|
940 | });
|
941 | });
|
942 | return result;
|
943 | };
|
944 | }
|
945 |
|
946 |
|
947 |
|
948 | ReedSolomonGenerator.multiply = function (x, y) {
|
949 | if (x >>> 8 != 0 || y >>> 8 != 0) throw "Byte out of range";
|
950 |
|
951 | var z = 0;
|
952 | for (var i = 7; i >= 0; i--) {
|
953 | z = z << 1 ^ (z >>> 7) * 0x11D;
|
954 | z ^= (y >>> i & 1) * x;
|
955 | }
|
956 | if (z >>> 8 != 0) throw "Assertion error";
|
957 | return z;
|
958 | };
|
959 |
|
960 | |
961 |
|
962 |
|
963 |
|
964 | function BitBuffer() {
|
965 | Array.call(this);
|
966 |
|
967 |
|
968 |
|
969 | this.appendBits = function (val, len) {
|
970 | if (len < 0 || len > 31 || val >>> len != 0) throw "Value out of range";
|
971 | for (var i = len - 1; i >= 0; i--) {
|
972 |
|
973 | this.push(val >>> i & 1);
|
974 | }
|
975 | };
|
976 | }
|
977 |
|
978 | BitBuffer.prototype = Object.create(Array.prototype);
|
979 | BitBuffer.prototype.constructor = BitBuffer;
|
980 | }();
|
981 |
|
982 | return qrcodegen;
|
983 |
|
984 | })));
|