forked from six-two/qr.html
-
Notifications
You must be signed in to change notification settings - Fork 0
/
qr.html
397 lines (356 loc) · 27.6 KB
/
qr.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Make sure that no communication with external sites is possible -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline' data:;">
<title>Offline QR code generator</title>
<script>
const get_or_default_int = (name, default_value) => {
const stored = localStorage.getItem(name);
if (stored != null) {
try {
return parseInt(stored);
} catch (error) {
console.warn(`Failed to parse stored entry for '${name}'`);
}
}
return default_value;
}
// ================== Start of user settings ==================
// You can modify these settings without breaking stuff
// Level L – up to 7% damage
// Level M – up to 15% damage
// Level Q – up to 25% damage
// Level H – up to 30% damage
let ERROR_CORRECTION_LEVEL = localStorage.getItem("ERROR_CORRECTION_LEVEL") || "L";
let MAX_TEXT_LENGTH = get_or_default_int("MAX_TEXT_LENGTH", 2950);// Only valid for Error Correction Level L, less for other levels
// Allow users to enable the clipboard monitoring function. Set this to false to hide the checkbox
let SHOW_CLIPBOARD_MONITORING_CONTROLS = true;
// Size of the QR code in pixels. Set it to 0 to automatically fit the free space
let QR_MIN_SIZE = get_or_default_int("QR_MIN_SIZE", 30); //in pixels
let QR_MAX_SIZE = get_or_default_int("QR_MAX_SIZE", Infinity); // in pixels
// If you want a border around the QR code (that will stay when you right click -> "Save image as..." the image, set this to something greater than zero)
let QR_BORDER_SIZE = get_or_default_int("QR_BORDER_SIZE", 0); // in pixels
let QR_BORDER_COLOR = localStorage.getItem("QR_BORDER_COLOR") || "#909090"; // standard html color codes
// =================== End of user settings ===================
</script>
<style>
* {
box-sizing: border-box;
}
body {
width: 100vw;
height: 100vh;
max-height: 100vh;
margin: 0px;
padding: 0px;
display: flex;
flex-direction: column;
background-color: lightgray;
overflow: hidden;
}
header {
width: 100%;
min-height: 40px;
background-color: #444;
color: lightgray;
display: flex;
font-size: 25px;
padding: 5px;
}
header a, header .button {
color: lightgray;
margin-right: 20px;
}
header .button {
cursor: pointer;
text-decoration: underline
}
.expand {
flex: 1;
}
#text-and-qr {
flex: 1;
display: flex;
flex-direction: column;
padding: 5px;
}
#input-div {
min-height: 100px;
min-width: 200px;
flex: 1;
display: flex;
flex-direction: column;
}
#input {
width: 100%;
height: 100%;
flex: 1;
resize: none;
}
#input-error {
margin-bottom: 10px;
display: none;
}
.error {
width: 100%;
background-color: rgb(255, 140, 140);
border: 2px solid red;
border-radius: 10px;
padding: 5px;
}
#qrcode {
width: 100%;
height: 100%;
padding: 5px;
}
#qrcode canvas {
margin: auto;
display: block;
}
</style>
</head>
<body>
<header id="header">
<div class="title">Offline QR code generator</div>
<div class="expand"></div>
<div id="monitor-clipboard" class="button"></div>
<a id="download" href="" download>Download</a>
<a href="https://github.com/six-two/qr.html" target="_blank" rel="noopener noreferrer">Source code</a>
</header>
<div id="text-and-qr">
<div id="input-div">
<div id="input-error" class="error"></div>
<textarea id="input" name="input" id="" autofocus>You can generate QR codes from any text by typing in in this box. The code generation is performed on your local device, no data is sent anywhere.
You can even download this file (see button on the top right) and use it when you are not connected to the Internet.
If the layout looks strange, try resizing your browser window. It always tries to maximize the QR code's size, which can look strange sometimes.
⚠️ The maximum size of a QR code is 2950 bytes
⚠️ Some features will not work in some browsers. In my experience Google Chrome works best.
🎉 This should now also work with Emojis
📁 You can now drag and drop a file in this text box
📁 And you can copy-paste a file to this text area
</textarea>
</div>
<div id="qrcode"><div class="error">You need to enable JavaScript to use this page</div></div>
</div>
<script type="text/javascript">
const header = document.getElementById("header");
const qrcode = document.getElementById("qrcode");
const input_area = document.getElementById("input");
const input_error = document.getElementById("input-error");
const text_and_qr = document.getElementById("text-and-qr");
const PADDING = 10; // 5px on any side for the text-and-qr element -> always 10px in total
const showInputError = (message) => {
// message is untrusted input, so we should escape it properly
if (message) {
input_error.innerHTML = ""; // remove current children
const messageElement = document.createTextNode(message);
input_error.appendChild(messageElement);
input_error.style.display = "block";
} else {
input_error.style.display = "none";
}
}
const showQrCodeGenerationError = (message) => {
// Bad practice, but XSS should not be possible
qrcode.innerHTML = `<div class="error"><b>QR code generation failed:</b><br>${message}</div>`;
}
const updateQRCode = () => {
const max_size_fit_window = Math.min(qrcode.clientWidth, qrcode.clientHeight, QR_MAX_SIZE);
const qr_size = Math.max(QR_MIN_SIZE, max_size_fit_window) - 10; // remove 2 * 5px for the paddings
text = input_area.value;
if (!text) {
showQrCodeGenerationError("You need to type some text in the input area! It will then be rendered as a QR code.");
} else if (text.length > MAX_TEXT_LENGTH) {
// This value was generated by trial and error
showQrCodeGenerationError(`Your text is ${text.length} characters long. Sadly, due to technical limitations, the maximum size QR code this app can generate is ${MAX_TEXT_LENGTH} characters. Try splitting your text into multiple smaller parts.`)
} else {
// Delete the error message (in case it existed) or the old QR code
qrcode.innerHTML = "";
// @TODO: I probably could do that border stuff more efficiently if I prepared a canvas and passed it to the render method
try {
QrCreator.render({
text: text,
radius: 0.0, // square pixels
ecLevel: ERROR_CORRECTION_LEVEL,
fill: '#000000',
background: "#ffffff",
size: qr_size - 2 * QR_BORDER_SIZE,
}, qrcode);
if (QR_BORDER_SIZE > 0) {
const canvas = qrcode.getElementsByTagName("canvas")[0];
// copy the old image
const image = document.createElement("img");
image.src = canvas.toDataURL();
ctx = canvas.getContext('2d');
// clear the whole image by filling it with the border color
ctx.fillStyle = QR_BORDER_COLOR;
ctx.fillRect(0, 0, qr_size, qr_size);
// Copy the saved image, but make it smaller, so that a bit of the border remains
ctx.drawImage(image, 0, 0, qr_size, qr_size,
QR_BORDER_SIZE, QR_BORDER_SIZE, qr_size - 2 * QR_BORDER_SIZE, qr_size - 2 * QR_BORDER_SIZE);
console.log(QR_BORDER_SIZE, qr_size - 2 * QR_BORDER_SIZE);
}
} catch (error) {
showQrCodeGenerationError("Failed to generate the QR code! Please try a shorter text and try removing special characters and emojis. If you are a programmer, look in the prowser console for the error message");
console.error("Error while generating QR code", error);
}
}
};
const handleFileUpload = (files) => {
if (files.length == 1) {
files[0].text().then(file_text => {
console.log("Received file:\n", file_text);
// Update the text field and the QR code
input_area.value = file_text
showInputError();
updateQRCode();
}).catch(error => {
showInputError(`Error reading the file: ${error}`);
})
} else {
showInputError(`Received ${files.length} files. Please use only one file at a time.`);
}
};
// Handle pasting a file (copying if in file manager and them pressing Crtl-V [Cmd-V on Mac] while in the browser)
document.addEventListener("paste", async e => {
if (e.clipboardData.files.length == 0) {
// do not prevent the default action, since it would break normal copy + paste
return
} else {
// process files as a file upload
e.preventDefault();
handleFileUpload(e.clipboardData.files)
}
});
// Support drag and drop for the input element
input_area.addEventListener("drop", e => {
// process files as a file upload
e.preventDefault();
handleFileUpload(e.dataTransfer.files)
return false;
});
// Update the QR code whenever the text or the window size changes
// Use max-width/height to keep the QR code div in square shape and allow the textarea to use the remaining space
input_area.addEventListener("input", updateQRCode);
const on_window_resize = () => {
const width = text_and_qr.clientWidth;
const height = text_and_qr.clientHeight;
if (!width || !height) {
console.warn(`Size can not be 0 or undefined. But size is (${width}, ${height})`)
}
const toStyleValue = (size_in_pixels) => {
return `${Math.max(size_in_pixels - PADDING, 0)}px`
}
if (width < height) {
const remaining_height = document.body.clientHeight - header.clientHeight - 100; // 100 is the minimum height of the input area div
// portrait view -> vertical
text_and_qr.style.flexDirection = "column";
qrcode.style.maxWidth = "";
qrcode.style.maxHeight = toStyleValue(Math.min(width, remaining_height));
} else {
// landscape view -> horizontal
const remaining_width = document.body.clientWidth - 200; // 200 is the minimum width of the input area div
text_and_qr.style.flexDirection = "row";
qrcode.style.maxWidth = toStyleValue(Math.min(height, remaining_width));
// Workaround for QR code not shrinking
qrcode.style.maxHeight = toStyleValue(document.body.clientHeight - header.clientHeight);
}
updateQRCode();
};
window.addEventListener("resize", on_window_resize);
// sometimes the event does not get called (for example when closing the browser console). So we call it regularly just to be sure
setInterval(on_window_resize, 1000)
// Call it shortly after loading the page to determine the inital layout
setTimeout(on_window_resize, 10);
if (SHOW_CLIPBOARD_MONITORING_CONTROLS) {
let monitor_clipboard_enabled = false;
let clipboard_interval_handle;
const monitor_clipboard_menuitem = document.getElementById("monitor-clipboard");
const update_cb_mon_menuitem = () => monitor_clipboard_menuitem.innerHTML = monitor_clipboard_enabled ? "Stop clipboard monitoring" : "Start clipboard monitoring";
monitor_clipboard_menuitem.addEventListener("click", () => {
if (window.isSecureContext) {
if (monitor_clipboard_enabled) {
console.log("Stopping clipboard monitoring");
clearInterval(clipboard_interval_handle);
monitor_clipboard_enabled = false;
showInputError(); // clear the error just in case it was caused by the clipboard
} else {
console.log("Starting clipboard monitoring");
clipboard_interval_handle = setInterval(check_clipboard, 100);
monitor_clipboard_enabled = true;
}
} else {
if (monitor_clipboard_enabled) {
// enable the user to hide the message by pressing stop
showInputError();
} else {
showInputError("Clipboard can not be monitored, because the page is not a secure context. See https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts for more details.")
}
monitor_clipboard_enabled = !monitor_clipboard_enabled;
}
update_cb_mon_menuitem()
});
update_cb_mon_menuitem();
let last_value_copied = "";
const check_clipboard = () => {
if (monitor_clipboard_enabled) {
console.debug("Reading clipboard");
let clipboard_promise;
try {
clipboard_promise = navigator.clipboard.readText();
} catch (error) {
// Firefox (at least on Linux and Mac) causes this
showInputError('Failed to read clipboard contents. This is a known problem in Firefox, please try using a different Browser like Chrome, Edge, etc (anything except Internet Explorer). If you only have Firefox, you could enable Clipboard access by typing "about:config" in the URL bar, searching for "dom.events.testing.asyncClipboard" and setting it to true (by double clicking). Afterwards you can reload the page. Please note, that doing this may have severe security implications, since other websites will be able to read your clipboard, which may contain sensitive information such as passwords.');
return;
}
clipboard_promise.then(
clip_text => {
// Only update the text when a different value is copied. This allows the user to change the text without it being reset every second.
if (clip_text && clip_text != last_value_copied) {
input.value = clip_text;
last_value_copied = clip_text;
}
showInputError();
}
).catch(error => {
// This error only occured on Mac with Safari
showInputError(`Failed to read clipboard contents because of the following error: ${error}`);
});
}
};
}
// ======= https://github.com/nimiq/qr-creator, MIT License, Copyright (c) 2017 The Nimiq Foundation =======
// Minified version from https://cdn.jsdelivr.net/npm/qr-creator/dist/qr-creator.min.js
/* jquery-qrcode v0.14.0 - https://larsjung.de/jquery-qrcode/ */
'use strict';let G=null;class H{}H.render=function(w,B){G(w,B)};self.QrCreator=H;
(function(w){function B(t,c,a,e){var b={},h=w(a,c);h.u(t);h.J();e=e||0;var r=h.h(),d=h.h()+2*e;b.text=t;b.level=c;b.version=a;b.O=d;b.a=function(b,a){b-=e;a-=e;return 0>b||b>=r||0>a||a>=r?!1:h.a(b,a)};return b}function C(t,c,a,e,b,h,r,d,g,x){function u(b,a,f,c,d,r,g){b?(t.lineTo(a+r,f+g),t.arcTo(a,f,c,d,h)):t.lineTo(a,f)}r?t.moveTo(c+h,a):t.moveTo(c,a);u(d,e,a,e,b,-h,0);u(g,e,b,c,b,0,-h);u(x,c,b,c,a,h,0);u(r,c,a,e,a,0,h)}function z(t,c,a,e,b,h,r,d,g,x){function u(b,a,c,d){t.moveTo(b+c,a);t.lineTo(b,
a);t.lineTo(b,a+d);t.arcTo(b,a,b+c,a,h)}r&&u(c,a,h,h);d&&u(e,a,-h,h);g&&u(e,b,-h,-h);x&&u(c,b,h,-h)}function A(t,c){var a=c.fill;if("string"===typeof a)t.fillStyle=a;else{var e=a.type,b=a.colorStops;a=a.position.map((b)=>Math.round(b*c.size));if("linear-gradient"===e)var h=t.createLinearGradient.apply(t,a);else if("radial-gradient"===e)h=t.createRadialGradient.apply(t,a);else throw Error("Unsupported fill");b.forEach(([b,a])=>{h.addColorStop(b,a)});t.fillStyle=h}}function y(t,c){a:{var a=c.text,e=
c.v,b=c.N,h=c.K,r=c.P;b=Math.max(1,b||1);for(h=Math.min(40,h||40);b<=h;b+=1)try{var d=B(a,e,b,r);break a}catch(J){}d=void 0}if(!d)return null;a=t.getContext("2d");c.background&&(a.fillStyle=c.background,a.fillRect(c.left,c.top,c.size,c.size));e=d.O;h=c.size/e;a.beginPath();for(r=0;r<e;r+=1)for(b=0;b<e;b+=1){var g=a,x=c.left+b*h,u=c.top+r*h,p=r,q=b,f=d.a,k=x+h,m=u+h,D=p-1,E=p+1,n=q-1,l=q+1,y=Math.floor(Math.min(.5,Math.max(0,c.R))*h),v=f(p,q),I=f(D,n),w=f(D,q);D=f(D,l);var F=f(p,l);l=f(E,l);q=f(E,
q);E=f(E,n);p=f(p,n);x=Math.round(x);u=Math.round(u);k=Math.round(k);m=Math.round(m);v?C(g,x,u,k,m,y,!w&&!p,!w&&!F,!q&&!F,!q&&!p):z(g,x,u,k,m,y,w&&p&&I,w&&F&&D,q&&F&&l,q&&p&&E)}A(a,c);a.fill();return t}var v={minVersion:1,maxVersion:40,ecLevel:"L",left:0,top:0,size:200,fill:"#000",background:null,text:"no text",radius:.5,quiet:0};G=function(t,c){var a={};Object.assign(a,v,t);a.N=a.minVersion;a.K=a.maxVersion;a.v=a.ecLevel;a.left=a.left;a.top=a.top;a.size=a.size;a.fill=a.fill;a.background=a.background;
a.text=a.text;a.R=a.radius;a.P=a.quiet;if(c instanceof HTMLCanvasElement){if(c.width!==a.size||c.height!==a.size)c.width=a.size,c.height=a.size;c.getContext("2d").clearRect(0,0,c.width,c.height);y(c,a)}else t=document.createElement("canvas"),t.width=a.size,t.height=a.size,a=y(t,a),c.appendChild(a)}})(function(){function w(c){var a=C.s(c);return{S:function(){return 4},b:function(){return a.length},write:function(c){for(var b=0;b<a.length;b+=1)c.put(a[b],8)}}}function B(){var c=[],a=0,e={B:function(){return c},
c:function(b){return 1==(c[Math.floor(b/8)]>>>7-b%8&1)},put:function(b,h){for(var a=0;a<h;a+=1)e.m(1==(b>>>h-a-1&1))},f:function(){return a},m:function(b){var h=Math.floor(a/8);c.length<=h&&c.push(0);b&&(c[h]|=128>>>a%8);a+=1}};return e}function C(c,a){function e(b,h){for(var a=-1;7>=a;a+=1)if(!(-1>=b+a||d<=b+a))for(var c=-1;7>=c;c+=1)-1>=h+c||d<=h+c||(r[b+a][h+c]=0<=a&&6>=a&&(0==c||6==c)||0<=c&&6>=c&&(0==a||6==a)||2<=a&&4>=a&&2<=c&&4>=c?!0:!1)}function b(b,a){for(var f=d=4*c+17,k=Array(f),m=0;m<
f;m+=1){k[m]=Array(f);for(var p=0;p<f;p+=1)k[m][p]=null}r=k;e(0,0);e(d-7,0);e(0,d-7);f=y.G(c);for(k=0;k<f.length;k+=1)for(m=0;m<f.length;m+=1){p=f[k];var q=f[m];if(null==r[p][q])for(var n=-2;2>=n;n+=1)for(var l=-2;2>=l;l+=1)r[p+n][q+l]=-2==n||2==n||-2==l||2==l||0==n&&0==l}for(f=8;f<d-8;f+=1)null==r[f][6]&&(r[f][6]=0==f%2);for(f=8;f<d-8;f+=1)null==r[6][f]&&(r[6][f]=0==f%2);f=y.w(h<<3|a);for(k=0;15>k;k+=1)m=!b&&1==(f>>k&1),r[6>k?k:8>k?k+1:d-15+k][8]=m,r[8][8>k?d-k-1:9>k?15-k:14-k]=m;r[d-8][8]=!b;if(7<=
c){f=y.A(c);for(k=0;18>k;k+=1)m=!b&&1==(f>>k&1),r[Math.floor(k/3)][k%3+d-8-3]=m;for(k=0;18>k;k+=1)m=!b&&1==(f>>k&1),r[k%3+d-8-3][Math.floor(k/3)]=m}if(null==g){b=t.I(c,h);f=B();for(k=0;k<x.length;k+=1)m=x[k],f.put(4,4),f.put(m.b(),y.f(4,c)),m.write(f);for(k=m=0;k<b.length;k+=1)m+=b[k].j;if(f.f()>8*m)throw Error("code length overflow. ("+f.f()+">"+8*m+")");for(f.f()+4<=8*m&&f.put(0,4);0!=f.f()%8;)f.m(!1);for(;!(f.f()>=8*m);){f.put(236,8);if(f.f()>=8*m)break;f.put(17,8)}var u=0;m=k=0;p=Array(b.length);
q=Array(b.length);for(n=0;n<b.length;n+=1){var v=b[n].j,w=b[n].o-v;k=Math.max(k,v);m=Math.max(m,w);p[n]=Array(v);for(l=0;l<p[n].length;l+=1)p[n][l]=255&f.B()[l+u];u+=v;l=y.C(w);v=z(p[n],l.b()-1).l(l);q[n]=Array(l.b()-1);for(l=0;l<q[n].length;l+=1)w=l+v.b()-q[n].length,q[n][l]=0<=w?v.c(w):0}for(l=f=0;l<b.length;l+=1)f+=b[l].o;f=Array(f);for(l=u=0;l<k;l+=1)for(n=0;n<b.length;n+=1)l<p[n].length&&(f[u]=p[n][l],u+=1);for(l=0;l<m;l+=1)for(n=0;n<b.length;n+=1)l<q[n].length&&(f[u]=q[n][l],u+=1);g=f}b=g;f=
-1;k=d-1;m=7;p=0;a=y.F(a);for(q=d-1;0<q;q-=2)for(6==q&&--q;;){for(n=0;2>n;n+=1)null==r[k][q-n]&&(l=!1,p<b.length&&(l=1==(b[p]>>>m&1)),a(k,q-n)&&(l=!l),r[k][q-n]=l,--m,-1==m&&(p+=1,m=7));k+=f;if(0>k||d<=k){k-=f;f=-f;break}}}var h=A[a],r=null,d=0,g=null,x=[],u={u:function(b){b=w(b);x.push(b);g=null},a:function(b,a){if(0>b||d<=b||0>a||d<=a)throw Error(b+","+a);return r[b][a]},h:function(){return d},J:function(){for(var a=0,h=0,c=0;8>c;c+=1){b(!0,c);var d=y.D(u);if(0==c||a>d)a=d,h=c}b(!1,h)}};return u}
function z(c,a){if("undefined"==typeof c.length)throw Error(c.length+"/"+a);var e=function(){for(var b=0;b<c.length&&0==c[b];)b+=1;for(var r=Array(c.length-b+a),d=0;d<c.length-b;d+=1)r[d]=c[d+b];return r}(),b={c:function(b){return e[b]},b:function(){return e.length},multiply:function(a){for(var h=Array(b.b()+a.b()-1),c=0;c<b.b();c+=1)for(var g=0;g<a.b();g+=1)h[c+g]^=v.i(v.g(b.c(c))+v.g(a.c(g)));return z(h,0)},l:function(a){if(0>b.b()-a.b())return b;for(var c=v.g(b.c(0))-v.g(a.c(0)),h=Array(b.b()),
g=0;g<b.b();g+=1)h[g]=b.c(g);for(g=0;g<a.b();g+=1)h[g]^=v.i(v.g(a.c(g))+c);return z(h,0).l(a)}};return b}C.s=function(c){for(var a=[],e=0;e<c.length;e++){var b=c.charCodeAt(e);128>b?a.push(b):2048>b?a.push(192|b>>6,128|b&63):55296>b||57344<=b?a.push(224|b>>12,128|b>>6&63,128|b&63):(e++,b=65536+((b&1023)<<10|c.charCodeAt(e)&1023),a.push(240|b>>18,128|b>>12&63,128|b>>6&63,128|b&63))}return a};var A={L:1,M:0,Q:3,H:2},y=function(){function c(b){for(var a=0;0!=b;)a+=1,b>>>=1;return a}var a=[[],[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]],e={w:function(b){for(var a=b<<10;0<=c(a)-c(1335);)a^=1335<<c(a)-c(1335);return(b<<10|a)^21522},A:function(b){for(var a=b<<12;0<=c(a)-c(7973);)a^=7973<<c(a)-c(7973);return b<<12|a},G:function(b){return a[b-1]},F:function(b){switch(b){case 0:return function(b,a){return 0==(b+a)%2};case 1:return function(b){return 0==b%2};case 2:return function(b,a){return 0==a%3};case 3:return function(b,a){return 0==
(b+a)%3};case 4:return function(b,a){return 0==(Math.floor(b/2)+Math.floor(a/3))%2};case 5:return function(b,a){return 0==b*a%2+b*a%3};case 6:return function(b,a){return 0==(b*a%2+b*a%3)%2};case 7:return function(b,a){return 0==(b*a%3+(b+a)%2)%2};default:throw Error("bad maskPattern:"+b);}},C:function(b){for(var a=z([1],0),c=0;c<b;c+=1)a=a.multiply(z([1,v.i(c)],0));return a},f:function(b,a){if(4!=b||1>a||40<a)throw Error("mode: "+b+"; type: "+a);return 10>a?8:16},D:function(b){for(var a=b.h(),c=0,
d=0;d<a;d+=1)for(var g=0;g<a;g+=1){for(var e=0,t=b.a(d,g),p=-1;1>=p;p+=1)if(!(0>d+p||a<=d+p))for(var q=-1;1>=q;q+=1)0>g+q||a<=g+q||(0!=p||0!=q)&&t==b.a(d+p,g+q)&&(e+=1);5<e&&(c+=3+e-5)}for(d=0;d<a-1;d+=1)for(g=0;g<a-1;g+=1)if(e=0,b.a(d,g)&&(e+=1),b.a(d+1,g)&&(e+=1),b.a(d,g+1)&&(e+=1),b.a(d+1,g+1)&&(e+=1),0==e||4==e)c+=3;for(d=0;d<a;d+=1)for(g=0;g<a-6;g+=1)b.a(d,g)&&!b.a(d,g+1)&&b.a(d,g+2)&&b.a(d,g+3)&&b.a(d,g+4)&&!b.a(d,g+5)&&b.a(d,g+6)&&(c+=40);for(g=0;g<a;g+=1)for(d=0;d<a-6;d+=1)b.a(d,g)&&!b.a(d+
1,g)&&b.a(d+2,g)&&b.a(d+3,g)&&b.a(d+4,g)&&!b.a(d+5,g)&&b.a(d+6,g)&&(c+=40);for(g=e=0;g<a;g+=1)for(d=0;d<a;d+=1)b.a(d,g)&&(e+=1);return c+=Math.abs(100*e/a/a-50)/5*10}};return e}(),v=function(){for(var c=Array(256),a=Array(256),e=0;8>e;e+=1)c[e]=1<<e;for(e=8;256>e;e+=1)c[e]=c[e-4]^c[e-5]^c[e-6]^c[e-8];for(e=0;255>e;e+=1)a[c[e]]=e;return{g:function(b){if(1>b)throw Error("glog("+b+")");return a[b]},i:function(b){for(;0>b;)b+=255;for(;256<=b;)b-=255;return c[b]}}}(),t=function(){function c(b,c){switch(c){case A.L:return a[4*
(b-1)];case A.M:return a[4*(b-1)+1];case A.Q:return a[4*(b-1)+2];case A.H:return a[4*(b-1)+3]}}var a=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,
2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12,7,37,13],[5,122,98,1,123,99],[7,73,
45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,
151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],
[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],
[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]],e={I:function(b,a){var e=c(b,a);if("undefined"==
typeof e)throw Error("bad rs block @ typeNumber:"+b+"/errorCorrectLevel:"+a);b=e.length/3;a=[];for(var d=0;d<b;d+=1)for(var g=e[3*d],h=e[3*d+1],t=e[3*d+2],p=0;p<g;p+=1){var q=t,f={};f.o=h;f.j=q;a.push(f)}return a}};return e}();return C}());
</script>
</body>
</html>