diff options
Diffstat (limited to 'devtools/shared/qrcode')
-rw-r--r-- | devtools/shared/qrcode/decoder/LICENSE | 201 | ||||
-rw-r--r-- | devtools/shared/qrcode/decoder/index.js | 2374 | ||||
-rw-r--r-- | devtools/shared/qrcode/decoder/moz.build | 9 | ||||
-rw-r--r-- | devtools/shared/qrcode/encoder/LICENSE | 19 | ||||
-rw-r--r-- | devtools/shared/qrcode/encoder/index.js | 1674 | ||||
-rw-r--r-- | devtools/shared/qrcode/encoder/moz.build | 9 | ||||
-rw-r--r-- | devtools/shared/qrcode/index.js | 116 | ||||
-rw-r--r-- | devtools/shared/qrcode/moz.build | 18 | ||||
-rw-r--r-- | devtools/shared/qrcode/tests/chrome/chrome.ini | 5 | ||||
-rw-r--r-- | devtools/shared/qrcode/tests/chrome/test_decode.html | 66 | ||||
-rw-r--r-- | devtools/shared/qrcode/tests/xpcshell/.eslintrc.js | 6 | ||||
-rw-r--r-- | devtools/shared/qrcode/tests/xpcshell/test_encode.js | 30 | ||||
-rw-r--r-- | devtools/shared/qrcode/tests/xpcshell/xpcshell.ini | 6 |
13 files changed, 4533 insertions, 0 deletions
diff --git a/devtools/shared/qrcode/decoder/LICENSE b/devtools/shared/qrcode/decoder/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/devtools/shared/qrcode/decoder/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/devtools/shared/qrcode/decoder/index.js b/devtools/shared/qrcode/decoder/index.js new file mode 100644 index 0000000000..f99a947cf4 --- /dev/null +++ b/devtools/shared/qrcode/decoder/index.js @@ -0,0 +1,2374 @@ +/* + Ported to JavaScript by Lazar Laszlo 2011 + + lazarsoft@gmail.com, www.lazarsoft.info +*/ +/* +* +* Copyright 2007 ZXing authors +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +var imgU8 = null; + +var imgU32 = null; + +var imgWidth = 0; + +var imgHeight = 0; + +var maxImgSize = 1024 * 1024; + +var sizeOfDataLengthInfo = [ [ 10, 9, 8, 8 ], [ 12, 11, 16, 10 ], [ 14, 13, 16, 12 ] ]; + +var GridSampler = {}; + +GridSampler.checkAndNudgePoints = function(image, points) { + let width = imgWidth; + let height = imgHeight; + let nudged = true; + for (let offset = 0; offset < points.length && nudged; offset += 2) { + let x = Math.floor(points[offset]); + let y = Math.floor(points[offset + 1]); + if (x < -1 || x > width || y < -1 || y > height) { + throw "Error.checkAndNudgePoints "; + } + nudged = false; + if (x == -1) { + points[offset] = 0; + nudged = true; + } else if (x == width) { + points[offset] = width - 1; + nudged = true; + } + if (y == -1) { + points[offset + 1] = 0; + nudged = true; + } else if (y == height) { + points[offset + 1] = height - 1; + nudged = true; + } + } + nudged = true; + for (let offset = points.length - 2; offset >= 0 && nudged; offset -= 2) { + let x = Math.floor(points[offset]); + let y = Math.floor(points[offset + 1]); + if (x < -1 || x > width || y < -1 || y > height) { + throw "Error.checkAndNudgePoints "; + } + nudged = false; + if (x == -1) { + points[offset] = 0; + nudged = true; + } else if (x == width) { + points[offset] = width - 1; + nudged = true; + } + if (y == -1) { + points[offset + 1] = 0; + nudged = true; + } else if (y == height) { + points[offset + 1] = height - 1; + nudged = true; + } + } +}; + +GridSampler.sampleGrid3 = function(image, dimension, transform) { + let bits = new BitMatrix(dimension); + let points = new Array(dimension << 1); + for (let y = 0; y < dimension; y++) { + let max = points.length; + let iValue = y + 0.5; + for (let x = 0; x < max; x += 2) { + points[x] = (x >> 1) + 0.5; + points[x + 1] = iValue; + } + transform.transformPoints1(points); + GridSampler.checkAndNudgePoints(image, points); + try { + for (let x = 0; x < max; x += 2) { + let xpoint = Math.floor(points[x]) * 4 + Math.floor(points[x + 1]) * imgWidth * 4; + let bit = image[Math.floor(points[x]) + imgWidth * Math.floor(points[x + 1])]; + imgU8[xpoint] = bit ? 255 : 0; + imgU8[xpoint + 1] = bit ? 255 : 0; + imgU8[xpoint + 2] = 0; + imgU8[xpoint + 3] = 255; + if (bit) bits.set_Renamed(x >> 1, y); + } + } catch (aioobe) { + throw "Error.checkAndNudgePoints"; + } + } + return bits; +}; + +GridSampler.sampleGridx = function(image, dimension, p1ToX, p1ToY, p2ToX, p2ToY, p3ToX, p3ToY, p4ToX, p4ToY, p1FromX, p1FromY, p2FromX, p2FromY, p3FromX, p3FromY, p4FromX, p4FromY) { + let transform = PerspectiveTransform.quadrilateralToQuadrilateral(p1ToX, p1ToY, p2ToX, p2ToY, p3ToX, p3ToY, p4ToX, p4ToY, p1FromX, p1FromY, p2FromX, p2FromY, p3FromX, p3FromY, p4FromX, p4FromY); + return GridSampler.sampleGrid3(image, dimension, transform); +}; + +function ECB(count, dataCodewords) { + this.count = count; + this.dataCodewords = dataCodewords; + this.__defineGetter__("Count", function() { + return this.count; + }); + this.__defineGetter__("DataCodewords", function() { + return this.dataCodewords; + }); +} + +function ECBlocks(ecCodewordsPerBlock, ecBlocks1, ecBlocks2) { + this.ecCodewordsPerBlock = ecCodewordsPerBlock; + if (ecBlocks2) this.ecBlocks = new Array(ecBlocks1, ecBlocks2); else this.ecBlocks = new Array(ecBlocks1); + this.__defineGetter__("ECCodewordsPerBlock", function() { + return this.ecCodewordsPerBlock; + }); + this.__defineGetter__("TotalECCodewords", function() { + return this.ecCodewordsPerBlock * this.NumBlocks; + }); + this.__defineGetter__("NumBlocks", function() { + let total = 0; + for (let i = 0; i < this.ecBlocks.length; i++) { + total += this.ecBlocks[i].length; + } + return total; + }); + this.getECBlocks = function() { + return this.ecBlocks; + }; +} + +function Version(versionNumber, alignmentPatternCenters, ecBlocks1, ecBlocks2, ecBlocks3, ecBlocks4) { + this.versionNumber = versionNumber; + this.alignmentPatternCenters = alignmentPatternCenters; + this.ecBlocks = new Array(ecBlocks1, ecBlocks2, ecBlocks3, ecBlocks4); + let total = 0; + let ecCodewords = ecBlocks1.ECCodewordsPerBlock; + let ecbArray = ecBlocks1.getECBlocks(); + for (let i = 0; i < ecbArray.length; i++) { + let ecBlock = ecbArray[i]; + total += ecBlock.Count * (ecBlock.DataCodewords + ecCodewords); + } + this.totalCodewords = total; + this.__defineGetter__("VersionNumber", function() { + return this.versionNumber; + }); + this.__defineGetter__("AlignmentPatternCenters", function() { + return this.alignmentPatternCenters; + }); + this.__defineGetter__("TotalCodewords", function() { + return this.totalCodewords; + }); + this.__defineGetter__("DimensionForVersion", function() { + return 17 + 4 * this.versionNumber; + }); + this.buildFunctionPattern = function() { + let dimension = this.DimensionForVersion; + let bitMatrix = new BitMatrix(dimension); + bitMatrix.setRegion(0, 0, 9, 9); + bitMatrix.setRegion(dimension - 8, 0, 8, 9); + bitMatrix.setRegion(0, dimension - 8, 9, 8); + let max = this.alignmentPatternCenters.length; + for (let x = 0; x < max; x++) { + let i = this.alignmentPatternCenters[x] - 2; + for (let y = 0; y < max; y++) { + if (x === 0 && (y === 0 || y === max - 1) || x === max - 1 && y === 0) { + continue; + } + bitMatrix.setRegion(this.alignmentPatternCenters[y] - 2, i, 5, 5); + } + } + bitMatrix.setRegion(6, 9, 1, dimension - 17); + bitMatrix.setRegion(9, 6, dimension - 17, 1); + if (this.versionNumber > 6) { + bitMatrix.setRegion(dimension - 11, 0, 3, 6); + bitMatrix.setRegion(0, dimension - 11, 6, 3); + } + return bitMatrix; + }; + this.getECBlocksForLevel = function(ecLevel) { + return this.ecBlocks[ecLevel.ordinal()]; + }; +} + +Version.VERSION_DECODE_INFO = new Array(31892, 34236, 39577, 42195, 48118, 51042, 55367, 58893, 63784, 68472, 70749, 76311, 79154, 84390, 87683, 92361, 96236, 102084, 102881, 110507, 110734, 117786, 119615, 126325, 127568, 133589, 136944, 141498, 145311, 150283, 152622, 158308, 161089, 167017); + +Version.VERSIONS = buildVersions(); + +Version.getVersionForNumber = function(versionNumber) { + if (versionNumber < 1 || versionNumber > 40) { + throw "ArgumentException"; + } + return Version.VERSIONS[versionNumber - 1]; +}; + +Version.getProvisionalVersionForDimension = function(dimension) { + if (dimension % 4 != 1) { + throw "Error getProvisionalVersionForDimension"; + } + try { + return Version.getVersionForNumber(dimension - 17 >> 2); + } catch (iae) { + throw "Error getVersionForNumber"; + } +}; + +Version.decodeVersionInformation = function(versionBits) { + let bestDifference = 4294967295; + let bestVersion = 0; + for (let i = 0; i < Version.VERSION_DECODE_INFO.length; i++) { + let targetVersion = Version.VERSION_DECODE_INFO[i]; + if (targetVersion == versionBits) { + return this.getVersionForNumber(i + 7); + } + let bitsDifference = FormatInformation.numBitsDiffering(versionBits, targetVersion); + if (bitsDifference < bestDifference) { + bestVersion = i + 7; + bestDifference = bitsDifference; + } + } + if (bestDifference <= 3) { + return this.getVersionForNumber(bestVersion); + } + return null; +}; + +function buildVersions() { + return new Array(new Version(1, new Array(), new ECBlocks(7, new ECB(1, 19)), new ECBlocks(10, new ECB(1, 16)), new ECBlocks(13, new ECB(1, 13)), new ECBlocks(17, new ECB(1, 9))), new Version(2, new Array(6, 18), new ECBlocks(10, new ECB(1, 34)), new ECBlocks(16, new ECB(1, 28)), new ECBlocks(22, new ECB(1, 22)), new ECBlocks(28, new ECB(1, 16))), new Version(3, new Array(6, 22), new ECBlocks(15, new ECB(1, 55)), new ECBlocks(26, new ECB(1, 44)), new ECBlocks(18, new ECB(2, 17)), new ECBlocks(22, new ECB(2, 13))), new Version(4, new Array(6, 26), new ECBlocks(20, new ECB(1, 80)), new ECBlocks(18, new ECB(2, 32)), new ECBlocks(26, new ECB(2, 24)), new ECBlocks(16, new ECB(4, 9))), new Version(5, new Array(6, 30), new ECBlocks(26, new ECB(1, 108)), new ECBlocks(24, new ECB(2, 43)), new ECBlocks(18, new ECB(2, 15), new ECB(2, 16)), new ECBlocks(22, new ECB(2, 11), new ECB(2, 12))), new Version(6, new Array(6, 34), new ECBlocks(18, new ECB(2, 68)), new ECBlocks(16, new ECB(4, 27)), new ECBlocks(24, new ECB(4, 19)), new ECBlocks(28, new ECB(4, 15))), new Version(7, new Array(6, 22, 38), new ECBlocks(20, new ECB(2, 78)), new ECBlocks(18, new ECB(4, 31)), new ECBlocks(18, new ECB(2, 14), new ECB(4, 15)), new ECBlocks(26, new ECB(4, 13), new ECB(1, 14))), new Version(8, new Array(6, 24, 42), new ECBlocks(24, new ECB(2, 97)), new ECBlocks(22, new ECB(2, 38), new ECB(2, 39)), new ECBlocks(22, new ECB(4, 18), new ECB(2, 19)), new ECBlocks(26, new ECB(4, 14), new ECB(2, 15))), new Version(9, new Array(6, 26, 46), new ECBlocks(30, new ECB(2, 116)), new ECBlocks(22, new ECB(3, 36), new ECB(2, 37)), new ECBlocks(20, new ECB(4, 16), new ECB(4, 17)), new ECBlocks(24, new ECB(4, 12), new ECB(4, 13))), new Version(10, new Array(6, 28, 50), new ECBlocks(18, new ECB(2, 68), new ECB(2, 69)), new ECBlocks(26, new ECB(4, 43), new ECB(1, 44)), new ECBlocks(24, new ECB(6, 19), new ECB(2, 20)), new ECBlocks(28, new ECB(6, 15), new ECB(2, 16))), new Version(11, new Array(6, 30, 54), new ECBlocks(20, new ECB(4, 81)), new ECBlocks(30, new ECB(1, 50), new ECB(4, 51)), new ECBlocks(28, new ECB(4, 22), new ECB(4, 23)), new ECBlocks(24, new ECB(3, 12), new ECB(8, 13))), new Version(12, new Array(6, 32, 58), new ECBlocks(24, new ECB(2, 92), new ECB(2, 93)), new ECBlocks(22, new ECB(6, 36), new ECB(2, 37)), new ECBlocks(26, new ECB(4, 20), new ECB(6, 21)), new ECBlocks(28, new ECB(7, 14), new ECB(4, 15))), new Version(13, new Array(6, 34, 62), new ECBlocks(26, new ECB(4, 107)), new ECBlocks(22, new ECB(8, 37), new ECB(1, 38)), new ECBlocks(24, new ECB(8, 20), new ECB(4, 21)), new ECBlocks(22, new ECB(12, 11), new ECB(4, 12))), new Version(14, new Array(6, 26, 46, 66), new ECBlocks(30, new ECB(3, 115), new ECB(1, 116)), new ECBlocks(24, new ECB(4, 40), new ECB(5, 41)), new ECBlocks(20, new ECB(11, 16), new ECB(5, 17)), new ECBlocks(24, new ECB(11, 12), new ECB(5, 13))), new Version(15, new Array(6, 26, 48, 70), new ECBlocks(22, new ECB(5, 87), new ECB(1, 88)), new ECBlocks(24, new ECB(5, 41), new ECB(5, 42)), new ECBlocks(30, new ECB(5, 24), new ECB(7, 25)), new ECBlocks(24, new ECB(11, 12), new ECB(7, 13))), new Version(16, new Array(6, 26, 50, 74), new ECBlocks(24, new ECB(5, 98), new ECB(1, 99)), new ECBlocks(28, new ECB(7, 45), new ECB(3, 46)), new ECBlocks(24, new ECB(15, 19), new ECB(2, 20)), new ECBlocks(30, new ECB(3, 15), new ECB(13, 16))), new Version(17, new Array(6, 30, 54, 78), new ECBlocks(28, new ECB(1, 107), new ECB(5, 108)), new ECBlocks(28, new ECB(10, 46), new ECB(1, 47)), new ECBlocks(28, new ECB(1, 22), new ECB(15, 23)), new ECBlocks(28, new ECB(2, 14), new ECB(17, 15))), new Version(18, new Array(6, 30, 56, 82), new ECBlocks(30, new ECB(5, 120), new ECB(1, 121)), new ECBlocks(26, new ECB(9, 43), new ECB(4, 44)), new ECBlocks(28, new ECB(17, 22), new ECB(1, 23)), new ECBlocks(28, new ECB(2, 14), new ECB(19, 15))), new Version(19, new Array(6, 30, 58, 86), new ECBlocks(28, new ECB(3, 113), new ECB(4, 114)), new ECBlocks(26, new ECB(3, 44), new ECB(11, 45)), new ECBlocks(26, new ECB(17, 21), new ECB(4, 22)), new ECBlocks(26, new ECB(9, 13), new ECB(16, 14))), new Version(20, new Array(6, 34, 62, 90), new ECBlocks(28, new ECB(3, 107), new ECB(5, 108)), new ECBlocks(26, new ECB(3, 41), new ECB(13, 42)), new ECBlocks(30, new ECB(15, 24), new ECB(5, 25)), new ECBlocks(28, new ECB(15, 15), new ECB(10, 16))), new Version(21, new Array(6, 28, 50, 72, 94), new ECBlocks(28, new ECB(4, 116), new ECB(4, 117)), new ECBlocks(26, new ECB(17, 42)), new ECBlocks(28, new ECB(17, 22), new ECB(6, 23)), new ECBlocks(30, new ECB(19, 16), new ECB(6, 17))), new Version(22, new Array(6, 26, 50, 74, 98), new ECBlocks(28, new ECB(2, 111), new ECB(7, 112)), new ECBlocks(28, new ECB(17, 46)), new ECBlocks(30, new ECB(7, 24), new ECB(16, 25)), new ECBlocks(24, new ECB(34, 13))), new Version(23, new Array(6, 30, 54, 74, 102), new ECBlocks(30, new ECB(4, 121), new ECB(5, 122)), new ECBlocks(28, new ECB(4, 47), new ECB(14, 48)), new ECBlocks(30, new ECB(11, 24), new ECB(14, 25)), new ECBlocks(30, new ECB(16, 15), new ECB(14, 16))), new Version(24, new Array(6, 28, 54, 80, 106), new ECBlocks(30, new ECB(6, 117), new ECB(4, 118)), new ECBlocks(28, new ECB(6, 45), new ECB(14, 46)), new ECBlocks(30, new ECB(11, 24), new ECB(16, 25)), new ECBlocks(30, new ECB(30, 16), new ECB(2, 17))), new Version(25, new Array(6, 32, 58, 84, 110), new ECBlocks(26, new ECB(8, 106), new ECB(4, 107)), new ECBlocks(28, new ECB(8, 47), new ECB(13, 48)), new ECBlocks(30, new ECB(7, 24), new ECB(22, 25)), new ECBlocks(30, new ECB(22, 15), new ECB(13, 16))), new Version(26, new Array(6, 30, 58, 86, 114), new ECBlocks(28, new ECB(10, 114), new ECB(2, 115)), new ECBlocks(28, new ECB(19, 46), new ECB(4, 47)), new ECBlocks(28, new ECB(28, 22), new ECB(6, 23)), new ECBlocks(30, new ECB(33, 16), new ECB(4, 17))), new Version(27, new Array(6, 34, 62, 90, 118), new ECBlocks(30, new ECB(8, 122), new ECB(4, 123)), new ECBlocks(28, new ECB(22, 45), new ECB(3, 46)), new ECBlocks(30, new ECB(8, 23), new ECB(26, 24)), new ECBlocks(30, new ECB(12, 15), new ECB(28, 16))), new Version(28, new Array(6, 26, 50, 74, 98, 122), new ECBlocks(30, new ECB(3, 117), new ECB(10, 118)), new ECBlocks(28, new ECB(3, 45), new ECB(23, 46)), new ECBlocks(30, new ECB(4, 24), new ECB(31, 25)), new ECBlocks(30, new ECB(11, 15), new ECB(31, 16))), new Version(29, new Array(6, 30, 54, 78, 102, 126), new ECBlocks(30, new ECB(7, 116), new ECB(7, 117)), new ECBlocks(28, new ECB(21, 45), new ECB(7, 46)), new ECBlocks(30, new ECB(1, 23), new ECB(37, 24)), new ECBlocks(30, new ECB(19, 15), new ECB(26, 16))), new Version(30, new Array(6, 26, 52, 78, 104, 130), new ECBlocks(30, new ECB(5, 115), new ECB(10, 116)), new ECBlocks(28, new ECB(19, 47), new ECB(10, 48)), new ECBlocks(30, new ECB(15, 24), new ECB(25, 25)), new ECBlocks(30, new ECB(23, 15), new ECB(25, 16))), new Version(31, new Array(6, 30, 56, 82, 108, 134), new ECBlocks(30, new ECB(13, 115), new ECB(3, 116)), new ECBlocks(28, new ECB(2, 46), new ECB(29, 47)), new ECBlocks(30, new ECB(42, 24), new ECB(1, 25)), new ECBlocks(30, new ECB(23, 15), new ECB(28, 16))), new Version(32, new Array(6, 34, 60, 86, 112, 138), new ECBlocks(30, new ECB(17, 115)), new ECBlocks(28, new ECB(10, 46), new ECB(23, 47)), new ECBlocks(30, new ECB(10, 24), new ECB(35, 25)), new ECBlocks(30, new ECB(19, 15), new ECB(35, 16))), new Version(33, new Array(6, 30, 58, 86, 114, 142), new ECBlocks(30, new ECB(17, 115), new ECB(1, 116)), new ECBlocks(28, new ECB(14, 46), new ECB(21, 47)), new ECBlocks(30, new ECB(29, 24), new ECB(19, 25)), new ECBlocks(30, new ECB(11, 15), new ECB(46, 16))), new Version(34, new Array(6, 34, 62, 90, 118, 146), new ECBlocks(30, new ECB(13, 115), new ECB(6, 116)), new ECBlocks(28, new ECB(14, 46), new ECB(23, 47)), new ECBlocks(30, new ECB(44, 24), new ECB(7, 25)), new ECBlocks(30, new ECB(59, 16), new ECB(1, 17))), new Version(35, new Array(6, 30, 54, 78, 102, 126, 150), new ECBlocks(30, new ECB(12, 121), new ECB(7, 122)), new ECBlocks(28, new ECB(12, 47), new ECB(26, 48)), new ECBlocks(30, new ECB(39, 24), new ECB(14, 25)), new ECBlocks(30, new ECB(22, 15), new ECB(41, 16))), new Version(36, new Array(6, 24, 50, 76, 102, 128, 154), new ECBlocks(30, new ECB(6, 121), new ECB(14, 122)), new ECBlocks(28, new ECB(6, 47), new ECB(34, 48)), new ECBlocks(30, new ECB(46, 24), new ECB(10, 25)), new ECBlocks(30, new ECB(2, 15), new ECB(64, 16))), new Version(37, new Array(6, 28, 54, 80, 106, 132, 158), new ECBlocks(30, new ECB(17, 122), new ECB(4, 123)), new ECBlocks(28, new ECB(29, 46), new ECB(14, 47)), new ECBlocks(30, new ECB(49, 24), new ECB(10, 25)), new ECBlocks(30, new ECB(24, 15), new ECB(46, 16))), new Version(38, new Array(6, 32, 58, 84, 110, 136, 162), new ECBlocks(30, new ECB(4, 122), new ECB(18, 123)), new ECBlocks(28, new ECB(13, 46), new ECB(32, 47)), new ECBlocks(30, new ECB(48, 24), new ECB(14, 25)), new ECBlocks(30, new ECB(42, 15), new ECB(32, 16))), new Version(39, new Array(6, 26, 54, 82, 110, 138, 166), new ECBlocks(30, new ECB(20, 117), new ECB(4, 118)), new ECBlocks(28, new ECB(40, 47), new ECB(7, 48)), new ECBlocks(30, new ECB(43, 24), new ECB(22, 25)), new ECBlocks(30, new ECB(10, 15), new ECB(67, 16))), new Version(40, new Array(6, 30, 58, 86, 114, 142, 170), new ECBlocks(30, new ECB(19, 118), new ECB(6, 119)), new ECBlocks(28, new ECB(18, 47), new ECB(31, 48)), new ECBlocks(30, new ECB(34, 24), new ECB(34, 25)), new ECBlocks(30, new ECB(20, 15), new ECB(61, 16)))); +} + +function PerspectiveTransform(a11, a21, a31, a12, a22, a32, a13, a23, a33) { + this.a11 = a11; + this.a12 = a12; + this.a13 = a13; + this.a21 = a21; + this.a22 = a22; + this.a23 = a23; + this.a31 = a31; + this.a32 = a32; + this.a33 = a33; + this.transformPoints1 = function(points) { + let max = points.length; + let a11 = this.a11; + let a12 = this.a12; + let a13 = this.a13; + let a21 = this.a21; + let a22 = this.a22; + let a23 = this.a23; + let a31 = this.a31; + let a32 = this.a32; + let a33 = this.a33; + for (let i = 0; i < max; i += 2) { + let x = points[i]; + let y = points[i + 1]; + let denominator = a13 * x + a23 * y + a33; + points[i] = (a11 * x + a21 * y + a31) / denominator; + points[i + 1] = (a12 * x + a22 * y + a32) / denominator; + } + }; + this.transformPoints2 = function(xValues, yValues) { + let n = xValues.length; + for (let i = 0; i < n; i++) { + let x = xValues[i]; + let y = yValues[i]; + let denominator = this.a13 * x + this.a23 * y + this.a33; + xValues[i] = (this.a11 * x + this.a21 * y + this.a31) / denominator; + yValues[i] = (this.a12 * x + this.a22 * y + this.a32) / denominator; + } + }; + this.buildAdjoint = function() { + return new PerspectiveTransform(this.a22 * this.a33 - this.a23 * this.a32, this.a23 * this.a31 - this.a21 * this.a33, this.a21 * this.a32 - this.a22 * this.a31, this.a13 * this.a32 - this.a12 * this.a33, this.a11 * this.a33 - this.a13 * this.a31, this.a12 * this.a31 - this.a11 * this.a32, this.a12 * this.a23 - this.a13 * this.a22, this.a13 * this.a21 - this.a11 * this.a23, this.a11 * this.a22 - this.a12 * this.a21); + }; + this.times = function(other) { + return new PerspectiveTransform(this.a11 * other.a11 + this.a21 * other.a12 + this.a31 * other.a13, this.a11 * other.a21 + this.a21 * other.a22 + this.a31 * other.a23, this.a11 * other.a31 + this.a21 * other.a32 + this.a31 * other.a33, this.a12 * other.a11 + this.a22 * other.a12 + this.a32 * other.a13, this.a12 * other.a21 + this.a22 * other.a22 + this.a32 * other.a23, this.a12 * other.a31 + this.a22 * other.a32 + this.a32 * other.a33, this.a13 * other.a11 + this.a23 * other.a12 + this.a33 * other.a13, this.a13 * other.a21 + this.a23 * other.a22 + this.a33 * other.a23, this.a13 * other.a31 + this.a23 * other.a32 + this.a33 * other.a33); + }; +} + +PerspectiveTransform.quadrilateralToQuadrilateral = function(x0, y0, x1, y1, x2, y2, x3, y3, x0p, y0p, x1p, y1p, x2p, y2p, x3p, y3p) { + let qToS = this.quadrilateralToSquare(x0, y0, x1, y1, x2, y2, x3, y3); + let sToQ = this.squareToQuadrilateral(x0p, y0p, x1p, y1p, x2p, y2p, x3p, y3p); + return sToQ.times(qToS); +}; + +PerspectiveTransform.squareToQuadrilateral = function(x0, y0, x1, y1, x2, y2, x3, y3) { + let dy2 = y3 - y2; + let dy3 = y0 - y1 + y2 - y3; + if (dy2 === 0 && dy3 === 0) { + return new PerspectiveTransform(x1 - x0, x2 - x1, x0, y1 - y0, y2 - y1, y0, 0, 0, 1); + } else { + let dx1 = x1 - x2; + let dx2 = x3 - x2; + let dx3 = x0 - x1 + x2 - x3; + let dy1 = y1 - y2; + let denominator = dx1 * dy2 - dx2 * dy1; + let a13 = (dx3 * dy2 - dx2 * dy3) / denominator; + let a23 = (dx1 * dy3 - dx3 * dy1) / denominator; + return new PerspectiveTransform(x1 - x0 + a13 * x1, x3 - x0 + a23 * x3, x0, y1 - y0 + a13 * y1, y3 - y0 + a23 * y3, y0, a13, a23, 1); + } +}; + +PerspectiveTransform.quadrilateralToSquare = function(x0, y0, x1, y1, x2, y2, x3, y3) { + return this.squareToQuadrilateral(x0, y0, x1, y1, x2, y2, x3, y3).buildAdjoint(); +}; + +function DetectorResult(bits, points) { + this.bits = bits; + this.points = points; +} + +function Detector(image) { + this.image = image; + this.resultPointCallback = null; + this.sizeOfBlackWhiteBlackRun = function(fromX, fromY, toX, toY) { + let steep = Math.abs(toY - fromY) > Math.abs(toX - fromX); + if (steep) { + let temp = fromX; + fromX = fromY; + fromY = temp; + temp = toX; + toX = toY; + toY = temp; + } + let dx = Math.abs(toX - fromX); + let dy = Math.abs(toY - fromY); + let error = -dx >> 1; + let ystep = fromY < toY ? 1 : -1; + let xstep = fromX < toX ? 1 : -1; + let state = 0; + for (let x = fromX, y = fromY; x != toX; x += xstep) { + let realX = steep ? y : x; + let realY = steep ? x : y; + if (state == 1) { + if (this.image[realX + realY * imgWidth]) { + state++; + } + } else { + if (!this.image[realX + realY * imgWidth]) { + state++; + } + } + if (state == 3) { + let diffX = x - fromX; + let diffY = y - fromY; + return Math.sqrt(diffX * diffX + diffY * diffY); + } + error += dy; + if (error > 0) { + if (y == toY) { + break; + } + y += ystep; + error -= dx; + } + } + let diffX2 = toX - fromX; + let diffY2 = toY - fromY; + return Math.sqrt(diffX2 * diffX2 + diffY2 * diffY2); + }; + this.sizeOfBlackWhiteBlackRunBothWays = function(fromX, fromY, toX, toY) { + let result = this.sizeOfBlackWhiteBlackRun(fromX, fromY, toX, toY); + let scale = 1; + let otherToX = fromX - (toX - fromX); + if (otherToX < 0) { + scale = fromX / (fromX - otherToX); + otherToX = 0; + } else if (otherToX >= imgWidth) { + scale = (imgWidth - 1 - fromX) / (otherToX - fromX); + otherToX = imgWidth - 1; + } + let otherToY = Math.floor(fromY - (toY - fromY) * scale); + scale = 1; + if (otherToY < 0) { + scale = fromY / (fromY - otherToY); + otherToY = 0; + } else if (otherToY >= imgHeight) { + scale = (imgHeight - 1 - fromY) / (otherToY - fromY); + otherToY = imgHeight - 1; + } + otherToX = Math.floor(fromX + (otherToX - fromX) * scale); + result += this.sizeOfBlackWhiteBlackRun(fromX, fromY, otherToX, otherToY); + return result - 1; + }; + this.calculateModuleSizeOneWay = function(pattern, otherPattern) { + let moduleSizeEst1 = this.sizeOfBlackWhiteBlackRunBothWays(Math.floor(pattern.X), Math.floor(pattern.Y), Math.floor(otherPattern.X), Math.floor(otherPattern.Y)); + let moduleSizeEst2 = this.sizeOfBlackWhiteBlackRunBothWays(Math.floor(otherPattern.X), Math.floor(otherPattern.Y), Math.floor(pattern.X), Math.floor(pattern.Y)); + if (isNaN(moduleSizeEst1)) { + return moduleSizeEst2 / 7; + } + if (isNaN(moduleSizeEst2)) { + return moduleSizeEst1 / 7; + } + return (moduleSizeEst1 + moduleSizeEst2) / 14; + }; + this.calculateModuleSize = function(topLeft, topRight, bottomLeft) { + return (this.calculateModuleSizeOneWay(topLeft, topRight) + this.calculateModuleSizeOneWay(topLeft, bottomLeft)) / 2; + }; + this.distance = function(pattern1, pattern2) { + let xDiff = pattern1.X - pattern2.X; + let yDiff = pattern1.Y - pattern2.Y; + return Math.sqrt(xDiff * xDiff + yDiff * yDiff); + }; + this.computeDimension = function(topLeft, topRight, bottomLeft, moduleSize) { + let tltrCentersDimension = Math.round(this.distance(topLeft, topRight) / moduleSize); + let tlblCentersDimension = Math.round(this.distance(topLeft, bottomLeft) / moduleSize); + let dimension = (tltrCentersDimension + tlblCentersDimension >> 1) + 7; + switch (dimension & 3) { + case 0: + dimension++; + break; + + case 2: + dimension--; + break; + + case 3: + throw "Error"; + } + return dimension; + }; + this.findAlignmentInRegion = function(overallEstModuleSize, estAlignmentX, estAlignmentY, allowanceFactor) { + let allowance = Math.floor(allowanceFactor * overallEstModuleSize); + let alignmentAreaLeftX = Math.max(0, estAlignmentX - allowance); + let alignmentAreaRightX = Math.min(imgWidth - 1, estAlignmentX + allowance); + if (alignmentAreaRightX - alignmentAreaLeftX < overallEstModuleSize * 3) { + throw "Error"; + } + let alignmentAreaTopY = Math.max(0, estAlignmentY - allowance); + let alignmentAreaBottomY = Math.min(imgHeight - 1, estAlignmentY + allowance); + let alignmentFinder = new AlignmentPatternFinder(this.image, alignmentAreaLeftX, alignmentAreaTopY, alignmentAreaRightX - alignmentAreaLeftX, alignmentAreaBottomY - alignmentAreaTopY, overallEstModuleSize, this.resultPointCallback); + return alignmentFinder.find(); + }; + this.createTransform = function(topLeft, topRight, bottomLeft, alignmentPattern, dimension) { + let dimMinusThree = dimension - 3.5; + let bottomRightX; + let bottomRightY; + let sourceBottomRightX; + let sourceBottomRightY; + if (alignmentPattern !== null) { + bottomRightX = alignmentPattern.X; + bottomRightY = alignmentPattern.Y; + sourceBottomRightX = sourceBottomRightY = dimMinusThree - 3; + } else { + bottomRightX = topRight.X - topLeft.X + bottomLeft.X; + bottomRightY = topRight.Y - topLeft.Y + bottomLeft.Y; + sourceBottomRightX = sourceBottomRightY = dimMinusThree; + } + let transform = PerspectiveTransform.quadrilateralToQuadrilateral(3.5, 3.5, dimMinusThree, 3.5, sourceBottomRightX, sourceBottomRightY, 3.5, dimMinusThree, topLeft.X, topLeft.Y, topRight.X, topRight.Y, bottomRightX, bottomRightY, bottomLeft.X, bottomLeft.Y); + return transform; + }; + this.sampleGrid = function(image, transform, dimension) { + let sampler = GridSampler; + return sampler.sampleGrid3(image, dimension, transform); + }; + this.processFinderPatternInfo = function(info) { + let topLeft = info.TopLeft; + let topRight = info.TopRight; + let bottomLeft = info.BottomLeft; + let moduleSize = this.calculateModuleSize(topLeft, topRight, bottomLeft); + if (moduleSize < 1) { + throw "Error"; + } + let dimension = this.computeDimension(topLeft, topRight, bottomLeft, moduleSize); + let provisionalVersion = Version.getProvisionalVersionForDimension(dimension); + let modulesBetweenFPCenters = provisionalVersion.DimensionForVersion - 7; + let alignmentPattern = null; + if (provisionalVersion.AlignmentPatternCenters.length > 0) { + let bottomRightX = topRight.X - topLeft.X + bottomLeft.X; + let bottomRightY = topRight.Y - topLeft.Y + bottomLeft.Y; + let correctionToTopLeft = 1 - 3 / modulesBetweenFPCenters; + let estAlignmentX = Math.floor(topLeft.X + correctionToTopLeft * (bottomRightX - topLeft.X)); + let estAlignmentY = Math.floor(topLeft.Y + correctionToTopLeft * (bottomRightY - topLeft.Y)); + for (let i = 4; i <= 16; i <<= 1) { + alignmentPattern = this.findAlignmentInRegion(moduleSize, estAlignmentX, estAlignmentY, i); + break; + } + } + let transform = this.createTransform(topLeft, topRight, bottomLeft, alignmentPattern, dimension); + let bits = this.sampleGrid(this.image, transform, dimension); + let points; + if (alignmentPattern === null) { + points = new Array(bottomLeft, topLeft, topRight); + } else { + points = new Array(bottomLeft, topLeft, topRight, alignmentPattern); + } + return new DetectorResult(bits, points); + }; + this.detect = function() { + let info = new FinderPatternFinder().findFinderPattern(this.image); + return this.processFinderPatternInfo(info); + }; +} + +var FORMAT_INFO_MASK_QR = 21522; + +var FORMAT_INFO_DECODE_LOOKUP = new Array(new Array(21522, 0), new Array(20773, 1), new Array(24188, 2), new Array(23371, 3), new Array(17913, 4), new Array(16590, 5), new Array(20375, 6), new Array(19104, 7), new Array(30660, 8), new Array(29427, 9), new Array(32170, 10), new Array(30877, 11), new Array(26159, 12), new Array(25368, 13), new Array(27713, 14), new Array(26998, 15), new Array(5769, 16), new Array(5054, 17), new Array(7399, 18), new Array(6608, 19), new Array(1890, 20), new Array(597, 21), new Array(3340, 22), new Array(2107, 23), new Array(13663, 24), new Array(12392, 25), new Array(16177, 26), new Array(14854, 27), new Array(9396, 28), new Array(8579, 29), new Array(11994, 30), new Array(11245, 31)); + +var BITS_SET_IN_HALF_BYTE = new Array(0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4); + +function FormatInformation(formatInfo) { + this.errorCorrectionLevel = ErrorCorrectionLevel.forBits(formatInfo >> 3 & 3); + this.dataMask = formatInfo & 7; + this.__defineGetter__("ErrorCorrectionLevel", function() { + return this.errorCorrectionLevel; + }); + this.__defineGetter__("DataMask", function() { + return this.dataMask; + }); + this.GetHashCode = function() { + return this.errorCorrectionLevel.ordinal() << 3 | this.dataMask; + }; + this.Equals = function(o) { + let other = o; + return this.errorCorrectionLevel == other.errorCorrectionLevel && this.dataMask == other.dataMask; + }; +} + +FormatInformation.numBitsDiffering = function(a, b) { + a ^= b; + return BITS_SET_IN_HALF_BYTE[a & 15] + BITS_SET_IN_HALF_BYTE[URShift(a, 4) & 15] + BITS_SET_IN_HALF_BYTE[URShift(a, 8) & 15] + BITS_SET_IN_HALF_BYTE[URShift(a, 12) & 15] + BITS_SET_IN_HALF_BYTE[URShift(a, 16) & 15] + BITS_SET_IN_HALF_BYTE[URShift(a, 20) & 15] + BITS_SET_IN_HALF_BYTE[URShift(a, 24) & 15] + BITS_SET_IN_HALF_BYTE[URShift(a, 28) & 15]; +}; + +FormatInformation.decodeFormatInformation = function(maskedFormatInfo) { + let formatInfo = FormatInformation.doDecodeFormatInformation(maskedFormatInfo); + if (formatInfo !== null) { + return formatInfo; + } + return FormatInformation.doDecodeFormatInformation(maskedFormatInfo ^ FORMAT_INFO_MASK_QR); +}; + +FormatInformation.doDecodeFormatInformation = function(maskedFormatInfo) { + let bestDifference = 4294967295; + let bestFormatInfo = 0; + for (let i = 0; i < FORMAT_INFO_DECODE_LOOKUP.length; i++) { + let decodeInfo = FORMAT_INFO_DECODE_LOOKUP[i]; + let targetInfo = decodeInfo[0]; + if (targetInfo == maskedFormatInfo) { + return new FormatInformation(decodeInfo[1]); + } + let bitsDifference = this.numBitsDiffering(maskedFormatInfo, targetInfo); + if (bitsDifference < bestDifference) { + bestFormatInfo = decodeInfo[1]; + bestDifference = bitsDifference; + } + } + if (bestDifference <= 3) { + return new FormatInformation(bestFormatInfo); + } + return null; +}; + +function ErrorCorrectionLevel(ordinal, bits, name) { + this.ordinal_Renamed_Field = ordinal; + this.bits = bits; + this.name = name; + this.__defineGetter__("Bits", function() { + return this.bits; + }); + this.__defineGetter__("Name", function() { + return this.name; + }); + this.ordinal = function() { + return this.ordinal_Renamed_Field; + }; +} + +var L = new ErrorCorrectionLevel(0, 1, "L"); + +var M = new ErrorCorrectionLevel(1, 0, "M"); + +var Q = new ErrorCorrectionLevel(2, 3, "Q"); + +var H = new ErrorCorrectionLevel(3, 2, "H"); + +var FOR_BITS = new Array(M, L, H, Q); + +ErrorCorrectionLevel.forBits = function(bits) { + if (bits < 0 || bits >= FOR_BITS.length) { + throw "ArgumentException"; + } + return FOR_BITS[bits]; +}; + +function BitMatrix(width, height) { + if (!height) height = width; + if (width < 1 || height < 1) { + throw "Both dimensions must be greater than 0"; + } + this.width = width; + this.height = height; + let rowSize = width >> 5; + if ((width & 31) !== 0) { + rowSize++; + } + this.rowSize = rowSize; + this.bits = new Array(rowSize * height); + for (let i = 0; i < this.bits.length; i++) this.bits[i] = 0; + this.__defineGetter__("Width", function() { + return this.width; + }); + this.__defineGetter__("Height", function() { + return this.height; + }); + this.__defineGetter__("Dimension", function() { + if (this.width != this.height) { + throw "Can't call getDimension() on a non-square matrix"; + } + return this.width; + }); + this.get_Renamed = function(x, y) { + let offset = y * this.rowSize + (x >> 5); + return (URShift(this.bits[offset], x & 31) & 1) !== 0; + }; + this.set_Renamed = function(x, y) { + let offset = y * this.rowSize + (x >> 5); + this.bits[offset] |= 1 << (x & 31); + }; + this.flip = function(x, y) { + let offset = y * this.rowSize + (x >> 5); + this.bits[offset] ^= 1 << (x & 31); + }; + this.clear = function() { + let max = this.bits.length; + for (let i = 0; i < max; i++) { + this.bits[i] = 0; + } + }; + this.setRegion = function(left, top, width, height) { + if (top < 0 || left < 0) { + throw "Left and top must be nonnegative"; + } + if (height < 1 || width < 1) { + throw "Height and width must be at least 1"; + } + let right = left + width; + let bottom = top + height; + if (bottom > this.height || right > this.width) { + throw "The region must fit inside the matrix"; + } + for (let y = top; y < bottom; y++) { + let offset = y * this.rowSize; + for (let x = left; x < right; x++) { + this.bits[offset + (x >> 5)] |= 1 << (x & 31); + } + } + }; +} + +function DataBlock(numDataCodewords, codewords) { + this.numDataCodewords = numDataCodewords; + this.codewords = codewords; + this.__defineGetter__("NumDataCodewords", function() { + return this.numDataCodewords; + }); + this.__defineGetter__("Codewords", function() { + return this.codewords; + }); +} + +DataBlock.getDataBlocks = function(rawCodewords, version, ecLevel) { + if (rawCodewords.length != version.TotalCodewords) { + throw "ArgumentException"; + } + let ecBlocks = version.getECBlocksForLevel(ecLevel); + let totalBlocks = 0; + let ecBlockArray = ecBlocks.getECBlocks(); + for (let i = 0; i < ecBlockArray.length; i++) { + totalBlocks += ecBlockArray[i].Count; + } + let result = new Array(totalBlocks); + let numResultBlocks = 0; + for (let j = 0; j < ecBlockArray.length; j++) { + let ecBlock = ecBlockArray[j]; + for (let i = 0; i < ecBlock.Count; i++) { + let numDataCodewords = ecBlock.DataCodewords; + let numBlockCodewords = ecBlocks.ECCodewordsPerBlock + numDataCodewords; + result[numResultBlocks++] = new DataBlock(numDataCodewords, new Array(numBlockCodewords)); + } + } + let shorterBlocksTotalCodewords = result[0].codewords.length; + let longerBlocksStartAt = result.length - 1; + while (longerBlocksStartAt >= 0) { + let numCodewords = result[longerBlocksStartAt].codewords.length; + if (numCodewords == shorterBlocksTotalCodewords) { + break; + } + longerBlocksStartAt--; + } + longerBlocksStartAt++; + let shorterBlocksNumDataCodewords = shorterBlocksTotalCodewords - ecBlocks.ECCodewordsPerBlock; + let rawCodewordsOffset = 0; + for (let i = 0; i < shorterBlocksNumDataCodewords; i++) { + for (let j = 0; j < numResultBlocks; j++) { + result[j].codewords[i] = rawCodewords[rawCodewordsOffset++]; + } + } + for (let j = longerBlocksStartAt; j < numResultBlocks; j++) { + result[j].codewords[shorterBlocksNumDataCodewords] = rawCodewords[rawCodewordsOffset++]; + } + let max = result[0].codewords.length; + for (let i = shorterBlocksNumDataCodewords; i < max; i++) { + for (let j = 0; j < numResultBlocks; j++) { + let iOffset = j < longerBlocksStartAt ? i : i + 1; + result[j].codewords[iOffset] = rawCodewords[rawCodewordsOffset++]; + } + } + return result; +}; + +var DataMask = {}; + +function BitMatrixParser(bitMatrix) { + let dimension = bitMatrix.Dimension; + if (dimension < 21 || (dimension & 3) != 1) { + throw "Error BitMatrixParser"; + } + this.bitMatrix = bitMatrix; + this.parsedVersion = null; + this.parsedFormatInfo = null; + this.copyBit = function(i, j, versionBits) { + return this.bitMatrix.get_Renamed(i, j) ? versionBits << 1 | 1 : versionBits << 1; + }; + this.readFormatInformation = function() { + if (this.parsedFormatInfo !== null) { + return this.parsedFormatInfo; + } + let formatInfoBits = 0; + for (let i = 0; i < 6; i++) { + formatInfoBits = this.copyBit(i, 8, formatInfoBits); + } + formatInfoBits = this.copyBit(7, 8, formatInfoBits); + formatInfoBits = this.copyBit(8, 8, formatInfoBits); + formatInfoBits = this.copyBit(8, 7, formatInfoBits); + for (let j = 5; j >= 0; j--) { + formatInfoBits = this.copyBit(8, j, formatInfoBits); + } + this.parsedFormatInfo = FormatInformation.decodeFormatInformation(formatInfoBits); + if (this.parsedFormatInfo !== null) { + return this.parsedFormatInfo; + } + let dimension = this.bitMatrix.Dimension; + formatInfoBits = 0; + let iMin = dimension - 8; + for (let i = dimension - 1; i >= iMin; i--) { + formatInfoBits = this.copyBit(i, 8, formatInfoBits); + } + for (let j = dimension - 7; j < dimension; j++) { + formatInfoBits = this.copyBit(8, j, formatInfoBits); + } + this.parsedFormatInfo = FormatInformation.decodeFormatInformation(formatInfoBits); + if (this.parsedFormatInfo !== null) { + return this.parsedFormatInfo; + } + throw "Error readFormatInformation"; + }; + this.readVersion = function() { + if (this.parsedVersion !== null) { + return this.parsedVersion; + } + let dimension = this.bitMatrix.Dimension; + let provisionalVersion = dimension - 17 >> 2; + if (provisionalVersion <= 6) { + return Version.getVersionForNumber(provisionalVersion); + } + let versionBits = 0; + let ijMin = dimension - 11; + for (let j = 5; j >= 0; j--) { + for (let i = dimension - 9; i >= ijMin; i--) { + versionBits = this.copyBit(i, j, versionBits); + } + } + this.parsedVersion = Version.decodeVersionInformation(versionBits); + if (this.parsedVersion !== null && this.parsedVersion.DimensionForVersion == dimension) { + return this.parsedVersion; + } + versionBits = 0; + for (let i = 5; i >= 0; i--) { + for (let j = dimension - 9; j >= ijMin; j--) { + versionBits = this.copyBit(i, j, versionBits); + } + } + this.parsedVersion = Version.decodeVersionInformation(versionBits); + if (this.parsedVersion !== null && this.parsedVersion.DimensionForVersion == dimension) { + return this.parsedVersion; + } + throw "Error readVersion"; + }; + this.readCodewords = function() { + let formatInfo = this.readFormatInformation(); + let version = this.readVersion(); + let dataMask = DataMask.forReference(formatInfo.DataMask); + let dimension = this.bitMatrix.Dimension; + dataMask.unmaskBitMatrix(this.bitMatrix, dimension); + let functionPattern = version.buildFunctionPattern(); + let readingUp = true; + let result = new Array(version.TotalCodewords); + let resultOffset = 0; + let currentByte = 0; + let bitsRead = 0; + for (let j = dimension - 1; j > 0; j -= 2) { + if (j == 6) { + j--; + } + for (let count = 0; count < dimension; count++) { + let i = readingUp ? dimension - 1 - count : count; + for (let col = 0; col < 2; col++) { + if (!functionPattern.get_Renamed(j - col, i)) { + bitsRead++; + currentByte <<= 1; + if (this.bitMatrix.get_Renamed(j - col, i)) { + currentByte |= 1; + } + if (bitsRead == 8) { + result[resultOffset++] = currentByte; + bitsRead = 0; + currentByte = 0; + } + } + } + } + readingUp ^= true; + } + if (resultOffset != version.TotalCodewords) { + throw "Error readCodewords"; + } + return result; + }; +} + +DataMask.forReference = function(reference) { + if (reference < 0 || reference > 7) { + throw "System.ArgumentException"; + } + return DataMask.DATA_MASKS[reference]; +}; + +function DataMask000() { + this.unmaskBitMatrix = function(bits, dimension) { + for (let i = 0; i < dimension; i++) { + for (let j = 0; j < dimension; j++) { + if (this.isMasked(i, j)) { + bits.flip(j, i); + } + } + } + }; + this.isMasked = function(i, j) { + return (i + j & 1) === 0; + }; +} + +function DataMask001() { + this.unmaskBitMatrix = function(bits, dimension) { + for (let i = 0; i < dimension; i++) { + for (let j = 0; j < dimension; j++) { + if (this.isMasked(i, j)) { + bits.flip(j, i); + } + } + } + }; + this.isMasked = function(i, j) { + return (i & 1) === 0; + }; +} + +function DataMask010() { + this.unmaskBitMatrix = function(bits, dimension) { + for (let i = 0; i < dimension; i++) { + for (let j = 0; j < dimension; j++) { + if (this.isMasked(i, j)) { + bits.flip(j, i); + } + } + } + }; + this.isMasked = function(i, j) { + return j % 3 === 0; + }; +} + +function DataMask011() { + this.unmaskBitMatrix = function(bits, dimension) { + for (let i = 0; i < dimension; i++) { + for (let j = 0; j < dimension; j++) { + if (this.isMasked(i, j)) { + bits.flip(j, i); + } + } + } + }; + this.isMasked = function(i, j) { + return (i + j) % 3 === 0; + }; +} + +function DataMask100() { + this.unmaskBitMatrix = function(bits, dimension) { + for (let i = 0; i < dimension; i++) { + for (let j = 0; j < dimension; j++) { + if (this.isMasked(i, j)) { + bits.flip(j, i); + } + } + } + }; + this.isMasked = function(i, j) { + return (URShift(i, 1) + j / 3 & 1) === 0; + }; +} + +function DataMask101() { + this.unmaskBitMatrix = function(bits, dimension) { + for (let i = 0; i < dimension; i++) { + for (let j = 0; j < dimension; j++) { + if (this.isMasked(i, j)) { + bits.flip(j, i); + } + } + } + }; + this.isMasked = function(i, j) { + let temp = i * j; + return (temp & 1) + temp % 3 === 0; + }; +} + +function DataMask110() { + this.unmaskBitMatrix = function(bits, dimension) { + for (let i = 0; i < dimension; i++) { + for (let j = 0; j < dimension; j++) { + if (this.isMasked(i, j)) { + bits.flip(j, i); + } + } + } + }; + this.isMasked = function(i, j) { + let temp = i * j; + return ((temp & 1) + temp % 3 & 1) === 0; + }; +} + +function DataMask111() { + this.unmaskBitMatrix = function(bits, dimension) { + for (let i = 0; i < dimension; i++) { + for (let j = 0; j < dimension; j++) { + if (this.isMasked(i, j)) { + bits.flip(j, i); + } + } + } + }; + this.isMasked = function(i, j) { + return ((i + j & 1) + i * j % 3 & 1) === 0; + }; +} + +DataMask.DATA_MASKS = new Array(new DataMask000(), new DataMask001(), new DataMask010(), new DataMask011(), new DataMask100(), new DataMask101(), new DataMask110(), new DataMask111()); + +function ReedSolomonDecoder(field) { + this.field = field; + this.decode = function(received, twoS) { + let poly = new GF256Poly(this.field, received); + let syndromeCoefficients = new Array(twoS); + for (let i = 0; i < syndromeCoefficients.length; i++) syndromeCoefficients[i] = 0; + let dataMatrix = false; + let noError = true; + for (let i = 0; i < twoS; i++) { + let value = poly.evaluateAt(this.field.exp(dataMatrix ? i + 1 : i)); + syndromeCoefficients[syndromeCoefficients.length - 1 - i] = value; + if (value !== 0) { + noError = false; + } + } + if (noError) { + return; + } + let syndrome = new GF256Poly(this.field, syndromeCoefficients); + let sigmaOmega = this.runEuclideanAlgorithm(this.field.buildMonomial(twoS, 1), syndrome, twoS); + let sigma = sigmaOmega[0]; + let omega = sigmaOmega[1]; + let errorLocations = this.findErrorLocations(sigma); + let errorMagnitudes = this.findErrorMagnitudes(omega, errorLocations, dataMatrix); + for (let i = 0; i < errorLocations.length; i++) { + let position = received.length - 1 - this.field.log(errorLocations[i]); + if (position < 0) { + throw "ReedSolomonException Bad error location"; + } + received[position] = GF256.addOrSubtract(received[position], errorMagnitudes[i]); + } + }; + this.runEuclideanAlgorithm = function(a, b, R) { + if (a.Degree < b.Degree) { + let temp = a; + a = b; + b = temp; + } + let rLast = a; + let r = b; + let sLast = this.field.One; + let s = this.field.Zero; + let tLast = this.field.Zero; + let t = this.field.One; + while (r.Degree >= Math.floor(R / 2)) { + let rLastLast = rLast; + let sLastLast = sLast; + let tLastLast = tLast; + rLast = r; + sLast = s; + tLast = t; + if (rLast.Zero) { + throw "r_{i-1} was zero"; + } + r = rLastLast; + let q = this.field.Zero; + let denominatorLeadingTerm = rLast.getCoefficient(rLast.Degree); + let dltInverse = this.field.inverse(denominatorLeadingTerm); + while (r.Degree >= rLast.Degree && !r.Zero) { + let degreeDiff = r.Degree - rLast.Degree; + let scale = this.field.multiply(r.getCoefficient(r.Degree), dltInverse); + q = q.addOrSubtract(this.field.buildMonomial(degreeDiff, scale)); + r = r.addOrSubtract(rLast.multiplyByMonomial(degreeDiff, scale)); + } + s = q.multiply1(sLast).addOrSubtract(sLastLast); + t = q.multiply1(tLast).addOrSubtract(tLastLast); + } + let sigmaTildeAtZero = t.getCoefficient(0); + if (sigmaTildeAtZero === 0) { + throw "ReedSolomonException sigmaTilde(0) was zero"; + } + let inverse = this.field.inverse(sigmaTildeAtZero); + let sigma = t.multiply2(inverse); + let omega = r.multiply2(inverse); + return new Array(sigma, omega); + }; + this.findErrorLocations = function(errorLocator) { + let numErrors = errorLocator.Degree; + if (numErrors == 1) { + return new Array(errorLocator.getCoefficient(1)); + } + let result = new Array(numErrors); + let e = 0; + for (let i = 1; i < 256 && e < numErrors; i++) { + if (errorLocator.evaluateAt(i) === 0) { + result[e] = this.field.inverse(i); + e++; + } + } + if (e != numErrors) { + throw "Error locator degree does not match number of roots"; + } + return result; + }; + this.findErrorMagnitudes = function(errorEvaluator, errorLocations, dataMatrix) { + let s = errorLocations.length; + let result = new Array(s); + for (let i = 0; i < s; i++) { + let xiInverse = this.field.inverse(errorLocations[i]); + let denominator = 1; + for (let j = 0; j < s; j++) { + if (i != j) { + denominator = this.field.multiply(denominator, GF256.addOrSubtract(1, this.field.multiply(errorLocations[j], xiInverse))); + } + } + result[i] = this.field.multiply(errorEvaluator.evaluateAt(xiInverse), this.field.inverse(denominator)); + if (dataMatrix) { + result[i] = this.field.multiply(result[i], xiInverse); + } + } + return result; + }; +} + +function GF256Poly(field, coefficients) { + if (coefficients === null || coefficients.length === 0) { + throw "System.ArgumentException"; + } + this.field = field; + let coefficientsLength = coefficients.length; + if (coefficientsLength > 1 && coefficients[0] === 0) { + let firstNonZero = 1; + while (firstNonZero < coefficientsLength && coefficients[firstNonZero] === 0) { + firstNonZero++; + } + if (firstNonZero == coefficientsLength) { + this.coefficients = field.Zero.coefficients; + } else { + this.coefficients = new Array(coefficientsLength - firstNonZero); + for (let i = 0; i < this.coefficients.length; i++) this.coefficients[i] = 0; + for (let ci = 0; ci < this.coefficients.length; ci++) this.coefficients[ci] = coefficients[firstNonZero + ci]; + } + } else { + this.coefficients = coefficients; + } + this.__defineGetter__("Zero", function() { + return this.coefficients[0] === 0; + }); + this.__defineGetter__("Degree", function() { + return this.coefficients.length - 1; + }); + this.__defineGetter__("Coefficients", function() { + return this.coefficients; + }); + this.getCoefficient = function(degree) { + return this.coefficients[this.coefficients.length - 1 - degree]; + }; + this.evaluateAt = function(a) { + if (a === 0) { + return this.getCoefficient(0); + } + let size = this.coefficients.length; + if (a == 1) { + let result = 0; + for (let i = 0; i < size; i++) { + result = GF256.addOrSubtract(result, this.coefficients[i]); + } + return result; + } + let result2 = this.coefficients[0]; + for (let i = 1; i < size; i++) { + result2 = GF256.addOrSubtract(this.field.multiply(a, result2), this.coefficients[i]); + } + return result2; + }; + this.addOrSubtract = function(other) { + if (this.field != other.field) { + throw "GF256Polys do not have same GF256 field"; + } + if (this.Zero) { + return other; + } + if (other.Zero) { + return this; + } + let smallerCoefficients = this.coefficients; + let largerCoefficients = other.coefficients; + if (smallerCoefficients.length > largerCoefficients.length) { + let temp = smallerCoefficients; + smallerCoefficients = largerCoefficients; + largerCoefficients = temp; + } + let sumDiff = new Array(largerCoefficients.length); + let lengthDiff = largerCoefficients.length - smallerCoefficients.length; + for (let ci = 0; ci < lengthDiff; ci++) sumDiff[ci] = largerCoefficients[ci]; + for (let i = lengthDiff; i < largerCoefficients.length; i++) { + sumDiff[i] = GF256.addOrSubtract(smallerCoefficients[i - lengthDiff], largerCoefficients[i]); + } + return new GF256Poly(field, sumDiff); + }; + this.multiply1 = function(other) { + if (this.field != other.field) { + throw "GF256Polys do not have same GF256 field"; + } + if (this.Zero || other.Zero) { + return this.field.Zero; + } + let aCoefficients = this.coefficients; + let aLength = aCoefficients.length; + let bCoefficients = other.coefficients; + let bLength = bCoefficients.length; + let product = new Array(aLength + bLength - 1); + for (let i = 0; i < aLength; i++) { + let aCoeff = aCoefficients[i]; + for (let j = 0; j < bLength; j++) { + product[i + j] = GF256.addOrSubtract(product[i + j], this.field.multiply(aCoeff, bCoefficients[j])); + } + } + return new GF256Poly(this.field, product); + }; + this.multiply2 = function(scalar) { + if (scalar === 0) { + return this.field.Zero; + } + if (scalar == 1) { + return this; + } + let size = this.coefficients.length; + let product = new Array(size); + for (let i = 0; i < size; i++) { + product[i] = this.field.multiply(this.coefficients[i], scalar); + } + return new GF256Poly(this.field, product); + }; + this.multiplyByMonomial = function(degree, coefficient) { + if (degree < 0) { + throw "System.ArgumentException"; + } + if (coefficient === 0) { + return this.field.Zero; + } + let size = this.coefficients.length; + let product = new Array(size + degree); + for (let i = 0; i < product.length; i++) product[i] = 0; + for (let i = 0; i < size; i++) { + product[i] = this.field.multiply(this.coefficients[i], coefficient); + } + return new GF256Poly(this.field, product); + }; + this.divide = function(other) { + if (this.field != other.field) { + throw "GF256Polys do not have same GF256 field"; + } + if (other.Zero) { + throw "Divide by 0"; + } + let quotient = this.field.Zero; + let remainder = this; + let denominatorLeadingTerm = other.getCoefficient(other.Degree); + let inverseDenominatorLeadingTerm = this.field.inverse(denominatorLeadingTerm); + while (remainder.Degree >= other.Degree && !remainder.Zero) { + let degreeDifference = remainder.Degree - other.Degree; + let scale = this.field.multiply(remainder.getCoefficient(remainder.Degree), inverseDenominatorLeadingTerm); + let term = other.multiplyByMonomial(degreeDifference, scale); + let iterationQuotient = this.field.buildMonomial(degreeDifference, scale); + quotient = quotient.addOrSubtract(iterationQuotient); + remainder = remainder.addOrSubtract(term); + } + return new Array(quotient, remainder); + }; +} + +function GF256(primitive) { + this.expTable = new Array(256); + this.logTable = new Array(256); + let x = 1; + for (let i = 0; i < 256; i++) { + this.expTable[i] = x; + x <<= 1; + if (x >= 256) { + x ^= primitive; + } + } + for (let i = 0; i < 255; i++) { + this.logTable[this.expTable[i]] = i; + } + let at0 = new Array(1); + at0[0] = 0; + this.zero = new GF256Poly(this, new Array(at0)); + let at1 = new Array(1); + at1[0] = 1; + this.one = new GF256Poly(this, new Array(at1)); + this.__defineGetter__("Zero", function() { + return this.zero; + }); + this.__defineGetter__("One", function() { + return this.one; + }); + this.buildMonomial = function(degree, coefficient) { + if (degree < 0) { + throw "System.ArgumentException"; + } + if (coefficient === 0) { + return this.zero; + } + let coefficients = new Array(degree + 1); + for (let i = 0; i < coefficients.length; i++) coefficients[i] = 0; + coefficients[0] = coefficient; + return new GF256Poly(this, coefficients); + }; + this.exp = function(a) { + return this.expTable[a]; + }; + this.log = function(a) { + if (a === 0) { + throw "System.ArgumentException"; + } + return this.logTable[a]; + }; + this.inverse = function(a) { + if (a === 0) { + throw "System.ArithmeticException"; + } + return this.expTable[255 - this.logTable[a]]; + }; + this.multiply = function(a, b) { + if (a === 0 || b === 0) { + return 0; + } + if (a == 1) { + return b; + } + if (b == 1) { + return a; + } + return this.expTable[(this.logTable[a] + this.logTable[b]) % 255]; + }; +} + +GF256.QR_CODE_FIELD = new GF256(285); + +GF256.DATA_MATRIX_FIELD = new GF256(301); + +GF256.addOrSubtract = function(a, b) { + return a ^ b; +}; + +var Decoder = {}; + +Decoder.rsDecoder = new ReedSolomonDecoder(GF256.QR_CODE_FIELD); + +Decoder.correctErrors = function(codewordBytes, numDataCodewords) { + let numCodewords = codewordBytes.length; + let codewordsInts = new Array(numCodewords); + for (let i = 0; i < numCodewords; i++) { + codewordsInts[i] = codewordBytes[i] & 255; + } + let numECCodewords = codewordBytes.length - numDataCodewords; + try { + Decoder.rsDecoder.decode(codewordsInts, numECCodewords); + } catch (rse) { + throw rse; + } + for (let i = 0; i < numDataCodewords; i++) { + codewordBytes[i] = codewordsInts[i]; + } +}; + +Decoder.decode = function(bits) { + let parser = new BitMatrixParser(bits); + let version = parser.readVersion(); + let ecLevel = parser.readFormatInformation().ErrorCorrectionLevel; + let codewords = parser.readCodewords(); + let dataBlocks = DataBlock.getDataBlocks(codewords, version, ecLevel); + let totalBytes = 0; + for (let i = 0; i < dataBlocks.length; i++) { + totalBytes += dataBlocks[i].NumDataCodewords; + } + let resultBytes = new Array(totalBytes); + let resultOffset = 0; + for (let j = 0; j < dataBlocks.length; j++) { + let dataBlock = dataBlocks[j]; + let codewordBytes = dataBlock.Codewords; + let numDataCodewords = dataBlock.NumDataCodewords; + Decoder.correctErrors(codewordBytes, numDataCodewords); + for (let i = 0; i < numDataCodewords; i++) { + resultBytes[resultOffset++] = codewordBytes[i]; + } + } + let reader = new QRCodeDataBlockReader(resultBytes, version.VersionNumber, ecLevel.Bits); + return reader; +}; + +// mozilla: Get access to a window + +var DevToolsServer = require("resource://devtools/server/devtools-server.js").DevToolsServer; + +var window = Services.wm.getMostRecentWindow(DevToolsServer.chromeWindowType); + +var document = window.document; + +var Image = window.Image; + +var HTML_NS = "http://www.w3.org/1999/xhtml"; + +var qrcode = {}; + +qrcode.callback = null; + +qrcode.errback = null; + +qrcode.decode = function(src) { + if (arguments.length === 0) { + let canvas_qr = document.getElementById("qr-canvas"); + let context = canvas_qr.getContext("2d"); + imgWidth = canvas_qr.width; + imgHeight = canvas_qr.height; + imgU8 = context.getImageData(0, 0, imgWidth, imgHeight).data; + imgU32 = new Uint32Array(imgU8.buffer); + qrcode.result = qrcode.process(context); + if (qrcode.callback !== null) { + qrcode.callback(qrcode.result); + } + return qrcode.result; + } else { + let image = new Image(); + image.onload = function() { + // mozilla: Use HTML namespace explicitly + let canvas_qr = document.createElementNS(HTML_NS, "canvas"); + let context = canvas_qr.getContext("2d"); + let nheight = image.height; + let nwidth = image.width; + if (image.width * image.height > maxImgSize) { + let ir = image.width / image.height; + nheight = Math.sqrt(maxImgSize / ir); + nwidth = ir * nheight; + } + canvas_qr.width = nwidth; + canvas_qr.height = nheight; + context.drawImage(image, 0, 0, canvas_qr.width, canvas_qr.height); + imgWidth = canvas_qr.width; + imgHeight = canvas_qr.height; + try { + imgU8 = context.getImageData(0, 0, canvas_qr.width, canvas_qr.height).data; + imgU32 = new Uint32Array(imgU8.buffer); + } catch (e) { + qrcode.result = "Cross domain image reading not supported in your browser! Save it to your computer then drag and drop the file!"; + if (qrcode.callback !== null) { + qrcode.callback(qrcode.result); + } + return; + } + try { + qrcode.result = qrcode.process(context); + if (qrcode.callback !== null) { + qrcode.callback(qrcode.result); + } + } catch (e) { + if (qrcode.errback !== null) { + qrcode.errback(e); + } else { + console.error(e); + } + qrcode.result = "error decoding QR Code"; + } + }; + image.src = src; + } +}; + +qrcode.isUrl = function(s) { + let regexp = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/; + return regexp.test(s); +}; + +qrcode.decode_url = function(s) { + let escaped = ""; + try { + escaped = escape(s); + } catch (e) { + console.log(e); + escaped = s; + } + let ret = ""; + try { + ret = decodeURIComponent(escaped); + } catch (e) { + console.log(e); + ret = escaped; + } + return ret; +}; + +qrcode.decode_utf8 = function(s) { + if (qrcode.isUrl(s)) return qrcode.decode_url(s); else return s; +}; + +qrcode.process = function(ctx) { + let image = qrcode.grayScaleToBitmap(qrcode.grayscale()); + let detector = new Detector(image); + let qRCodeMatrix = detector.detect(); + let reader = Decoder.decode(qRCodeMatrix.bits); + let data = reader.DataByte; + let str = ""; + for (let i = 0; i < data.length; i++) { + for (let j = 0; j < data[i].length; j++) str += String.fromCharCode(data[i][j]); + } + return qrcode.decode_utf8(str); +}; + +qrcode.getMiddleBrightnessPerArea = function(image) { + let numSqrtArea = 4; + let areaWidth = Math.floor(imgWidth / numSqrtArea); + let areaHeight = Math.floor(imgHeight / numSqrtArea); + let minmax = new Array(numSqrtArea); + for (let i = 0; i < numSqrtArea; i++) { + minmax[i] = new Array(numSqrtArea); + for (let i2 = 0; i2 < numSqrtArea; i2++) { + minmax[i][i2] = new Array(0, 0); + } + } + for (let ay = 0; ay < numSqrtArea; ay++) { + for (let ax = 0; ax < numSqrtArea; ax++) { + minmax[ax][ay][0] = 255; + for (let dy = 0; dy < areaHeight; dy++) { + for (let dx = 0; dx < areaWidth; dx++) { + let target = image[areaWidth * ax + dx + (areaHeight * ay + dy) * imgWidth]; + if (target < minmax[ax][ay][0]) minmax[ax][ay][0] = target; + if (target > minmax[ax][ay][1]) minmax[ax][ay][1] = target; + } + } + } + } + let middle = new Array(numSqrtArea); + for (let i3 = 0; i3 < numSqrtArea; i3++) { + middle[i3] = new Array(numSqrtArea); + } + for (let ay = 0; ay < numSqrtArea; ay++) { + for (let ax = 0; ax < numSqrtArea; ax++) { + middle[ax][ay] = Math.floor((minmax[ax][ay][0] + minmax[ax][ay][1]) / 2); + } + } + return middle; +}; + +qrcode.grayScaleToBitmap = function(grayScale) { + let middle = qrcode.getMiddleBrightnessPerArea(grayScale); + let sqrtNumArea = middle.length; + let areaWidth = Math.floor(imgWidth / sqrtNumArea); + let areaHeight = Math.floor(imgHeight / sqrtNumArea); + let bitmap = new Array(imgHeight * imgWidth); + for (let ay = 0; ay < sqrtNumArea; ay++) { + for (let ax = 0; ax < sqrtNumArea; ax++) { + for (let dy = 0; dy < areaHeight; dy++) { + for (let dx = 0; dx < areaWidth; dx++) { + bitmap[areaWidth * ax + dx + (areaHeight * ay + dy) * imgWidth] = grayScale[areaWidth * ax + dx + (areaHeight * ay + dy) * imgWidth] < middle[ax][ay] ? true : false; + } + } + } + } + return bitmap; +}; + +qrcode.grayscale = function() { + let ret = new Uint8ClampedArray(imgWidth * imgHeight); + for (let y = 0; y < imgHeight; y++) { + for (let x = 0; x < imgWidth; x++) { + let point = x + y * imgWidth; + let rgba = imgU32[point]; + let p = (rgba & 0xFF) + ((rgba >> 8) & 0xFF) + ((rgba >> 16) & 0xFF); + ret[x + y * imgWidth] = p / 3; + } + } + return ret; +}; + +function URShift(number, bits) { + if (number >= 0) return number >> bits; else return (number >> bits) + (2 << ~bits); +} + +// mozilla: Add module support +module.exports = { + decodeFromURI: function(src, cb, errcb) { + if (cb) { + qrcode.callback = cb; + } + if (errcb) { + qrcode.errback = errcb; + } + return qrcode.decode(src); + }, + decodeFromCanvas: function(canvas, cb) { + let context = canvas.getContext("2d"); + imgWidth = canvas.width; + imgHeight = canvas.height; + imgU8 = context.getImageData(0, 0, imgWidth, imgHeight).data; + imgU32 = new Uint32Array(imgU8.buffer); + let result = qrcode.process(context); + if (cb) { + cb(result); + } + return result; + } +}; + +var MIN_SKIP = 3; + +var MAX_MODULES = 57; + +var INTEGER_MATH_SHIFT = 8; + +var CENTER_QUORUM = 2; + +qrcode.orderBestPatterns = function(patterns) { + function distance(pattern1, pattern2) { + let xDiff = pattern1.X - pattern2.X; + let yDiff = pattern1.Y - pattern2.Y; + return Math.sqrt(xDiff * xDiff + yDiff * yDiff); + } + function crossProductZ(pointA, pointB, pointC) { + let bX = pointB.x; + let bY = pointB.y; + return (pointC.x - bX) * (pointA.y - bY) - (pointC.y - bY) * (pointA.x - bX); + } + let zeroOneDistance = distance(patterns[0], patterns[1]); + let oneTwoDistance = distance(patterns[1], patterns[2]); + let zeroTwoDistance = distance(patterns[0], patterns[2]); + let pointA, pointB, pointC; + if (oneTwoDistance >= zeroOneDistance && oneTwoDistance >= zeroTwoDistance) { + pointB = patterns[0]; + pointA = patterns[1]; + pointC = patterns[2]; + } else if (zeroTwoDistance >= oneTwoDistance && zeroTwoDistance >= zeroOneDistance) { + pointB = patterns[1]; + pointA = patterns[0]; + pointC = patterns[2]; + } else { + pointB = patterns[2]; + pointA = patterns[0]; + pointC = patterns[1]; + } + if (crossProductZ(pointA, pointB, pointC) < 0) { + let temp = pointA; + pointA = pointC; + pointC = temp; + } + patterns[0] = pointA; + patterns[1] = pointB; + patterns[2] = pointC; +}; + +function FinderPattern(posX, posY, estimatedModuleSize) { + this.x = posX; + this.y = posY; + this.count = 1; + this.estimatedModuleSize = estimatedModuleSize; + this.__defineGetter__("EstimatedModuleSize", function() { + return this.estimatedModuleSize; + }); + this.__defineGetter__("Count", function() { + return this.count; + }); + this.__defineGetter__("X", function() { + return this.x; + }); + this.__defineGetter__("Y", function() { + return this.y; + }); + this.incrementCount = function() { + this.count++; + }; + this.aboutEquals = function(moduleSize, i, j) { + if (Math.abs(i - this.y) <= moduleSize && Math.abs(j - this.x) <= moduleSize) { + let moduleSizeDiff = Math.abs(moduleSize - this.estimatedModuleSize); + return moduleSizeDiff <= 1 || moduleSizeDiff / this.estimatedModuleSize <= 1; + } + return false; + }; +} + +function FinderPatternInfo(patternCenters) { + this.bottomLeft = patternCenters[0]; + this.topLeft = patternCenters[1]; + this.topRight = patternCenters[2]; + this.__defineGetter__("BottomLeft", function() { + return this.bottomLeft; + }); + this.__defineGetter__("TopLeft", function() { + return this.topLeft; + }); + this.__defineGetter__("TopRight", function() { + return this.topRight; + }); +} + +function FinderPatternFinder() { + this.image = null; + this.possibleCenters = []; + this.hasSkipped = false; + this.crossCheckStateCount = new Array(0, 0, 0, 0, 0); + this.resultPointCallback = null; + this.__defineGetter__("CrossCheckStateCount", function() { + this.crossCheckStateCount[0] = 0; + this.crossCheckStateCount[1] = 0; + this.crossCheckStateCount[2] = 0; + this.crossCheckStateCount[3] = 0; + this.crossCheckStateCount[4] = 0; + return this.crossCheckStateCount; + }); + this.foundPatternCross = function(stateCount) { + let totalModuleSize = 0; + for (let i = 0; i < 5; i++) { + let count = stateCount[i]; + if (count === 0) { + return false; + } + totalModuleSize += count; + } + if (totalModuleSize < 7) { + return false; + } + let moduleSize = Math.floor((totalModuleSize << INTEGER_MATH_SHIFT) / 7); + let maxVariance = Math.floor(moduleSize / 2); + return Math.abs(moduleSize - (stateCount[0] << INTEGER_MATH_SHIFT)) < maxVariance && Math.abs(moduleSize - (stateCount[1] << INTEGER_MATH_SHIFT)) < maxVariance && Math.abs(3 * moduleSize - (stateCount[2] << INTEGER_MATH_SHIFT)) < 3 * maxVariance && Math.abs(moduleSize - (stateCount[3] << INTEGER_MATH_SHIFT)) < maxVariance && Math.abs(moduleSize - (stateCount[4] << INTEGER_MATH_SHIFT)) < maxVariance; + }; + this.centerFromEnd = function(stateCount, end) { + return end - stateCount[4] - stateCount[3] - stateCount[2] / 2; + }; + this.crossCheckVertical = function(startI, centerJ, maxCount, originalStateCountTotal) { + let image = this.image; + let maxI = imgHeight; + let stateCount = this.CrossCheckStateCount; + let i = startI; + while (i >= 0 && image[centerJ + i * imgWidth]) { + stateCount[2]++; + i--; + } + if (i < 0) { + return NaN; + } + while (i >= 0 && !image[centerJ + i * imgWidth] && stateCount[1] <= maxCount) { + stateCount[1]++; + i--; + } + if (i < 0 || stateCount[1] > maxCount) { + return NaN; + } + while (i >= 0 && image[centerJ + i * imgWidth] && stateCount[0] <= maxCount) { + stateCount[0]++; + i--; + } + if (stateCount[0] > maxCount) { + return NaN; + } + i = startI + 1; + while (i < maxI && image[centerJ + i * imgWidth]) { + stateCount[2]++; + i++; + } + if (i == maxI) { + return NaN; + } + while (i < maxI && !image[centerJ + i * imgWidth] && stateCount[3] < maxCount) { + stateCount[3]++; + i++; + } + if (i == maxI || stateCount[3] >= maxCount) { + return NaN; + } + while (i < maxI && image[centerJ + i * imgWidth] && stateCount[4] < maxCount) { + stateCount[4]++; + i++; + } + if (stateCount[4] >= maxCount) { + return NaN; + } + let stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4]; + if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= 2 * originalStateCountTotal) { + return NaN; + } + return this.foundPatternCross(stateCount) ? this.centerFromEnd(stateCount, i) : NaN; + }; + this.crossCheckHorizontal = function(startJ, centerI, maxCount, originalStateCountTotal) { + let image = this.image; + let maxJ = imgWidth; + let stateCount = this.CrossCheckStateCount; + let j = startJ; + while (j >= 0 && image[j + centerI * imgWidth]) { + stateCount[2]++; + j--; + } + if (j < 0) { + return NaN; + } + while (j >= 0 && !image[j + centerI * imgWidth] && stateCount[1] <= maxCount) { + stateCount[1]++; + j--; + } + if (j < 0 || stateCount[1] > maxCount) { + return NaN; + } + while (j >= 0 && image[j + centerI * imgWidth] && stateCount[0] <= maxCount) { + stateCount[0]++; + j--; + } + if (stateCount[0] > maxCount) { + return NaN; + } + j = startJ + 1; + while (j < maxJ && image[j + centerI * imgWidth]) { + stateCount[2]++; + j++; + } + if (j == maxJ) { + return NaN; + } + while (j < maxJ && !image[j + centerI * imgWidth] && stateCount[3] < maxCount) { + stateCount[3]++; + j++; + } + if (j == maxJ || stateCount[3] >= maxCount) { + return NaN; + } + while (j < maxJ && image[j + centerI * imgWidth] && stateCount[4] < maxCount) { + stateCount[4]++; + j++; + } + if (stateCount[4] >= maxCount) { + return NaN; + } + let stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4]; + if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= originalStateCountTotal) { + return NaN; + } + return this.foundPatternCross(stateCount) ? this.centerFromEnd(stateCount, j) : NaN; + }; + this.handlePossibleCenter = function(stateCount, i, j) { + let stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4]; + let centerJ = this.centerFromEnd(stateCount, j); + let centerI = this.crossCheckVertical(i, Math.floor(centerJ), stateCount[2], stateCountTotal); + if (!isNaN(centerI)) { + centerJ = this.crossCheckHorizontal(Math.floor(centerJ), Math.floor(centerI), stateCount[2], stateCountTotal); + if (!isNaN(centerJ)) { + let estimatedModuleSize = stateCountTotal / 7; + let found = false; + let max = this.possibleCenters.length; + for (let index = 0; index < max; index++) { + let center = this.possibleCenters[index]; + if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) { + center.incrementCount(); + found = true; + break; + } + } + if (!found) { + let point = new FinderPattern(centerJ, centerI, estimatedModuleSize); + this.possibleCenters.push(point); + if (this.resultPointCallback !== null) { + this.resultPointCallback.foundPossibleResultPoint(point); + } + } + return true; + } + } + return false; + }; + this.selectBestPatterns = function() { + let startSize = this.possibleCenters.length; + if (startSize < 3) { + throw Error("Couldn't find enough finder patterns"); + } + if (startSize > 3) { + let totalModuleSize = 0; + let square = 0; + for (let i = 0; i < startSize; i++) { + let centerValue = this.possibleCenters[i].EstimatedModuleSize; + totalModuleSize += centerValue; + square += centerValue * centerValue; + } + let average = totalModuleSize / startSize; + this.possibleCenters.sort(function(center1, center2) { + let dA = Math.abs(center2.EstimatedModuleSize - average); + let dB = Math.abs(center1.EstimatedModuleSize - average); + if (dA < dB) { + return -1; + } else if (dA == dB) { + return 0; + } else { + return 1; + } + }); + let stdDev = Math.sqrt(square / startSize - average * average); + let limit = Math.max(0.2 * average, stdDev); + for (let i = 0; i < this.possibleCenters.length && this.possibleCenters.length > 3; i++) { + let pattern = this.possibleCenters[i]; + if (Math.abs(pattern.EstimatedModuleSize - average) > limit) { + // mozilla: use splice instead + this.possibleCenters.splice(i, 1); + i--; + } + } + } + if (this.possibleCenters.length > 3) { + this.possibleCenters.sort(function(a, b) { + if (a.count > b.count) { + return -1; + } + if (a.count < b.count) { + return 1; + } + return 0; + }); + } + return new Array(this.possibleCenters[0], this.possibleCenters[1], this.possibleCenters[2]); + }; + this.findRowSkip = function() { + let max = this.possibleCenters.length; + if (max <= 1) { + return 0; + } + let firstConfirmedCenter = null; + for (let i = 0; i < max; i++) { + let center = this.possibleCenters[i]; + if (center.Count >= CENTER_QUORUM) { + if (firstConfirmedCenter === null) { + firstConfirmedCenter = center; + } else { + this.hasSkipped = true; + return Math.floor((Math.abs(firstConfirmedCenter.X - center.X) - Math.abs(firstConfirmedCenter.Y - center.Y)) / 2); + } + } + } + return 0; + }; + this.haveMultiplyConfirmedCenters = function() { + let confirmedCount = 0; + let totalModuleSize = 0; + let max = this.possibleCenters.length; + for (let i = 0; i < max; i++) { + let pattern = this.possibleCenters[i]; + if (pattern.Count >= CENTER_QUORUM) { + confirmedCount++; + totalModuleSize += pattern.EstimatedModuleSize; + } + } + if (confirmedCount < 3) { + return false; + } + let average = totalModuleSize / max; + let totalDeviation = 0; + for (let i = 0; i < max; i++) { + let pattern = this.possibleCenters[i]; + totalDeviation += Math.abs(pattern.EstimatedModuleSize - average); + } + return totalDeviation <= 0.05 * totalModuleSize; + }; + this.findFinderPattern = function(image) { + let tryHarder = false; + this.image = image; + let maxI = imgHeight; + let maxJ = imgWidth; + let iSkip = Math.floor(3 * maxI / (4 * MAX_MODULES)); + if (iSkip < MIN_SKIP || tryHarder) { + iSkip = MIN_SKIP; + } + let done = false; + let stateCount = new Array(5); + for (let i = iSkip - 1; i < maxI && !done; i += iSkip) { + stateCount[0] = 0; + stateCount[1] = 0; + stateCount[2] = 0; + stateCount[3] = 0; + stateCount[4] = 0; + let currentState = 0; + for (let j = 0; j < maxJ; j++) { + if (image[j + i * imgWidth]) { + if ((currentState & 1) == 1) { + currentState++; + } + stateCount[currentState]++; + } else { + if ((currentState & 1) === 0) { + if (currentState == 4) { + if (this.foundPatternCross(stateCount)) { + let confirmed = this.handlePossibleCenter(stateCount, i, j); + if (confirmed) { + iSkip = 2; + if (this.hasSkipped) { + done = this.haveMultiplyConfirmedCenters(); + } else { + let rowSkip = this.findRowSkip(); + if (rowSkip > stateCount[2]) { + i += rowSkip - stateCount[2] - iSkip; + j = maxJ - 1; + } + } + } else { + do { + j++; + } while (j < maxJ && !image[j + i * imgWidth]); + j--; + } + currentState = 0; + stateCount[0] = 0; + stateCount[1] = 0; + stateCount[2] = 0; + stateCount[3] = 0; + stateCount[4] = 0; + } else { + stateCount[0] = stateCount[2]; + stateCount[1] = stateCount[3]; + stateCount[2] = stateCount[4]; + stateCount[3] = 1; + stateCount[4] = 0; + currentState = 3; + } + } else { + stateCount[++currentState]++; + } + } else { + stateCount[currentState]++; + } + } + } + if (this.foundPatternCross(stateCount)) { + let confirmed = this.handlePossibleCenter(stateCount, i, maxJ); + if (confirmed) { + iSkip = stateCount[0]; + if (this.hasSkipped) { + done = this.haveMultiplyConfirmedCenters(); + } + } + } + } + let patternInfo = this.selectBestPatterns(); + qrcode.orderBestPatterns(patternInfo); + return new FinderPatternInfo(patternInfo); + }; +} + +function AlignmentPattern(posX, posY, estimatedModuleSize) { + this.x = posX; + this.y = posY; + this.count = 1; + this.estimatedModuleSize = estimatedModuleSize; + this.__defineGetter__("EstimatedModuleSize", function() { + return this.estimatedModuleSize; + }); + this.__defineGetter__("Count", function() { + return this.count; + }); + this.__defineGetter__("X", function() { + return Math.floor(this.x); + }); + this.__defineGetter__("Y", function() { + return Math.floor(this.y); + }); + this.incrementCount = function() { + this.count++; + }; + this.aboutEquals = function(moduleSize, i, j) { + if (Math.abs(i - this.y) <= moduleSize && Math.abs(j - this.x) <= moduleSize) { + let moduleSizeDiff = Math.abs(moduleSize - this.estimatedModuleSize); + return moduleSizeDiff <= 1 || moduleSizeDiff / this.estimatedModuleSize <= 1; + } + return false; + }; +} + +function AlignmentPatternFinder(image, startX, startY, width, height, moduleSize, resultPointCallback) { + this.image = image; + this.possibleCenters = []; + this.startX = startX; + this.startY = startY; + this.width = width; + this.height = height; + this.moduleSize = moduleSize; + this.crossCheckStateCount = new Array(0, 0, 0); + this.resultPointCallback = resultPointCallback; + this.centerFromEnd = function(stateCount, end) { + return end - stateCount[2] - stateCount[1] / 2; + }; + this.foundPatternCross = function(stateCount) { + let moduleSize = this.moduleSize; + let maxVariance = moduleSize / 2; + for (let i = 0; i < 3; i++) { + if (Math.abs(moduleSize - stateCount[i]) >= maxVariance) { + return false; + } + } + return true; + }; + this.crossCheckVertical = function(startI, centerJ, maxCount, originalStateCountTotal) { + let image = this.image; + let maxI = imgHeight; + let stateCount = this.crossCheckStateCount; + stateCount[0] = 0; + stateCount[1] = 0; + stateCount[2] = 0; + let i = startI; + while (i >= 0 && image[centerJ + i * imgWidth] && stateCount[1] <= maxCount) { + stateCount[1]++; + i--; + } + if (i < 0 || stateCount[1] > maxCount) { + return NaN; + } + while (i >= 0 && !image[centerJ + i * imgWidth] && stateCount[0] <= maxCount) { + stateCount[0]++; + i--; + } + if (stateCount[0] > maxCount) { + return NaN; + } + i = startI + 1; + while (i < maxI && image[centerJ + i * imgWidth] && stateCount[1] <= maxCount) { + stateCount[1]++; + i++; + } + if (i == maxI || stateCount[1] > maxCount) { + return NaN; + } + while (i < maxI && !image[centerJ + i * imgWidth] && stateCount[2] <= maxCount) { + stateCount[2]++; + i++; + } + if (stateCount[2] > maxCount) { + return NaN; + } + let stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2]; + if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= 2 * originalStateCountTotal) { + return NaN; + } + return this.foundPatternCross(stateCount) ? this.centerFromEnd(stateCount, i) : NaN; + }; + this.handlePossibleCenter = function(stateCount, i, j) { + let stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2]; + let centerJ = this.centerFromEnd(stateCount, j); + let centerI = this.crossCheckVertical(i, Math.floor(centerJ), 2 * stateCount[1], stateCountTotal); + if (!isNaN(centerI)) { + let estimatedModuleSize = (stateCount[0] + stateCount[1] + stateCount[2]) / 3; + let max = this.possibleCenters.length; + for (let index = 0; index < max; index++) { + let center = this.possibleCenters[index]; + if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) { + return new AlignmentPattern(centerJ, centerI, estimatedModuleSize); + } + } + let point = new AlignmentPattern(centerJ, centerI, estimatedModuleSize); + this.possibleCenters.push(point); + if (this.resultPointCallback !== null) { + this.resultPointCallback.foundPossibleResultPoint(point); + } + } + return null; + }; + this.find = function() { + let startX = this.startX; + let height = this.height; + let maxJ = startX + width; + let middleI = startY + (height >> 1); + let stateCount = new Array(0, 0, 0); + for (let iGen = 0; iGen < height; iGen++) { + let i = middleI + ((iGen & 1) === 0 ? iGen + 1 >> 1 : -(iGen + 1 >> 1)); + stateCount[0] = 0; + stateCount[1] = 0; + stateCount[2] = 0; + let j = startX; + while (j < maxJ && !image[j + imgWidth * i]) { + j++; + } + let currentState = 0; + while (j < maxJ) { + if (image[j + i * imgWidth]) { + if (currentState == 1) { + stateCount[currentState]++; + } else { + if (currentState == 2) { + if (this.foundPatternCross(stateCount)) { + let confirmed = this.handlePossibleCenter(stateCount, i, j); + if (confirmed !== null) { + return confirmed; + } + } + stateCount[0] = stateCount[2]; + stateCount[1] = 1; + stateCount[2] = 0; + currentState = 1; + } else { + stateCount[++currentState]++; + } + } + } else { + if (currentState == 1) { + currentState++; + } + stateCount[currentState]++; + } + j++; + } + if (this.foundPatternCross(stateCount)) { + let confirmed = this.handlePossibleCenter(stateCount, i, maxJ); + if (confirmed !== null) { + return confirmed; + } + } + } + if (this.possibleCenters.length !== 0) { + return this.possibleCenters[0]; + } + throw "Couldn't find enough alignment patterns"; + }; +} + +function QRCodeDataBlockReader(blocks, version, numErrorCorrectionCode) { + this.blockPointer = 0; + this.bitPointer = 7; + this.dataLength = 0; + this.blocks = blocks; + this.numErrorCorrectionCode = numErrorCorrectionCode; + if (version <= 9) this.dataLengthMode = 0; else if (version >= 10 && version <= 26) this.dataLengthMode = 1; else if (version >= 27 && version <= 40) this.dataLengthMode = 2; + this.getNextBits = function(numBits) { + let bits = 0; + if (numBits < this.bitPointer + 1) { + let mask = 0; + for (let i = 0; i < numBits; i++) { + mask += 1 << i; + } + mask <<= this.bitPointer - numBits + 1; + bits = (this.blocks[this.blockPointer] & mask) >> this.bitPointer - numBits + 1; + this.bitPointer -= numBits; + return bits; + } else if (numBits < this.bitPointer + 1 + 8) { + let mask1 = 0; + for (let i = 0; i < this.bitPointer + 1; i++) { + mask1 += 1 << i; + } + bits = (this.blocks[this.blockPointer] & mask1) << numBits - (this.bitPointer + 1); + this.blockPointer++; + bits += this.blocks[this.blockPointer] >> 8 - (numBits - (this.bitPointer + 1)); + this.bitPointer = this.bitPointer - numBits % 8; + if (this.bitPointer < 0) { + this.bitPointer = 8 + this.bitPointer; + } + return bits; + } else if (numBits < this.bitPointer + 1 + 16) { + let mask1 = 0; + let mask3 = 0; + for (let i = 0; i < this.bitPointer + 1; i++) { + mask1 += 1 << i; + } + let bitsFirstBlock = (this.blocks[this.blockPointer] & mask1) << numBits - (this.bitPointer + 1); + this.blockPointer++; + let bitsSecondBlock = this.blocks[this.blockPointer] << numBits - (this.bitPointer + 1 + 8); + this.blockPointer++; + for (let i = 0; i < numBits - (this.bitPointer + 1 + 8); i++) { + mask3 += 1 << i; + } + mask3 <<= 8 - (numBits - (this.bitPointer + 1 + 8)); + let bitsThirdBlock = (this.blocks[this.blockPointer] & mask3) >> 8 - (numBits - (this.bitPointer + 1 + 8)); + bits = bitsFirstBlock + bitsSecondBlock + bitsThirdBlock; + this.bitPointer = this.bitPointer - (numBits - 8) % 8; + if (this.bitPointer < 0) { + this.bitPointer = 8 + this.bitPointer; + } + return bits; + } else { + return 0; + } + }; + this.NextMode = function() { + if (this.blockPointer > this.blocks.length - this.numErrorCorrectionCode - 2) return 0; else return this.getNextBits(4); + }; + this.getDataLength = function(modeIndicator) { + let index = 0; + while (true) { + if (modeIndicator >> index == 1) break; + index++; + } + return this.getNextBits(sizeOfDataLengthInfo[this.dataLengthMode][index]); + }; + this.getRomanAndFigureString = function(dataLength) { + var length = dataLength; + let intData = 0; + let strData = ""; + let tableRomanAndFigure = new Array("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", " ", "$", "%", "*", "+", "-", ".", "/", ":"); + do { + if (length > 1) { + intData = this.getNextBits(11); + let firstLetter = Math.floor(intData / 45); + let secondLetter = intData % 45; + strData += tableRomanAndFigure[firstLetter]; + strData += tableRomanAndFigure[secondLetter]; + length -= 2; + } else if (length == 1) { + intData = this.getNextBits(6); + strData += tableRomanAndFigure[intData]; + length -= 1; + } + } while (length > 0); + return strData; + }; + this.getFigureString = function(dataLength) { + var length = dataLength; + let intData = 0; + let strData = ""; + do { + if (length >= 3) { + intData = this.getNextBits(10); + if (intData < 100) strData += "0"; + if (intData < 10) strData += "0"; + length -= 3; + } else if (length == 2) { + intData = this.getNextBits(7); + if (intData < 10) strData += "0"; + length -= 2; + } else if (length == 1) { + intData = this.getNextBits(4); + length -= 1; + } + strData += intData; + } while (length > 0); + return strData; + }; + this.get8bitByteArray = function(dataLength) { + var length = dataLength; + let intData = 0; + let output = []; + do { + intData = this.getNextBits(8); + output.push(intData); + length--; + } while (length > 0); + return output; + }; + this.getKanjiString = function(dataLength) { + var length = dataLength; + let intData = 0; + let unicodeString = ""; + do { + intData = this.getNextBits(13); + let lowerByte = intData % 192; + let higherByte = intData / 192; + let tempWord = (higherByte << 8) + lowerByte; + let shiftjisWord = 0; + if (tempWord + 33088 <= 40956) { + shiftjisWord = tempWord + 33088; + } else { + shiftjisWord = tempWord + 49472; + } + unicodeString += String.fromCharCode(shiftjisWord); + length--; + } while (length > 0); + return unicodeString; + }; + this.__defineGetter__("DataByte", function() { + let output = []; + let MODE_NUMBER = 1; + let MODE_ROMAN_AND_NUMBER = 2; + let MODE_8BIT_BYTE = 4; + let MODE_KANJI = 8; + do { + let mode = this.NextMode(); + if (mode === 0) { + if (output.length > 0) break; else throw "Empty data block"; + } + if (mode != MODE_NUMBER && mode != MODE_ROMAN_AND_NUMBER && mode != MODE_8BIT_BYTE && mode != MODE_KANJI) { + throw "Invalid mode: " + mode + " in (block:" + this.blockPointer + " bit:" + this.bitPointer + ")"; + } + let dataLength = this.getDataLength(mode); + if (dataLength < 1) { + throw "Invalid data length: " + dataLength; + } + let temp_str; + let ta; + switch (mode) { + case MODE_NUMBER: + temp_str = this.getFigureString(dataLength); + ta = new Array(temp_str.length); + for (let j = 0; j < temp_str.length; j++) ta[j] = temp_str.charCodeAt(j); + output.push(ta); + break; + + case MODE_ROMAN_AND_NUMBER: + temp_str = this.getRomanAndFigureString(dataLength); + ta = new Array(temp_str.length); + for (let j = 0; j < temp_str.length; j++) ta[j] = temp_str.charCodeAt(j); + output.push(ta); + break; + + case MODE_8BIT_BYTE: + let temp_sbyteArray3 = this.get8bitByteArray(dataLength); + output.push(temp_sbyteArray3); + break; + + case MODE_KANJI: + temp_str = this.getKanjiString(dataLength); + output.push(temp_str); + break; + } + } while (true); + return output; + }); +} diff --git a/devtools/shared/qrcode/decoder/moz.build b/devtools/shared/qrcode/decoder/moz.build new file mode 100644 index 0000000000..4442a2e906 --- /dev/null +++ b/devtools/shared/qrcode/decoder/moz.build @@ -0,0 +1,9 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DevToolsModules( + 'index.js', +) diff --git a/devtools/shared/qrcode/encoder/LICENSE b/devtools/shared/qrcode/encoder/LICENSE new file mode 100644 index 0000000000..a93630a3ba --- /dev/null +++ b/devtools/shared/qrcode/encoder/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2009 Kazuhiko Arase + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/devtools/shared/qrcode/encoder/index.js b/devtools/shared/qrcode/encoder/index.js new file mode 100644 index 0000000000..487d0f471a --- /dev/null +++ b/devtools/shared/qrcode/encoder/index.js @@ -0,0 +1,1674 @@ +//--------------------------------------------------------------------- +// +// QR Code Generator for JavaScript +// +// Copyright (c) 2009 Kazuhiko Arase +// +// URL: http://www.d-project.com/ +// +// Licensed under the MIT license: +// http://www.opensource.org/licenses/mit-license.php +// +// The word 'QR Code' is registered trademark of +// DENSO WAVE INCORPORATED +// http://www.denso-wave.com/qrcode/faqpatent-e.html +// +//--------------------------------------------------------------------- + +var qrcode = function() { + + //--------------------------------------------------------------------- + // qrcode + //--------------------------------------------------------------------- + + /** + * qrcode + * @param typeNumber 1 to 10 + * @param errorCorrectLevel 'L','M','Q','H' + */ + var qrcode = function(typeNumber, errorCorrectLevel) { + + var PAD0 = 0xEC; + var PAD1 = 0x11; + + var _typeNumber = typeNumber; + var _errorCorrectLevel = QRErrorCorrectLevel[errorCorrectLevel]; + var _modules = null; + var _moduleCount = 0; + var _dataCache = null; + var _dataList = new Array(); + + var _this = {}; + + var makeImpl = function(test, maskPattern) { + + _moduleCount = _typeNumber * 4 + 17; + _modules = function(moduleCount) { + var modules = new Array(moduleCount); + for (var row = 0; row < moduleCount; row += 1) { + modules[row] = new Array(moduleCount); + for (var col = 0; col < moduleCount; col += 1) { + modules[row][col] = null; + } + } + return modules; + }(_moduleCount); + + setupPositionProbePattern(0, 0); + setupPositionProbePattern(_moduleCount - 7, 0); + setupPositionProbePattern(0, _moduleCount - 7); + setupPositionAdjustPattern(); + setupTimingPattern(); + setupTypeInfo(test, maskPattern); + + if (_typeNumber >= 7) { + setupTypeNumber(test); + } + + if (_dataCache == null) { + _dataCache = createData(_typeNumber, _errorCorrectLevel, _dataList); + } + + mapData(_dataCache, maskPattern); + }; + + var setupPositionProbePattern = function(row, col) { + + for (var r = -1; r <= 7; r += 1) { + + if (row + r <= -1 || _moduleCount <= row + r) continue; + + for (var c = -1; c <= 7; c += 1) { + + if (col + c <= -1 || _moduleCount <= col + c) continue; + + if ( (0 <= r && r <= 6 && (c == 0 || c == 6) ) + || (0 <= c && c <= 6 && (r == 0 || r == 6) ) + || (2 <= r && r <= 4 && 2 <= c && c <= 4) ) { + _modules[row + r][col + c] = true; + } else { + _modules[row + r][col + c] = false; + } + } + } + }; + + var getBestMaskPattern = function() { + + var minLostPoint = 0; + var pattern = 0; + + for (var i = 0; i < 8; i += 1) { + + makeImpl(true, i); + + var lostPoint = QRUtil.getLostPoint(_this); + + if (i == 0 || minLostPoint > lostPoint) { + minLostPoint = lostPoint; + pattern = i; + } + } + + return pattern; + }; + + var setupTimingPattern = function() { + + for (var r = 8; r < _moduleCount - 8; r += 1) { + if (_modules[r][6] != null) { + continue; + } + _modules[r][6] = (r % 2 == 0); + } + + for (var c = 8; c < _moduleCount - 8; c += 1) { + if (_modules[6][c] != null) { + continue; + } + _modules[6][c] = (c % 2 == 0); + } + }; + + var setupPositionAdjustPattern = function() { + + var pos = QRUtil.getPatternPosition(_typeNumber); + + for (var i = 0; i < pos.length; i += 1) { + + for (var j = 0; j < pos.length; j += 1) { + + var row = pos[i]; + var col = pos[j]; + + if (_modules[row][col] != null) { + continue; + } + + for (var r = -2; r <= 2; r += 1) { + + for (var c = -2; c <= 2; c += 1) { + + if (r == -2 || r == 2 || c == -2 || c == 2 + || (r == 0 && c == 0) ) { + _modules[row + r][col + c] = true; + } else { + _modules[row + r][col + c] = false; + } + } + } + } + } + }; + + var setupTypeNumber = function(test) { + + var bits = QRUtil.getBCHTypeNumber(_typeNumber); + + for (var i = 0; i < 18; i += 1) { + var mod = (!test && ( (bits >> i) & 1) == 1); + _modules[Math.floor(i / 3)][i % 3 + _moduleCount - 8 - 3] = mod; + } + + for (var i = 0; i < 18; i += 1) { + var mod = (!test && ( (bits >> i) & 1) == 1); + _modules[i % 3 + _moduleCount - 8 - 3][Math.floor(i / 3)] = mod; + } + }; + + var setupTypeInfo = function(test, maskPattern) { + + var data = (_errorCorrectLevel << 3) | maskPattern; + var bits = QRUtil.getBCHTypeInfo(data); + + // vertical + for (var i = 0; i < 15; i += 1) { + + var mod = (!test && ( (bits >> i) & 1) == 1); + + if (i < 6) { + _modules[i][8] = mod; + } else if (i < 8) { + _modules[i + 1][8] = mod; + } else { + _modules[_moduleCount - 15 + i][8] = mod; + } + } + + // horizontal + for (var i = 0; i < 15; i += 1) { + + var mod = (!test && ( (bits >> i) & 1) == 1); + + if (i < 8) { + _modules[8][_moduleCount - i - 1] = mod; + } else if (i < 9) { + _modules[8][15 - i - 1 + 1] = mod; + } else { + _modules[8][15 - i - 1] = mod; + } + } + + // fixed module + _modules[_moduleCount - 8][8] = (!test); + }; + + var mapData = function(data, maskPattern) { + + var inc = -1; + var row = _moduleCount - 1; + var bitIndex = 7; + var byteIndex = 0; + var maskFunc = QRUtil.getMaskFunction(maskPattern); + + for (var col = _moduleCount - 1; col > 0; col -= 2) { + + if (col == 6) col -= 1; + + while (true) { + + for (var c = 0; c < 2; c += 1) { + + if (_modules[row][col - c] == null) { + + var dark = false; + + if (byteIndex < data.length) { + dark = ( ( (data[byteIndex] >>> bitIndex) & 1) == 1); + } + + var mask = maskFunc(row, col - c); + + if (mask) { + dark = !dark; + } + + _modules[row][col - c] = dark; + bitIndex -= 1; + + if (bitIndex == -1) { + byteIndex += 1; + bitIndex = 7; + } + } + } + + row += inc; + + if (row < 0 || _moduleCount <= row) { + row -= inc; + inc = -inc; + break; + } + } + } + }; + + var createBytes = function(buffer, rsBlocks) { + + var offset = 0; + + var maxDcCount = 0; + var maxEcCount = 0; + + var dcdata = new Array(rsBlocks.length); + var ecdata = new Array(rsBlocks.length); + + for (var r = 0; r < rsBlocks.length; r += 1) { + + var dcCount = rsBlocks[r].dataCount; + var ecCount = rsBlocks[r].totalCount - dcCount; + + maxDcCount = Math.max(maxDcCount, dcCount); + maxEcCount = Math.max(maxEcCount, ecCount); + + dcdata[r] = new Array(dcCount); + + for (var i = 0; i < dcdata[r].length; i += 1) { + dcdata[r][i] = 0xff & buffer.getBuffer()[i + offset]; + } + offset += dcCount; + + var rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount); + var rawPoly = qrPolynomial(dcdata[r], rsPoly.getLength() - 1); + + var modPoly = rawPoly.mod(rsPoly); + ecdata[r] = new Array(rsPoly.getLength() - 1); + for (var i = 0; i < ecdata[r].length; i += 1) { + var modIndex = i + modPoly.getLength() - ecdata[r].length; + ecdata[r][i] = (modIndex >= 0)? modPoly.getAt(modIndex) : 0; + } + } + + var totalCodeCount = 0; + for (var i = 0; i < rsBlocks.length; i += 1) { + totalCodeCount += rsBlocks[i].totalCount; + } + + var data = new Array(totalCodeCount); + var index = 0; + + for (var i = 0; i < maxDcCount; i += 1) { + for (var r = 0; r < rsBlocks.length; r += 1) { + if (i < dcdata[r].length) { + data[index] = dcdata[r][i]; + index += 1; + } + } + } + + for (var i = 0; i < maxEcCount; i += 1) { + for (var r = 0; r < rsBlocks.length; r += 1) { + if (i < ecdata[r].length) { + data[index] = ecdata[r][i]; + index += 1; + } + } + } + + return data; + }; + + var createData = function(typeNumber, errorCorrectLevel, dataList) { + + var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, errorCorrectLevel); + + var buffer = qrBitBuffer(); + + for (var i = 0; i < dataList.length; i += 1) { + var data = dataList[i]; + buffer.put(data.getMode(), 4); + buffer.put(data.getLength(), QRUtil.getLengthInBits(data.getMode(), typeNumber) ); + data.write(buffer); + } + + // calc num max data. + var totalDataCount = 0; + for (var i = 0; i < rsBlocks.length; i += 1) { + totalDataCount += rsBlocks[i].dataCount; + } + + if (buffer.getLengthInBits() > totalDataCount * 8) { + throw new Error('code length overflow. (' + + buffer.getLengthInBits() + + '>' + + totalDataCount * 8 + + ')'); + } + + // end code + if (buffer.getLengthInBits() + 4 <= totalDataCount * 8) { + buffer.put(0, 4); + } + + // padding + while (buffer.getLengthInBits() % 8 != 0) { + buffer.putBit(false); + } + + // padding + while (true) { + + if (buffer.getLengthInBits() >= totalDataCount * 8) { + break; + } + buffer.put(PAD0, 8); + + if (buffer.getLengthInBits() >= totalDataCount * 8) { + break; + } + buffer.put(PAD1, 8); + } + + return createBytes(buffer, rsBlocks); + }; + + _this.addData = function(data) { + var newData = qr8BitByte(data); + _dataList.push(newData); + _dataCache = null; + }; + + _this.isDark = function(row, col) { + if (row < 0 || _moduleCount <= row || col < 0 || _moduleCount <= col) { + throw new Error(row + ',' + col); + } + return _modules[row][col]; + }; + + _this.getModuleCount = function() { + return _moduleCount; + }; + + _this.make = function() { + makeImpl(false, getBestMaskPattern() ); + }; + + _this.createTableTag = function(cellSize, margin) { + + cellSize = cellSize || 2; + margin = (typeof margin == 'undefined')? cellSize * 4 : margin; + + var qrHtml = ''; + + qrHtml += '<table style="'; + qrHtml += ' border-width: 0px; border-style: none;'; + qrHtml += ' border-collapse: collapse;'; + qrHtml += ' padding: 0px; margin: ' + margin + 'px;'; + qrHtml += '">'; + qrHtml += '<tbody>'; + + for (var r = 0; r < _this.getModuleCount(); r += 1) { + + qrHtml += '<tr>'; + + for (var c = 0; c < _this.getModuleCount(); c += 1) { + qrHtml += '<td style="'; + qrHtml += ' border-width: 0px; border-style: none;'; + qrHtml += ' border-collapse: collapse;'; + qrHtml += ' padding: 0px; margin: 0px;'; + qrHtml += ' width: ' + cellSize + 'px;'; + qrHtml += ' height: ' + cellSize + 'px;'; + qrHtml += ' background-color: '; + qrHtml += _this.isDark(r, c)? '#000000' : '#ffffff'; + qrHtml += ';'; + qrHtml += '"/>'; + } + + qrHtml += '</tr>'; + } + + qrHtml += '</tbody>'; + qrHtml += '</table>'; + + return qrHtml; + }; + + _this.createImgTag = function(cellSize, margin) { + + cellSize = cellSize || 2; + margin = (typeof margin == 'undefined')? cellSize * 4 : margin; + + var size = _this.getModuleCount() * cellSize + margin * 2; + var min = margin; + var max = size - margin; + + return createImgTag(size, size, function(x, y) { + if (min <= x && x < max && min <= y && y < max) { + var c = Math.floor( (x - min) / cellSize); + var r = Math.floor( (y - min) / cellSize); + return _this.isDark(r, c)? 0 : 1; + } else { + return 1; + } + } ); + }; + + _this.createImgData = function(cellSize, margin) { + + cellSize = cellSize || 2; + margin = (typeof margin == 'undefined')? cellSize * 4 : margin; + + var size = _this.getModuleCount() * cellSize + margin * 2; + var min = margin; + var max = size - margin; + + var base64 = createGifData(size, size, function(x, y) { + if (min <= x && x < max && min <= y && y < max) { + var c = Math.floor( (x - min) / cellSize); + var r = Math.floor( (y - min) / cellSize); + return _this.isDark(r, c)? 0 : 1; + } else { + return 1; + } + } ); + + return { + src: 'data:image/gif;base64,' + base64, + width: size, + height: size + }; + }; + + return _this; + }; + + //--------------------------------------------------------------------- + // qrcode.stringToBytes + //--------------------------------------------------------------------- + + qrcode.stringToBytes = function(s) { + var bytes = new Array(); + for (var i = 0; i < s.length; i += 1) { + var c = s.charCodeAt(i); + bytes.push(c & 0xff); + } + return bytes; + }; + + //--------------------------------------------------------------------- + // qrcode.createStringToBytes + //--------------------------------------------------------------------- + + /** + * @param unicodeData base64 string of byte array. + * [16bit Unicode],[16bit Bytes], ... + * @param numChars + */ + qrcode.createStringToBytes = function(unicodeData, numChars) { + + // create conversion map. + + var unicodeMap = function() { + + var bin = base64DecodeInputStream(unicodeData); + var read = function() { + var b = bin.read(); + if (b == -1) throw new Error(); + return b; + }; + + var count = 0; + var unicodeMap = {}; + while (true) { + var b0 = bin.read(); + if (b0 == -1) break; + var b1 = read(); + var b2 = read(); + var b3 = read(); + var k = String.fromCharCode( (b0 << 8) | b1); + var v = (b2 << 8) | b3; + unicodeMap[k] = v; + count += 1; + } + if (count != numChars) { + throw new Error(count + ' != ' + numChars); + } + + return unicodeMap; + }(); + + var unknownChar = '?'.charCodeAt(0); + + return function(s) { + var bytes = new Array(); + for (var i = 0; i < s.length; i += 1) { + var c = s.charCodeAt(i); + if (c < 128) { + bytes.push(c); + } else { + var b = unicodeMap[s.charAt(i)]; + if (typeof b == 'number') { + if ( (b & 0xff) == b) { + // 1byte + bytes.push(b); + } else { + // 2bytes + bytes.push(b >>> 8); + bytes.push(b & 0xff); + } + } else { + bytes.push(unknownChar); + } + } + } + return bytes; + }; + }; + + //--------------------------------------------------------------------- + // QRMode + //--------------------------------------------------------------------- + + var QRMode = { + MODE_NUMBER : 1 << 0, + MODE_ALPHA_NUM : 1 << 1, + MODE_8BIT_BYTE : 1 << 2, + MODE_KANJI : 1 << 3 + }; + + //--------------------------------------------------------------------- + // QRErrorCorrectLevel + //--------------------------------------------------------------------- + + var QRErrorCorrectLevel = { + L : 1, + M : 0, + Q : 3, + H : 2 + }; + // mozilla: Add module support + exports.QRErrorCorrectLevel = QRErrorCorrectLevel; + + //--------------------------------------------------------------------- + // QRMaskPattern + //--------------------------------------------------------------------- + + var QRMaskPattern = { + PATTERN000 : 0, + PATTERN001 : 1, + PATTERN010 : 2, + PATTERN011 : 3, + PATTERN100 : 4, + PATTERN101 : 5, + PATTERN110 : 6, + PATTERN111 : 7 + }; + + //--------------------------------------------------------------------- + // QRUtil + //--------------------------------------------------------------------- + + var QRUtil = function() { + + var PATTERN_POSITION_TABLE = [ + [], + [6, 18], + [6, 22], + [6, 26], + [6, 30], + [6, 34], + [6, 22, 38], + [6, 24, 42], + [6, 26, 46], + [6, 28, 50], + [6, 30, 54], + [6, 32, 58], + [6, 34, 62], + [6, 26, 46, 66], + [6, 26, 48, 70], + [6, 26, 50, 74], + [6, 30, 54, 78], + [6, 30, 56, 82], + [6, 30, 58, 86], + [6, 34, 62, 90], + [6, 28, 50, 72, 94], + [6, 26, 50, 74, 98], + [6, 30, 54, 78, 102], + [6, 28, 54, 80, 106], + [6, 32, 58, 84, 110], + [6, 30, 58, 86, 114], + [6, 34, 62, 90, 118], + [6, 26, 50, 74, 98, 122], + [6, 30, 54, 78, 102, 126], + [6, 26, 52, 78, 104, 130], + [6, 30, 56, 82, 108, 134], + [6, 34, 60, 86, 112, 138], + [6, 30, 58, 86, 114, 142], + [6, 34, 62, 90, 118, 146], + [6, 30, 54, 78, 102, 126, 150], + [6, 24, 50, 76, 102, 128, 154], + [6, 28, 54, 80, 106, 132, 158], + [6, 32, 58, 84, 110, 136, 162], + [6, 26, 54, 82, 110, 138, 166], + [6, 30, 58, 86, 114, 142, 170] + ]; + var G15 = (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0); + var G18 = (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0); + var G15_MASK = (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1); + + var _this = {}; + + var getBCHDigit = function(data) { + var digit = 0; + while (data != 0) { + digit += 1; + data >>>= 1; + } + return digit; + }; + + _this.getBCHTypeInfo = function(data) { + var d = data << 10; + while (getBCHDigit(d) - getBCHDigit(G15) >= 0) { + d ^= (G15 << (getBCHDigit(d) - getBCHDigit(G15) ) ); + } + return ( (data << 10) | d) ^ G15_MASK; + }; + + _this.getBCHTypeNumber = function(data) { + var d = data << 12; + while (getBCHDigit(d) - getBCHDigit(G18) >= 0) { + d ^= (G18 << (getBCHDigit(d) - getBCHDigit(G18) ) ); + } + return (data << 12) | d; + }; + + _this.getPatternPosition = function(typeNumber) { + return PATTERN_POSITION_TABLE[typeNumber - 1]; + }; + + _this.getMaskFunction = function(maskPattern) { + + switch (maskPattern) { + + case QRMaskPattern.PATTERN000 : + return function(i, j) { return (i + j) % 2 == 0; }; + case QRMaskPattern.PATTERN001 : + return function(i, j) { return i % 2 == 0; }; + case QRMaskPattern.PATTERN010 : + return function(i, j) { return j % 3 == 0; }; + case QRMaskPattern.PATTERN011 : + return function(i, j) { return (i + j) % 3 == 0; }; + case QRMaskPattern.PATTERN100 : + return function(i, j) { return (Math.floor(i / 2) + Math.floor(j / 3) ) % 2 == 0; }; + case QRMaskPattern.PATTERN101 : + return function(i, j) { return (i * j) % 2 + (i * j) % 3 == 0; }; + case QRMaskPattern.PATTERN110 : + return function(i, j) { return ( (i * j) % 2 + (i * j) % 3) % 2 == 0; }; + case QRMaskPattern.PATTERN111 : + return function(i, j) { return ( (i * j) % 3 + (i + j) % 2) % 2 == 0; }; + + default : + throw new Error('bad maskPattern:' + maskPattern); + } + }; + + _this.getErrorCorrectPolynomial = function(errorCorrectLength) { + var a = qrPolynomial([1], 0); + for (var i = 0; i < errorCorrectLength; i += 1) { + a = a.multiply(qrPolynomial([1, QRMath.gexp(i)], 0) ); + } + return a; + }; + + _this.getLengthInBits = function(mode, type) { + + if (1 <= type && type < 10) { + + // 1 - 9 + + switch(mode) { + case QRMode.MODE_NUMBER : return 10; + case QRMode.MODE_ALPHA_NUM : return 9; + case QRMode.MODE_8BIT_BYTE : return 8; + case QRMode.MODE_KANJI : return 8; + default : + throw new Error('mode:' + mode); + } + + } else if (type < 27) { + + // 10 - 26 + + switch(mode) { + case QRMode.MODE_NUMBER : return 12; + case QRMode.MODE_ALPHA_NUM : return 11; + case QRMode.MODE_8BIT_BYTE : return 16; + case QRMode.MODE_KANJI : return 10; + default : + throw new Error('mode:' + mode); + } + + } else if (type < 41) { + + // 27 - 40 + + switch(mode) { + case QRMode.MODE_NUMBER : return 14; + case QRMode.MODE_ALPHA_NUM : return 13; + case QRMode.MODE_8BIT_BYTE : return 16; + case QRMode.MODE_KANJI : return 12; + default : + throw new Error('mode:' + mode); + } + + } else { + throw new Error('type:' + type); + } + }; + + _this.getLostPoint = function(qrcode) { + + var moduleCount = qrcode.getModuleCount(); + + var lostPoint = 0; + + // LEVEL1 + + for (var row = 0; row < moduleCount; row += 1) { + for (var col = 0; col < moduleCount; col += 1) { + + var sameCount = 0; + var dark = qrcode.isDark(row, col); + + for (var r = -1; r <= 1; r += 1) { + + if (row + r < 0 || moduleCount <= row + r) { + continue; + } + + for (var c = -1; c <= 1; c += 1) { + + if (col + c < 0 || moduleCount <= col + c) { + continue; + } + + if (r == 0 && c == 0) { + continue; + } + + if (dark == qrcode.isDark(row + r, col + c) ) { + sameCount += 1; + } + } + } + + if (sameCount > 5) { + lostPoint += (3 + sameCount - 5); + } + } + }; + + // LEVEL2 + + for (var row = 0; row < moduleCount - 1; row += 1) { + for (var col = 0; col < moduleCount - 1; col += 1) { + var count = 0; + if (qrcode.isDark(row, col) ) count += 1; + if (qrcode.isDark(row + 1, col) ) count += 1; + if (qrcode.isDark(row, col + 1) ) count += 1; + if (qrcode.isDark(row + 1, col + 1) ) count += 1; + if (count == 0 || count == 4) { + lostPoint += 3; + } + } + } + + // LEVEL3 + + for (var row = 0; row < moduleCount; row += 1) { + for (var col = 0; col < moduleCount - 6; col += 1) { + if (qrcode.isDark(row, col) + && !qrcode.isDark(row, col + 1) + && qrcode.isDark(row, col + 2) + && qrcode.isDark(row, col + 3) + && qrcode.isDark(row, col + 4) + && !qrcode.isDark(row, col + 5) + && qrcode.isDark(row, col + 6) ) { + lostPoint += 40; + } + } + } + + for (var col = 0; col < moduleCount; col += 1) { + for (var row = 0; row < moduleCount - 6; row += 1) { + if (qrcode.isDark(row, col) + && !qrcode.isDark(row + 1, col) + && qrcode.isDark(row + 2, col) + && qrcode.isDark(row + 3, col) + && qrcode.isDark(row + 4, col) + && !qrcode.isDark(row + 5, col) + && qrcode.isDark(row + 6, col) ) { + lostPoint += 40; + } + } + } + + // LEVEL4 + + var darkCount = 0; + + for (var col = 0; col < moduleCount; col += 1) { + for (var row = 0; row < moduleCount; row += 1) { + if (qrcode.isDark(row, col) ) { + darkCount += 1; + } + } + } + + var ratio = Math.abs(100 * darkCount / moduleCount / moduleCount - 50) / 5; + lostPoint += ratio * 10; + + return lostPoint; + }; + + return _this; + }(); + + //--------------------------------------------------------------------- + // QRMath + //--------------------------------------------------------------------- + + var QRMath = function() { + + var EXP_TABLE = new Array(256); + var LOG_TABLE = new Array(256); + + // initialize tables + for (var i = 0; i < 8; i += 1) { + EXP_TABLE[i] = 1 << i; + } + for (var i = 8; i < 256; i += 1) { + EXP_TABLE[i] = EXP_TABLE[i - 4] + ^ EXP_TABLE[i - 5] + ^ EXP_TABLE[i - 6] + ^ EXP_TABLE[i - 8]; + } + for (var i = 0; i < 255; i += 1) { + LOG_TABLE[EXP_TABLE[i] ] = i; + } + + var _this = {}; + + _this.glog = function(n) { + + if (n < 1) { + throw new Error('glog(' + n + ')'); + } + + return LOG_TABLE[n]; + }; + + _this.gexp = function(n) { + + while (n < 0) { + n += 255; + } + + while (n >= 256) { + n -= 255; + } + + return EXP_TABLE[n]; + }; + + return _this; + }(); + + //--------------------------------------------------------------------- + // qrPolynomial + //--------------------------------------------------------------------- + + function qrPolynomial(num, shift) { + + if (typeof num.length == 'undefined') { + throw new Error(num.length + '/' + shift); + } + + var _num = function() { + var offset = 0; + while (offset < num.length && num[offset] == 0) { + offset += 1; + } + var _num = new Array(num.length - offset + shift); + for (var i = 0; i < num.length - offset; i += 1) { + _num[i] = num[i + offset]; + } + return _num; + }(); + + var _this = {}; + + _this.getAt = function(index) { + return _num[index]; + }; + + _this.getLength = function() { + return _num.length; + }; + + _this.multiply = function(e) { + + var num = new Array(_this.getLength() + e.getLength() - 1); + + for (var i = 0; i < _this.getLength(); i += 1) { + for (var j = 0; j < e.getLength(); j += 1) { + num[i + j] ^= QRMath.gexp(QRMath.glog(_this.getAt(i) ) + QRMath.glog(e.getAt(j) ) ); + } + } + + return qrPolynomial(num, 0); + }; + + _this.mod = function(e) { + + if (_this.getLength() - e.getLength() < 0) { + return _this; + } + + var ratio = QRMath.glog(_this.getAt(0) ) - QRMath.glog(e.getAt(0) ); + + var num = new Array(_this.getLength() ); + for (var i = 0; i < _this.getLength(); i += 1) { + num[i] = _this.getAt(i); + } + + for (var i = 0; i < e.getLength(); i += 1) { + num[i] ^= QRMath.gexp(QRMath.glog(e.getAt(i) ) + ratio); + } + + // recursive call + return qrPolynomial(num, 0).mod(e); + }; + + return _this; + }; + + //--------------------------------------------------------------------- + // QRRSBlock + //--------------------------------------------------------------------- + + var QRRSBlock = function() { + + var RS_BLOCK_TABLE = [ + + // L + // M + // Q + // H + + // 1 + [1, 26, 19], + [1, 26, 16], + [1, 26, 13], + [1, 26, 9], + + // 2 + [1, 44, 34], + [1, 44, 28], + [1, 44, 22], + [1, 44, 16], + + // 3 + [1, 70, 55], + [1, 70, 44], + [2, 35, 17], + [2, 35, 13], + + // 4 + [1, 100, 80], + [2, 50, 32], + [2, 50, 24], + [4, 25, 9], + + // 5 + [1, 134, 108], + [2, 67, 43], + [2, 33, 15, 2, 34, 16], + [2, 33, 11, 2, 34, 12], + + // 6 + [2, 86, 68], + [4, 43, 27], + [4, 43, 19], + [4, 43, 15], + + // 7 + [2, 98, 78], + [4, 49, 31], + [2, 32, 14, 4, 33, 15], + [4, 39, 13, 1, 40, 14], + + // 8 + [2, 121, 97], + [2, 60, 38, 2, 61, 39], + [4, 40, 18, 2, 41, 19], + [4, 40, 14, 2, 41, 15], + + // 9 + [2, 146, 116], + [3, 58, 36, 2, 59, 37], + [4, 36, 16, 4, 37, 17], + [4, 36, 12, 4, 37, 13], + + // 10 + [2, 86, 68, 2, 87, 69], + [4, 69, 43, 1, 70, 44], + [6, 43, 19, 2, 44, 20], + [6, 43, 15, 2, 44, 16] + ]; + + var qrRSBlock = function(totalCount, dataCount) { + var _this = {}; + _this.totalCount = totalCount; + _this.dataCount = dataCount; + return _this; + }; + + var _this = {}; + + var getRsBlockTable = function(typeNumber, errorCorrectLevel) { + + switch(errorCorrectLevel) { + case QRErrorCorrectLevel.L : + return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 0]; + case QRErrorCorrectLevel.M : + return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 1]; + case QRErrorCorrectLevel.Q : + return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 2]; + case QRErrorCorrectLevel.H : + return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3]; + default : + return undefined; + } + }; + + _this.getRSBlocks = function(typeNumber, errorCorrectLevel) { + + var rsBlock = getRsBlockTable(typeNumber, errorCorrectLevel); + + if (typeof rsBlock == 'undefined') { + throw new Error('bad rs block @ typeNumber:' + typeNumber + + '/errorCorrectLevel:' + errorCorrectLevel); + } + + var length = rsBlock.length / 3; + + var list = new Array(); + + for (var i = 0; i < length; i += 1) { + + var count = rsBlock[i * 3 + 0]; + var totalCount = rsBlock[i * 3 + 1]; + var dataCount = rsBlock[i * 3 + 2]; + + for (var j = 0; j < count; j += 1) { + list.push(qrRSBlock(totalCount, dataCount) ); + } + } + + return list; + }; + + return _this; + }(); + + // mozilla: Add module support + exports.QRRSBlock = QRRSBlock; + + //--------------------------------------------------------------------- + // qrBitBuffer + //--------------------------------------------------------------------- + + var qrBitBuffer = function() { + + var _buffer = new Array(); + var _length = 0; + + var _this = {}; + + _this.getBuffer = function() { + return _buffer; + }; + + _this.getAt = function(index) { + var bufIndex = Math.floor(index / 8); + return ( (_buffer[bufIndex] >>> (7 - index % 8) ) & 1) == 1; + }; + + _this.put = function(num, length) { + for (var i = 0; i < length; i += 1) { + _this.putBit( ( (num >>> (length - i - 1) ) & 1) == 1); + } + }; + + _this.getLengthInBits = function() { + return _length; + }; + + _this.putBit = function(bit) { + + var bufIndex = Math.floor(_length / 8); + if (_buffer.length <= bufIndex) { + _buffer.push(0); + } + + if (bit) { + _buffer[bufIndex] |= (0x80 >>> (_length % 8) ); + } + + _length += 1; + }; + + return _this; + }; + + //--------------------------------------------------------------------- + // qr8BitByte + //--------------------------------------------------------------------- + + var qr8BitByte = function(data) { + + var _mode = QRMode.MODE_8BIT_BYTE; + var _data = data; + var _bytes = qrcode.stringToBytes(data); + + var _this = {}; + + _this.getMode = function() { + return _mode; + }; + + _this.getLength = function(buffer) { + return _bytes.length; + }; + + _this.write = function(buffer) { + for (var i = 0; i < _bytes.length; i += 1) { + buffer.put(_bytes[i], 8); + } + }; + + return _this; + }; + + //===================================================================== + // GIF Support etc. + // + + //--------------------------------------------------------------------- + // byteArrayOutputStream + //--------------------------------------------------------------------- + + var byteArrayOutputStream = function() { + + var _bytes = new Array(); + + var _this = {}; + + _this.writeByte = function(b) { + _bytes.push(b & 0xff); + }; + + _this.writeShort = function(i) { + _this.writeByte(i); + _this.writeByte(i >>> 8); + }; + + _this.writeBytes = function(b, off, len) { + off = off || 0; + len = len || b.length; + for (var i = 0; i < len; i += 1) { + _this.writeByte(b[i + off]); + } + }; + + _this.writeString = function(s) { + for (var i = 0; i < s.length; i += 1) { + _this.writeByte(s.charCodeAt(i) ); + } + }; + + _this.toByteArray = function() { + return _bytes; + }; + + _this.toString = function() { + var s = ''; + s += '['; + for (var i = 0; i < _bytes.length; i += 1) { + if (i > 0) { + s += ','; + } + s += _bytes[i]; + } + s += ']'; + return s; + }; + + return _this; + }; + + //--------------------------------------------------------------------- + // base64EncodeOutputStream + //--------------------------------------------------------------------- + + var base64EncodeOutputStream = function() { + + var _buffer = 0; + var _buflen = 0; + var _length = 0; + var _base64 = ''; + + var _this = {}; + + var writeEncoded = function(b) { + _base64 += String.fromCharCode(encode(b & 0x3f) ); + }; + + var encode = function(n) { + if (n < 0) { + // error. + } else if (n < 26) { + return 0x41 + n; + } else if (n < 52) { + return 0x61 + (n - 26); + } else if (n < 62) { + return 0x30 + (n - 52); + } else if (n == 62) { + return 0x2b; + } else if (n == 63) { + return 0x2f; + } + throw new Error('n:' + n); + }; + + _this.writeByte = function(n) { + + _buffer = (_buffer << 8) | (n & 0xff); + _buflen += 8; + _length += 1; + + while (_buflen >= 6) { + writeEncoded(_buffer >>> (_buflen - 6) ); + _buflen -= 6; + } + }; + + _this.flush = function() { + + if (_buflen > 0) { + writeEncoded(_buffer << (6 - _buflen) ); + _buffer = 0; + _buflen = 0; + } + + if (_length % 3 != 0) { + // padding + var padlen = 3 - _length % 3; + for (var i = 0; i < padlen; i += 1) { + _base64 += '='; + } + } + }; + + _this.toString = function() { + return _base64; + }; + + return _this; + }; + + //--------------------------------------------------------------------- + // base64DecodeInputStream + //--------------------------------------------------------------------- + + var base64DecodeInputStream = function(str) { + + var _str = str; + var _pos = 0; + var _buffer = 0; + var _buflen = 0; + + var _this = {}; + + _this.read = function() { + + while (_buflen < 8) { + + if (_pos >= _str.length) { + if (_buflen == 0) { + return -1; + } + throw new Error('unexpected end of file./' + _buflen); + } + + var c = _str.charAt(_pos); + _pos += 1; + + if (c == '=') { + _buflen = 0; + return -1; + } else if (c.match(/^\s$/) ) { + // ignore if whitespace. + continue; + } + + _buffer = (_buffer << 6) | decode(c.charCodeAt(0) ); + _buflen += 6; + } + + var n = (_buffer >>> (_buflen - 8) ) & 0xff; + _buflen -= 8; + return n; + }; + + var decode = function(c) { + if (0x41 <= c && c <= 0x5a) { + return c - 0x41; + } else if (0x61 <= c && c <= 0x7a) { + return c - 0x61 + 26; + } else if (0x30 <= c && c <= 0x39) { + return c - 0x30 + 52; + } else if (c == 0x2b) { + return 62; + } else if (c == 0x2f) { + return 63; + } else { + throw new Error('c:' + c); + } + }; + + return _this; + }; + + //--------------------------------------------------------------------- + // gifImage (B/W) + //--------------------------------------------------------------------- + + var gifImage = function(width, height) { + + var _width = width; + var _height = height; + var _data = new Array(width * height); + + var _this = {}; + + _this.setPixel = function(x, y, pixel) { + _data[y * _width + x] = pixel; + }; + + _this.write = function(out) { + + //--------------------------------- + // GIF Signature + + out.writeString('GIF87a'); + + //--------------------------------- + // Screen Descriptor + + out.writeShort(_width); + out.writeShort(_height); + + out.writeByte(0x80); // 2bit + out.writeByte(0); + out.writeByte(0); + + //--------------------------------- + // Global Color Map + + // black + out.writeByte(0x00); + out.writeByte(0x00); + out.writeByte(0x00); + + // white + out.writeByte(0xff); + out.writeByte(0xff); + out.writeByte(0xff); + + //--------------------------------- + // Image Descriptor + + out.writeString(','); + out.writeShort(0); + out.writeShort(0); + out.writeShort(_width); + out.writeShort(_height); + out.writeByte(0); + + //--------------------------------- + // Local Color Map + + //--------------------------------- + // Raster Data + + var lzwMinCodeSize = 2; + var raster = getLZWRaster(lzwMinCodeSize); + + out.writeByte(lzwMinCodeSize); + + var offset = 0; + + while (raster.length - offset > 255) { + out.writeByte(255); + out.writeBytes(raster, offset, 255); + offset += 255; + } + + out.writeByte(raster.length - offset); + out.writeBytes(raster, offset, raster.length - offset); + out.writeByte(0x00); + + //--------------------------------- + // GIF Terminator + out.writeString(';'); + }; + + var bitOutputStream = function(out) { + + var _out = out; + var _bitLength = 0; + var _bitBuffer = 0; + + var _this = {}; + + _this.write = function(data, length) { + + if ( (data >>> length) != 0) { + throw new Error('length over'); + } + + while (_bitLength + length >= 8) { + _out.writeByte(0xff & ( (data << _bitLength) | _bitBuffer) ); + length -= (8 - _bitLength); + data >>>= (8 - _bitLength); + _bitBuffer = 0; + _bitLength = 0; + } + + _bitBuffer = (data << _bitLength) | _bitBuffer; + _bitLength = _bitLength + length; + }; + + _this.flush = function() { + if (_bitLength > 0) { + _out.writeByte(_bitBuffer); + } + }; + + return _this; + }; + + var getLZWRaster = function(lzwMinCodeSize) { + + var clearCode = 1 << lzwMinCodeSize; + var endCode = (1 << lzwMinCodeSize) + 1; + var bitLength = lzwMinCodeSize + 1; + + // Setup LZWTable + var table = lzwTable(); + + for (var i = 0; i < clearCode; i += 1) { + table.add(String.fromCharCode(i) ); + } + table.add(String.fromCharCode(clearCode) ); + table.add(String.fromCharCode(endCode) ); + + var byteOut = byteArrayOutputStream(); + var bitOut = bitOutputStream(byteOut); + + // clear code + bitOut.write(clearCode, bitLength); + + var dataIndex = 0; + + var s = String.fromCharCode(_data[dataIndex]); + dataIndex += 1; + + while (dataIndex < _data.length) { + + var c = String.fromCharCode(_data[dataIndex]); + dataIndex += 1; + + if (table.contains(s + c) ) { + + s = s + c; + + } else { + + bitOut.write(table.indexOf(s), bitLength); + + if (table.size() < 0xfff) { + + if (table.size() == (1 << bitLength) ) { + bitLength += 1; + } + + table.add(s + c); + } + + s = c; + } + } + + bitOut.write(table.indexOf(s), bitLength); + + // end code + bitOut.write(endCode, bitLength); + + bitOut.flush(); + + return byteOut.toByteArray(); + }; + + var lzwTable = function() { + + var _map = {}; + var _size = 0; + + var _this = {}; + + _this.add = function(key) { + if (_this.contains(key) ) { + throw new Error('dup key:' + key); + } + _map[key] = _size; + _size += 1; + }; + + _this.size = function() { + return _size; + }; + + _this.indexOf = function(key) { + return _map[key]; + }; + + _this.contains = function(key) { + return typeof _map[key] != 'undefined'; + }; + + return _this; + }; + + return _this; + }; + + var createGifData = function(width, height, getPixel) { + + var gif = gifImage(width, height); + for (var y = 0; y < height; y += 1) { + for (var x = 0; x < width; x += 1) { + gif.setPixel(x, y, getPixel(x, y) ); + } + } + + var b = byteArrayOutputStream(); + gif.write(b); + + var base64 = base64EncodeOutputStream(); + var bytes = b.toByteArray(); + for (var i = 0; i < bytes.length; i += 1) { + base64.writeByte(bytes[i]); + } + base64.flush(); + return base64; + + }; + + var createImgTag = function(width, height, getPixel, alt) { + + var base64 = createGifData(width, height, getPixel); + var img = ''; + img += '<img'; + img += '\u0020src="'; + img += 'data:image/gif;base64,'; + img += base64; + img += '"'; + img += '\u0020width="'; + img += width; + img += '"'; + img += '\u0020height="'; + img += height; + img += '"'; + if (alt) { + img += '\u0020alt="'; + img += alt; + img += '"'; + } + img += '/>'; + + return img; + }; + + //--------------------------------------------------------------------- + // returns qrcode function. + + return qrcode; +}(); + +// mozilla: Add module support +exports.Encoder = qrcode; diff --git a/devtools/shared/qrcode/encoder/moz.build b/devtools/shared/qrcode/encoder/moz.build new file mode 100644 index 0000000000..4442a2e906 --- /dev/null +++ b/devtools/shared/qrcode/encoder/moz.build @@ -0,0 +1,9 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DevToolsModules( + 'index.js', +) diff --git a/devtools/shared/qrcode/index.js b/devtools/shared/qrcode/index.js new file mode 100644 index 0000000000..4dcc4f6598 --- /dev/null +++ b/devtools/shared/qrcode/index.js @@ -0,0 +1,116 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Lazily require encoder and decoder in case only one is needed +Object.defineProperty(this, "Encoder", { + get: () => + require("resource://devtools/shared/qrcode/encoder/index.js").Encoder, +}); +Object.defineProperty(this, "QRRSBlock", { + get: () => + require("resource://devtools/shared/qrcode/encoder/index.js").QRRSBlock, +}); +Object.defineProperty(this, "QRErrorCorrectLevel", { + get: () => + require("resource://devtools/shared/qrcode/encoder/index.js") + .QRErrorCorrectLevel, +}); +Object.defineProperty(this, "decoder", { + get: () => { + // Some applications don't ship the decoder, see moz.build + try { + return require("resource://devtools/shared/qrcode/decoder/index.js"); + } catch (e) { + return null; + } + }, +}); + +/** + * There are many "versions" of QR codes, which describes how many dots appear + * in the resulting image, thus limiting the amount of data that can be + * represented. + * + * The encoder used here allows for versions 1 - 10 (more dots for larger + * versions). + * + * It expects you to pick a version large enough to contain your message. Here + * we search for the mimimum version based on the message length. + * @param string message + * Text to encode + * @param string quality + * Quality level: L, M, Q, H + * @return integer + */ +exports.findMinimumVersion = function (message, quality) { + const msgLength = message.length; + const qualityLevel = QRErrorCorrectLevel[quality]; + for (let version = 1; version <= 10; version++) { + const rsBlocks = QRRSBlock.getRSBlocks(version, qualityLevel); + let maxLength = rsBlocks.reduce((prev, block) => { + return prev + block.dataCount; + }, 0); + // Remove two bytes to fit header info + maxLength -= 2; + if (msgLength <= maxLength) { + return version; + } + } + throw new Error("Message too large"); +}; + +/** + * Simple wrapper around the underlying encoder's API. + * @param string message + * Text to encode + * @param string quality (optional) + Quality level: L, M, Q, H + * @param integer version (optional) + * QR code "version" large enough to contain the message + * @return object with the following fields: + * * src: an image encoded a data URI + * * height: image height + * * width: image width + */ +exports.encodeToDataURI = function (message, quality, version) { + quality = quality || "H"; + version = version || exports.findMinimumVersion(message, quality); + const encoder = new Encoder(version, quality); + encoder.addData(message); + encoder.make(); + return encoder.createImgData(); +}; + +/** + * Simple wrapper around the underlying decoder's API. + * @param string URI + * URI of an image of a QR code + * @return Promise + * The promise will be resolved with a string, which is the data inside + * the QR code. + */ +exports.decodeFromURI = function (URI) { + if (!decoder) { + return Promise.reject(); + } + return new Promise((resolve, reject) => { + decoder.decodeFromURI(URI, resolve, reject); + }); +}; + +/** + * Decode a QR code that has been drawn to a canvas element. + * @param Canvas canvas + * <canvas> element to read from + * @return string + * The data inside the QR code + */ +exports.decodeFromCanvas = function (canvas) { + if (!decoder) { + throw new Error("Decoder not available"); + } + return decoder.decodeFromCanvas(canvas); +}; diff --git a/devtools/shared/qrcode/moz.build b/devtools/shared/qrcode/moz.build new file mode 100644 index 0000000000..241793b401 --- /dev/null +++ b/devtools/shared/qrcode/moz.build @@ -0,0 +1,18 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DIRS += ["encoder"] + +# Save file size on Fennec until there are active plans to use the decoder there +if CONFIG["MOZ_BUILD_APP"] != "mobile/android": + DIRS += ["decoder"] + +DevToolsModules( + "index.js", +) + +XPCSHELL_TESTS_MANIFESTS += ["tests/xpcshell/xpcshell.ini"] +MOCHITEST_CHROME_MANIFESTS += ["tests/chrome/chrome.ini"] diff --git a/devtools/shared/qrcode/tests/chrome/chrome.ini b/devtools/shared/qrcode/tests/chrome/chrome.ini new file mode 100644 index 0000000000..f73f845e74 --- /dev/null +++ b/devtools/shared/qrcode/tests/chrome/chrome.ini @@ -0,0 +1,5 @@ +[DEFAULT] +skip-if = os == 'android' +tags = devtools + +[test_decode.html] diff --git a/devtools/shared/qrcode/tests/chrome/test_decode.html b/devtools/shared/qrcode/tests/chrome/test_decode.html new file mode 100644 index 0000000000..cc74de2967 --- /dev/null +++ b/devtools/shared/qrcode/tests/chrome/test_decode.html @@ -0,0 +1,66 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test decoding a simple message +--> +<head> + <meta charset="utf-8"> + <title>Test decoding a simple message</title> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript"> +"use strict"; + +window.onload = function() { + + const { require } = ChromeUtils.importESModule("resource://devtools/shared/loader/Loader.sys.mjs"); + + const QR = require("devtools/shared/qrcode/index"); + + SimpleTest.waitForExplicitFinish(); + + const testImage = + "data:image/gif;base64,R0lGODdhOgA6AIAAAAAAAP///ywAAAAAOgA6AAAC" + + "/4yPqcvtD6OctNqLs968+w+G4gKU5nkaKKquLuW+QVy2tAkDTj3rfQts8CRDko" + + "+HPPoYRUgy9YsyldDm44mLWhHYZM6W7WaDqyCRGkZDySxpRGw2sqvLt1q5w/fo" + + "XyE6vnUQOJUHBlinMGh046V1F5PDqNcoqcgBOWKBKbK2N+aY+Ih49VkmqMcl2l" + + "dkhZUK1umE6jZXJ2ZJaujZaRqH4bpb2uZrJxvIt4Ebe9qoYYrJOsw8apz2bCut" + + "m9kqDcw52uuImyr5Oh1KXH1jrn2anuunywtODU/o2c6teceW39ZcLFg/fNMo1b" + + "t3jVw2dwTPwJq1KYG3gAklCgu37yGxeScYKyiCc+7DR34hPVQiuQ7UhJMagyEb" + + "lymmzJk0a9q8iTOnzp0NCgAAOw=="; + + (async function () { + let result = await QR.decodeFromURI(testImage); + is(result, "HELLO", "Decoded data URI result matches"); + const canvas = await drawToCanvas(testImage); + result = QR.decodeFromCanvas(canvas); + is(result, "HELLO", "Decoded canvas result matches"); + })().then(SimpleTest.finish, ok.bind(null, false)); + + function drawToCanvas(src) { + return new Promise(resolve => { + const canvas = document.createElement("canvas"); + const context = canvas.getContext("2d"); + const image = new Image(); + + image.onload = () => { + canvas.width = image.width; + canvas.height = image.height; + context.drawImage(image, 0, 0); + resolve(canvas); + }; + image.src = src; + }); + } +}; + </script> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/devtools/shared/qrcode/tests/xpcshell/.eslintrc.js b/devtools/shared/qrcode/tests/xpcshell/.eslintrc.js new file mode 100644 index 0000000000..7f6b62a9e5 --- /dev/null +++ b/devtools/shared/qrcode/tests/xpcshell/.eslintrc.js @@ -0,0 +1,6 @@ +"use strict"; + +module.exports = { + // Extend from the shared list of defined globals for mochitests. + extends: "../../../../.eslintrc.xpcshell.js", +}; diff --git a/devtools/shared/qrcode/tests/xpcshell/test_encode.js b/devtools/shared/qrcode/tests/xpcshell/test_encode.js new file mode 100644 index 0000000000..7c82cf50c0 --- /dev/null +++ b/devtools/shared/qrcode/tests/xpcshell/test_encode.js @@ -0,0 +1,30 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +/** + * Test encoding a simple message. + */ + +const { require } = ChromeUtils.importESModule( + "resource://devtools/shared/loader/Loader.sys.mjs" +); + +const QR = require("resource://devtools/shared/qrcode/index.js"); + +function run_test() { + const imgData = QR.encodeToDataURI("HELLO", "L"); + Assert.equal( + imgData.src, + "data:image/gif;base64,R0lGODdhOgA6AIAAAAAAAP///ywAAAAAOgA6AAAC" + + "/4yPqcvtD6OctNqLs968+w+G4gKU5nkaKKquLuW+QVy2tAkDTj3rfQts8CRDko" + + "+HPPoYRUgy9YsyldDm44mLWhHYZM6W7WaDqyCRGkZDySxpRGw2sqvLt1q5w/fo" + + "XyE6vnUQOJUHBlinMGh046V1F5PDqNcoqcgBOWKBKbK2N+aY+Ih49VkmqMcl2l" + + "dkhZUK1umE6jZXJ2ZJaujZaRqH4bpb2uZrJxvIt4Ebe9qoYYrJOsw8apz2bCut" + + "m9kqDcw52uuImyr5Oh1KXH1jrn2anuunywtODU/o2c6teceW39ZcLFg/fNMo1b" + + "t3jVw2dwTPwJq1KYG3gAklCgu37yGxeScYKyiCc+7DR34hPVQiuQ7UhJMagyEb" + + "lymmzJk0a9q8iTOnzp0NCgAAOw==" + ); + Assert.equal(imgData.width, 58); + Assert.equal(imgData.height, 58); +} diff --git a/devtools/shared/qrcode/tests/xpcshell/xpcshell.ini b/devtools/shared/qrcode/tests/xpcshell/xpcshell.ini new file mode 100644 index 0000000000..68d274268d --- /dev/null +++ b/devtools/shared/qrcode/tests/xpcshell/xpcshell.ini @@ -0,0 +1,6 @@ +[DEFAULT] +tags = devtools +head = +firefox-appdir = browser + +[test_encode.js] |