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