Repository navigation
Expand file tree
/
Copy pathindex.html
More file actions
524 lines (466 loc) · 33.1 KB
/
Copy pathindex.html
File metadata and controls
524 lines (466 loc) · 33.1 KB
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
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
<!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 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:;">
<title>Offline QR code generator</title>
<script>
console.log("Offline QR code generator by github.com/six-two - Version 2024-09-14 - Nayuki Engine")
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
// Thanks to @d3xbot for providing the numbers
// ERROR_CORRECTION_LEVEL L – up to 7% damage - MAX_BYTES: 2953
// ERROR_CORRECTION_LEVEL M – up to 15% damage - MAX_BYTES: 2331
// ERROR_CORRECTION_LEVEL Q – up to 25% damage - MAX_BYTES: 1663
// ERROR_CORRECTION_LEVEL H – up to 30% damage - MAX_BYTES: 1273
let ERROR_CORRECTION_LEVEL = localStorage.getItem("ERROR_CORRECTION_LEVEL") || "L";
// 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" style="display: none;">
<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" style="display: none;">
<div id="input-error" class="error"></div>
<textarea id="input" name="input" placeholder="Type the data you want to convert to a QR code here. Alternatively you can try to drag and drop or copy-paste a file in this text box, but not all browsers/operating systems support that." 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 dependent on its contents. It is 2953 bytes or 7089 digits.
⚠️ 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
Here are some examples of what you can do with QR codes:
- Link to a website: https://your-website.com/some/page?with=parameters
- Share email address: mailto:your.name@gmail.com
- Share phone number: tel:+49123456789
- Share WLAN network password: WIFI:T:WPA;S:Your WLAN name;P:Your WLAN password;;
- Send SMS: smsto:+49123456789:Hi John, how are you?
- Send email: mailto:recipient@example.com?subject=Test email&body=This is a test email
- Add second factor to Google Authenticator: otpauth://totp/Issuer Name:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Issuer Name
</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");
let ecc_level = "TODO";
let max_size_digits = "TODO";
let max_size_alpha_numeric = "TODO";
let max_size_byte = "TODO";
let estimated_size_emoji = "TODO";
window.addEventListener("load", () => {
// This needs to be done after loading the library
setEccLevel(ERROR_CORRECTION_LEVEL);
})
const onHashUpdate = () => {
if (location.hash.length > 1) {
let initial_value = location.hash.slice(1); // remove leading '#'
initial_value = decodeURIComponent(initial_value); // URL encoded because of special characters
console.log("Using initial value from URL hash:", initial_value);
input_area.value = initial_value;
}
}
// You can set the initial value of the page via the # after the URL. This enables stuff like links and bookmarks for common values
// we use the hash instead of an URL parameter, since the hash is never sent to a server -> better privacy (only relevant if you use the hosted version)
onHashUpdate();
// If the user changes the hash, we should update the text box with it
window.addEventListener("hashchange", onHashUpdate);
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
const 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.");
return
}
try {
const qr_code_object = qrcodegen.QrCode.encodeText(text, ecc_level);
qrcode.innerHTML = toSvgString(qr_code_object, QR_BORDER_SIZE, "white", "black");
} catch (error) {
suggestions = '<li><a href="javascript:fixByCuttingTextOfAfterQrCodeMaximumSize()">Cut off the input that does not fit into the QR code</a>';
if (/[^A-Z0-9 $%*+.\/:-]+/.test(text)) {
// Contains non alphanum characters
suggestions += '<li><a href="javascript:tryFixByConvertingToAlphaNum()">Convert to alpha numeric: Try to map Unicode to nearest ASCII character, make all letters uppercase and remove all special or unicode characters except for "$%*+-./:"</a>';
}
if (ecc_level != qrcodegen.QrCode.Ecc.LOW) {
// The ecc level is not at the lowest setting
suggestions += `<li><a href="javascript:setEccLevel('L')">Use the lowest error correction level</a>`
}
showQrCodeGenerationError(`Failed to generate the QR code! Your text is ${text.length} characters long. Please try a shorter text and try removing special characters and emojis. Maximum length depends on the contents and error correction level. With the current error correction level you can fit the following number of characters:
<ul>
<li>digits (<i>0123456789</i>) -> ${max_size_digits}
<li>alphanumeric (<i>0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:</i>) -> ${max_size_alpha_numeric}
<li>byte (<i>Any ASCII character</i>) -> ${max_size_byte}
<li>Emoji (<i>Stadard emoji without modifiers like 😀</i>) -> Around ${estimated_size_emoji}
</ul>
<br>
The following functions can try to help:
<ul>
${suggestions}
</ul>
`);
// console.error("Error while generating QR code", error);
}
};
const setEccLevel = (eccLevelString) => {
const ECC_MAP = {
"L": qrcodegen.QrCode.Ecc.LOW,
"M": qrcodegen.QrCode.Ecc.MEDIUM,
"Q": qrcodegen.QrCode.Ecc.QUARTILE,
"H": qrcodegen.QrCode.Ecc.HIGH,
}
try {
ecc_level = ECC_MAP[eccLevelString];
} catch {
console.warn(`Unknown error correction level '${eccLevelString}', defaulting to 'L' (low)`);
ecc_level = qrcodegen.QrCode.Ecc.LOW;
}
max_size_digits = getMaxQrCodeLength("0", ecc_level);
max_size_alpha_numeric = getMaxQrCodeLength("A", ecc_level);
max_size_byte = getMaxQrCodeLength("a", ecc_level);
estimated_size_emoji = getMaxQrCodeLength("😀", ecc_level);
}
const fixByCuttingTextOfAfterQrCodeMaximumSize = () => {
input_area.value = getMaxQrCodeString(input_area.value, ecc_level);
}
const tryFixByConvertingToAlphaNum = () => {
input_area.value = unicodeToAlphanumericOnly(input_area.value);
}
const unicodeToAlphanumericOnly = (string) => {
// Convert all whitespace to spaces
string = string.replaceAll("\t", " ").replaceAll(/\s/g, " ");
// This rewrites the unicode so that ä becomes a + a diacritic mark. Later on we can drop the diacritic. This way we convert it to the nearest unicode character
string = string.normalize("NFD");
// The alphanumeric charset allows only uppercase characters, so we convert all lowercase ones
string = string.toUpperCase();
// Finally we drop all not allowed characters (or replace them with a place holder like '.'?)
string = string.replaceAll(/[^A-Z0-9 $%*+.\/:-]+/g, "");
return string;
}
window.test = unicodeToAlphanumericOnly;
const getMaxQrCodeLength = (character, ecc_level) => {
const string = character.repeat(7089); // From https://en.wikipedia.org/wiki/QR_code#Information_capacity -> maximum numeric only value
return getMaxQrCodeString(string, ecc_level).length;
}
const getMaxQrCodeString = (string_to_try_to_use, ecc_level) => {
try {
// First check whether the whole string fits
qrcodegen.QrCode.encodeText(string_to_try_to_use, ecc_level);
return string_to_try_to_use
} catch {
// If not, binary search for the cutof point
let min = 0;
let max = string_to_try_to_use.length;
while (min < max) {
const middle = Math.ceil((min + max) / 2);
try {
qrcodegen.QrCode.encodeText(string_to_try_to_use.slice(0, middle), ecc_level);
// QR code generation worked -> set minimum to this size
min = middle;
} catch (ex) {
// QR code generation failed -> set maximum to middle - 1
max = middle - 1;
//console.error(ex);
}
console.log(`Max length between ${min} and ${max} for ${string_to_try_to_use.slice(0, 10)}...`);
}
console.log(`Determined maxiumu length ${min} for ${string_to_try_to_use.slice(0, 10)}...`);
return string_to_try_to_use.slice(0, min);
}
}
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/nayuki/QR-Code-generator, Copyright © 2024 Project Nayuki. (MIT License) =======
// Minified version of https://github.com/nayuki/QR-Code-generator/releases/download/v1.8.0/qrcodegen-v1.8.0-es6.js using https://closure-compiler.appspot.com/
'use strict';var qrcodegen;
(function(q){function m(b,a,c){if(0>a||31<a||0!=b>>>a)throw new RangeError("Value out of range");for(--a;0<=a;a--)c.push(b>>>a&1)}function h(b){if(!b)throw Error("Assertion error");}class e{constructor(b,a,c,d){this.version=b;this.errorCorrectionLevel=a;this.modules=[];this.isFunction=[];if(b<e.MIN_VERSION||b>e.MAX_VERSION)throw new RangeError("Version value out of range");if(-1>d||7<d)throw new RangeError("Mask value out of range");this.size=4*b+17;b=[];for(a=0;a<this.size;a++)b.push(!1);for(a=0;a<
this.size;a++)this.modules.push(b.slice()),this.isFunction.push(b.slice());this.drawFunctionPatterns();c=this.addEccAndInterleave(c);this.drawCodewords(c);if(-1==d)for(c=1E9,b=0;8>b;b++)this.applyMask(b),this.drawFormatBits(b),a=this.getPenaltyScore(),a<c&&(d=b,c=a),this.applyMask(b);h(0<=d&&7>=d);this.mask=d;this.applyMask(d);this.drawFormatBits(d);this.isFunction=[]}static encodeText(b,a){b=q.QrSegment.makeSegments(b);return e.encodeSegments(b,a)}static encodeBinary(b,a){b=q.QrSegment.makeBytes(b);
return e.encodeSegments([b],a)}static encodeSegments(b,a,c=1,d=40,g=-1,f=!0){if(!(e.MIN_VERSION<=c&&c<=d&&d<=e.MAX_VERSION)||-1>g||7<g)throw new RangeError("Invalid value");for(;;c++){const p=8*e.getNumDataCodewords(c,a),n=k.getTotalBits(b,c);if(n<=p){d=n;break}if(c>=d)throw new RangeError("Data too long");}for(const p of[e.Ecc.MEDIUM,e.Ecc.QUARTILE,e.Ecc.HIGH])f&&d<=8*e.getNumDataCodewords(c,p)&&(a=p);f=[];for(var l of b){m(l.mode.modeBits,4,f);m(l.numChars,l.mode.numCharCountBits(c),f);for(const p of l.getData())f.push(p)}h(f.length==
d);b=8*e.getNumDataCodewords(c,a);h(f.length<=b);m(0,Math.min(4,b-f.length),f);m(0,(8-f.length%8)%8,f);h(0==f.length%8);for(l=236;f.length<b;l^=253)m(l,8,f);let r=[];for(;8*r.length<f.length;)r.push(0);f.forEach((p,n)=>r[n>>>3]|=p<<7-(n&7));return new e(c,a,r,g)}getModule(b,a){return 0<=b&&b<this.size&&0<=a&&a<this.size&&this.modules[a][b]}drawFunctionPatterns(){for(var b=0;b<this.size;b++)this.setFunctionModule(6,b,0==b%2),this.setFunctionModule(b,6,0==b%2);this.drawFinderPattern(3,3);this.drawFinderPattern(this.size-
4,3);this.drawFinderPattern(3,this.size-4);b=this.getAlignmentPatternPositions();const a=b.length;for(let c=0;c<a;c++)for(let d=0;d<a;d++)0==c&&0==d||0==c&&d==a-1||c==a-1&&0==d||this.drawAlignmentPattern(b[c],b[d]);this.drawFormatBits(0);this.drawVersion()}drawFormatBits(b){var a=b|=this.errorCorrectionLevel.formatBits<<3;for(let c=0;10>c;c++)a=a<<1^1335*(a>>>9);b=(b<<10|a)^21522;h(0==b>>>15);for(a=0;5>=a;a++)this.setFunctionModule(8,a,0!=(b>>>a&1));this.setFunctionModule(8,7,0!=(b>>>6&1));this.setFunctionModule(8,
8,0!=(b>>>7&1));this.setFunctionModule(7,8,0!=(b>>>8&1));for(a=9;15>a;a++)this.setFunctionModule(14-a,8,0!=(b>>>a&1));for(a=0;8>a;a++)this.setFunctionModule(this.size-1-a,8,0!=(b>>>a&1));for(a=8;15>a;a++)this.setFunctionModule(8,this.size-15+a,0!=(b>>>a&1));this.setFunctionModule(8,this.size-8,!0)}drawVersion(){if(!(7>this.version)){var b=this.version;for(var a=0;12>a;a++)b=b<<1^7973*(b>>>11);b|=this.version<<12;h(0==b>>>18);for(a=0;18>a;a++){const c=0!=(b>>>a&1),d=this.size-11+a%3,g=Math.floor(a/
3);this.setFunctionModule(d,g,c);this.setFunctionModule(g,d,c)}}}drawFinderPattern(b,a){for(let c=-4;4>=c;c++)for(let d=-4;4>=d;d++){const g=Math.max(Math.abs(d),Math.abs(c)),f=b+d,l=a+c;0<=f&&f<this.size&&0<=l&&l<this.size&&this.setFunctionModule(f,l,2!=g&&4!=g)}}drawAlignmentPattern(b,a){for(let c=-2;2>=c;c++)for(let d=-2;2>=d;d++)this.setFunctionModule(b+d,a+c,1!=Math.max(Math.abs(d),Math.abs(c)))}setFunctionModule(b,a,c){this.modules[a][b]=c;this.isFunction[a][b]=!0}addEccAndInterleave(b){var a=
this.version,c=this.errorCorrectionLevel;if(b.length!=e.getNumDataCodewords(a,c))throw new RangeError("Invalid argument");const d=e.NUM_ERROR_CORRECTION_BLOCKS[c.ordinal][a],g=e.ECC_CODEWORDS_PER_BLOCK[c.ordinal][a];a=Math.floor(e.getNumRawDataModules(a)/8);const f=d-a%d,l=Math.floor(a/d);c=[];const r=e.reedSolomonComputeDivisor(g);for(let n=0,u=0;n<d;n++){let t=b.slice(u,u+l-g+(n<f?0:1));u+=t.length;const v=e.reedSolomonComputeRemainder(t,r);n<f&&t.push(0);c.push(t.concat(v))}let p=[];for(let n=
0;n<c[0].length;n++)c.forEach((u,t)=>{(n!=l-g||t>=f)&&p.push(u[n])});h(p.length==a);return p}drawCodewords(b){if(b.length!=Math.floor(e.getNumRawDataModules(this.version)/8))throw new RangeError("Invalid argument");let a=0;for(let c=this.size-1;1<=c;c-=2){6==c&&(c=5);for(let d=0;d<this.size;d++)for(let g=0;2>g;g++){const f=c-g,l=0==(c+1&2)?this.size-1-d:d;!this.isFunction[l][f]&&a<8*b.length&&(this.modules[l][f]=0!=(b[a>>>3]>>>7-(a&7)&1),a++)}}h(a==8*b.length)}applyMask(b){if(0>b||7<b)throw new RangeError("Mask value out of range");
for(let a=0;a<this.size;a++)for(let c=0;c<this.size;c++){let d;switch(b){case 0:d=0==(c+a)%2;break;case 1:d=0==a%2;break;case 2:d=0==c%3;break;case 3:d=0==(c+a)%3;break;case 4:d=0==(Math.floor(c/3)+Math.floor(a/2))%2;break;case 5:d=0==c*a%2+c*a%3;break;case 6:d=0==(c*a%2+c*a%3)%2;break;case 7:d=0==((c+a)%2+c*a%3)%2;break;default:throw Error("Unreachable");}!this.isFunction[a][c]&&d&&(this.modules[a][c]=!this.modules[a][c])}}getPenaltyScore(){let b=0;for(var a=0;a<this.size;a++){var c=!1,d=0,g=[0,
0,0,0,0,0,0];for(var f=0;f<this.size;f++)this.modules[a][f]==c?(d++,5==d?b+=e.PENALTY_N1:5<d&&b++):(this.finderPenaltyAddHistory(d,g),c||(b+=this.finderPenaltyCountPatterns(g)*e.PENALTY_N3),c=this.modules[a][f],d=1);b+=this.finderPenaltyTerminateAndCount(c,d,g)*e.PENALTY_N3}for(a=0;a<this.size;a++){c=!1;d=0;g=[0,0,0,0,0,0,0];for(f=0;f<this.size;f++)this.modules[f][a]==c?(d++,5==d?b+=e.PENALTY_N1:5<d&&b++):(this.finderPenaltyAddHistory(d,g),c||(b+=this.finderPenaltyCountPatterns(g)*e.PENALTY_N3),c=
this.modules[f][a],d=1);b+=this.finderPenaltyTerminateAndCount(c,d,g)*e.PENALTY_N3}for(a=0;a<this.size-1;a++)for(c=0;c<this.size-1;c++)d=this.modules[a][c],d==this.modules[a][c+1]&&d==this.modules[a+1][c]&&d==this.modules[a+1][c+1]&&(b+=e.PENALTY_N2);a=0;for(var l of this.modules)a=l.reduce((r,p)=>r+(p?1:0),a);l=this.size*this.size;l=Math.ceil(Math.abs(20*a-10*l)/l)-1;h(0<=l&&9>=l);b+=l*e.PENALTY_N4;h(0<=b&&2568888>=b);return b}getAlignmentPatternPositions(){if(1==this.version)return[];const b=Math.floor(this.version/
7)+2,a=32==this.version?26:2*Math.ceil((4*this.version+4)/(2*b-2));let c=[6];for(let d=this.size-7;c.length<b;d-=a)c.splice(1,0,d);return c}static getNumRawDataModules(b){if(b<e.MIN_VERSION||b>e.MAX_VERSION)throw new RangeError("Version number out of range");let a=(16*b+128)*b+64;if(2<=b){const c=Math.floor(b/7)+2;a-=(25*c-10)*c-55;7<=b&&(a-=36)}h(208<=a&&29648>=a);return a}static getNumDataCodewords(b,a){return Math.floor(e.getNumRawDataModules(b)/8)-e.ECC_CODEWORDS_PER_BLOCK[a.ordinal][b]*e.NUM_ERROR_CORRECTION_BLOCKS[a.ordinal][b]}static reedSolomonComputeDivisor(b){if(1>
b||255<b)throw new RangeError("Degree out of range");let a=[];for(var c=0;c<b-1;c++)a.push(0);a.push(1);c=1;for(let d=0;d<b;d++){for(let g=0;g<a.length;g++)a[g]=e.reedSolomonMultiply(a[g],c),g+1<a.length&&(a[g]^=a[g+1]);c=e.reedSolomonMultiply(c,2)}return a}static reedSolomonComputeRemainder(b,a){let c=a.map(d=>0);for(const d of b){const g=d^c.shift();c.push(0);a.forEach((f,l)=>c[l]^=e.reedSolomonMultiply(f,g))}return c}static reedSolomonMultiply(b,a){if(0!=b>>>8||0!=a>>>8)throw new RangeError("Byte out of range");
let c=0;for(let d=7;0<=d;d--)c=c<<1^285*(c>>>7),c^=(a>>>d&1)*b;h(0==c>>>8);return c}finderPenaltyCountPatterns(b){const a=b[1];h(a<=3*this.size);const c=0<a&&b[2]==a&&b[3]==3*a&&b[4]==a&&b[5]==a;return(c&&b[0]>=4*a&&b[6]>=a?1:0)+(c&&b[6]>=4*a&&b[0]>=a?1:0)}finderPenaltyTerminateAndCount(b,a,c){b&&(this.finderPenaltyAddHistory(a,c),a=0);a+=this.size;this.finderPenaltyAddHistory(a,c);return this.finderPenaltyCountPatterns(c)}finderPenaltyAddHistory(b,a){0==a[0]&&(b+=this.size);a.pop();a.unshift(b)}}
e.MIN_VERSION=1;e.MAX_VERSION=40;e.PENALTY_N1=3;e.PENALTY_N2=3;e.PENALTY_N3=40;e.PENALTY_N4=10;e.ECC_CODEWORDS_PER_BLOCK=[[-1,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],[-1,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],[-1,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],[-1,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]];e.NUM_ERROR_CORRECTION_BLOCKS=[[-1,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],[-1,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],[-1,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],[-1,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]];q.QrCode=e;class k{constructor(b,a,c){this.mode=b;this.numChars=a;this.bitData=c;if(0>a)throw new RangeError("Invalid argument");this.bitData=c.slice()}static makeBytes(b){let a=[];for(const c of b)m(c,8,a);return new k(k.Mode.BYTE,b.length,a)}static makeNumeric(b){if(!k.isNumeric(b))throw new RangeError("String contains non-numeric characters");let a=[];for(let c=0;c<b.length;){const d=Math.min(b.length-c,
3);m(parseInt(b.substr(c,d),10),3*d+1,a);c+=d}return new k(k.Mode.NUMERIC,b.length,a)}static makeAlphanumeric(b){if(!k.isAlphanumeric(b))throw new RangeError("String contains unencodable characters in alphanumeric mode");let a=[],c;for(c=0;c+2<=b.length;c+=2){let d=45*k.ALPHANUMERIC_CHARSET.indexOf(b.charAt(c));d+=k.ALPHANUMERIC_CHARSET.indexOf(b.charAt(c+1));m(d,11,a)}c<b.length&&m(k.ALPHANUMERIC_CHARSET.indexOf(b.charAt(c)),6,a);return new k(k.Mode.ALPHANUMERIC,b.length,a)}static makeSegments(b){return""==
b?[]:k.isNumeric(b)?[k.makeNumeric(b)]:k.isAlphanumeric(b)?[k.makeAlphanumeric(b)]:[k.makeBytes(k.toUtf8ByteArray(b))]}static makeEci(b){let a=[];if(0>b)throw new RangeError("ECI assignment value out of range");if(128>b)m(b,8,a);else if(16384>b)m(2,2,a),m(b,14,a);else if(1E6>b)m(6,3,a),m(b,21,a);else throw new RangeError("ECI assignment value out of range");return new k(k.Mode.ECI,0,a)}static isNumeric(b){return k.NUMERIC_REGEX.test(b)}static isAlphanumeric(b){return k.ALPHANUMERIC_REGEX.test(b)}getData(){return this.bitData.slice()}static getTotalBits(b,
a){let c=0;for(const d of b){b=d.mode.numCharCountBits(a);if(d.numChars>=1<<b)return Infinity;c+=4+b+d.bitData.length}return c}static toUtf8ByteArray(b){b=encodeURI(b);let a=[];for(let c=0;c<b.length;c++)"%"!=b.charAt(c)?a.push(b.charCodeAt(c)):(a.push(parseInt(b.substr(c+1,2),16)),c+=2);return a}}k.NUMERIC_REGEX=/^[0-9]*$/;k.ALPHANUMERIC_REGEX=/^[A-Z0-9 $%*+.\/:-]*$/;k.ALPHANUMERIC_CHARSET="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:";q.QrSegment=k})(qrcodegen||={});
(function(q){(function(m){class h{constructor(e,k){this.ordinal=e;this.formatBits=k}}h.LOW=new h(0,1);h.MEDIUM=new h(1,0);h.QUARTILE=new h(2,3);h.HIGH=new h(3,2);m.Ecc=h})(q.QrCode||(q.QrCode={}))})(qrcodegen||={});
(function(q){(function(m){class h{constructor(e,k){this.modeBits=e;this.numBitsCharCount=k}numCharCountBits(e){return this.numBitsCharCount[Math.floor((e+7)/17)]}}h.NUMERIC=new h(1,[10,12,14]);h.ALPHANUMERIC=new h(2,[9,11,13]);h.BYTE=new h(4,[8,16,16]);h.KANJI=new h(8,[8,10,12]);h.ECI=new h(7,[0,0,0]);m.Mode=h})(q.QrSegment||(q.QrSegment={}))})(qrcodegen||={});
// Returns a string of SVG code for an image depicting the given QR Code, with the given number
// of border modules. The string always uses Unix newlines (\n), regardless of the platform.
function toSvgString(qr, border, lightColor, darkColor) {
if (border < 0)
throw new RangeError("Border must be non-negative");
let parts = [];
for (let y = 0; y < qr.size; y++) {
for (let x = 0; x < qr.size; x++) {
if (qr.getModule(x, y))
parts.push(`M${x + border},${y + border}h1v1h-1z`);
}
}
return `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 ${qr.size + border * 2} ${qr.size + border * 2}" stroke="none">
<rect width="100%" height="100%" fill="${lightColor}"/>
<path d="${parts.join(" ")}" fill="${darkColor}"/>
</svg>
`;
}
</script>
</body>
</html>