CyberMax commited on
Commit
71993f0
·
verified ·
1 Parent(s): 08e465b

Deploy utm-builder to the hub

Browse files
utm-builder/app.js ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* UTM Builder UI. © 2026 CyberMax. All rights reserved. */
2
+ (function () {
3
+ "use strict";
4
+ var U = window.UtmCore;
5
+ var el = function (id) { return document.getElementById(id); };
6
+ var KEY = "cybermax-utm:v1:";
7
+ function load(k, d) { try { var v = localStorage.getItem(KEY + k); return v ? JSON.parse(v) : d; } catch (e) { return d; } }
8
+ function save(k, v) { try { localStorage.setItem(KEY + k, JSON.stringify(v)); } catch (e) { /* ignore */ } }
9
+ function esc(s) { return String(s).replace(/[&<>"]/g, function (c) { return { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[c]; }); }
10
+ var toastT;
11
+ function toast(m) { var t = el("toast"); t.textContent = m; t.classList.add("show"); clearTimeout(toastT); toastT = setTimeout(function () { t.classList.remove("show"); }, 1600); }
12
+ function copy(text, msg) {
13
+ var done = function () { toast(msg || "Copied"); };
14
+ if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(text).then(done, function () { fallback(text); done(); });
15
+ else { fallback(text); done(); }
16
+ }
17
+ function fallback(text) { var ta = document.createElement("textarea"); ta.value = text; document.body.appendChild(ta); ta.select(); try { document.execCommand("copy"); } catch (e) { /* ignore */ } ta.remove(); }
18
+ function download(name, text, type) {
19
+ var a = document.createElement("a");
20
+ a.href = URL.createObjectURL(new Blob([text], { type: type }));
21
+ a.download = name; document.body.appendChild(a); a.click(); a.remove();
22
+ setTimeout(function () { URL.revokeObjectURL(a.href); }, 1000);
23
+ }
24
+
25
+ // ---------- tabs ----------
26
+ function showTab(name) {
27
+ document.querySelectorAll(".tab").forEach(function (t) { t.setAttribute("aria-selected", String(t.dataset.tab === name)); });
28
+ document.querySelectorAll(".panel").forEach(function (p) { p.hidden = p.id !== "panel-" + name; });
29
+ if (name === "history") renderHistory();
30
+ }
31
+ document.querySelectorAll(".tab").forEach(function (t) { t.onclick = function () { showTab(t.dataset.tab); }; });
32
+
33
+ // ---------- builder ----------
34
+ var fields = ["url"].concat(U.KEYS);
35
+ function params() { var p = {}; U.KEYS.forEach(function (k) { p[k] = el("f-" + k).value; }); return p; }
36
+ function opts() { return { lower: el("o-lower").checked, space: el("o-space").value }; }
37
+ var current = "";
38
+
39
+ var presets = el("presets");
40
+ Object.keys(U.PRESETS).forEach(function (name) {
41
+ var b = document.createElement("button");
42
+ b.type = "button"; b.className = "chip"; b.textContent = name;
43
+ b.onclick = function () {
44
+ var p = U.PRESETS[name];
45
+ for (var k in p) el("f-" + k).value = p[k];
46
+ presets.querySelectorAll(".chip").forEach(function (c) { c.classList.toggle("on", c === b); });
47
+ update();
48
+ };
49
+ presets.appendChild(b);
50
+ });
51
+
52
+ function update() {
53
+ var r = U.build(el("f-url").value, params(), opts());
54
+ var out = el("out");
55
+ if (r.error || !r.tagged) {
56
+ current = "";
57
+ out.textContent = el("f-url").value.trim() ? (r.error || "Add a source, medium and campaign.") : "Fill in the URL, source, medium and campaign.";
58
+ } else {
59
+ current = r.url;
60
+ out.textContent = r.url;
61
+ }
62
+ el("warn").innerHTML = (r.warnings || []).filter(function () { return !!current; }).map(function (w) { return "⚠ " + esc(w); }).join("<br>");
63
+ el("btn-copy").disabled = !current;
64
+ el("btn-qr").disabled = !current;
65
+ var open = el("btn-open");
66
+ if (current) { open.href = current; open.removeAttribute("aria-disabled"); } else { open.removeAttribute("href"); open.setAttribute("aria-disabled", "true"); }
67
+ drawQr(current);
68
+ var state = {}; fields.forEach(function (f) { state[f] = el("f-" + f).value; });
69
+ state.lower = el("o-lower").checked; state.space = el("o-space").value;
70
+ save("form", state);
71
+ }
72
+
73
+ function drawQr(text) {
74
+ var c = el("qr"), ctx = c.getContext("2d");
75
+ ctx.fillStyle = "#fff"; ctx.fillRect(0, 0, c.width, c.height);
76
+ if (!text) {
77
+ ctx.fillStyle = "#9aa4b6"; ctx.font = "14px system-ui, sans-serif"; ctx.textAlign = "center";
78
+ ctx.fillText("QR code appears here", c.width / 2, c.height / 2);
79
+ return;
80
+ }
81
+ try {
82
+ var qr = window.qrcode(0, "M");
83
+ qr.addData(text); qr.make();
84
+ var n = qr.getModuleCount(), quiet = 4, size = Math.floor(c.width / (n + quiet * 2));
85
+ var off = Math.floor((c.width - size * n) / 2);
86
+ ctx.fillStyle = "#000";
87
+ for (var r = 0; r < n; r++) for (var col = 0; col < n; col++) if (qr.isDark(r, col)) ctx.fillRect(off + col * size, off + r * size, size, size);
88
+ } catch (e) {
89
+ ctx.fillStyle = "#b25e00"; ctx.font = "13px system-ui, sans-serif"; ctx.textAlign = "center";
90
+ ctx.fillText("Link too long for a QR code", c.width / 2, c.height / 2);
91
+ }
92
+ }
93
+ function qrPng() {
94
+ // Re-render at 1024px for print quality.
95
+ var qr = window.qrcode(0, "M"); qr.addData(current); qr.make();
96
+ var n = qr.getModuleCount(), cell = Math.floor(1024 / (n + 8)), size = cell * (n + 8);
97
+ var c = document.createElement("canvas"); c.width = c.height = size;
98
+ var ctx = c.getContext("2d"); ctx.fillStyle = "#fff"; ctx.fillRect(0, 0, size, size); ctx.fillStyle = "#000";
99
+ for (var r = 0; r < n; r++) for (var col = 0; col < n; col++) if (qr.isDark(r, col)) ctx.fillRect((col + 4) * cell, (r + 4) * cell, cell, cell);
100
+ var a = document.createElement("a"); a.href = c.toDataURL("image/png");
101
+ var camp = (el("f-utm_campaign").value || "link").toLowerCase().replace(/[^a-z0-9_-]+/g, "-");
102
+ a.download = "qr-" + camp + ".png"; document.body.appendChild(a); a.click(); a.remove();
103
+ }
104
+
105
+ fields.forEach(function (f) { el("f-" + f).addEventListener("input", update); });
106
+ el("o-lower").addEventListener("change", update);
107
+ el("o-space").addEventListener("change", update);
108
+ el("btn-copy").onclick = function () { if (!current) return; copy(current, "Link copied"); addHistory(current); };
109
+ el("btn-qr").onclick = function () { if (current) qrPng(); };
110
+ el("btn-reset").onclick = function () { fields.forEach(function (f) { el("f-" + f).value = ""; }); presets.querySelectorAll(".chip").forEach(function (c) { c.classList.remove("on"); }); update(); el("f-url").focus(); };
111
+
112
+ // ---------- bulk ----------
113
+ var bulkRows = [];
114
+ el("btn-bulk").onclick = function () {
115
+ bulkRows = U.bulk(el("bulk-in").value, params(), opts());
116
+ var tb = el("bulk-table").querySelector("tbody");
117
+ tb.innerHTML = bulkRows.map(function (r, i) {
118
+ return "<tr><td class='num'>" + (i + 1) + "</td><td style='word-break:break-all'>" + esc(r.url || r.input) + "</td><td>" +
119
+ (r.error ? "<span style='color:var(--warn)'>" + esc(r.error) + "</span>" : esc(r.warnings.filter(function (w) { return !/^Added https/.test(w); }).slice(0, 2).join(" "))) + "</td></tr>";
120
+ }).join("");
121
+ el("bulk-table").hidden = !bulkRows.length;
122
+ var okN = bulkRows.filter(function (r) { return !r.error; }).length;
123
+ el("btn-bulk-copy").disabled = el("btn-bulk-csv").disabled = !okN;
124
+ toast(bulkRows.length ? okN + " of " + bulkRows.length + " links built" : "Paste some URLs first");
125
+ };
126
+ el("btn-bulk-copy").onclick = function () { copy(bulkRows.filter(function (r) { return !r.error; }).map(function (r) { return r.url; }).join("\n"), "All links copied"); };
127
+ el("btn-bulk-csv").onclick = function () { download("utm-links.csv", U.toCsv(bulkRows), "text/csv"); };
128
+
129
+ // ---------- decode ----------
130
+ var decoded = null;
131
+ el("dec-in").addEventListener("input", function () {
132
+ var v = el("dec-in").value.trim(), tb = el("dec-table").querySelector("tbody");
133
+ decoded = v ? U.parse(v) : null;
134
+ if (!decoded || decoded.error) { tb.innerHTML = v ? "<tr><td>" + esc(decoded.error) + "</td></tr>" : ""; el("dec-clean").textContent = ""; return; }
135
+ var keys = Object.keys(decoded.utm);
136
+ tb.innerHTML = keys.length ? keys.map(function (k) { return "<tr><th>" + esc(k) + "</th><td>" + esc(decoded.utm[k]) + "</td></tr>"; }).join("")
137
+ : "<tr><td>No UTM parameters in this link.</td></tr>";
138
+ el("dec-clean").textContent = decoded.clean;
139
+ });
140
+ el("btn-dec-edit").onclick = function () {
141
+ if (!decoded || decoded.error) return;
142
+ el("f-url").value = decoded.clean;
143
+ U.KEYS.forEach(function (k) { el("f-" + k).value = decoded.utm[k] || ""; });
144
+ showTab("single"); update();
145
+ };
146
+
147
+ // ---------- history ----------
148
+ function addHistory(url) {
149
+ var h = load("history", []).filter(function (x) { return x.url !== url; });
150
+ h.unshift({ url: url, at: new Date().toISOString().slice(0, 16).replace("T", " ") });
151
+ save("history", h.slice(0, 25));
152
+ }
153
+ function renderHistory() {
154
+ var h = load("history", []), tb = el("hist-table").querySelector("tbody");
155
+ tb.innerHTML = h.length ? h.map(function (x, i) {
156
+ return "<tr><td style='white-space:nowrap;color:var(--muted)'>" + esc(x.at) + "</td><td style='word-break:break-all'>" + esc(x.url) + "</td><td><button class='chip' data-i='" + i + "'>Copy</button></td></tr>";
157
+ }).join("") : "<tr><td>Links you copy will appear here.</td></tr>";
158
+ tb.querySelectorAll("button").forEach(function (b) { b.onclick = function () { copy(h[+b.dataset.i].url, "Link copied"); }; });
159
+ }
160
+ el("btn-hist-clear").onclick = function () { save("history", []); renderHistory(); };
161
+
162
+ // ---------- restore ----------
163
+ var st = load("form", null);
164
+ if (st) {
165
+ fields.forEach(function (f) { if (st[f]) el("f-" + f).value = st[f]; });
166
+ el("o-lower").checked = st.lower !== false;
167
+ if (st.space) el("o-space").value = st.space;
168
+ }
169
+ update();
170
+ window.UtmApp = { update: update, current: function () { return current; } };
171
+ if ("serviceWorker" in navigator && location.protocol === "https:") navigator.serviceWorker.register("sw.js").catch(function () {});
172
+ })();
utm-builder/icon-192.png ADDED
utm-builder/icon-512.png ADDED
utm-builder/icon.svg ADDED
utm-builder/index.html ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <title>Free UTM Builder — Bulk Campaign URL Builder + QR Code</title>
7
+ <meta name="description" content="Free UTM builder: tag campaign URLs for Google Analytics 4, build hundreds at once from a list or CSV, make a QR code, and decode tagged links. No sign-up.">
8
+ <meta name="keywords" content="utm builder, utm generator, campaign url builder, bulk utm builder, utm qr code generator, google analytics utm, ga4 utm parameters">
9
+ <meta name="theme-color" content="#3056d3">
10
+ <link rel="canonical" href="https://shaw276-cybermax.static.hf.space/utm-builder/">
11
+ <link rel="manifest" href="manifest.webmanifest">
12
+ <link rel="icon" href="icon.svg" type="image/svg+xml">
13
+ <link rel="apple-touch-icon" href="icon-192.png">
14
+ <meta property="og:type" content="website">
15
+ <meta property="og:title" content="Free UTM Builder — bulk links + QR codes">
16
+ <meta property="og:description" content="Tag one link or a thousand for GA4, with QR codes and a link decoder. Free, private, no sign-up.">
17
+ <meta property="og:url" content="https://shaw276-cybermax.static.hf.space/utm-builder/">
18
+ <meta property="og:image" content="https://shaw276-cybermax.static.hf.space/utm-builder/og.png">
19
+ <meta name="twitter:card" content="summary_large_image">
20
+ <script type="application/ld+json">
21
+ {"@context":"https://schema.org","@graph":[
22
+ {"@type":"WebApplication","name":"CyberMax UTM Builder","url":"https://shaw276-cybermax.static.hf.space/utm-builder/","applicationCategory":"BusinessApplication","operatingSystem":"Any","description":"Free UTM campaign URL builder with bulk mode, QR codes and a UTM decoder.","offers":{"@type":"Offer","price":"0","priceCurrency":"USD"},"publisher":{"@type":"Organization","name":"CyberMax"}},
23
+ {"@type":"FAQPage","mainEntity":[
24
+ {"@type":"Question","name":"What are UTM parameters?","acceptedAnswer":{"@type":"Answer","text":"UTM parameters are tags added to a link (utm_source, utm_medium, utm_campaign, utm_term, utm_content, utm_id) that tell analytics tools like Google Analytics 4 where a visit came from."}},
25
+ {"@type":"Question","name":"Which UTM parameters are required?","acceptedAnswer":{"@type":"Answer","text":"Google Analytics only needs utm_source, but you should always set utm_source, utm_medium and utm_campaign so GA4 can assign the visit to the right channel and campaign."}},
26
+ {"@type":"Question","name":"Should UTM values be lowercase?","acceptedAnswer":{"@type":"Answer","text":"Yes. UTM values are case-sensitive, so Facebook and facebook show up as two different sources. This builder lowercases values by default."}},
27
+ {"@type":"Question","name":"Is my data sent anywhere?","acceptedAnswer":{"@type":"Answer","text":"No. Every link is built in your browser. Nothing is uploaded or stored on a server."}}]}
28
+ ]}
29
+ </script>
30
+ <link rel="stylesheet" href="style.css">
31
+ </head>
32
+ <body>
33
+ <header class="top"><div class="wrap">
34
+ <div class="brand"><span class="dot">?u</span> UTM Builder <small>by CyberMax</small></div>
35
+ <a class="btn ghost" href="#faq">FAQ</a>
36
+ </div></header>
37
+
38
+ <main class="wrap">
39
+ <h1>Free UTM Builder: campaign URLs, bulk tagging and QR codes</h1>
40
+ <p class="lede">Tag links for Google Analytics 4 in seconds. Build one link or hundreds at once, catch mistakes that break your reports, and download a QR code. Everything runs in your browser.</p>
41
+
42
+ <div class="tabs" role="tablist">
43
+ <button class="tab" role="tab" aria-selected="true" data-tab="single" id="tab-single">Builder</button>
44
+ <button class="tab" role="tab" aria-selected="false" data-tab="bulk" id="tab-bulk">Bulk</button>
45
+ <button class="tab" role="tab" aria-selected="false" data-tab="decode" id="tab-decode">Decode a link</button>
46
+ <button class="tab" role="tab" aria-selected="false" data-tab="history" id="tab-history">History</button>
47
+ </div>
48
+
49
+ <section class="panel" id="panel-single" role="tabpanel">
50
+ <div class="grid">
51
+ <div class="card">
52
+ <h2>1. Your link and campaign</h2>
53
+ <label for="f-url">Website URL</label>
54
+ <input type="url" id="f-url" placeholder="https://example.com/landing-page" autocomplete="url" inputmode="url">
55
+ <label>Quick presets</label>
56
+ <div class="chips" id="presets"></div>
57
+ <div class="row2">
58
+ <div><label for="f-utm_source">Source <span class="opt">utm_source</span></label><input type="text" id="f-utm_source" placeholder="google, newsletter"></div>
59
+ <div><label for="f-utm_medium">Medium <span class="opt">utm_medium</span></label><input type="text" id="f-utm_medium" placeholder="cpc, email, social" list="mediums"></div>
60
+ </div>
61
+ <datalist id="mediums"><option value="cpc"><option value="email"><option value="social"><option value="paid_social"><option value="display"><option value="affiliate"><option value="referral"><option value="video"><option value="sms"><option value="print"></datalist>
62
+ <label for="f-utm_campaign">Campaign name <span class="opt">utm_campaign</span></label>
63
+ <input type="text" id="f-utm_campaign" placeholder="spring_sale_2026">
64
+ <div class="row3">
65
+ <div><label for="f-utm_id">Campaign ID <span class="opt">optional</span></label><input type="text" id="f-utm_id" placeholder="abc.123"></div>
66
+ <div><label for="f-utm_term">Term <span class="opt">optional</span></label><input type="text" id="f-utm_term" placeholder="running shoes"></div>
67
+ <div><label for="f-utm_content">Content <span class="opt">optional</span></label><input type="text" id="f-utm_content" placeholder="hero_button"></div>
68
+ </div>
69
+ <label class="check"><input type="checkbox" id="o-lower" checked> Force lowercase (recommended)</label>
70
+ <label for="o-space">Replace spaces with</label>
71
+ <select id="o-space"><option value="-">Hyphen (spring-sale)</option><option value="_">Underscore (spring_sale)</option><option value="keep">Keep spaces (%20)</option></select>
72
+ </div>
73
+ <div class="card">
74
+ <h2>2. Your tagged link</h2>
75
+ <div class="out" id="out" aria-live="polite">Fill in the URL, source, medium and campaign.</div>
76
+ <div class="warn" id="warn"></div>
77
+ <div class="btns">
78
+ <button class="btn" id="btn-copy" disabled>Copy link</button>
79
+ <a class="btn ghost" id="btn-open" target="_blank" rel="noopener" aria-disabled="true">Test link ↗</a>
80
+ <button class="btn ghost" id="btn-reset">Clear</button>
81
+ </div>
82
+ <h2 style="margin-top:18px">QR code</h2>
83
+ <canvas id="qr" width="220" height="220" aria-label="QR code for the tagged link" style="background:#fff;border-radius:10px;border:1px solid var(--line);max-width:100%"></canvas>
84
+ <div class="btns"><button class="btn ghost" id="btn-qr" disabled>Download QR (PNG)</button></div>
85
+ <p class="hint">Tip: for printed QR codes use source = where it's printed (poster, flyer) and medium = print.</p>
86
+ </div>
87
+ </div>
88
+ </section>
89
+
90
+ <section class="panel" id="panel-bulk" role="tabpanel" hidden>
91
+ <div class="card">
92
+ <h2>Bulk UTM builder</h2>
93
+ <p class="hint">One URL per line, or CSV columns <code>url,source,medium,campaign,term,content</code> (a header row is optional). Blank columns use the values from the Builder tab. Up to 5,000 rows.</p>
94
+ <textarea id="bulk-in" placeholder="https://example.com/pricing&#10;https://example.com/blog/post,linkedin,social,launch_week&#10;example.com/docs,newsletter,email,october_digest,,footer_link"></textarea>
95
+ <div class="btns">
96
+ <button class="btn" id="btn-bulk">Build links</button>
97
+ <button class="btn ghost" id="btn-bulk-copy" disabled>Copy all</button>
98
+ <button class="btn ghost" id="btn-bulk-csv" disabled>Download CSV</button>
99
+ </div>
100
+ <div class="table-wrap" style="margin-top:12px"><table id="bulk-table" hidden><thead><tr><th>#</th><th>Tagged URL</th><th>Notes</th></tr></thead><tbody></tbody></table></div>
101
+ </div>
102
+ </section>
103
+
104
+ <section class="panel" id="panel-decode" role="tabpanel" hidden>
105
+ <div class="card">
106
+ <h2>Decode a tagged link</h2>
107
+ <label for="dec-in">Paste any URL</label>
108
+ <input type="url" id="dec-in" placeholder="https://example.com/?utm_source=google&utm_medium=cpc&utm_campaign=brand">
109
+ <div class="table-wrap" style="margin-top:12px"><table id="dec-table"><tbody></tbody></table></div>
110
+ <label>Clean URL (UTM tags removed)</label>
111
+ <div class="out" id="dec-clean"></div>
112
+ <div class="btns"><button class="btn ghost" id="btn-dec-edit">Edit in builder</button></div>
113
+ </div>
114
+ </section>
115
+
116
+ <section class="panel" id="panel-history" role="tabpanel" hidden>
117
+ <div class="card">
118
+ <h2>Recent links</h2>
119
+ <p class="hint">Your last 25 copied links, saved only in this browser.</p>
120
+ <div class="table-wrap"><table id="hist-table"><tbody></tbody></table></div>
121
+ <div class="btns"><button class="btn ghost" id="btn-hist-clear">Clear history</button></div>
122
+ </div>
123
+ </section>
124
+
125
+ <aside class="related">
126
+ <h2>Plan the campaign, not just the link</h2>
127
+ <p>Pick <b>utm_term</b> and ad keywords from real searches with <a href="https://apify.com/cybermax/keyword-ideas" target="_blank" rel="noopener">Keyfern keyword ideas</a> (Google, YouTube and Amazon autocomplete), and time launches with <a href="https://apify.com/cybermax/google-trends" target="_blank" rel="noopener">Swellmeter Google Trends</a>. Both are paid CyberMax tools on the Apify Store, charged per result, and a first run costs under $0.01.</p>
128
+ </aside>
129
+
130
+ <section class="seo" id="faq">
131
+ <h2>How to build a UTM link</h2>
132
+ <p>Paste the page URL, choose where the link will be shared (source), the type of traffic (medium) and a campaign name. The builder adds <code>utm_source</code>, <code>utm_medium</code> and <code>utm_campaign</code> to your link. Existing query parameters and #anchors are kept.</p>
133
+ <h3>What are UTM parameters?</h3>
134
+ <p>Tags on a link that tell Google Analytics 4, Matomo, Plausible and other analytics tools where a visitor came from: <b>utm_source</b> (google, newsletter), <b>utm_medium</b> (cpc, email, social), <b>utm_campaign</b> (spring_sale), and optionally <b>utm_term</b> (the keyword), <b>utm_content</b> (which button or ad variant) and <b>utm_id</b> (a campaign ID).</p>
135
+ <h3>Why do my UTM visits show as "Unassigned" in GA4?</h3>
136
+ <p>GA4 sorts traffic into channels using the medium. Values it doesn't recognise, like "newsletter" as a medium or "Paid-Social", can land in "Unassigned". The builder warns you when a medium probably won't match GA4's default channel groups.</p>
137
+ <h3>Should UTM values be lowercase?</h3>
138
+ <p>Yes. UTM values are case-sensitive, so "Facebook" and "facebook" count as two sources. Lowercase is on by default.</p>
139
+ <h3>Can I build UTM links in bulk?</h3>
140
+ <p>Yes. Open the Bulk tab and paste a list of URLs, or a CSV with a source, medium and campaign for each row. You'll get a table you can copy or download as CSV for a spreadsheet.</p>
141
+ <h3>Is it private?</h3>
142
+ <p>Yes. The links are built in your browser. Nothing is uploaded, and the history stays on your device. The app works offline once loaded.</p>
143
+ </section>
144
+
145
+ <footer class="more">
146
+ <div class="more-title">More from CyberMax</div>
147
+ <div class="more-links">
148
+ <a href="https://shaw276-cybermax.static.hf.space/profit-margin-calculator/" target="_blank" rel="noopener">Profit Margin Calculator</a>
149
+ <a href="https://shaw276-cybermax.static.hf.space/salary-to-hourly-calculator/" target="_blank" rel="noopener">Salary to Hourly Calculator</a>
150
+ <a href="https://shaw276-cybermax.static.hf.space/sumbloom/" target="_blank" rel="noopener">Sumbloom · daily number puzzle</a>
151
+ <a href="https://shaw276-cybermax.static.hf.space/honeyfold/" target="_blank" rel="noopener">Honeyfold · idle bee game</a>
152
+ <a href="https://huggingface.co/spaces/shaw276/cybermax" target="_blank" rel="noopener">All CyberMax tools</a>
153
+ </div>
154
+ <div class="copy">© 2026 CyberMax. All rights reserved. QR codes by qrcode-generator (MIT, Kazuhiko Arase). "QR Code" is a registered trademark of DENSO WAVE INCORPORATED.</div>
155
+ </footer>
156
+ </main>
157
+ <div class="toast" id="toast" role="status" aria-live="polite"></div>
158
+ <script src="vendor/qrcode.js"></script>
159
+ <script src="utm.js"></script>
160
+ <script src="app.js"></script>
161
+ </body>
162
+ </html>
utm-builder/manifest.webmanifest ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "UTM Builder by CyberMax",
3
+ "short_name": "UTM Builder",
4
+ "description": "Build UTM campaign links one at a time or in bulk, with QR codes and a link decoder.",
5
+ "start_url": "./",
6
+ "scope": "./",
7
+ "display": "standalone",
8
+ "background_color": "#f6f7fb",
9
+ "theme_color": "#3056d3",
10
+ "icons": [
11
+ {"src": "icon.svg", "sizes": "any", "type": "image/svg+xml"},
12
+ {"src": "icon-192.png", "sizes": "192x192", "type": "image/png"},
13
+ {"src": "icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any maskable"}
14
+ ]
15
+ }
utm-builder/og.png ADDED
utm-builder/robots.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ User-agent: *
2
+ Allow: /
3
+ Sitemap: https://shaw276-cybermax.static.hf.space/utm-builder/sitemap.xml
utm-builder/sitemap.xml ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"><url><loc>https://shaw276-cybermax.static.hf.space/utm-builder/</loc><lastmod>2026-09-24</lastmod><changefreq>monthly</changefreq></url></urlset>
utm-builder/style.css ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* CyberMax web app base. © 2026 CyberMax. All rights reserved. */
2
+ :root{
3
+ --bg:#f6f7fb;--surface:#fff;--ink:#1d2433;--muted:#5d6678;--line:#e1e5ee;--accent:#3056d3;--accent-ink:#fff;
4
+ --accent-soft:#e8edfc;--good:#1f8a4c;--warn:#b25e00;--warn-soft:#fff3e0;--code:#f0f2f7;color-scheme:light;
5
+ }
6
+ @media (prefers-color-scheme:dark){
7
+ :root{--bg:#12151c;--surface:#1b2029;--ink:#e8ecf4;--muted:#9aa4b6;--line:#2d3440;--accent:#7c9cff;--accent-ink:#0d1220;
8
+ --accent-soft:#232c45;--good:#5ccf8b;--warn:#ffb25c;--warn-soft:#3a2b18;--code:#232833;color-scheme:dark}
9
+ }
10
+ *{box-sizing:border-box}
11
+ html,body{margin:0}
12
+ body{background:var(--bg);color:var(--ink);font:15px/1.5 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif}
13
+ a{color:var(--accent)}
14
+ button,input,select,textarea{font:inherit;color:inherit}
15
+ .wrap{max-width:1040px;margin:0 auto;padding:0 16px}
16
+ .top{background:var(--surface);border-bottom:1px solid var(--line)}
17
+ .top .wrap{display:flex;align-items:center;justify-content:space-between;gap:12px;padding-top:12px;padding-bottom:12px}
18
+ .brand{font-weight:800;font-size:18px;display:flex;align-items:center;gap:8px}
19
+ .brand .dot{width:26px;height:26px;border-radius:8px;background:var(--accent);color:var(--accent-ink);display:inline-flex;align-items:center;justify-content:center;font-size:14px}
20
+ .brand small{font-weight:600;color:var(--muted);font-size:13px}
21
+ h1{font-size:clamp(24px,4.5vw,34px);line-height:1.15;margin:22px 0 6px}
22
+ .lede{color:var(--muted);margin:0 0 16px;max-width:720px}
23
+ .tabs{display:flex;gap:4px;border-bottom:1px solid var(--line);margin-bottom:16px;overflow-x:auto}
24
+ .tab{border:0;background:none;padding:10px 14px;font-weight:700;color:var(--muted);border-bottom:3px solid transparent;cursor:pointer;white-space:nowrap}
25
+ .tab[aria-selected="true"]{color:var(--accent);border-bottom-color:var(--accent)}
26
+ .panel[hidden]{display:none}
27
+ .grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:16px}
28
+ .card{background:var(--surface);border:1px solid var(--line);border-radius:14px;padding:16px}
29
+ .card h2{font-size:16px;margin:0 0 12px}
30
+ label{display:block;font-size:13px;font-weight:700;margin:10px 0 4px}
31
+ label .opt{font-weight:500;color:var(--muted)}
32
+ .hint{font-size:12px;color:var(--muted);margin-top:3px}
33
+ input[type=text],input[type=url],input[type=number],select,textarea{width:100%;padding:10px 12px;border:1px solid var(--line);border-radius:10px;background:var(--bg);outline:none;min-width:0}
34
+ input:focus,select:focus,textarea:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
35
+ textarea{min-height:110px;resize:vertical;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px}
36
+ .row2{display:grid;grid-template-columns:1fr 1fr;gap:10px}
37
+ .row3{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}
38
+ .chips{display:flex;flex-wrap:wrap;gap:6px}
39
+ .chip{border:1px solid var(--line);background:var(--bg);border-radius:999px;padding:5px 11px;font-size:13px;font-weight:600;cursor:pointer}
40
+ .chip.on,.chip:hover{border-color:var(--accent);color:var(--accent)}
41
+ .btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;border:0;border-radius:10px;background:var(--accent);color:var(--accent-ink);font-weight:700;padding:10px 16px;cursor:pointer;text-decoration:none}
42
+ .btn.ghost{background:var(--surface);color:var(--ink);border:1px solid var(--line)}
43
+ .btn:disabled{opacity:.5;cursor:default}
44
+ .btns{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}
45
+ .out{background:var(--code);border-radius:10px;padding:12px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;word-break:break-all;min-height:46px}
46
+ .warn{background:var(--warn-soft);color:var(--warn);border-radius:10px;padding:8px 12px;font-size:13px;margin-top:10px}
47
+ .warn:empty{display:none}
48
+ .check{display:flex;align-items:center;gap:8px;font-weight:600;font-size:13px;margin:8px 0}
49
+ .check input{width:18px;height:18px}
50
+ table{width:100%;border-collapse:collapse;font-size:13px}
51
+ th,td{text-align:left;padding:7px 8px;border-bottom:1px solid var(--line);vertical-align:top}
52
+ th{font-size:12px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px}
53
+ td.num,th.num{text-align:right;font-variant-numeric:tabular-nums}
54
+ .table-wrap{overflow-x:auto}
55
+ .big{font-size:clamp(26px,5vw,38px);font-weight:800;font-variant-numeric:tabular-nums;line-height:1.1}
56
+ .stat{background:var(--bg);border-radius:12px;padding:12px}
57
+ .stat .k{font-size:12px;color:var(--muted);font-weight:700;text-transform:uppercase;letter-spacing:.5px}
58
+ .stat .v{font-size:22px;font-weight:800;font-variant-numeric:tabular-nums}
59
+ .stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:10px}
60
+ .related{border:1px dashed var(--accent);background:var(--accent-soft);border-radius:14px;padding:14px 16px;margin:18px 0}
61
+ .related h2{font-size:15px;margin:0 0 6px}
62
+ .related p{margin:4px 0;font-size:14px}
63
+ .seo{color:var(--muted);font-size:14px;margin-top:10px}
64
+ .seo h2{color:var(--ink);font-size:18px;margin:22px 0 6px}
65
+ .seo h3{color:var(--ink);font-size:15px;margin:14px 0 4px}
66
+ .more{border-top:1px solid var(--line);margin-top:28px;padding:18px 0 22px;text-align:center;font-size:14px}
67
+ .more-title{font-weight:700;margin-bottom:8px}
68
+ .more-links{display:flex;flex-wrap:wrap;gap:8px;justify-content:center}
69
+ .more-links a{color:var(--ink);background:var(--surface);border:1px solid var(--line);border-radius:999px;padding:6px 12px;text-decoration:none}
70
+ .more-links a:hover{border-color:var(--accent)}
71
+ .copy{margin-top:12px;color:var(--muted);font-size:12px}
72
+ .toast{position:fixed;bottom:18px;left:50%;transform:translateX(-50%);background:var(--ink);color:var(--bg);padding:10px 16px;border-radius:10px;font-weight:600;opacity:0;pointer-events:none;transition:opacity .2s;z-index:20}
73
+ .toast.show{opacity:1}
74
+ @media (max-width:760px){.grid,.row3{grid-template-columns:minmax(0,1fr)}}
75
+ @media (max-width:420px){.row2{grid-template-columns:minmax(0,1fr)}}
utm-builder/sw.js ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* UTM Builder offline cache. © 2026 CyberMax. All rights reserved. */
2
+ var CACHE = "utm-builder-v1";
3
+ var FILES = ["./", "index.html", "style.css", "utm.js", "app.js", "vendor/qrcode.js", "manifest.webmanifest", "icon.svg", "icon-192.png", "icon-512.png"];
4
+ self.addEventListener("install", function (e) {
5
+ e.waitUntil(caches.open(CACHE).then(function (c) { return c.addAll(FILES); }).then(function () { return self.skipWaiting(); }));
6
+ });
7
+ self.addEventListener("activate", function (e) {
8
+ e.waitUntil(caches.keys().then(function (keys) {
9
+ return Promise.all(keys.filter(function (k) { return k !== CACHE; }).map(function (k) { return caches.delete(k); }));
10
+ }).then(function () { return self.clients.claim(); }));
11
+ });
12
+ self.addEventListener("fetch", function (e) {
13
+ if (e.request.method !== "GET" || new URL(e.request.url).origin !== location.origin) return;
14
+ // Network first so updates arrive quickly; cache as offline fallback.
15
+ e.respondWith(fetch(e.request).then(function (r) {
16
+ var copy = r.clone();
17
+ caches.open(CACHE).then(function (c) { c.put(e.request, copy); });
18
+ return r;
19
+ }).catch(function () { return caches.match(e.request, { ignoreSearch: true }); }));
20
+ });
utm-builder/utm.js ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* UTM Builder core (pure, no DOM). © 2026 CyberMax. All rights reserved. */
2
+ (function (root) {
3
+ "use strict";
4
+
5
+ var KEYS = ["utm_source", "utm_medium", "utm_campaign", "utm_id", "utm_term", "utm_content"];
6
+ // Mediums that GA4's default channel groups recognise (patterns from Google's published rules, simplified).
7
+ var KNOWN_MEDIUM = /^(.*cp.*|ppc|retargeting|paid.*|e[-_ ]?mail|display|banner|expandable|interstitial|affiliate|referral|app|link|social|social[-_ ]network|social[-_ ]media|sm|organic|video|audio|sms|push|mobile|notification|qr|print|offline)$/;
8
+
9
+ var PRESETS = {
10
+ "Google Ads": { utm_source: "google", utm_medium: "cpc" },
11
+ "Microsoft Ads": { utm_source: "bing", utm_medium: "cpc" },
12
+ "Meta Ads": { utm_source: "facebook", utm_medium: "paid_social" },
13
+ "Instagram bio": { utm_source: "instagram", utm_medium: "social" },
14
+ "LinkedIn post": { utm_source: "linkedin", utm_medium: "social" },
15
+ "X / Twitter": { utm_source: "twitter", utm_medium: "social" },
16
+ "TikTok": { utm_source: "tiktok", utm_medium: "social" },
17
+ "YouTube": { utm_source: "youtube", utm_medium: "video" },
18
+ "Newsletter": { utm_source: "newsletter", utm_medium: "email" },
19
+ "QR / print": { utm_source: "qr", utm_medium: "print" },
20
+ "Affiliate": { utm_source: "partner", utm_medium: "affiliate" }
21
+ };
22
+
23
+ function clean(v, opts) {
24
+ v = String(v == null ? "" : v).trim();
25
+ if (opts.lower) v = v.toLowerCase();
26
+ if (opts.space && opts.space !== "keep") v = v.replace(/\s+/g, opts.space);
27
+ return v;
28
+ }
29
+
30
+ function normalizeBase(base) {
31
+ base = String(base || "").trim();
32
+ if (!base) return { error: "Enter the page URL you want to track." };
33
+ var added = false;
34
+ if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(base)) { base = "https://" + base; added = true; }
35
+ var u;
36
+ try { u = new URL(base); } catch (e) { return { error: "That doesn't look like a valid URL." }; }
37
+ if (!/^https?:$/.test(u.protocol)) return { error: "Use an http:// or https:// URL." };
38
+ if (!/\./.test(u.hostname) && u.hostname !== "localhost") return { error: "That URL has no domain name." };
39
+ return { url: u, added: added };
40
+ }
41
+
42
+ // Build a tagged URL. params: {utm_source, ...}; opts: {lower: bool, space: "-"|"_"|"%20"|"keep"}.
43
+ function build(base, params, opts) {
44
+ opts = opts || { lower: true, space: "-" };
45
+ var warnings = [];
46
+ var nb = normalizeBase(base);
47
+ if (nb.error) return { url: "", error: nb.error, warnings: [] };
48
+ var u = nb.url;
49
+ if (nb.added) warnings.push("Added https:// to the URL.");
50
+ var replaced = [];
51
+ var kept = [];
52
+ u.search.replace(/^\?/, "").split("&").forEach(function (pair) {
53
+ if (!pair) return;
54
+ var k = decodeURIComponent(pair.split("=")[0].replace(/\+/g, " ")).toLowerCase();
55
+ if (KEYS.indexOf(k) >= 0) replaced.push(k); else kept.push(pair);
56
+ });
57
+ var tags = [];
58
+ KEYS.forEach(function (k) {
59
+ var raw = params[k];
60
+ var v = clean(raw, opts);
61
+ if (v) tags.push(k + "=" + encodeURIComponent(v).replace(/%2F/gi, "/"));
62
+ if (raw && /\s/.test(String(raw).trim()) && opts.space === "keep") warnings.push(k + " contains spaces; they'll be encoded as %20.");
63
+ if (raw && !opts.lower && /[A-Z]/.test(raw)) warnings.push(k + " has capitals. Analytics treats \"Facebook\" and \"facebook\" as different sources.");
64
+ });
65
+ ["utm_source", "utm_medium", "utm_campaign"].forEach(function (k) {
66
+ if (!clean(params[k], opts)) warnings.push(k + " is empty. GA4 needs source, medium and campaign to report the visit properly.");
67
+ });
68
+ var med = clean(params.utm_medium, opts);
69
+ if (med && !KNOWN_MEDIUM.test(med.toLowerCase())) warnings.push("utm_medium \"" + med + "\" isn't one GA4's default channel groups recognise, so these visits may show as \"Unassigned\". Try cpc, email, social, paid_social, display, affiliate or referral.");
70
+ if (replaced.length) warnings.push("Replaced existing " + replaced.join(", ") + " in the URL.");
71
+ var search = kept.concat(tags).join("&");
72
+ var out = u.origin + u.pathname + (search ? "?" + search : "") + u.hash;
73
+ if (out.length > 2000) warnings.push("The URL is over 2,000 characters; some platforms truncate long links.");
74
+ return { url: out, warnings: warnings, tagged: tags.length > 0 };
75
+ }
76
+
77
+ function parse(url) {
78
+ var nb = normalizeBase(url);
79
+ if (nb.error) return { error: nb.error };
80
+ var u = nb.url, utm = {}, other = [];
81
+ u.searchParams.forEach(function (v, k) {
82
+ if (/^utm_/i.test(k)) utm[k.toLowerCase()] = v; else other.push(k + "=" + v);
83
+ });
84
+ var cleanUrl = u.origin + u.pathname;
85
+ var rest = [];
86
+ u.search.replace(/^\?/, "").split("&").forEach(function (p) { if (p && !/^utm_/i.test(p)) rest.push(p); });
87
+ if (rest.length) cleanUrl += "?" + rest.join("&");
88
+ cleanUrl += u.hash;
89
+ return { utm: utm, clean: cleanUrl, other: other };
90
+ }
91
+
92
+ // CSV line splitter with quotes.
93
+ function splitCsv(line) {
94
+ var out = [], cur = "", q = false;
95
+ for (var i = 0; i < line.length; i++) {
96
+ var c = line[i];
97
+ if (q) { if (c === '"' && line[i + 1] === '"') { cur += '"'; i++; } else if (c === '"') q = false; else cur += c; }
98
+ else if (c === '"') q = true;
99
+ else if (c === "," || c === "\t") { out.push(cur); cur = ""; }
100
+ else cur += c;
101
+ }
102
+ out.push(cur);
103
+ return out.map(function (s) { return s.trim(); });
104
+ }
105
+
106
+ // Bulk: each line "url" or "url,source,medium,campaign,term,content". Blank columns use defaults.
107
+ var BULK_COLS = ["url", "utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content"];
108
+ function bulk(text, defaults, opts) {
109
+ var lines = String(text || "").split(/\r?\n/).map(function (l) { return l.trim(); }).filter(Boolean);
110
+ var cols = BULK_COLS;
111
+ if (lines.length && /^url\b/i.test(lines[0])) {
112
+ cols = splitCsv(lines.shift()).map(function (h) { h = h.toLowerCase().replace(/^utm_/, ""); return h === "url" ? "url" : "utm_" + h; });
113
+ }
114
+ return lines.slice(0, 5000).map(function (line) {
115
+ var cells = splitCsv(line), p = {};
116
+ KEYS.forEach(function (k) { p[k] = defaults[k] || ""; });
117
+ var url = "";
118
+ cols.forEach(function (c, i) { if (c === "url") url = cells[i] || ""; else if (cells[i]) p[c] = cells[i]; });
119
+ var r = build(url, p, opts);
120
+ return { input: url, url: r.url, error: r.error || "", warnings: r.warnings || [] };
121
+ });
122
+ }
123
+
124
+ function csvCell(s) { s = String(s == null ? "" : s); return /[",\n]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s; }
125
+ function toCsv(rows) {
126
+ return "input_url,tagged_url,error\n" + rows.map(function (r) { return [r.input, r.url, r.error].map(csvCell).join(","); }).join("\n") + "\n";
127
+ }
128
+
129
+ var api = { KEYS: KEYS, PRESETS: PRESETS, build: build, parse: parse, bulk: bulk, toCsv: toCsv, splitCsv: splitCsv };
130
+ root.UtmCore = api;
131
+ if (typeof module !== "undefined") module.exports = api;
132
+ })(typeof window !== "undefined" ? window : globalThis);
utm-builder/vendor/LICENSE-qrcode-generator.txt ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ qrcode-generator 1.4.4 (vendor/qrcode.js) — https://github.com/kazuhikoarase/qrcode-generator
2
+
3
+ The MIT License (MIT)
4
+
5
+ Copyright (c) 2009 Kazuhiko Arase
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in
15
+ all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ THE SOFTWARE.
24
+
25
+ The word "QR Code" is a registered trademark of DENSO WAVE INCORPORATED.
utm-builder/vendor/qrcode.js ADDED
@@ -0,0 +1,2297 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //---------------------------------------------------------------------
2
+ //
3
+ // QR Code Generator for JavaScript
4
+ //
5
+ // Copyright (c) 2009 Kazuhiko Arase
6
+ //
7
+ // URL: http://www.d-project.com/
8
+ //
9
+ // Licensed under the MIT license:
10
+ // http://www.opensource.org/licenses/mit-license.php
11
+ //
12
+ // The word 'QR Code' is registered trademark of
13
+ // DENSO WAVE INCORPORATED
14
+ // http://www.denso-wave.com/qrcode/faqpatent-e.html
15
+ //
16
+ //---------------------------------------------------------------------
17
+
18
+ var qrcode = function() {
19
+
20
+ //---------------------------------------------------------------------
21
+ // qrcode
22
+ //---------------------------------------------------------------------
23
+
24
+ /**
25
+ * qrcode
26
+ * @param typeNumber 1 to 40
27
+ * @param errorCorrectionLevel 'L','M','Q','H'
28
+ */
29
+ var qrcode = function(typeNumber, errorCorrectionLevel) {
30
+
31
+ var PAD0 = 0xEC;
32
+ var PAD1 = 0x11;
33
+
34
+ var _typeNumber = typeNumber;
35
+ var _errorCorrectionLevel = QRErrorCorrectionLevel[errorCorrectionLevel];
36
+ var _modules = null;
37
+ var _moduleCount = 0;
38
+ var _dataCache = null;
39
+ var _dataList = [];
40
+
41
+ var _this = {};
42
+
43
+ var makeImpl = function(test, maskPattern) {
44
+
45
+ _moduleCount = _typeNumber * 4 + 17;
46
+ _modules = function(moduleCount) {
47
+ var modules = new Array(moduleCount);
48
+ for (var row = 0; row < moduleCount; row += 1) {
49
+ modules[row] = new Array(moduleCount);
50
+ for (var col = 0; col < moduleCount; col += 1) {
51
+ modules[row][col] = null;
52
+ }
53
+ }
54
+ return modules;
55
+ }(_moduleCount);
56
+
57
+ setupPositionProbePattern(0, 0);
58
+ setupPositionProbePattern(_moduleCount - 7, 0);
59
+ setupPositionProbePattern(0, _moduleCount - 7);
60
+ setupPositionAdjustPattern();
61
+ setupTimingPattern();
62
+ setupTypeInfo(test, maskPattern);
63
+
64
+ if (_typeNumber >= 7) {
65
+ setupTypeNumber(test);
66
+ }
67
+
68
+ if (_dataCache == null) {
69
+ _dataCache = createData(_typeNumber, _errorCorrectionLevel, _dataList);
70
+ }
71
+
72
+ mapData(_dataCache, maskPattern);
73
+ };
74
+
75
+ var setupPositionProbePattern = function(row, col) {
76
+
77
+ for (var r = -1; r <= 7; r += 1) {
78
+
79
+ if (row + r <= -1 || _moduleCount <= row + r) continue;
80
+
81
+ for (var c = -1; c <= 7; c += 1) {
82
+
83
+ if (col + c <= -1 || _moduleCount <= col + c) continue;
84
+
85
+ if ( (0 <= r && r <= 6 && (c == 0 || c == 6) )
86
+ || (0 <= c && c <= 6 && (r == 0 || r == 6) )
87
+ || (2 <= r && r <= 4 && 2 <= c && c <= 4) ) {
88
+ _modules[row + r][col + c] = true;
89
+ } else {
90
+ _modules[row + r][col + c] = false;
91
+ }
92
+ }
93
+ }
94
+ };
95
+
96
+ var getBestMaskPattern = function() {
97
+
98
+ var minLostPoint = 0;
99
+ var pattern = 0;
100
+
101
+ for (var i = 0; i < 8; i += 1) {
102
+
103
+ makeImpl(true, i);
104
+
105
+ var lostPoint = QRUtil.getLostPoint(_this);
106
+
107
+ if (i == 0 || minLostPoint > lostPoint) {
108
+ minLostPoint = lostPoint;
109
+ pattern = i;
110
+ }
111
+ }
112
+
113
+ return pattern;
114
+ };
115
+
116
+ var setupTimingPattern = function() {
117
+
118
+ for (var r = 8; r < _moduleCount - 8; r += 1) {
119
+ if (_modules[r][6] != null) {
120
+ continue;
121
+ }
122
+ _modules[r][6] = (r % 2 == 0);
123
+ }
124
+
125
+ for (var c = 8; c < _moduleCount - 8; c += 1) {
126
+ if (_modules[6][c] != null) {
127
+ continue;
128
+ }
129
+ _modules[6][c] = (c % 2 == 0);
130
+ }
131
+ };
132
+
133
+ var setupPositionAdjustPattern = function() {
134
+
135
+ var pos = QRUtil.getPatternPosition(_typeNumber);
136
+
137
+ for (var i = 0; i < pos.length; i += 1) {
138
+
139
+ for (var j = 0; j < pos.length; j += 1) {
140
+
141
+ var row = pos[i];
142
+ var col = pos[j];
143
+
144
+ if (_modules[row][col] != null) {
145
+ continue;
146
+ }
147
+
148
+ for (var r = -2; r <= 2; r += 1) {
149
+
150
+ for (var c = -2; c <= 2; c += 1) {
151
+
152
+ if (r == -2 || r == 2 || c == -2 || c == 2
153
+ || (r == 0 && c == 0) ) {
154
+ _modules[row + r][col + c] = true;
155
+ } else {
156
+ _modules[row + r][col + c] = false;
157
+ }
158
+ }
159
+ }
160
+ }
161
+ }
162
+ };
163
+
164
+ var setupTypeNumber = function(test) {
165
+
166
+ var bits = QRUtil.getBCHTypeNumber(_typeNumber);
167
+
168
+ for (var i = 0; i < 18; i += 1) {
169
+ var mod = (!test && ( (bits >> i) & 1) == 1);
170
+ _modules[Math.floor(i / 3)][i % 3 + _moduleCount - 8 - 3] = mod;
171
+ }
172
+
173
+ for (var i = 0; i < 18; i += 1) {
174
+ var mod = (!test && ( (bits >> i) & 1) == 1);
175
+ _modules[i % 3 + _moduleCount - 8 - 3][Math.floor(i / 3)] = mod;
176
+ }
177
+ };
178
+
179
+ var setupTypeInfo = function(test, maskPattern) {
180
+
181
+ var data = (_errorCorrectionLevel << 3) | maskPattern;
182
+ var bits = QRUtil.getBCHTypeInfo(data);
183
+
184
+ // vertical
185
+ for (var i = 0; i < 15; i += 1) {
186
+
187
+ var mod = (!test && ( (bits >> i) & 1) == 1);
188
+
189
+ if (i < 6) {
190
+ _modules[i][8] = mod;
191
+ } else if (i < 8) {
192
+ _modules[i + 1][8] = mod;
193
+ } else {
194
+ _modules[_moduleCount - 15 + i][8] = mod;
195
+ }
196
+ }
197
+
198
+ // horizontal
199
+ for (var i = 0; i < 15; i += 1) {
200
+
201
+ var mod = (!test && ( (bits >> i) & 1) == 1);
202
+
203
+ if (i < 8) {
204
+ _modules[8][_moduleCount - i - 1] = mod;
205
+ } else if (i < 9) {
206
+ _modules[8][15 - i - 1 + 1] = mod;
207
+ } else {
208
+ _modules[8][15 - i - 1] = mod;
209
+ }
210
+ }
211
+
212
+ // fixed module
213
+ _modules[_moduleCount - 8][8] = (!test);
214
+ };
215
+
216
+ var mapData = function(data, maskPattern) {
217
+
218
+ var inc = -1;
219
+ var row = _moduleCount - 1;
220
+ var bitIndex = 7;
221
+ var byteIndex = 0;
222
+ var maskFunc = QRUtil.getMaskFunction(maskPattern);
223
+
224
+ for (var col = _moduleCount - 1; col > 0; col -= 2) {
225
+
226
+ if (col == 6) col -= 1;
227
+
228
+ while (true) {
229
+
230
+ for (var c = 0; c < 2; c += 1) {
231
+
232
+ if (_modules[row][col - c] == null) {
233
+
234
+ var dark = false;
235
+
236
+ if (byteIndex < data.length) {
237
+ dark = ( ( (data[byteIndex] >>> bitIndex) & 1) == 1);
238
+ }
239
+
240
+ var mask = maskFunc(row, col - c);
241
+
242
+ if (mask) {
243
+ dark = !dark;
244
+ }
245
+
246
+ _modules[row][col - c] = dark;
247
+ bitIndex -= 1;
248
+
249
+ if (bitIndex == -1) {
250
+ byteIndex += 1;
251
+ bitIndex = 7;
252
+ }
253
+ }
254
+ }
255
+
256
+ row += inc;
257
+
258
+ if (row < 0 || _moduleCount <= row) {
259
+ row -= inc;
260
+ inc = -inc;
261
+ break;
262
+ }
263
+ }
264
+ }
265
+ };
266
+
267
+ var createBytes = function(buffer, rsBlocks) {
268
+
269
+ var offset = 0;
270
+
271
+ var maxDcCount = 0;
272
+ var maxEcCount = 0;
273
+
274
+ var dcdata = new Array(rsBlocks.length);
275
+ var ecdata = new Array(rsBlocks.length);
276
+
277
+ for (var r = 0; r < rsBlocks.length; r += 1) {
278
+
279
+ var dcCount = rsBlocks[r].dataCount;
280
+ var ecCount = rsBlocks[r].totalCount - dcCount;
281
+
282
+ maxDcCount = Math.max(maxDcCount, dcCount);
283
+ maxEcCount = Math.max(maxEcCount, ecCount);
284
+
285
+ dcdata[r] = new Array(dcCount);
286
+
287
+ for (var i = 0; i < dcdata[r].length; i += 1) {
288
+ dcdata[r][i] = 0xff & buffer.getBuffer()[i + offset];
289
+ }
290
+ offset += dcCount;
291
+
292
+ var rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount);
293
+ var rawPoly = qrPolynomial(dcdata[r], rsPoly.getLength() - 1);
294
+
295
+ var modPoly = rawPoly.mod(rsPoly);
296
+ ecdata[r] = new Array(rsPoly.getLength() - 1);
297
+ for (var i = 0; i < ecdata[r].length; i += 1) {
298
+ var modIndex = i + modPoly.getLength() - ecdata[r].length;
299
+ ecdata[r][i] = (modIndex >= 0)? modPoly.getAt(modIndex) : 0;
300
+ }
301
+ }
302
+
303
+ var totalCodeCount = 0;
304
+ for (var i = 0; i < rsBlocks.length; i += 1) {
305
+ totalCodeCount += rsBlocks[i].totalCount;
306
+ }
307
+
308
+ var data = new Array(totalCodeCount);
309
+ var index = 0;
310
+
311
+ for (var i = 0; i < maxDcCount; i += 1) {
312
+ for (var r = 0; r < rsBlocks.length; r += 1) {
313
+ if (i < dcdata[r].length) {
314
+ data[index] = dcdata[r][i];
315
+ index += 1;
316
+ }
317
+ }
318
+ }
319
+
320
+ for (var i = 0; i < maxEcCount; i += 1) {
321
+ for (var r = 0; r < rsBlocks.length; r += 1) {
322
+ if (i < ecdata[r].length) {
323
+ data[index] = ecdata[r][i];
324
+ index += 1;
325
+ }
326
+ }
327
+ }
328
+
329
+ return data;
330
+ };
331
+
332
+ var createData = function(typeNumber, errorCorrectionLevel, dataList) {
333
+
334
+ var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, errorCorrectionLevel);
335
+
336
+ var buffer = qrBitBuffer();
337
+
338
+ for (var i = 0; i < dataList.length; i += 1) {
339
+ var data = dataList[i];
340
+ buffer.put(data.getMode(), 4);
341
+ buffer.put(data.getLength(), QRUtil.getLengthInBits(data.getMode(), typeNumber) );
342
+ data.write(buffer);
343
+ }
344
+
345
+ // calc num max data.
346
+ var totalDataCount = 0;
347
+ for (var i = 0; i < rsBlocks.length; i += 1) {
348
+ totalDataCount += rsBlocks[i].dataCount;
349
+ }
350
+
351
+ if (buffer.getLengthInBits() > totalDataCount * 8) {
352
+ throw 'code length overflow. ('
353
+ + buffer.getLengthInBits()
354
+ + '>'
355
+ + totalDataCount * 8
356
+ + ')';
357
+ }
358
+
359
+ // end code
360
+ if (buffer.getLengthInBits() + 4 <= totalDataCount * 8) {
361
+ buffer.put(0, 4);
362
+ }
363
+
364
+ // padding
365
+ while (buffer.getLengthInBits() % 8 != 0) {
366
+ buffer.putBit(false);
367
+ }
368
+
369
+ // padding
370
+ while (true) {
371
+
372
+ if (buffer.getLengthInBits() >= totalDataCount * 8) {
373
+ break;
374
+ }
375
+ buffer.put(PAD0, 8);
376
+
377
+ if (buffer.getLengthInBits() >= totalDataCount * 8) {
378
+ break;
379
+ }
380
+ buffer.put(PAD1, 8);
381
+ }
382
+
383
+ return createBytes(buffer, rsBlocks);
384
+ };
385
+
386
+ _this.addData = function(data, mode) {
387
+
388
+ mode = mode || 'Byte';
389
+
390
+ var newData = null;
391
+
392
+ switch(mode) {
393
+ case 'Numeric' :
394
+ newData = qrNumber(data);
395
+ break;
396
+ case 'Alphanumeric' :
397
+ newData = qrAlphaNum(data);
398
+ break;
399
+ case 'Byte' :
400
+ newData = qr8BitByte(data);
401
+ break;
402
+ case 'Kanji' :
403
+ newData = qrKanji(data);
404
+ break;
405
+ default :
406
+ throw 'mode:' + mode;
407
+ }
408
+
409
+ _dataList.push(newData);
410
+ _dataCache = null;
411
+ };
412
+
413
+ _this.isDark = function(row, col) {
414
+ if (row < 0 || _moduleCount <= row || col < 0 || _moduleCount <= col) {
415
+ throw row + ',' + col;
416
+ }
417
+ return _modules[row][col];
418
+ };
419
+
420
+ _this.getModuleCount = function() {
421
+ return _moduleCount;
422
+ };
423
+
424
+ _this.make = function() {
425
+ if (_typeNumber < 1) {
426
+ var typeNumber = 1;
427
+
428
+ for (; typeNumber < 40; typeNumber++) {
429
+ var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, _errorCorrectionLevel);
430
+ var buffer = qrBitBuffer();
431
+
432
+ for (var i = 0; i < _dataList.length; i++) {
433
+ var data = _dataList[i];
434
+ buffer.put(data.getMode(), 4);
435
+ buffer.put(data.getLength(), QRUtil.getLengthInBits(data.getMode(), typeNumber) );
436
+ data.write(buffer);
437
+ }
438
+
439
+ var totalDataCount = 0;
440
+ for (var i = 0; i < rsBlocks.length; i++) {
441
+ totalDataCount += rsBlocks[i].dataCount;
442
+ }
443
+
444
+ if (buffer.getLengthInBits() <= totalDataCount * 8) {
445
+ break;
446
+ }
447
+ }
448
+
449
+ _typeNumber = typeNumber;
450
+ }
451
+
452
+ makeImpl(false, getBestMaskPattern() );
453
+ };
454
+
455
+ _this.createTableTag = function(cellSize, margin) {
456
+
457
+ cellSize = cellSize || 2;
458
+ margin = (typeof margin == 'undefined')? cellSize * 4 : margin;
459
+
460
+ var qrHtml = '';
461
+
462
+ qrHtml += '<table style="';
463
+ qrHtml += ' border-width: 0px; border-style: none;';
464
+ qrHtml += ' border-collapse: collapse;';
465
+ qrHtml += ' padding: 0px; margin: ' + margin + 'px;';
466
+ qrHtml += '">';
467
+ qrHtml += '<tbody>';
468
+
469
+ for (var r = 0; r < _this.getModuleCount(); r += 1) {
470
+
471
+ qrHtml += '<tr>';
472
+
473
+ for (var c = 0; c < _this.getModuleCount(); c += 1) {
474
+ qrHtml += '<td style="';
475
+ qrHtml += ' border-width: 0px; border-style: none;';
476
+ qrHtml += ' border-collapse: collapse;';
477
+ qrHtml += ' padding: 0px; margin: 0px;';
478
+ qrHtml += ' width: ' + cellSize + 'px;';
479
+ qrHtml += ' height: ' + cellSize + 'px;';
480
+ qrHtml += ' background-color: ';
481
+ qrHtml += _this.isDark(r, c)? '#000000' : '#ffffff';
482
+ qrHtml += ';';
483
+ qrHtml += '"/>';
484
+ }
485
+
486
+ qrHtml += '</tr>';
487
+ }
488
+
489
+ qrHtml += '</tbody>';
490
+ qrHtml += '</table>';
491
+
492
+ return qrHtml;
493
+ };
494
+
495
+ _this.createSvgTag = function(cellSize, margin, alt, title) {
496
+
497
+ var opts = {};
498
+ if (typeof arguments[0] == 'object') {
499
+ // Called by options.
500
+ opts = arguments[0];
501
+ // overwrite cellSize and margin.
502
+ cellSize = opts.cellSize;
503
+ margin = opts.margin;
504
+ alt = opts.alt;
505
+ title = opts.title;
506
+ }
507
+
508
+ cellSize = cellSize || 2;
509
+ margin = (typeof margin == 'undefined')? cellSize * 4 : margin;
510
+
511
+ // Compose alt property surrogate
512
+ alt = (typeof alt === 'string') ? {text: alt} : alt || {};
513
+ alt.text = alt.text || null;
514
+ alt.id = (alt.text) ? alt.id || 'qrcode-description' : null;
515
+
516
+ // Compose title property surrogate
517
+ title = (typeof title === 'string') ? {text: title} : title || {};
518
+ title.text = title.text || null;
519
+ title.id = (title.text) ? title.id || 'qrcode-title' : null;
520
+
521
+ var size = _this.getModuleCount() * cellSize + margin * 2;
522
+ var c, mc, r, mr, qrSvg='', rect;
523
+
524
+ rect = 'l' + cellSize + ',0 0,' + cellSize +
525
+ ' -' + cellSize + ',0 0,-' + cellSize + 'z ';
526
+
527
+ qrSvg += '<svg version="1.1" xmlns="http://www.w3.org/2000/svg"';
528
+ qrSvg += !opts.scalable ? ' width="' + size + 'px" height="' + size + 'px"' : '';
529
+ qrSvg += ' viewBox="0 0 ' + size + ' ' + size + '" ';
530
+ qrSvg += ' preserveAspectRatio="xMinYMin meet"';
531
+ qrSvg += (title.text || alt.text) ? ' role="img" aria-labelledby="' +
532
+ escapeXml([title.id, alt.id].join(' ').trim() ) + '"' : '';
533
+ qrSvg += '>';
534
+ qrSvg += (title.text) ? '<title id="' + escapeXml(title.id) + '">' +
535
+ escapeXml(title.text) + '</title>' : '';
536
+ qrSvg += (alt.text) ? '<description id="' + escapeXml(alt.id) + '">' +
537
+ escapeXml(alt.text) + '</description>' : '';
538
+ qrSvg += '<rect width="100%" height="100%" fill="white" cx="0" cy="0"/>';
539
+ qrSvg += '<path d="';
540
+
541
+ for (r = 0; r < _this.getModuleCount(); r += 1) {
542
+ mr = r * cellSize + margin;
543
+ for (c = 0; c < _this.getModuleCount(); c += 1) {
544
+ if (_this.isDark(r, c) ) {
545
+ mc = c*cellSize+margin;
546
+ qrSvg += 'M' + mc + ',' + mr + rect;
547
+ }
548
+ }
549
+ }
550
+
551
+ qrSvg += '" stroke="transparent" fill="black"/>';
552
+ qrSvg += '</svg>';
553
+
554
+ return qrSvg;
555
+ };
556
+
557
+ _this.createDataURL = function(cellSize, margin) {
558
+
559
+ cellSize = cellSize || 2;
560
+ margin = (typeof margin == 'undefined')? cellSize * 4 : margin;
561
+
562
+ var size = _this.getModuleCount() * cellSize + margin * 2;
563
+ var min = margin;
564
+ var max = size - margin;
565
+
566
+ return createDataURL(size, size, function(x, y) {
567
+ if (min <= x && x < max && min <= y && y < max) {
568
+ var c = Math.floor( (x - min) / cellSize);
569
+ var r = Math.floor( (y - min) / cellSize);
570
+ return _this.isDark(r, c)? 0 : 1;
571
+ } else {
572
+ return 1;
573
+ }
574
+ } );
575
+ };
576
+
577
+ _this.createImgTag = function(cellSize, margin, alt) {
578
+
579
+ cellSize = cellSize || 2;
580
+ margin = (typeof margin == 'undefined')? cellSize * 4 : margin;
581
+
582
+ var size = _this.getModuleCount() * cellSize + margin * 2;
583
+
584
+ var img = '';
585
+ img += '<img';
586
+ img += '\u0020src="';
587
+ img += _this.createDataURL(cellSize, margin);
588
+ img += '"';
589
+ img += '\u0020width="';
590
+ img += size;
591
+ img += '"';
592
+ img += '\u0020height="';
593
+ img += size;
594
+ img += '"';
595
+ if (alt) {
596
+ img += '\u0020alt="';
597
+ img += escapeXml(alt);
598
+ img += '"';
599
+ }
600
+ img += '/>';
601
+
602
+ return img;
603
+ };
604
+
605
+ var escapeXml = function(s) {
606
+ var escaped = '';
607
+ for (var i = 0; i < s.length; i += 1) {
608
+ var c = s.charAt(i);
609
+ switch(c) {
610
+ case '<': escaped += '&lt;'; break;
611
+ case '>': escaped += '&gt;'; break;
612
+ case '&': escaped += '&amp;'; break;
613
+ case '"': escaped += '&quot;'; break;
614
+ default : escaped += c; break;
615
+ }
616
+ }
617
+ return escaped;
618
+ };
619
+
620
+ var _createHalfASCII = function(margin) {
621
+ var cellSize = 1;
622
+ margin = (typeof margin == 'undefined')? cellSize * 2 : margin;
623
+
624
+ var size = _this.getModuleCount() * cellSize + margin * 2;
625
+ var min = margin;
626
+ var max = size - margin;
627
+
628
+ var y, x, r1, r2, p;
629
+
630
+ var blocks = {
631
+ '██': '█',
632
+ '█ ': '▀',
633
+ ' █': '▄',
634
+ ' ': ' '
635
+ };
636
+
637
+ var blocksLastLineNoMargin = {
638
+ '██': '▀',
639
+ '█ ': '▀',
640
+ ' █': ' ',
641
+ ' ': ' '
642
+ };
643
+
644
+ var ascii = '';
645
+ for (y = 0; y < size; y += 2) {
646
+ r1 = Math.floor((y - min) / cellSize);
647
+ r2 = Math.floor((y + 1 - min) / cellSize);
648
+ for (x = 0; x < size; x += 1) {
649
+ p = '█';
650
+
651
+ if (min <= x && x < max && min <= y && y < max && _this.isDark(r1, Math.floor((x - min) / cellSize))) {
652
+ p = ' ';
653
+ }
654
+
655
+ if (min <= x && x < max && min <= y+1 && y+1 < max && _this.isDark(r2, Math.floor((x - min) / cellSize))) {
656
+ p += ' ';
657
+ }
658
+ else {
659
+ p += '█';
660
+ }
661
+
662
+ // Output 2 characters per pixel, to create full square. 1 character per pixels gives only half width of square.
663
+ ascii += (margin < 1 && y+1 >= max) ? blocksLastLineNoMargin[p] : blocks[p];
664
+ }
665
+
666
+ ascii += '\n';
667
+ }
668
+
669
+ if (size % 2 && margin > 0) {
670
+ return ascii.substring(0, ascii.length - size - 1) + Array(size+1).join('▀');
671
+ }
672
+
673
+ return ascii.substring(0, ascii.length-1);
674
+ };
675
+
676
+ _this.createASCII = function(cellSize, margin) {
677
+ cellSize = cellSize || 1;
678
+
679
+ if (cellSize < 2) {
680
+ return _createHalfASCII(margin);
681
+ }
682
+
683
+ cellSize -= 1;
684
+ margin = (typeof margin == 'undefined')? cellSize * 2 : margin;
685
+
686
+ var size = _this.getModuleCount() * cellSize + margin * 2;
687
+ var min = margin;
688
+ var max = size - margin;
689
+
690
+ var y, x, r, p;
691
+
692
+ var white = Array(cellSize+1).join('██');
693
+ var black = Array(cellSize+1).join(' ');
694
+
695
+ var ascii = '';
696
+ var line = '';
697
+ for (y = 0; y < size; y += 1) {
698
+ r = Math.floor( (y - min) / cellSize);
699
+ line = '';
700
+ for (x = 0; x < size; x += 1) {
701
+ p = 1;
702
+
703
+ if (min <= x && x < max && min <= y && y < max && _this.isDark(r, Math.floor((x - min) / cellSize))) {
704
+ p = 0;
705
+ }
706
+
707
+ // Output 2 characters per pixel, to create full square. 1 character per pixels gives only half width of square.
708
+ line += p ? white : black;
709
+ }
710
+
711
+ for (r = 0; r < cellSize; r += 1) {
712
+ ascii += line + '\n';
713
+ }
714
+ }
715
+
716
+ return ascii.substring(0, ascii.length-1);
717
+ };
718
+
719
+ _this.renderTo2dContext = function(context, cellSize) {
720
+ cellSize = cellSize || 2;
721
+ var length = _this.getModuleCount();
722
+ for (var row = 0; row < length; row++) {
723
+ for (var col = 0; col < length; col++) {
724
+ context.fillStyle = _this.isDark(row, col) ? 'black' : 'white';
725
+ context.fillRect(row * cellSize, col * cellSize, cellSize, cellSize);
726
+ }
727
+ }
728
+ }
729
+
730
+ return _this;
731
+ };
732
+
733
+ //---------------------------------------------------------------------
734
+ // qrcode.stringToBytes
735
+ //---------------------------------------------------------------------
736
+
737
+ qrcode.stringToBytesFuncs = {
738
+ 'default' : function(s) {
739
+ var bytes = [];
740
+ for (var i = 0; i < s.length; i += 1) {
741
+ var c = s.charCodeAt(i);
742
+ bytes.push(c & 0xff);
743
+ }
744
+ return bytes;
745
+ }
746
+ };
747
+
748
+ qrcode.stringToBytes = qrcode.stringToBytesFuncs['default'];
749
+
750
+ //---------------------------------------------------------------------
751
+ // qrcode.createStringToBytes
752
+ //---------------------------------------------------------------------
753
+
754
+ /**
755
+ * @param unicodeData base64 string of byte array.
756
+ * [16bit Unicode],[16bit Bytes], ...
757
+ * @param numChars
758
+ */
759
+ qrcode.createStringToBytes = function(unicodeData, numChars) {
760
+
761
+ // create conversion map.
762
+
763
+ var unicodeMap = function() {
764
+
765
+ var bin = base64DecodeInputStream(unicodeData);
766
+ var read = function() {
767
+ var b = bin.read();
768
+ if (b == -1) throw 'eof';
769
+ return b;
770
+ };
771
+
772
+ var count = 0;
773
+ var unicodeMap = {};
774
+ while (true) {
775
+ var b0 = bin.read();
776
+ if (b0 == -1) break;
777
+ var b1 = read();
778
+ var b2 = read();
779
+ var b3 = read();
780
+ var k = String.fromCharCode( (b0 << 8) | b1);
781
+ var v = (b2 << 8) | b3;
782
+ unicodeMap[k] = v;
783
+ count += 1;
784
+ }
785
+ if (count != numChars) {
786
+ throw count + ' != ' + numChars;
787
+ }
788
+
789
+ return unicodeMap;
790
+ }();
791
+
792
+ var unknownChar = '?'.charCodeAt(0);
793
+
794
+ return function(s) {
795
+ var bytes = [];
796
+ for (var i = 0; i < s.length; i += 1) {
797
+ var c = s.charCodeAt(i);
798
+ if (c < 128) {
799
+ bytes.push(c);
800
+ } else {
801
+ var b = unicodeMap[s.charAt(i)];
802
+ if (typeof b == 'number') {
803
+ if ( (b & 0xff) == b) {
804
+ // 1byte
805
+ bytes.push(b);
806
+ } else {
807
+ // 2bytes
808
+ bytes.push(b >>> 8);
809
+ bytes.push(b & 0xff);
810
+ }
811
+ } else {
812
+ bytes.push(unknownChar);
813
+ }
814
+ }
815
+ }
816
+ return bytes;
817
+ };
818
+ };
819
+
820
+ //---------------------------------------------------------------------
821
+ // QRMode
822
+ //---------------------------------------------------------------------
823
+
824
+ var QRMode = {
825
+ MODE_NUMBER : 1 << 0,
826
+ MODE_ALPHA_NUM : 1 << 1,
827
+ MODE_8BIT_BYTE : 1 << 2,
828
+ MODE_KANJI : 1 << 3
829
+ };
830
+
831
+ //---------------------------------------------------------------------
832
+ // QRErrorCorrectionLevel
833
+ //---------------------------------------------------------------------
834
+
835
+ var QRErrorCorrectionLevel = {
836
+ L : 1,
837
+ M : 0,
838
+ Q : 3,
839
+ H : 2
840
+ };
841
+
842
+ //---------------------------------------------------------------------
843
+ // QRMaskPattern
844
+ //---------------------------------------------------------------------
845
+
846
+ var QRMaskPattern = {
847
+ PATTERN000 : 0,
848
+ PATTERN001 : 1,
849
+ PATTERN010 : 2,
850
+ PATTERN011 : 3,
851
+ PATTERN100 : 4,
852
+ PATTERN101 : 5,
853
+ PATTERN110 : 6,
854
+ PATTERN111 : 7
855
+ };
856
+
857
+ //---------------------------------------------------------------------
858
+ // QRUtil
859
+ //---------------------------------------------------------------------
860
+
861
+ var QRUtil = function() {
862
+
863
+ var PATTERN_POSITION_TABLE = [
864
+ [],
865
+ [6, 18],
866
+ [6, 22],
867
+ [6, 26],
868
+ [6, 30],
869
+ [6, 34],
870
+ [6, 22, 38],
871
+ [6, 24, 42],
872
+ [6, 26, 46],
873
+ [6, 28, 50],
874
+ [6, 30, 54],
875
+ [6, 32, 58],
876
+ [6, 34, 62],
877
+ [6, 26, 46, 66],
878
+ [6, 26, 48, 70],
879
+ [6, 26, 50, 74],
880
+ [6, 30, 54, 78],
881
+ [6, 30, 56, 82],
882
+ [6, 30, 58, 86],
883
+ [6, 34, 62, 90],
884
+ [6, 28, 50, 72, 94],
885
+ [6, 26, 50, 74, 98],
886
+ [6, 30, 54, 78, 102],
887
+ [6, 28, 54, 80, 106],
888
+ [6, 32, 58, 84, 110],
889
+ [6, 30, 58, 86, 114],
890
+ [6, 34, 62, 90, 118],
891
+ [6, 26, 50, 74, 98, 122],
892
+ [6, 30, 54, 78, 102, 126],
893
+ [6, 26, 52, 78, 104, 130],
894
+ [6, 30, 56, 82, 108, 134],
895
+ [6, 34, 60, 86, 112, 138],
896
+ [6, 30, 58, 86, 114, 142],
897
+ [6, 34, 62, 90, 118, 146],
898
+ [6, 30, 54, 78, 102, 126, 150],
899
+ [6, 24, 50, 76, 102, 128, 154],
900
+ [6, 28, 54, 80, 106, 132, 158],
901
+ [6, 32, 58, 84, 110, 136, 162],
902
+ [6, 26, 54, 82, 110, 138, 166],
903
+ [6, 30, 58, 86, 114, 142, 170]
904
+ ];
905
+ var G15 = (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0);
906
+ var G18 = (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0);
907
+ var G15_MASK = (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1);
908
+
909
+ var _this = {};
910
+
911
+ var getBCHDigit = function(data) {
912
+ var digit = 0;
913
+ while (data != 0) {
914
+ digit += 1;
915
+ data >>>= 1;
916
+ }
917
+ return digit;
918
+ };
919
+
920
+ _this.getBCHTypeInfo = function(data) {
921
+ var d = data << 10;
922
+ while (getBCHDigit(d) - getBCHDigit(G15) >= 0) {
923
+ d ^= (G15 << (getBCHDigit(d) - getBCHDigit(G15) ) );
924
+ }
925
+ return ( (data << 10) | d) ^ G15_MASK;
926
+ };
927
+
928
+ _this.getBCHTypeNumber = function(data) {
929
+ var d = data << 12;
930
+ while (getBCHDigit(d) - getBCHDigit(G18) >= 0) {
931
+ d ^= (G18 << (getBCHDigit(d) - getBCHDigit(G18) ) );
932
+ }
933
+ return (data << 12) | d;
934
+ };
935
+
936
+ _this.getPatternPosition = function(typeNumber) {
937
+ return PATTERN_POSITION_TABLE[typeNumber - 1];
938
+ };
939
+
940
+ _this.getMaskFunction = function(maskPattern) {
941
+
942
+ switch (maskPattern) {
943
+
944
+ case QRMaskPattern.PATTERN000 :
945
+ return function(i, j) { return (i + j) % 2 == 0; };
946
+ case QRMaskPattern.PATTERN001 :
947
+ return function(i, j) { return i % 2 == 0; };
948
+ case QRMaskPattern.PATTERN010 :
949
+ return function(i, j) { return j % 3 == 0; };
950
+ case QRMaskPattern.PATTERN011 :
951
+ return function(i, j) { return (i + j) % 3 == 0; };
952
+ case QRMaskPattern.PATTERN100 :
953
+ return function(i, j) { return (Math.floor(i / 2) + Math.floor(j / 3) ) % 2 == 0; };
954
+ case QRMaskPattern.PATTERN101 :
955
+ return function(i, j) { return (i * j) % 2 + (i * j) % 3 == 0; };
956
+ case QRMaskPattern.PATTERN110 :
957
+ return function(i, j) { return ( (i * j) % 2 + (i * j) % 3) % 2 == 0; };
958
+ case QRMaskPattern.PATTERN111 :
959
+ return function(i, j) { return ( (i * j) % 3 + (i + j) % 2) % 2 == 0; };
960
+
961
+ default :
962
+ throw 'bad maskPattern:' + maskPattern;
963
+ }
964
+ };
965
+
966
+ _this.getErrorCorrectPolynomial = function(errorCorrectLength) {
967
+ var a = qrPolynomial([1], 0);
968
+ for (var i = 0; i < errorCorrectLength; i += 1) {
969
+ a = a.multiply(qrPolynomial([1, QRMath.gexp(i)], 0) );
970
+ }
971
+ return a;
972
+ };
973
+
974
+ _this.getLengthInBits = function(mode, type) {
975
+
976
+ if (1 <= type && type < 10) {
977
+
978
+ // 1 - 9
979
+
980
+ switch(mode) {
981
+ case QRMode.MODE_NUMBER : return 10;
982
+ case QRMode.MODE_ALPHA_NUM : return 9;
983
+ case QRMode.MODE_8BIT_BYTE : return 8;
984
+ case QRMode.MODE_KANJI : return 8;
985
+ default :
986
+ throw 'mode:' + mode;
987
+ }
988
+
989
+ } else if (type < 27) {
990
+
991
+ // 10 - 26
992
+
993
+ switch(mode) {
994
+ case QRMode.MODE_NUMBER : return 12;
995
+ case QRMode.MODE_ALPHA_NUM : return 11;
996
+ case QRMode.MODE_8BIT_BYTE : return 16;
997
+ case QRMode.MODE_KANJI : return 10;
998
+ default :
999
+ throw 'mode:' + mode;
1000
+ }
1001
+
1002
+ } else if (type < 41) {
1003
+
1004
+ // 27 - 40
1005
+
1006
+ switch(mode) {
1007
+ case QRMode.MODE_NUMBER : return 14;
1008
+ case QRMode.MODE_ALPHA_NUM : return 13;
1009
+ case QRMode.MODE_8BIT_BYTE : return 16;
1010
+ case QRMode.MODE_KANJI : return 12;
1011
+ default :
1012
+ throw 'mode:' + mode;
1013
+ }
1014
+
1015
+ } else {
1016
+ throw 'type:' + type;
1017
+ }
1018
+ };
1019
+
1020
+ _this.getLostPoint = function(qrcode) {
1021
+
1022
+ var moduleCount = qrcode.getModuleCount();
1023
+
1024
+ var lostPoint = 0;
1025
+
1026
+ // LEVEL1
1027
+
1028
+ for (var row = 0; row < moduleCount; row += 1) {
1029
+ for (var col = 0; col < moduleCount; col += 1) {
1030
+
1031
+ var sameCount = 0;
1032
+ var dark = qrcode.isDark(row, col);
1033
+
1034
+ for (var r = -1; r <= 1; r += 1) {
1035
+
1036
+ if (row + r < 0 || moduleCount <= row + r) {
1037
+ continue;
1038
+ }
1039
+
1040
+ for (var c = -1; c <= 1; c += 1) {
1041
+
1042
+ if (col + c < 0 || moduleCount <= col + c) {
1043
+ continue;
1044
+ }
1045
+
1046
+ if (r == 0 && c == 0) {
1047
+ continue;
1048
+ }
1049
+
1050
+ if (dark == qrcode.isDark(row + r, col + c) ) {
1051
+ sameCount += 1;
1052
+ }
1053
+ }
1054
+ }
1055
+
1056
+ if (sameCount > 5) {
1057
+ lostPoint += (3 + sameCount - 5);
1058
+ }
1059
+ }
1060
+ };
1061
+
1062
+ // LEVEL2
1063
+
1064
+ for (var row = 0; row < moduleCount - 1; row += 1) {
1065
+ for (var col = 0; col < moduleCount - 1; col += 1) {
1066
+ var count = 0;
1067
+ if (qrcode.isDark(row, col) ) count += 1;
1068
+ if (qrcode.isDark(row + 1, col) ) count += 1;
1069
+ if (qrcode.isDark(row, col + 1) ) count += 1;
1070
+ if (qrcode.isDark(row + 1, col + 1) ) count += 1;
1071
+ if (count == 0 || count == 4) {
1072
+ lostPoint += 3;
1073
+ }
1074
+ }
1075
+ }
1076
+
1077
+ // LEVEL3
1078
+
1079
+ for (var row = 0; row < moduleCount; row += 1) {
1080
+ for (var col = 0; col < moduleCount - 6; col += 1) {
1081
+ if (qrcode.isDark(row, col)
1082
+ && !qrcode.isDark(row, col + 1)
1083
+ && qrcode.isDark(row, col + 2)
1084
+ && qrcode.isDark(row, col + 3)
1085
+ && qrcode.isDark(row, col + 4)
1086
+ && !qrcode.isDark(row, col + 5)
1087
+ && qrcode.isDark(row, col + 6) ) {
1088
+ lostPoint += 40;
1089
+ }
1090
+ }
1091
+ }
1092
+
1093
+ for (var col = 0; col < moduleCount; col += 1) {
1094
+ for (var row = 0; row < moduleCount - 6; row += 1) {
1095
+ if (qrcode.isDark(row, col)
1096
+ && !qrcode.isDark(row + 1, col)
1097
+ && qrcode.isDark(row + 2, col)
1098
+ && qrcode.isDark(row + 3, col)
1099
+ && qrcode.isDark(row + 4, col)
1100
+ && !qrcode.isDark(row + 5, col)
1101
+ && qrcode.isDark(row + 6, col) ) {
1102
+ lostPoint += 40;
1103
+ }
1104
+ }
1105
+ }
1106
+
1107
+ // LEVEL4
1108
+
1109
+ var darkCount = 0;
1110
+
1111
+ for (var col = 0; col < moduleCount; col += 1) {
1112
+ for (var row = 0; row < moduleCount; row += 1) {
1113
+ if (qrcode.isDark(row, col) ) {
1114
+ darkCount += 1;
1115
+ }
1116
+ }
1117
+ }
1118
+
1119
+ var ratio = Math.abs(100 * darkCount / moduleCount / moduleCount - 50) / 5;
1120
+ lostPoint += ratio * 10;
1121
+
1122
+ return lostPoint;
1123
+ };
1124
+
1125
+ return _this;
1126
+ }();
1127
+
1128
+ //---------------------------------------------------------------------
1129
+ // QRMath
1130
+ //---------------------------------------------------------------------
1131
+
1132
+ var QRMath = function() {
1133
+
1134
+ var EXP_TABLE = new Array(256);
1135
+ var LOG_TABLE = new Array(256);
1136
+
1137
+ // initialize tables
1138
+ for (var i = 0; i < 8; i += 1) {
1139
+ EXP_TABLE[i] = 1 << i;
1140
+ }
1141
+ for (var i = 8; i < 256; i += 1) {
1142
+ EXP_TABLE[i] = EXP_TABLE[i - 4]
1143
+ ^ EXP_TABLE[i - 5]
1144
+ ^ EXP_TABLE[i - 6]
1145
+ ^ EXP_TABLE[i - 8];
1146
+ }
1147
+ for (var i = 0; i < 255; i += 1) {
1148
+ LOG_TABLE[EXP_TABLE[i] ] = i;
1149
+ }
1150
+
1151
+ var _this = {};
1152
+
1153
+ _this.glog = function(n) {
1154
+
1155
+ if (n < 1) {
1156
+ throw 'glog(' + n + ')';
1157
+ }
1158
+
1159
+ return LOG_TABLE[n];
1160
+ };
1161
+
1162
+ _this.gexp = function(n) {
1163
+
1164
+ while (n < 0) {
1165
+ n += 255;
1166
+ }
1167
+
1168
+ while (n >= 256) {
1169
+ n -= 255;
1170
+ }
1171
+
1172
+ return EXP_TABLE[n];
1173
+ };
1174
+
1175
+ return _this;
1176
+ }();
1177
+
1178
+ //---------------------------------------------------------------------
1179
+ // qrPolynomial
1180
+ //---------------------------------------------------------------------
1181
+
1182
+ function qrPolynomial(num, shift) {
1183
+
1184
+ if (typeof num.length == 'undefined') {
1185
+ throw num.length + '/' + shift;
1186
+ }
1187
+
1188
+ var _num = function() {
1189
+ var offset = 0;
1190
+ while (offset < num.length && num[offset] == 0) {
1191
+ offset += 1;
1192
+ }
1193
+ var _num = new Array(num.length - offset + shift);
1194
+ for (var i = 0; i < num.length - offset; i += 1) {
1195
+ _num[i] = num[i + offset];
1196
+ }
1197
+ return _num;
1198
+ }();
1199
+
1200
+ var _this = {};
1201
+
1202
+ _this.getAt = function(index) {
1203
+ return _num[index];
1204
+ };
1205
+
1206
+ _this.getLength = function() {
1207
+ return _num.length;
1208
+ };
1209
+
1210
+ _this.multiply = function(e) {
1211
+
1212
+ var num = new Array(_this.getLength() + e.getLength() - 1);
1213
+
1214
+ for (var i = 0; i < _this.getLength(); i += 1) {
1215
+ for (var j = 0; j < e.getLength(); j += 1) {
1216
+ num[i + j] ^= QRMath.gexp(QRMath.glog(_this.getAt(i) ) + QRMath.glog(e.getAt(j) ) );
1217
+ }
1218
+ }
1219
+
1220
+ return qrPolynomial(num, 0);
1221
+ };
1222
+
1223
+ _this.mod = function(e) {
1224
+
1225
+ if (_this.getLength() - e.getLength() < 0) {
1226
+ return _this;
1227
+ }
1228
+
1229
+ var ratio = QRMath.glog(_this.getAt(0) ) - QRMath.glog(e.getAt(0) );
1230
+
1231
+ var num = new Array(_this.getLength() );
1232
+ for (var i = 0; i < _this.getLength(); i += 1) {
1233
+ num[i] = _this.getAt(i);
1234
+ }
1235
+
1236
+ for (var i = 0; i < e.getLength(); i += 1) {
1237
+ num[i] ^= QRMath.gexp(QRMath.glog(e.getAt(i) ) + ratio);
1238
+ }
1239
+
1240
+ // recursive call
1241
+ return qrPolynomial(num, 0).mod(e);
1242
+ };
1243
+
1244
+ return _this;
1245
+ };
1246
+
1247
+ //---------------------------------------------------------------------
1248
+ // QRRSBlock
1249
+ //---------------------------------------------------------------------
1250
+
1251
+ var QRRSBlock = function() {
1252
+
1253
+ var RS_BLOCK_TABLE = [
1254
+
1255
+ // L
1256
+ // M
1257
+ // Q
1258
+ // H
1259
+
1260
+ // 1
1261
+ [1, 26, 19],
1262
+ [1, 26, 16],
1263
+ [1, 26, 13],
1264
+ [1, 26, 9],
1265
+
1266
+ // 2
1267
+ [1, 44, 34],
1268
+ [1, 44, 28],
1269
+ [1, 44, 22],
1270
+ [1, 44, 16],
1271
+
1272
+ // 3
1273
+ [1, 70, 55],
1274
+ [1, 70, 44],
1275
+ [2, 35, 17],
1276
+ [2, 35, 13],
1277
+
1278
+ // 4
1279
+ [1, 100, 80],
1280
+ [2, 50, 32],
1281
+ [2, 50, 24],
1282
+ [4, 25, 9],
1283
+
1284
+ // 5
1285
+ [1, 134, 108],
1286
+ [2, 67, 43],
1287
+ [2, 33, 15, 2, 34, 16],
1288
+ [2, 33, 11, 2, 34, 12],
1289
+
1290
+ // 6
1291
+ [2, 86, 68],
1292
+ [4, 43, 27],
1293
+ [4, 43, 19],
1294
+ [4, 43, 15],
1295
+
1296
+ // 7
1297
+ [2, 98, 78],
1298
+ [4, 49, 31],
1299
+ [2, 32, 14, 4, 33, 15],
1300
+ [4, 39, 13, 1, 40, 14],
1301
+
1302
+ // 8
1303
+ [2, 121, 97],
1304
+ [2, 60, 38, 2, 61, 39],
1305
+ [4, 40, 18, 2, 41, 19],
1306
+ [4, 40, 14, 2, 41, 15],
1307
+
1308
+ // 9
1309
+ [2, 146, 116],
1310
+ [3, 58, 36, 2, 59, 37],
1311
+ [4, 36, 16, 4, 37, 17],
1312
+ [4, 36, 12, 4, 37, 13],
1313
+
1314
+ // 10
1315
+ [2, 86, 68, 2, 87, 69],
1316
+ [4, 69, 43, 1, 70, 44],
1317
+ [6, 43, 19, 2, 44, 20],
1318
+ [6, 43, 15, 2, 44, 16],
1319
+
1320
+ // 11
1321
+ [4, 101, 81],
1322
+ [1, 80, 50, 4, 81, 51],
1323
+ [4, 50, 22, 4, 51, 23],
1324
+ [3, 36, 12, 8, 37, 13],
1325
+
1326
+ // 12
1327
+ [2, 116, 92, 2, 117, 93],
1328
+ [6, 58, 36, 2, 59, 37],
1329
+ [4, 46, 20, 6, 47, 21],
1330
+ [7, 42, 14, 4, 43, 15],
1331
+
1332
+ // 13
1333
+ [4, 133, 107],
1334
+ [8, 59, 37, 1, 60, 38],
1335
+ [8, 44, 20, 4, 45, 21],
1336
+ [12, 33, 11, 4, 34, 12],
1337
+
1338
+ // 14
1339
+ [3, 145, 115, 1, 146, 116],
1340
+ [4, 64, 40, 5, 65, 41],
1341
+ [11, 36, 16, 5, 37, 17],
1342
+ [11, 36, 12, 5, 37, 13],
1343
+
1344
+ // 15
1345
+ [5, 109, 87, 1, 110, 88],
1346
+ [5, 65, 41, 5, 66, 42],
1347
+ [5, 54, 24, 7, 55, 25],
1348
+ [11, 36, 12, 7, 37, 13],
1349
+
1350
+ // 16
1351
+ [5, 122, 98, 1, 123, 99],
1352
+ [7, 73, 45, 3, 74, 46],
1353
+ [15, 43, 19, 2, 44, 20],
1354
+ [3, 45, 15, 13, 46, 16],
1355
+
1356
+ // 17
1357
+ [1, 135, 107, 5, 136, 108],
1358
+ [10, 74, 46, 1, 75, 47],
1359
+ [1, 50, 22, 15, 51, 23],
1360
+ [2, 42, 14, 17, 43, 15],
1361
+
1362
+ // 18
1363
+ [5, 150, 120, 1, 151, 121],
1364
+ [9, 69, 43, 4, 70, 44],
1365
+ [17, 50, 22, 1, 51, 23],
1366
+ [2, 42, 14, 19, 43, 15],
1367
+
1368
+ // 19
1369
+ [3, 141, 113, 4, 142, 114],
1370
+ [3, 70, 44, 11, 71, 45],
1371
+ [17, 47, 21, 4, 48, 22],
1372
+ [9, 39, 13, 16, 40, 14],
1373
+
1374
+ // 20
1375
+ [3, 135, 107, 5, 136, 108],
1376
+ [3, 67, 41, 13, 68, 42],
1377
+ [15, 54, 24, 5, 55, 25],
1378
+ [15, 43, 15, 10, 44, 16],
1379
+
1380
+ // 21
1381
+ [4, 144, 116, 4, 145, 117],
1382
+ [17, 68, 42],
1383
+ [17, 50, 22, 6, 51, 23],
1384
+ [19, 46, 16, 6, 47, 17],
1385
+
1386
+ // 22
1387
+ [2, 139, 111, 7, 140, 112],
1388
+ [17, 74, 46],
1389
+ [7, 54, 24, 16, 55, 25],
1390
+ [34, 37, 13],
1391
+
1392
+ // 23
1393
+ [4, 151, 121, 5, 152, 122],
1394
+ [4, 75, 47, 14, 76, 48],
1395
+ [11, 54, 24, 14, 55, 25],
1396
+ [16, 45, 15, 14, 46, 16],
1397
+
1398
+ // 24
1399
+ [6, 147, 117, 4, 148, 118],
1400
+ [6, 73, 45, 14, 74, 46],
1401
+ [11, 54, 24, 16, 55, 25],
1402
+ [30, 46, 16, 2, 47, 17],
1403
+
1404
+ // 25
1405
+ [8, 132, 106, 4, 133, 107],
1406
+ [8, 75, 47, 13, 76, 48],
1407
+ [7, 54, 24, 22, 55, 25],
1408
+ [22, 45, 15, 13, 46, 16],
1409
+
1410
+ // 26
1411
+ [10, 142, 114, 2, 143, 115],
1412
+ [19, 74, 46, 4, 75, 47],
1413
+ [28, 50, 22, 6, 51, 23],
1414
+ [33, 46, 16, 4, 47, 17],
1415
+
1416
+ // 27
1417
+ [8, 152, 122, 4, 153, 123],
1418
+ [22, 73, 45, 3, 74, 46],
1419
+ [8, 53, 23, 26, 54, 24],
1420
+ [12, 45, 15, 28, 46, 16],
1421
+
1422
+ // 28
1423
+ [3, 147, 117, 10, 148, 118],
1424
+ [3, 73, 45, 23, 74, 46],
1425
+ [4, 54, 24, 31, 55, 25],
1426
+ [11, 45, 15, 31, 46, 16],
1427
+
1428
+ // 29
1429
+ [7, 146, 116, 7, 147, 117],
1430
+ [21, 73, 45, 7, 74, 46],
1431
+ [1, 53, 23, 37, 54, 24],
1432
+ [19, 45, 15, 26, 46, 16],
1433
+
1434
+ // 30
1435
+ [5, 145, 115, 10, 146, 116],
1436
+ [19, 75, 47, 10, 76, 48],
1437
+ [15, 54, 24, 25, 55, 25],
1438
+ [23, 45, 15, 25, 46, 16],
1439
+
1440
+ // 31
1441
+ [13, 145, 115, 3, 146, 116],
1442
+ [2, 74, 46, 29, 75, 47],
1443
+ [42, 54, 24, 1, 55, 25],
1444
+ [23, 45, 15, 28, 46, 16],
1445
+
1446
+ // 32
1447
+ [17, 145, 115],
1448
+ [10, 74, 46, 23, 75, 47],
1449
+ [10, 54, 24, 35, 55, 25],
1450
+ [19, 45, 15, 35, 46, 16],
1451
+
1452
+ // 33
1453
+ [17, 145, 115, 1, 146, 116],
1454
+ [14, 74, 46, 21, 75, 47],
1455
+ [29, 54, 24, 19, 55, 25],
1456
+ [11, 45, 15, 46, 46, 16],
1457
+
1458
+ // 34
1459
+ [13, 145, 115, 6, 146, 116],
1460
+ [14, 74, 46, 23, 75, 47],
1461
+ [44, 54, 24, 7, 55, 25],
1462
+ [59, 46, 16, 1, 47, 17],
1463
+
1464
+ // 35
1465
+ [12, 151, 121, 7, 152, 122],
1466
+ [12, 75, 47, 26, 76, 48],
1467
+ [39, 54, 24, 14, 55, 25],
1468
+ [22, 45, 15, 41, 46, 16],
1469
+
1470
+ // 36
1471
+ [6, 151, 121, 14, 152, 122],
1472
+ [6, 75, 47, 34, 76, 48],
1473
+ [46, 54, 24, 10, 55, 25],
1474
+ [2, 45, 15, 64, 46, 16],
1475
+
1476
+ // 37
1477
+ [17, 152, 122, 4, 153, 123],
1478
+ [29, 74, 46, 14, 75, 47],
1479
+ [49, 54, 24, 10, 55, 25],
1480
+ [24, 45, 15, 46, 46, 16],
1481
+
1482
+ // 38
1483
+ [4, 152, 122, 18, 153, 123],
1484
+ [13, 74, 46, 32, 75, 47],
1485
+ [48, 54, 24, 14, 55, 25],
1486
+ [42, 45, 15, 32, 46, 16],
1487
+
1488
+ // 39
1489
+ [20, 147, 117, 4, 148, 118],
1490
+ [40, 75, 47, 7, 76, 48],
1491
+ [43, 54, 24, 22, 55, 25],
1492
+ [10, 45, 15, 67, 46, 16],
1493
+
1494
+ // 40
1495
+ [19, 148, 118, 6, 149, 119],
1496
+ [18, 75, 47, 31, 76, 48],
1497
+ [34, 54, 24, 34, 55, 25],
1498
+ [20, 45, 15, 61, 46, 16]
1499
+ ];
1500
+
1501
+ var qrRSBlock = function(totalCount, dataCount) {
1502
+ var _this = {};
1503
+ _this.totalCount = totalCount;
1504
+ _this.dataCount = dataCount;
1505
+ return _this;
1506
+ };
1507
+
1508
+ var _this = {};
1509
+
1510
+ var getRsBlockTable = function(typeNumber, errorCorrectionLevel) {
1511
+
1512
+ switch(errorCorrectionLevel) {
1513
+ case QRErrorCorrectionLevel.L :
1514
+ return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 0];
1515
+ case QRErrorCorrectionLevel.M :
1516
+ return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 1];
1517
+ case QRErrorCorrectionLevel.Q :
1518
+ return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 2];
1519
+ case QRErrorCorrectionLevel.H :
1520
+ return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3];
1521
+ default :
1522
+ return undefined;
1523
+ }
1524
+ };
1525
+
1526
+ _this.getRSBlocks = function(typeNumber, errorCorrectionLevel) {
1527
+
1528
+ var rsBlock = getRsBlockTable(typeNumber, errorCorrectionLevel);
1529
+
1530
+ if (typeof rsBlock == 'undefined') {
1531
+ throw 'bad rs block @ typeNumber:' + typeNumber +
1532
+ '/errorCorrectionLevel:' + errorCorrectionLevel;
1533
+ }
1534
+
1535
+ var length = rsBlock.length / 3;
1536
+
1537
+ var list = [];
1538
+
1539
+ for (var i = 0; i < length; i += 1) {
1540
+
1541
+ var count = rsBlock[i * 3 + 0];
1542
+ var totalCount = rsBlock[i * 3 + 1];
1543
+ var dataCount = rsBlock[i * 3 + 2];
1544
+
1545
+ for (var j = 0; j < count; j += 1) {
1546
+ list.push(qrRSBlock(totalCount, dataCount) );
1547
+ }
1548
+ }
1549
+
1550
+ return list;
1551
+ };
1552
+
1553
+ return _this;
1554
+ }();
1555
+
1556
+ //---------------------------------------------------------------------
1557
+ // qrBitBuffer
1558
+ //---------------------------------------------------------------------
1559
+
1560
+ var qrBitBuffer = function() {
1561
+
1562
+ var _buffer = [];
1563
+ var _length = 0;
1564
+
1565
+ var _this = {};
1566
+
1567
+ _this.getBuffer = function() {
1568
+ return _buffer;
1569
+ };
1570
+
1571
+ _this.getAt = function(index) {
1572
+ var bufIndex = Math.floor(index / 8);
1573
+ return ( (_buffer[bufIndex] >>> (7 - index % 8) ) & 1) == 1;
1574
+ };
1575
+
1576
+ _this.put = function(num, length) {
1577
+ for (var i = 0; i < length; i += 1) {
1578
+ _this.putBit( ( (num >>> (length - i - 1) ) & 1) == 1);
1579
+ }
1580
+ };
1581
+
1582
+ _this.getLengthInBits = function() {
1583
+ return _length;
1584
+ };
1585
+
1586
+ _this.putBit = function(bit) {
1587
+
1588
+ var bufIndex = Math.floor(_length / 8);
1589
+ if (_buffer.length <= bufIndex) {
1590
+ _buffer.push(0);
1591
+ }
1592
+
1593
+ if (bit) {
1594
+ _buffer[bufIndex] |= (0x80 >>> (_length % 8) );
1595
+ }
1596
+
1597
+ _length += 1;
1598
+ };
1599
+
1600
+ return _this;
1601
+ };
1602
+
1603
+ //---------------------------------------------------------------------
1604
+ // qrNumber
1605
+ //---------------------------------------------------------------------
1606
+
1607
+ var qrNumber = function(data) {
1608
+
1609
+ var _mode = QRMode.MODE_NUMBER;
1610
+ var _data = data;
1611
+
1612
+ var _this = {};
1613
+
1614
+ _this.getMode = function() {
1615
+ return _mode;
1616
+ };
1617
+
1618
+ _this.getLength = function(buffer) {
1619
+ return _data.length;
1620
+ };
1621
+
1622
+ _this.write = function(buffer) {
1623
+
1624
+ var data = _data;
1625
+
1626
+ var i = 0;
1627
+
1628
+ while (i + 2 < data.length) {
1629
+ buffer.put(strToNum(data.substring(i, i + 3) ), 10);
1630
+ i += 3;
1631
+ }
1632
+
1633
+ if (i < data.length) {
1634
+ if (data.length - i == 1) {
1635
+ buffer.put(strToNum(data.substring(i, i + 1) ), 4);
1636
+ } else if (data.length - i == 2) {
1637
+ buffer.put(strToNum(data.substring(i, i + 2) ), 7);
1638
+ }
1639
+ }
1640
+ };
1641
+
1642
+ var strToNum = function(s) {
1643
+ var num = 0;
1644
+ for (var i = 0; i < s.length; i += 1) {
1645
+ num = num * 10 + chatToNum(s.charAt(i) );
1646
+ }
1647
+ return num;
1648
+ };
1649
+
1650
+ var chatToNum = function(c) {
1651
+ if ('0' <= c && c <= '9') {
1652
+ return c.charCodeAt(0) - '0'.charCodeAt(0);
1653
+ }
1654
+ throw 'illegal char :' + c;
1655
+ };
1656
+
1657
+ return _this;
1658
+ };
1659
+
1660
+ //---------------------------------------------------------------------
1661
+ // qrAlphaNum
1662
+ //---------------------------------------------------------------------
1663
+
1664
+ var qrAlphaNum = function(data) {
1665
+
1666
+ var _mode = QRMode.MODE_ALPHA_NUM;
1667
+ var _data = data;
1668
+
1669
+ var _this = {};
1670
+
1671
+ _this.getMode = function() {
1672
+ return _mode;
1673
+ };
1674
+
1675
+ _this.getLength = function(buffer) {
1676
+ return _data.length;
1677
+ };
1678
+
1679
+ _this.write = function(buffer) {
1680
+
1681
+ var s = _data;
1682
+
1683
+ var i = 0;
1684
+
1685
+ while (i + 1 < s.length) {
1686
+ buffer.put(
1687
+ getCode(s.charAt(i) ) * 45 +
1688
+ getCode(s.charAt(i + 1) ), 11);
1689
+ i += 2;
1690
+ }
1691
+
1692
+ if (i < s.length) {
1693
+ buffer.put(getCode(s.charAt(i) ), 6);
1694
+ }
1695
+ };
1696
+
1697
+ var getCode = function(c) {
1698
+
1699
+ if ('0' <= c && c <= '9') {
1700
+ return c.charCodeAt(0) - '0'.charCodeAt(0);
1701
+ } else if ('A' <= c && c <= 'Z') {
1702
+ return c.charCodeAt(0) - 'A'.charCodeAt(0) + 10;
1703
+ } else {
1704
+ switch (c) {
1705
+ case ' ' : return 36;
1706
+ case '$' : return 37;
1707
+ case '%' : return 38;
1708
+ case '*' : return 39;
1709
+ case '+' : return 40;
1710
+ case '-' : return 41;
1711
+ case '.' : return 42;
1712
+ case '/' : return 43;
1713
+ case ':' : return 44;
1714
+ default :
1715
+ throw 'illegal char :' + c;
1716
+ }
1717
+ }
1718
+ };
1719
+
1720
+ return _this;
1721
+ };
1722
+
1723
+ //---------------------------------------------------------------------
1724
+ // qr8BitByte
1725
+ //---------------------------------------------------------------------
1726
+
1727
+ var qr8BitByte = function(data) {
1728
+
1729
+ var _mode = QRMode.MODE_8BIT_BYTE;
1730
+ var _data = data;
1731
+ var _bytes = qrcode.stringToBytes(data);
1732
+
1733
+ var _this = {};
1734
+
1735
+ _this.getMode = function() {
1736
+ return _mode;
1737
+ };
1738
+
1739
+ _this.getLength = function(buffer) {
1740
+ return _bytes.length;
1741
+ };
1742
+
1743
+ _this.write = function(buffer) {
1744
+ for (var i = 0; i < _bytes.length; i += 1) {
1745
+ buffer.put(_bytes[i], 8);
1746
+ }
1747
+ };
1748
+
1749
+ return _this;
1750
+ };
1751
+
1752
+ //---------------------------------------------------------------------
1753
+ // qrKanji
1754
+ //---------------------------------------------------------------------
1755
+
1756
+ var qrKanji = function(data) {
1757
+
1758
+ var _mode = QRMode.MODE_KANJI;
1759
+ var _data = data;
1760
+
1761
+ var stringToBytes = qrcode.stringToBytesFuncs['SJIS'];
1762
+ if (!stringToBytes) {
1763
+ throw 'sjis not supported.';
1764
+ }
1765
+ !function(c, code) {
1766
+ // self test for sjis support.
1767
+ var test = stringToBytes(c);
1768
+ if (test.length != 2 || ( (test[0] << 8) | test[1]) != code) {
1769
+ throw 'sjis not supported.';
1770
+ }
1771
+ }('\u53cb', 0x9746);
1772
+
1773
+ var _bytes = stringToBytes(data);
1774
+
1775
+ var _this = {};
1776
+
1777
+ _this.getMode = function() {
1778
+ return _mode;
1779
+ };
1780
+
1781
+ _this.getLength = function(buffer) {
1782
+ return ~~(_bytes.length / 2);
1783
+ };
1784
+
1785
+ _this.write = function(buffer) {
1786
+
1787
+ var data = _bytes;
1788
+
1789
+ var i = 0;
1790
+
1791
+ while (i + 1 < data.length) {
1792
+
1793
+ var c = ( (0xff & data[i]) << 8) | (0xff & data[i + 1]);
1794
+
1795
+ if (0x8140 <= c && c <= 0x9FFC) {
1796
+ c -= 0x8140;
1797
+ } else if (0xE040 <= c && c <= 0xEBBF) {
1798
+ c -= 0xC140;
1799
+ } else {
1800
+ throw 'illegal char at ' + (i + 1) + '/' + c;
1801
+ }
1802
+
1803
+ c = ( (c >>> 8) & 0xff) * 0xC0 + (c & 0xff);
1804
+
1805
+ buffer.put(c, 13);
1806
+
1807
+ i += 2;
1808
+ }
1809
+
1810
+ if (i < data.length) {
1811
+ throw 'illegal char at ' + (i + 1);
1812
+ }
1813
+ };
1814
+
1815
+ return _this;
1816
+ };
1817
+
1818
+ //=====================================================================
1819
+ // GIF Support etc.
1820
+ //
1821
+
1822
+ //---------------------------------------------------------------------
1823
+ // byteArrayOutputStream
1824
+ //---------------------------------------------------------------------
1825
+
1826
+ var byteArrayOutputStream = function() {
1827
+
1828
+ var _bytes = [];
1829
+
1830
+ var _this = {};
1831
+
1832
+ _this.writeByte = function(b) {
1833
+ _bytes.push(b & 0xff);
1834
+ };
1835
+
1836
+ _this.writeShort = function(i) {
1837
+ _this.writeByte(i);
1838
+ _this.writeByte(i >>> 8);
1839
+ };
1840
+
1841
+ _this.writeBytes = function(b, off, len) {
1842
+ off = off || 0;
1843
+ len = len || b.length;
1844
+ for (var i = 0; i < len; i += 1) {
1845
+ _this.writeByte(b[i + off]);
1846
+ }
1847
+ };
1848
+
1849
+ _this.writeString = function(s) {
1850
+ for (var i = 0; i < s.length; i += 1) {
1851
+ _this.writeByte(s.charCodeAt(i) );
1852
+ }
1853
+ };
1854
+
1855
+ _this.toByteArray = function() {
1856
+ return _bytes;
1857
+ };
1858
+
1859
+ _this.toString = function() {
1860
+ var s = '';
1861
+ s += '[';
1862
+ for (var i = 0; i < _bytes.length; i += 1) {
1863
+ if (i > 0) {
1864
+ s += ',';
1865
+ }
1866
+ s += _bytes[i];
1867
+ }
1868
+ s += ']';
1869
+ return s;
1870
+ };
1871
+
1872
+ return _this;
1873
+ };
1874
+
1875
+ //---------------------------------------------------------------------
1876
+ // base64EncodeOutputStream
1877
+ //---------------------------------------------------------------------
1878
+
1879
+ var base64EncodeOutputStream = function() {
1880
+
1881
+ var _buffer = 0;
1882
+ var _buflen = 0;
1883
+ var _length = 0;
1884
+ var _base64 = '';
1885
+
1886
+ var _this = {};
1887
+
1888
+ var writeEncoded = function(b) {
1889
+ _base64 += String.fromCharCode(encode(b & 0x3f) );
1890
+ };
1891
+
1892
+ var encode = function(n) {
1893
+ if (n < 0) {
1894
+ // error.
1895
+ } else if (n < 26) {
1896
+ return 0x41 + n;
1897
+ } else if (n < 52) {
1898
+ return 0x61 + (n - 26);
1899
+ } else if (n < 62) {
1900
+ return 0x30 + (n - 52);
1901
+ } else if (n == 62) {
1902
+ return 0x2b;
1903
+ } else if (n == 63) {
1904
+ return 0x2f;
1905
+ }
1906
+ throw 'n:' + n;
1907
+ };
1908
+
1909
+ _this.writeByte = function(n) {
1910
+
1911
+ _buffer = (_buffer << 8) | (n & 0xff);
1912
+ _buflen += 8;
1913
+ _length += 1;
1914
+
1915
+ while (_buflen >= 6) {
1916
+ writeEncoded(_buffer >>> (_buflen - 6) );
1917
+ _buflen -= 6;
1918
+ }
1919
+ };
1920
+
1921
+ _this.flush = function() {
1922
+
1923
+ if (_buflen > 0) {
1924
+ writeEncoded(_buffer << (6 - _buflen) );
1925
+ _buffer = 0;
1926
+ _buflen = 0;
1927
+ }
1928
+
1929
+ if (_length % 3 != 0) {
1930
+ // padding
1931
+ var padlen = 3 - _length % 3;
1932
+ for (var i = 0; i < padlen; i += 1) {
1933
+ _base64 += '=';
1934
+ }
1935
+ }
1936
+ };
1937
+
1938
+ _this.toString = function() {
1939
+ return _base64;
1940
+ };
1941
+
1942
+ return _this;
1943
+ };
1944
+
1945
+ //---------------------------------------------------------------------
1946
+ // base64DecodeInputStream
1947
+ //---------------------------------------------------------------------
1948
+
1949
+ var base64DecodeInputStream = function(str) {
1950
+
1951
+ var _str = str;
1952
+ var _pos = 0;
1953
+ var _buffer = 0;
1954
+ var _buflen = 0;
1955
+
1956
+ var _this = {};
1957
+
1958
+ _this.read = function() {
1959
+
1960
+ while (_buflen < 8) {
1961
+
1962
+ if (_pos >= _str.length) {
1963
+ if (_buflen == 0) {
1964
+ return -1;
1965
+ }
1966
+ throw 'unexpected end of file./' + _buflen;
1967
+ }
1968
+
1969
+ var c = _str.charAt(_pos);
1970
+ _pos += 1;
1971
+
1972
+ if (c == '=') {
1973
+ _buflen = 0;
1974
+ return -1;
1975
+ } else if (c.match(/^\s$/) ) {
1976
+ // ignore if whitespace.
1977
+ continue;
1978
+ }
1979
+
1980
+ _buffer = (_buffer << 6) | decode(c.charCodeAt(0) );
1981
+ _buflen += 6;
1982
+ }
1983
+
1984
+ var n = (_buffer >>> (_buflen - 8) ) & 0xff;
1985
+ _buflen -= 8;
1986
+ return n;
1987
+ };
1988
+
1989
+ var decode = function(c) {
1990
+ if (0x41 <= c && c <= 0x5a) {
1991
+ return c - 0x41;
1992
+ } else if (0x61 <= c && c <= 0x7a) {
1993
+ return c - 0x61 + 26;
1994
+ } else if (0x30 <= c && c <= 0x39) {
1995
+ return c - 0x30 + 52;
1996
+ } else if (c == 0x2b) {
1997
+ return 62;
1998
+ } else if (c == 0x2f) {
1999
+ return 63;
2000
+ } else {
2001
+ throw 'c:' + c;
2002
+ }
2003
+ };
2004
+
2005
+ return _this;
2006
+ };
2007
+
2008
+ //---------------------------------------------------------------------
2009
+ // gifImage (B/W)
2010
+ //---------------------------------------------------------------------
2011
+
2012
+ var gifImage = function(width, height) {
2013
+
2014
+ var _width = width;
2015
+ var _height = height;
2016
+ var _data = new Array(width * height);
2017
+
2018
+ var _this = {};
2019
+
2020
+ _this.setPixel = function(x, y, pixel) {
2021
+ _data[y * _width + x] = pixel;
2022
+ };
2023
+
2024
+ _this.write = function(out) {
2025
+
2026
+ //---------------------------------
2027
+ // GIF Signature
2028
+
2029
+ out.writeString('GIF87a');
2030
+
2031
+ //---------------------------------
2032
+ // Screen Descriptor
2033
+
2034
+ out.writeShort(_width);
2035
+ out.writeShort(_height);
2036
+
2037
+ out.writeByte(0x80); // 2bit
2038
+ out.writeByte(0);
2039
+ out.writeByte(0);
2040
+
2041
+ //---------------------------------
2042
+ // Global Color Map
2043
+
2044
+ // black
2045
+ out.writeByte(0x00);
2046
+ out.writeByte(0x00);
2047
+ out.writeByte(0x00);
2048
+
2049
+ // white
2050
+ out.writeByte(0xff);
2051
+ out.writeByte(0xff);
2052
+ out.writeByte(0xff);
2053
+
2054
+ //---------------------------------
2055
+ // Image Descriptor
2056
+
2057
+ out.writeString(',');
2058
+ out.writeShort(0);
2059
+ out.writeShort(0);
2060
+ out.writeShort(_width);
2061
+ out.writeShort(_height);
2062
+ out.writeByte(0);
2063
+
2064
+ //---------------------------------
2065
+ // Local Color Map
2066
+
2067
+ //---------------------------------
2068
+ // Raster Data
2069
+
2070
+ var lzwMinCodeSize = 2;
2071
+ var raster = getLZWRaster(lzwMinCodeSize);
2072
+
2073
+ out.writeByte(lzwMinCodeSize);
2074
+
2075
+ var offset = 0;
2076
+
2077
+ while (raster.length - offset > 255) {
2078
+ out.writeByte(255);
2079
+ out.writeBytes(raster, offset, 255);
2080
+ offset += 255;
2081
+ }
2082
+
2083
+ out.writeByte(raster.length - offset);
2084
+ out.writeBytes(raster, offset, raster.length - offset);
2085
+ out.writeByte(0x00);
2086
+
2087
+ //---------------------------------
2088
+ // GIF Terminator
2089
+ out.writeString(';');
2090
+ };
2091
+
2092
+ var bitOutputStream = function(out) {
2093
+
2094
+ var _out = out;
2095
+ var _bitLength = 0;
2096
+ var _bitBuffer = 0;
2097
+
2098
+ var _this = {};
2099
+
2100
+ _this.write = function(data, length) {
2101
+
2102
+ if ( (data >>> length) != 0) {
2103
+ throw 'length over';
2104
+ }
2105
+
2106
+ while (_bitLength + length >= 8) {
2107
+ _out.writeByte(0xff & ( (data << _bitLength) | _bitBuffer) );
2108
+ length -= (8 - _bitLength);
2109
+ data >>>= (8 - _bitLength);
2110
+ _bitBuffer = 0;
2111
+ _bitLength = 0;
2112
+ }
2113
+
2114
+ _bitBuffer = (data << _bitLength) | _bitBuffer;
2115
+ _bitLength = _bitLength + length;
2116
+ };
2117
+
2118
+ _this.flush = function() {
2119
+ if (_bitLength > 0) {
2120
+ _out.writeByte(_bitBuffer);
2121
+ }
2122
+ };
2123
+
2124
+ return _this;
2125
+ };
2126
+
2127
+ var getLZWRaster = function(lzwMinCodeSize) {
2128
+
2129
+ var clearCode = 1 << lzwMinCodeSize;
2130
+ var endCode = (1 << lzwMinCodeSize) + 1;
2131
+ var bitLength = lzwMinCodeSize + 1;
2132
+
2133
+ // Setup LZWTable
2134
+ var table = lzwTable();
2135
+
2136
+ for (var i = 0; i < clearCode; i += 1) {
2137
+ table.add(String.fromCharCode(i) );
2138
+ }
2139
+ table.add(String.fromCharCode(clearCode) );
2140
+ table.add(String.fromCharCode(endCode) );
2141
+
2142
+ var byteOut = byteArrayOutputStream();
2143
+ var bitOut = bitOutputStream(byteOut);
2144
+
2145
+ // clear code
2146
+ bitOut.write(clearCode, bitLength);
2147
+
2148
+ var dataIndex = 0;
2149
+
2150
+ var s = String.fromCharCode(_data[dataIndex]);
2151
+ dataIndex += 1;
2152
+
2153
+ while (dataIndex < _data.length) {
2154
+
2155
+ var c = String.fromCharCode(_data[dataIndex]);
2156
+ dataIndex += 1;
2157
+
2158
+ if (table.contains(s + c) ) {
2159
+
2160
+ s = s + c;
2161
+
2162
+ } else {
2163
+
2164
+ bitOut.write(table.indexOf(s), bitLength);
2165
+
2166
+ if (table.size() < 0xfff) {
2167
+
2168
+ if (table.size() == (1 << bitLength) ) {
2169
+ bitLength += 1;
2170
+ }
2171
+
2172
+ table.add(s + c);
2173
+ }
2174
+
2175
+ s = c;
2176
+ }
2177
+ }
2178
+
2179
+ bitOut.write(table.indexOf(s), bitLength);
2180
+
2181
+ // end code
2182
+ bitOut.write(endCode, bitLength);
2183
+
2184
+ bitOut.flush();
2185
+
2186
+ return byteOut.toByteArray();
2187
+ };
2188
+
2189
+ var lzwTable = function() {
2190
+
2191
+ var _map = {};
2192
+ var _size = 0;
2193
+
2194
+ var _this = {};
2195
+
2196
+ _this.add = function(key) {
2197
+ if (_this.contains(key) ) {
2198
+ throw 'dup key:' + key;
2199
+ }
2200
+ _map[key] = _size;
2201
+ _size += 1;
2202
+ };
2203
+
2204
+ _this.size = function() {
2205
+ return _size;
2206
+ };
2207
+
2208
+ _this.indexOf = function(key) {
2209
+ return _map[key];
2210
+ };
2211
+
2212
+ _this.contains = function(key) {
2213
+ return typeof _map[key] != 'undefined';
2214
+ };
2215
+
2216
+ return _this;
2217
+ };
2218
+
2219
+ return _this;
2220
+ };
2221
+
2222
+ var createDataURL = function(width, height, getPixel) {
2223
+ var gif = gifImage(width, height);
2224
+ for (var y = 0; y < height; y += 1) {
2225
+ for (var x = 0; x < width; x += 1) {
2226
+ gif.setPixel(x, y, getPixel(x, y) );
2227
+ }
2228
+ }
2229
+
2230
+ var b = byteArrayOutputStream();
2231
+ gif.write(b);
2232
+
2233
+ var base64 = base64EncodeOutputStream();
2234
+ var bytes = b.toByteArray();
2235
+ for (var i = 0; i < bytes.length; i += 1) {
2236
+ base64.writeByte(bytes[i]);
2237
+ }
2238
+ base64.flush();
2239
+
2240
+ return 'data:image/gif;base64,' + base64;
2241
+ };
2242
+
2243
+ //---------------------------------------------------------------------
2244
+ // returns qrcode function.
2245
+
2246
+ return qrcode;
2247
+ }();
2248
+
2249
+ // multibyte support
2250
+ !function() {
2251
+
2252
+ qrcode.stringToBytesFuncs['UTF-8'] = function(s) {
2253
+ // http://stackoverflow.com/questions/18729405/how-to-convert-utf8-string-to-byte-array
2254
+ function toUTF8Array(str) {
2255
+ var utf8 = [];
2256
+ for (var i=0; i < str.length; i++) {
2257
+ var charcode = str.charCodeAt(i);
2258
+ if (charcode < 0x80) utf8.push(charcode);
2259
+ else if (charcode < 0x800) {
2260
+ utf8.push(0xc0 | (charcode >> 6),
2261
+ 0x80 | (charcode & 0x3f));
2262
+ }
2263
+ else if (charcode < 0xd800 || charcode >= 0xe000) {
2264
+ utf8.push(0xe0 | (charcode >> 12),
2265
+ 0x80 | ((charcode>>6) & 0x3f),
2266
+ 0x80 | (charcode & 0x3f));
2267
+ }
2268
+ // surrogate pair
2269
+ else {
2270
+ i++;
2271
+ // UTF-16 encodes 0x10000-0x10FFFF by
2272
+ // subtracting 0x10000 and splitting the
2273
+ // 20 bits of 0x0-0xFFFFF into two halves
2274
+ charcode = 0x10000 + (((charcode & 0x3ff)<<10)
2275
+ | (str.charCodeAt(i) & 0x3ff));
2276
+ utf8.push(0xf0 | (charcode >>18),
2277
+ 0x80 | ((charcode>>12) & 0x3f),
2278
+ 0x80 | ((charcode>>6) & 0x3f),
2279
+ 0x80 | (charcode & 0x3f));
2280
+ }
2281
+ }
2282
+ return utf8;
2283
+ }
2284
+ return toUTF8Array(s);
2285
+ };
2286
+
2287
+ }();
2288
+
2289
+ (function (factory) {
2290
+ if (typeof define === 'function' && define.amd) {
2291
+ define([], factory);
2292
+ } else if (typeof exports === 'object') {
2293
+ module.exports = factory();
2294
+ }
2295
+ }(function () {
2296
+ return qrcode;
2297
+ }));