diff --git a/wowstash/static/css/main.css b/wowstash/static/css/main.css index e1c6d41..9599989 100644 --- a/wowstash/static/css/main.css +++ b/wowstash/static/css/main.css @@ -542,3 +542,13 @@ ol li { font-size: 1.1em; padding-bottom: .75em; } + +.hidden { + display: none; +} + +#startVideo { + border: 2px solid white; + border-radius: 4px; + padding: 6px; +} diff --git a/wowstash/static/js/zxing.js b/wowstash/static/js/zxing.js new file mode 100644 index 0000000..179c680 --- /dev/null +++ b/wowstash/static/js/zxing.js @@ -0,0 +1,24225 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define(['exports'], factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ZXing = {})); +}(this, (function (exports) { 'use strict'; + + /*! ***************************************************************************** + Copyright (c) Microsoft Corporation. All rights reserved. + 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 + + THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED + WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, + MERCHANTABLITY OR NON-INFRINGEMENT. + + See the Apache Version 2.0 License for specific language governing permissions + and limitations under the License. + ***************************************************************************** */ + /* global Reflect, Promise */ + + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + + function __extends(d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + } + + function fixProto(target, prototype) { + var setPrototypeOf = Object.setPrototypeOf; + setPrototypeOf ? setPrototypeOf(target, prototype) : (target.__proto__ = prototype); + } + + function fixStack(target, fn) { + if (fn === void 0) { + fn = target.constructor; + } + var captureStackTrace = Error.captureStackTrace; + captureStackTrace && captureStackTrace(target, fn); + } + + + + + + var CustomError = (function (_super) { + __extends(CustomError, _super); + function CustomError(message) { + var _newTarget = this.constructor; + var _this = _super.call(this, message) || this; + Object.defineProperty(_this, 'name', { + value: _newTarget.name, + enumerable: false + }); + fixProto(_this, _newTarget.prototype); + fixStack(_this); + return _this; + } + + return CustomError; + })(Error); + + /** + * Custom Error class of type Exception. + */ + class Exception extends CustomError { + /** + * Allows Exception to be constructed directly + * with some message and prototype definition. + */ + constructor(message = undefined) { + super(message); + this.message = message; + } + getKind() { + const ex = this.constructor; + return ex.kind; + } + } + /** + * It's typed as string so it can be extended and overriden. + */ + Exception.kind = 'Exception'; + + /** + * Custom Error class of type Exception. + */ + class ArgumentException extends Exception { + } + ArgumentException.kind = 'ArgumentException'; + + /** + * Custom Error class of type Exception. + */ + class IllegalArgumentException extends Exception { + } + IllegalArgumentException.kind = 'IllegalArgumentException'; + + /* + * Copyright 2009 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. + */ + class BinaryBitmap { + constructor(binarizer) { + this.binarizer = binarizer; + if (binarizer === null) { + throw new IllegalArgumentException('Binarizer must be non-null.'); + } + } + /** + * @return The width of the bitmap. + */ + getWidth() { + return this.binarizer.getWidth(); + } + /** + * @return The height of the bitmap. + */ + getHeight() { + return this.binarizer.getHeight(); + } + /** + * Converts one row of luminance data to 1 bit data. May actually do the conversion, or return + * cached data. Callers should assume this method is expensive and call it as seldom as possible. + * This method is intended for decoding 1D barcodes and may choose to apply sharpening. + * + * @param y The row to fetch, which must be in [0, bitmap height) + * @param row An optional preallocated array. If null or too small, it will be ignored. + * If used, the Binarizer will call BitArray.clear(). Always use the returned object. + * @return The array of bits for this row (true means black). + * @throws NotFoundException if row can't be binarized + */ + getBlackRow(y /*int*/, row) { + return this.binarizer.getBlackRow(y, row); + } + /** + * Converts a 2D array of luminance data to 1 bit. As above, assume this method is expensive + * and do not call it repeatedly. This method is intended for decoding 2D barcodes and may or + * may not apply sharpening. Therefore, a row from this matrix may not be identical to one + * fetched using getBlackRow(), so don't mix and match between them. + * + * @return The 2D array of bits for the image (true means black). + * @throws NotFoundException if image can't be binarized to make a matrix + */ + getBlackMatrix() { + // The matrix is created on demand the first time it is requested, then cached. There are two + // reasons for this: + // 1. This work will never be done if the caller only installs 1D Reader objects, or if a + // 1D Reader finds a barcode before the 2D Readers run. + // 2. This work will only be done once even if the caller installs multiple 2D Readers. + if (this.matrix === null || this.matrix === undefined) { + this.matrix = this.binarizer.getBlackMatrix(); + } + return this.matrix; + } + /** + * @return Whether this bitmap can be cropped. + */ + isCropSupported() { + return this.binarizer.getLuminanceSource().isCropSupported(); + } + /** + * Returns a new object with cropped image data. Implementations may keep a reference to the + * original data rather than a copy. Only callable if isCropSupported() is true. + * + * @param left The left coordinate, which must be in [0,getWidth()) + * @param top The top coordinate, which must be in [0,getHeight()) + * @param width The width of the rectangle to crop. + * @param height The height of the rectangle to crop. + * @return A cropped version of this object. + */ + crop(left /*int*/, top /*int*/, width /*int*/, height /*int*/) { + const newSource = this.binarizer.getLuminanceSource().crop(left, top, width, height); + return new BinaryBitmap(this.binarizer.createBinarizer(newSource)); + } + /** + * @return Whether this bitmap supports counter-clockwise rotation. + */ + isRotateSupported() { + return this.binarizer.getLuminanceSource().isRotateSupported(); + } + /** + * Returns a new object with rotated image data by 90 degrees counterclockwise. + * Only callable if {@link #isRotateSupported()} is true. + * + * @return A rotated version of this object. + */ + rotateCounterClockwise() { + const newSource = this.binarizer.getLuminanceSource().rotateCounterClockwise(); + return new BinaryBitmap(this.binarizer.createBinarizer(newSource)); + } + /** + * Returns a new object with rotated image data by 45 degrees counterclockwise. + * Only callable if {@link #isRotateSupported()} is true. + * + * @return A rotated version of this object. + */ + rotateCounterClockwise45() { + const newSource = this.binarizer.getLuminanceSource().rotateCounterClockwise45(); + return new BinaryBitmap(this.binarizer.createBinarizer(newSource)); + } + /*@Override*/ + toString() { + try { + return this.getBlackMatrix().toString(); + } + catch (e /*: NotFoundException*/) { + return ''; + } + } + } + + /** + * Custom Error class of type Exception. + */ + class ChecksumException extends Exception { + static getChecksumInstance() { + return new ChecksumException(); + } + } + ChecksumException.kind = 'ChecksumException'; + + /* + * Copyright 2009 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. + */ + /** + * This class hierarchy provides a set of methods to convert luminance data to 1 bit data. + * It allows the algorithm to vary polymorphically, for example allowing a very expensive + * thresholding technique for servers and a fast one for mobile. It also permits the implementation + * to vary, e.g. a JNI version for Android and a Java fallback version for other platforms. + * + * @author dswitkin@google.com (Daniel Switkin) + */ + class Binarizer { + constructor(source) { + this.source = source; + } + getLuminanceSource() { + return this.source; + } + getWidth() { + return this.source.getWidth(); + } + getHeight() { + return this.source.getHeight(); + } + } + + class System { + // public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) + /** + * Makes a copy of a array. + */ + static arraycopy(src, srcPos, dest, destPos, length) { + // TODO: better use split or set? + while (length--) { + dest[destPos++] = src[srcPos++]; + } + } + /** + * Returns the current time in milliseconds. + */ + static currentTimeMillis() { + return Date.now(); + } + } + + /** + * Custom Error class of type Exception. + */ + class IndexOutOfBoundsException extends Exception { + } + IndexOutOfBoundsException.kind = 'IndexOutOfBoundsException'; + + /** + * Custom Error class of type Exception. + */ + class ArrayIndexOutOfBoundsException extends IndexOutOfBoundsException { + constructor(index = undefined, message = undefined) { + super(message); + this.index = index; + this.message = message; + } + } + ArrayIndexOutOfBoundsException.kind = 'ArrayIndexOutOfBoundsException'; + + class Arrays { + /** + * Assigns the specified int value to each element of the specified array + * of ints. + * + * @param a the array to be filled + * @param val the value to be stored in all elements of the array + */ + static fill(a, val) { + for (let i = 0, len = a.length; i < len; i++) + a[i] = val; + } + /** + * Assigns the specified int value to each element of the specified + * range of the specified array of ints. The range to be filled + * extends from index {@code fromIndex}, inclusive, to index + * {@code toIndex}, exclusive. (If {@code fromIndex==toIndex}, the + * range to be filled is empty.) + * + * @param a the array to be filled + * @param fromIndex the index of the first element (inclusive) to be + * filled with the specified value + * @param toIndex the index of the last element (exclusive) to be + * filled with the specified value + * @param val the value to be stored in all elements of the array + * @throws IllegalArgumentException if {@code fromIndex > toIndex} + * @throws ArrayIndexOutOfBoundsException if {@code fromIndex < 0} or + * {@code toIndex > a.length} + */ + static fillWithin(a, fromIndex, toIndex, val) { + Arrays.rangeCheck(a.length, fromIndex, toIndex); + for (let i = fromIndex; i < toIndex; i++) + a[i] = val; + } + /** + * Checks that {@code fromIndex} and {@code toIndex} are in + * the range and throws an exception if they aren't. + */ + static rangeCheck(arrayLength, fromIndex, toIndex) { + if (fromIndex > toIndex) { + throw new IllegalArgumentException('fromIndex(' + fromIndex + ') > toIndex(' + toIndex + ')'); + } + if (fromIndex < 0) { + throw new ArrayIndexOutOfBoundsException(fromIndex); + } + if (toIndex > arrayLength) { + throw new ArrayIndexOutOfBoundsException(toIndex); + } + } + static asList(...args) { + return args; + } + static create(rows, cols, value) { + let arr = Array.from({ length: rows }); + return arr.map(x => Array.from({ length: cols }).fill(value)); + } + static createInt32Array(rows, cols, value) { + let arr = Array.from({ length: rows }); + return arr.map(x => Int32Array.from({ length: cols }).fill(value)); + } + static equals(first, second) { + if (!first) { + return false; + } + if (!second) { + return false; + } + if (!first.length) { + return false; + } + if (!second.length) { + return false; + } + if (first.length !== second.length) { + return false; + } + for (let i = 0, length = first.length; i < length; i++) { + if (first[i] !== second[i]) { + return false; + } + } + return true; + } + static hashCode(a) { + if (a === null) { + return 0; + } + let result = 1; + for (const element of a) { + result = 31 * result + element; + } + return result; + } + static fillUint8Array(a, value) { + for (let i = 0; i !== a.length; i++) { + a[i] = value; + } + } + static copyOf(original, newLength) { + return original.slice(0, newLength); + } + static copyOfUint8Array(original, newLength) { + if (original.length <= newLength) { + const newArray = new Uint8Array(newLength); + newArray.set(original); + return newArray; + } + return original.slice(0, newLength); + } + static copyOfRange(original, from, to) { + const newLength = to - from; + const copy = new Int32Array(newLength); + System.arraycopy(original, from, copy, 0, newLength); + return copy; + } + /* + * Returns the index of of the element in a sorted array or (-n-1) where n is the insertion point + * for the new element. + * Parameters: + * ar - A sorted array + * el - An element to search for + * comparator - A comparator function. The function takes two arguments: (a, b) and returns: + * a negative number if a is less than b; + * 0 if a is equal to b; + * a positive number of a is greater than b. + * The array may contain duplicate elements. If there are more than one equal elements in the array, + * the returned value can be the index of any one of the equal elements. + * + * http://jsfiddle.net/aryzhov/pkfst550/ + */ + static binarySearch(ar, el, comparator) { + if (undefined === comparator) { + comparator = Arrays.numberComparator; + } + let m = 0; + let n = ar.length - 1; + while (m <= n) { + const k = (n + m) >> 1; + const cmp = comparator(el, ar[k]); + if (cmp > 0) { + m = k + 1; + } + else if (cmp < 0) { + n = k - 1; + } + else { + return k; + } + } + return -m - 1; + } + static numberComparator(a, b) { + return a - b; + } + } + + /** + * Ponyfill for Java's Integer class. + */ + class Integer { + static numberOfTrailingZeros(i) { + let y; + if (i === 0) + return 32; + let n = 31; + y = i << 16; + if (y !== 0) { + n -= 16; + i = y; + } + y = i << 8; + if (y !== 0) { + n -= 8; + i = y; + } + y = i << 4; + if (y !== 0) { + n -= 4; + i = y; + } + y = i << 2; + if (y !== 0) { + n -= 2; + i = y; + } + return n - ((i << 1) >>> 31); + } + static numberOfLeadingZeros(i) { + // HD, Figure 5-6 + if (i === 0) { + return 32; + } + let n = 1; + if (i >>> 16 === 0) { + n += 16; + i <<= 16; + } + if (i >>> 24 === 0) { + n += 8; + i <<= 8; + } + if (i >>> 28 === 0) { + n += 4; + i <<= 4; + } + if (i >>> 30 === 0) { + n += 2; + i <<= 2; + } + n -= i >>> 31; + return n; + } + static toHexString(i) { + return i.toString(16); + } + static toBinaryString(intNumber) { + return String(parseInt(String(intNumber), 2)); + } + // Returns the number of one-bits in the two's complement binary representation of the specified int value. This function is sometimes referred to as the population count. + // Returns: + // the number of one-bits in the two's complement binary representation of the specified int value. + static bitCount(i) { + // HD, Figure 5-2 + i = i - ((i >>> 1) & 0x55555555); + i = (i & 0x33333333) + ((i >>> 2) & 0x33333333); + i = (i + (i >>> 4)) & 0x0f0f0f0f; + i = i + (i >>> 8); + i = i + (i >>> 16); + return i & 0x3f; + } + static truncDivision(dividend, divisor) { + return Math.trunc(dividend / divisor); + } + /** + * Converts A string to an integer. + * @param s A string to convert into a number. + * @param radix A value between 2 and 36 that specifies the base of the number in numString. If this argument is not supplied, strings with a prefix of '0x' are considered hexadecimal. All other strings are considered decimal. + */ + static parseInt(num, radix = undefined) { + return parseInt(num, radix); + } + } + Integer.MIN_VALUE_32_BITS = -2147483648; + Integer.MAX_VALUE = Number.MAX_SAFE_INTEGER; + + /* + * 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. + */ + /** + *
A simple, fast array of bits, represented compactly by an array of ints internally.
+ * + * @author Sean Owen + */ + class BitArray /*implements Cloneable*/ { + // public constructor() { + // this.size = 0 + // this.bits = new Int32Array(1) + // } + // public constructor(size?: number /*int*/) { + // if (undefined === size) { + // this.size = 0 + // } else { + // this.size = size + // } + // this.bits = this.makeArray(size) + // } + // For testing only + constructor(size /*int*/, bits) { + if (undefined === size) { + this.size = 0; + this.bits = new Int32Array(1); + } + else { + this.size = size; + if (undefined === bits || null === bits) { + this.bits = BitArray.makeArray(size); + } + else { + this.bits = bits; + } + } + } + getSize() { + return this.size; + } + getSizeInBytes() { + return Math.floor((this.size + 7) / 8); + } + ensureCapacity(size /*int*/) { + if (size > this.bits.length * 32) { + const newBits = BitArray.makeArray(size); + System.arraycopy(this.bits, 0, newBits, 0, this.bits.length); + this.bits = newBits; + } + } + /** + * @param i bit to get + * @return true iff bit i is set + */ + get(i /*int*/) { + return (this.bits[Math.floor(i / 32)] & (1 << (i & 0x1F))) !== 0; + } + /** + * Sets bit i. + * + * @param i bit to set + */ + set(i /*int*/) { + this.bits[Math.floor(i / 32)] |= 1 << (i & 0x1F); + } + /** + * Flips bit i. + * + * @param i bit to set + */ + flip(i /*int*/) { + this.bits[Math.floor(i / 32)] ^= 1 << (i & 0x1F); + } + /** + * @param from first bit to check + * @return index of first bit that is set, starting from the given index, or size if none are set + * at or beyond this given index + * @see #getNextUnset(int) + */ + getNextSet(from /*int*/) { + const size = this.size; + if (from >= size) { + return size; + } + const bits = this.bits; + let bitsOffset = Math.floor(from / 32); + let currentBits = bits[bitsOffset]; + // mask off lesser bits first + currentBits &= ~((1 << (from & 0x1F)) - 1); + const length = bits.length; + while (currentBits === 0) { + if (++bitsOffset === length) { + return size; + } + currentBits = bits[bitsOffset]; + } + const result = (bitsOffset * 32) + Integer.numberOfTrailingZeros(currentBits); + return result > size ? size : result; + } + /** + * @param from index to start looking for unset bit + * @return index of next unset bit, or {@code size} if none are unset until the end + * @see #getNextSet(int) + */ + getNextUnset(from /*int*/) { + const size = this.size; + if (from >= size) { + return size; + } + const bits = this.bits; + let bitsOffset = Math.floor(from / 32); + let currentBits = ~bits[bitsOffset]; + // mask off lesser bits first + currentBits &= ~((1 << (from & 0x1F)) - 1); + const length = bits.length; + while (currentBits === 0) { + if (++bitsOffset === length) { + return size; + } + currentBits = ~bits[bitsOffset]; + } + const result = (bitsOffset * 32) + Integer.numberOfTrailingZeros(currentBits); + return result > size ? size : result; + } + /** + * Sets a block of 32 bits, starting at bit i. + * + * @param i first bit to set + * @param newBits the new value of the next 32 bits. Note again that the least-significant bit + * corresponds to bit i, the next-least-significant to i+1, and so on. + */ + setBulk(i /*int*/, newBits /*int*/) { + this.bits[Math.floor(i / 32)] = newBits; + } + /** + * Sets a range of bits. + * + * @param start start of range, inclusive. + * @param end end of range, exclusive + */ + setRange(start /*int*/, end /*int*/) { + if (end < start || start < 0 || end > this.size) { + throw new IllegalArgumentException(); + } + if (end === start) { + return; + } + end--; // will be easier to treat this as the last actually set bit -- inclusive + const firstInt = Math.floor(start / 32); + const lastInt = Math.floor(end / 32); + const bits = this.bits; + for (let i = firstInt; i <= lastInt; i++) { + const firstBit = i > firstInt ? 0 : start & 0x1F; + const lastBit = i < lastInt ? 31 : end & 0x1F; + // Ones from firstBit to lastBit, inclusive + const mask = (2 << lastBit) - (1 << firstBit); + bits[i] |= mask; + } + } + /** + * Clears all bits (sets to false). + */ + clear() { + const max = this.bits.length; + const bits = this.bits; + for (let i = 0; i < max; i++) { + bits[i] = 0; + } + } + /** + * Efficient method to check if a range of bits is set, or not set. + * + * @param start start of range, inclusive. + * @param end end of range, exclusive + * @param value if true, checks that bits in range are set, otherwise checks that they are not set + * @return true iff all bits are set or not set in range, according to value argument + * @throws IllegalArgumentException if end is less than start or the range is not contained in the array + */ + isRange(start /*int*/, end /*int*/, value) { + if (end < start || start < 0 || end > this.size) { + throw new IllegalArgumentException(); + } + if (end === start) { + return true; // empty range matches + } + end--; // will be easier to treat this as the last actually set bit -- inclusive + const firstInt = Math.floor(start / 32); + const lastInt = Math.floor(end / 32); + const bits = this.bits; + for (let i = firstInt; i <= lastInt; i++) { + const firstBit = i > firstInt ? 0 : start & 0x1F; + const lastBit = i < lastInt ? 31 : end & 0x1F; + // Ones from firstBit to lastBit, inclusive + const mask = (2 << lastBit) - (1 << firstBit) & 0xFFFFFFFF; + // TYPESCRIPTPORT: & 0xFFFFFFFF added to discard anything after 32 bits, as ES has 53 bits + // Return false if we're looking for 1s and the masked bits[i] isn't all 1s (is: that, + // equals the mask, or we're looking for 0s and the masked portion is not all 0s + if ((bits[i] & mask) !== (value ? mask : 0)) { + return false; + } + } + return true; + } + appendBit(bit) { + this.ensureCapacity(this.size + 1); + if (bit) { + this.bits[Math.floor(this.size / 32)] |= 1 << (this.size & 0x1F); + } + this.size++; + } + /** + * Appends the least-significant bits, from value, in order from most-significant to + * least-significant. For example, appending 6 bits from 0x000001E will append the bits + * 0, 1, 1, 1, 1, 0 in that order. + * + * @param value {@code int} containing bits to append + * @param numBits bits from value to append + */ + appendBits(value /*int*/, numBits /*int*/) { + if (numBits < 0 || numBits > 32) { + throw new IllegalArgumentException('Num bits must be between 0 and 32'); + } + this.ensureCapacity(this.size + numBits); + // const appendBit = this.appendBit; + for (let numBitsLeft = numBits; numBitsLeft > 0; numBitsLeft--) { + this.appendBit(((value >> (numBitsLeft - 1)) & 0x01) === 1); + } + } + appendBitArray(other) { + const otherSize = other.size; + this.ensureCapacity(this.size + otherSize); + // const appendBit = this.appendBit; + for (let i = 0; i < otherSize; i++) { + this.appendBit(other.get(i)); + } + } + xor(other) { + if (this.size !== other.size) { + throw new IllegalArgumentException('Sizes don\'t match'); + } + const bits = this.bits; + for (let i = 0, length = bits.length; i < length; i++) { + // The last int could be incomplete (i.e. not have 32 bits in + // it) but there is no problem since 0 XOR 0 == 0. + bits[i] ^= other.bits[i]; + } + } + /** + * + * @param bitOffset first bit to start writing + * @param array array to write into. Bytes are written most-significant byte first. This is the opposite + * of the internal representation, which is exposed by {@link #getBitArray()} + * @param offset position in array to start writing + * @param numBytes how many bytes to write + */ + toBytes(bitOffset /*int*/, array, offset /*int*/, numBytes /*int*/) { + for (let i = 0; i < numBytes; i++) { + let theByte = 0; + for (let j = 0; j < 8; j++) { + if (this.get(bitOffset)) { + theByte |= 1 << (7 - j); + } + bitOffset++; + } + array[offset + i] = /*(byte)*/ theByte; + } + } + /** + * @return underlying array of ints. The first element holds the first 32 bits, and the least + * significant bit is bit 0. + */ + getBitArray() { + return this.bits; + } + /** + * Reverses all bits in the array. + */ + reverse() { + const newBits = new Int32Array(this.bits.length); + // reverse all int's first + const len = Math.floor((this.size - 1) / 32); + const oldBitsLen = len + 1; + const bits = this.bits; + for (let i = 0; i < oldBitsLen; i++) { + let x = bits[i]; + x = ((x >> 1) & 0x55555555) | ((x & 0x55555555) << 1); + x = ((x >> 2) & 0x33333333) | ((x & 0x33333333) << 2); + x = ((x >> 4) & 0x0f0f0f0f) | ((x & 0x0f0f0f0f) << 4); + x = ((x >> 8) & 0x00ff00ff) | ((x & 0x00ff00ff) << 8); + x = ((x >> 16) & 0x0000ffff) | ((x & 0x0000ffff) << 16); + newBits[len - i] = /*(int)*/ x; + } + // now correct the int's if the bit size isn't a multiple of 32 + if (this.size !== oldBitsLen * 32) { + const leftOffset = oldBitsLen * 32 - this.size; + let currentInt = newBits[0] >>> leftOffset; + for (let i = 1; i < oldBitsLen; i++) { + const nextInt = newBits[i]; + currentInt |= nextInt << (32 - leftOffset); + newBits[i - 1] = currentInt; + currentInt = nextInt >>> leftOffset; + } + newBits[oldBitsLen - 1] = currentInt; + } + this.bits = newBits; + } + static makeArray(size /*int*/) { + return new Int32Array(Math.floor((size + 31) / 32)); + } + /*@Override*/ + equals(o) { + if (!(o instanceof BitArray)) { + return false; + } + const other = o; + return this.size === other.size && Arrays.equals(this.bits, other.bits); + } + /*@Override*/ + hashCode() { + return 31 * this.size + Arrays.hashCode(this.bits); + } + /*@Override*/ + toString() { + let result = ''; + for (let i = 0, size = this.size; i < size; i++) { + if ((i & 0x07) === 0) { + result += ' '; + } + result += this.get(i) ? 'X' : '.'; + } + return result; + } + /*@Override*/ + clone() { + return new BitArray(this.size, this.bits.slice()); + } + } + + /* + * Copyright 2009 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. + */ + /*namespace com.google.zxing {*/ + /** + * Encapsulates a type of hint that a caller may pass to a barcode reader to help it + * more quickly or accurately decode it. It is up to implementations to decide what, + * if anything, to do with the information that is supplied. + * + * @author Sean Owen + * @author dswitkin@google.com (Daniel Switkin) + * @see Reader#decode(BinaryBitmap,java.util.Map) + */ + var DecodeHintType; + (function (DecodeHintType) { + /** + * Unspecified, application-specific hint. Maps to an unspecified {@link Object}. + */ + DecodeHintType[DecodeHintType["OTHER"] = 0] = "OTHER"; /*(Object.class)*/ + /** + * Image is a pure monochrome image of a barcode. Doesn't matter what it maps to; + * use {@link Boolean#TRUE}. + */ + DecodeHintType[DecodeHintType["PURE_BARCODE"] = 1] = "PURE_BARCODE"; /*(Void.class)*/ + /** + * Image is known to be of one of a few possible formats. + * Maps to a {@link List} of {@link BarcodeFormat}s. + */ + DecodeHintType[DecodeHintType["POSSIBLE_FORMATS"] = 2] = "POSSIBLE_FORMATS"; /*(List.class)*/ + /** + * Spend more time to try to find a barcode; optimize for accuracy, not speed. + * Doesn't matter what it maps to; use {@link Boolean#TRUE}. + */ + DecodeHintType[DecodeHintType["TRY_HARDER"] = 3] = "TRY_HARDER"; /*(Void.class)*/ + /** + * Specifies what character encoding to use when decoding, where applicable (type String) + */ + DecodeHintType[DecodeHintType["CHARACTER_SET"] = 4] = "CHARACTER_SET"; /*(String.class)*/ + /** + * Allowed lengths of encoded data -- reject anything else. Maps to an {@code Int32Array}. + */ + DecodeHintType[DecodeHintType["ALLOWED_LENGTHS"] = 5] = "ALLOWED_LENGTHS"; /*(Int32Array.class)*/ + /** + * Assume Code 39 codes employ a check digit. Doesn't matter what it maps to; + * use {@link Boolean#TRUE}. + */ + DecodeHintType[DecodeHintType["ASSUME_CODE_39_CHECK_DIGIT"] = 6] = "ASSUME_CODE_39_CHECK_DIGIT"; /*(Void.class)*/ + /** + * Assume the barcode is being processed as a GS1 barcode, and modify behavior as needed. + * For example this affects FNC1 handling for Code 128 (aka GS1-128). Doesn't matter what it maps to; + * use {@link Boolean#TRUE}. + */ + DecodeHintType[DecodeHintType["ASSUME_GS1"] = 7] = "ASSUME_GS1"; /*(Void.class)*/ + /** + * If true, return the start and end digits in a Codabar barcode instead of stripping them. They + * are alpha, whereas the rest are numeric. By default, they are stripped, but this causes them + * to not be. Doesn't matter what it maps to; use {@link Boolean#TRUE}. + */ + DecodeHintType[DecodeHintType["RETURN_CODABAR_START_END"] = 8] = "RETURN_CODABAR_START_END"; /*(Void.class)*/ + /** + * The caller needs to be notified via callback when a possible {@link ResultPoint} + * is found. Maps to a {@link ResultPointCallback}. + */ + DecodeHintType[DecodeHintType["NEED_RESULT_POINT_CALLBACK"] = 9] = "NEED_RESULT_POINT_CALLBACK"; /*(ResultPointCallback.class)*/ + /** + * Allowed extension lengths for EAN or UPC barcodes. Other formats will ignore this. + * Maps to an {@code Int32Array} of the allowed extension lengths, for example [2], [5], or [2, 5]. + * If it is optional to have an extension, do not set this hint. If this is set, + * and a UPC or EAN barcode is found but an extension is not, then no result will be returned + * at all. + */ + DecodeHintType[DecodeHintType["ALLOWED_EAN_EXTENSIONS"] = 10] = "ALLOWED_EAN_EXTENSIONS"; /*(Int32Array.class)*/ + // End of enumeration values. + /** + * Data type the hint is expecting. + * Among the possible values the {@link Void} stands out as being used for + * hints that do not expect a value to be supplied (flag hints). Such hints + * will possibly have their value ignored, or replaced by a + * {@link Boolean#TRUE}. Hint suppliers should probably use + * {@link Boolean#TRUE} as directed by the actual hint documentation. + */ + // private valueType: Class> + // DecodeHintType(valueType: Class>) { + // this.valueType = valueType + // } + // public getValueType(): Class> { + // return valueType + // } + })(DecodeHintType || (DecodeHintType = {})); + var DecodeHintType$1 = DecodeHintType; + + /** + * Custom Error class of type Exception. + */ + class FormatException extends Exception { + static getFormatInstance() { + return new FormatException(); + } + } + FormatException.kind = 'FormatException'; + + /* + * Copyright 2008 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. + */ + /*import java.util.HashMap;*/ + /*import java.util.Map;*/ + var CharacterSetValueIdentifiers; + (function (CharacterSetValueIdentifiers) { + CharacterSetValueIdentifiers[CharacterSetValueIdentifiers["Cp437"] = 0] = "Cp437"; + CharacterSetValueIdentifiers[CharacterSetValueIdentifiers["ISO8859_1"] = 1] = "ISO8859_1"; + CharacterSetValueIdentifiers[CharacterSetValueIdentifiers["ISO8859_2"] = 2] = "ISO8859_2"; + CharacterSetValueIdentifiers[CharacterSetValueIdentifiers["ISO8859_3"] = 3] = "ISO8859_3"; + CharacterSetValueIdentifiers[CharacterSetValueIdentifiers["ISO8859_4"] = 4] = "ISO8859_4"; + CharacterSetValueIdentifiers[CharacterSetValueIdentifiers["ISO8859_5"] = 5] = "ISO8859_5"; + CharacterSetValueIdentifiers[CharacterSetValueIdentifiers["ISO8859_6"] = 6] = "ISO8859_6"; + CharacterSetValueIdentifiers[CharacterSetValueIdentifiers["ISO8859_7"] = 7] = "ISO8859_7"; + CharacterSetValueIdentifiers[CharacterSetValueIdentifiers["ISO8859_8"] = 8] = "ISO8859_8"; + CharacterSetValueIdentifiers[CharacterSetValueIdentifiers["ISO8859_9"] = 9] = "ISO8859_9"; + CharacterSetValueIdentifiers[CharacterSetValueIdentifiers["ISO8859_10"] = 10] = "ISO8859_10"; + CharacterSetValueIdentifiers[CharacterSetValueIdentifiers["ISO8859_11"] = 11] = "ISO8859_11"; + CharacterSetValueIdentifiers[CharacterSetValueIdentifiers["ISO8859_13"] = 12] = "ISO8859_13"; + CharacterSetValueIdentifiers[CharacterSetValueIdentifiers["ISO8859_14"] = 13] = "ISO8859_14"; + CharacterSetValueIdentifiers[CharacterSetValueIdentifiers["ISO8859_15"] = 14] = "ISO8859_15"; + CharacterSetValueIdentifiers[CharacterSetValueIdentifiers["ISO8859_16"] = 15] = "ISO8859_16"; + CharacterSetValueIdentifiers[CharacterSetValueIdentifiers["SJIS"] = 16] = "SJIS"; + CharacterSetValueIdentifiers[CharacterSetValueIdentifiers["Cp1250"] = 17] = "Cp1250"; + CharacterSetValueIdentifiers[CharacterSetValueIdentifiers["Cp1251"] = 18] = "Cp1251"; + CharacterSetValueIdentifiers[CharacterSetValueIdentifiers["Cp1252"] = 19] = "Cp1252"; + CharacterSetValueIdentifiers[CharacterSetValueIdentifiers["Cp1256"] = 20] = "Cp1256"; + CharacterSetValueIdentifiers[CharacterSetValueIdentifiers["UnicodeBigUnmarked"] = 21] = "UnicodeBigUnmarked"; + CharacterSetValueIdentifiers[CharacterSetValueIdentifiers["UTF8"] = 22] = "UTF8"; + CharacterSetValueIdentifiers[CharacterSetValueIdentifiers["ASCII"] = 23] = "ASCII"; + CharacterSetValueIdentifiers[CharacterSetValueIdentifiers["Big5"] = 24] = "Big5"; + CharacterSetValueIdentifiers[CharacterSetValueIdentifiers["GB18030"] = 25] = "GB18030"; + CharacterSetValueIdentifiers[CharacterSetValueIdentifiers["EUC_KR"] = 26] = "EUC_KR"; + })(CharacterSetValueIdentifiers || (CharacterSetValueIdentifiers = {})); + /** + * Encapsulates a Character Set ECI, according to "Extended Channel Interpretations" 5.3.1.1 + * of ISO 18004. + * + * @author Sean Owen + */ + class CharacterSetECI { + constructor(valueIdentifier, valuesParam, name, ...otherEncodingNames) { + this.valueIdentifier = valueIdentifier; + this.name = name; + if (typeof valuesParam === 'number') { + this.values = Int32Array.from([valuesParam]); + } + else { + this.values = valuesParam; + } + this.otherEncodingNames = otherEncodingNames; + CharacterSetECI.VALUE_IDENTIFIER_TO_ECI.set(valueIdentifier, this); + CharacterSetECI.NAME_TO_ECI.set(name, this); + const values = this.values; + for (let i = 0, length = values.length; i !== length; i++) { + const v = values[i]; + CharacterSetECI.VALUES_TO_ECI.set(v, this); + } + for (const otherName of otherEncodingNames) { + CharacterSetECI.NAME_TO_ECI.set(otherName, this); + } + } + // CharacterSetECI(value: number /*int*/) { + // this(new Int32Array {value}) + // } + // CharacterSetECI(value: number /*int*/, String... otherEncodingNames) { + // this.values = new Int32Array {value} + // this.otherEncodingNames = otherEncodingNames + // } + // CharacterSetECI(values: Int32Array, String... otherEncodingNames) { + // this.values = values + // this.otherEncodingNames = otherEncodingNames + // } + getValueIdentifier() { + return this.valueIdentifier; + } + getName() { + return this.name; + } + getValue() { + return this.values[0]; + } + /** + * @param value character set ECI value + * @return {@code CharacterSetECI} representing ECI of given value, or null if it is legal but + * unsupported + * @throws FormatException if ECI value is invalid + */ + static getCharacterSetECIByValue(value /*int*/) { + if (value < 0 || value >= 900) { + throw new FormatException('incorect value'); + } + const characterSet = CharacterSetECI.VALUES_TO_ECI.get(value); + if (undefined === characterSet) { + throw new FormatException('incorect value'); + } + return characterSet; + } + /** + * @param name character set ECI encoding name + * @return CharacterSetECI representing ECI for character encoding, or null if it is legal + * but unsupported + */ + static getCharacterSetECIByName(name) { + const characterSet = CharacterSetECI.NAME_TO_ECI.get(name); + if (undefined === characterSet) { + throw new FormatException('incorect value'); + } + return characterSet; + } + equals(o) { + if (!(o instanceof CharacterSetECI)) { + return false; + } + const other = o; + return this.getName() === other.getName(); + } + } + CharacterSetECI.VALUE_IDENTIFIER_TO_ECI = new Map(); + CharacterSetECI.VALUES_TO_ECI = new Map(); + CharacterSetECI.NAME_TO_ECI = new Map(); + // Enum name is a Java encoding valid for java.lang and java.io + // TYPESCRIPTPORT: changed the main label for ISO as the TextEncoder did not recognized them in the form from java + // (eg ISO8859_1 must be ISO88591 or ISO8859-1 or ISO-8859-1) + // later on: well, except 16 wich does not work with ISO885916 so used ISO-8859-1 form for default + CharacterSetECI.Cp437 = new CharacterSetECI(CharacterSetValueIdentifiers.Cp437, Int32Array.from([0, 2]), 'Cp437'); + CharacterSetECI.ISO8859_1 = new CharacterSetECI(CharacterSetValueIdentifiers.ISO8859_1, Int32Array.from([1, 3]), 'ISO-8859-1', 'ISO88591', 'ISO8859_1'); + CharacterSetECI.ISO8859_2 = new CharacterSetECI(CharacterSetValueIdentifiers.ISO8859_2, 4, 'ISO-8859-2', 'ISO88592', 'ISO8859_2'); + CharacterSetECI.ISO8859_3 = new CharacterSetECI(CharacterSetValueIdentifiers.ISO8859_3, 5, 'ISO-8859-3', 'ISO88593', 'ISO8859_3'); + CharacterSetECI.ISO8859_4 = new CharacterSetECI(CharacterSetValueIdentifiers.ISO8859_4, 6, 'ISO-8859-4', 'ISO88594', 'ISO8859_4'); + CharacterSetECI.ISO8859_5 = new CharacterSetECI(CharacterSetValueIdentifiers.ISO8859_5, 7, 'ISO-8859-5', 'ISO88595', 'ISO8859_5'); + CharacterSetECI.ISO8859_6 = new CharacterSetECI(CharacterSetValueIdentifiers.ISO8859_6, 8, 'ISO-8859-6', 'ISO88596', 'ISO8859_6'); + CharacterSetECI.ISO8859_7 = new CharacterSetECI(CharacterSetValueIdentifiers.ISO8859_7, 9, 'ISO-8859-7', 'ISO88597', 'ISO8859_7'); + CharacterSetECI.ISO8859_8 = new CharacterSetECI(CharacterSetValueIdentifiers.ISO8859_8, 10, 'ISO-8859-8', 'ISO88598', 'ISO8859_8'); + CharacterSetECI.ISO8859_9 = new CharacterSetECI(CharacterSetValueIdentifiers.ISO8859_9, 11, 'ISO-8859-9', 'ISO88599', 'ISO8859_9'); + CharacterSetECI.ISO8859_10 = new CharacterSetECI(CharacterSetValueIdentifiers.ISO8859_10, 12, 'ISO-8859-10', 'ISO885910', 'ISO8859_10'); + CharacterSetECI.ISO8859_11 = new CharacterSetECI(CharacterSetValueIdentifiers.ISO8859_11, 13, 'ISO-8859-11', 'ISO885911', 'ISO8859_11'); + CharacterSetECI.ISO8859_13 = new CharacterSetECI(CharacterSetValueIdentifiers.ISO8859_13, 15, 'ISO-8859-13', 'ISO885913', 'ISO8859_13'); + CharacterSetECI.ISO8859_14 = new CharacterSetECI(CharacterSetValueIdentifiers.ISO8859_14, 16, 'ISO-8859-14', 'ISO885914', 'ISO8859_14'); + CharacterSetECI.ISO8859_15 = new CharacterSetECI(CharacterSetValueIdentifiers.ISO8859_15, 17, 'ISO-8859-15', 'ISO885915', 'ISO8859_15'); + CharacterSetECI.ISO8859_16 = new CharacterSetECI(CharacterSetValueIdentifiers.ISO8859_16, 18, 'ISO-8859-16', 'ISO885916', 'ISO8859_16'); + CharacterSetECI.SJIS = new CharacterSetECI(CharacterSetValueIdentifiers.SJIS, 20, 'SJIS', 'Shift_JIS'); + CharacterSetECI.Cp1250 = new CharacterSetECI(CharacterSetValueIdentifiers.Cp1250, 21, 'Cp1250', 'windows-1250'); + CharacterSetECI.Cp1251 = new CharacterSetECI(CharacterSetValueIdentifiers.Cp1251, 22, 'Cp1251', 'windows-1251'); + CharacterSetECI.Cp1252 = new CharacterSetECI(CharacterSetValueIdentifiers.Cp1252, 23, 'Cp1252', 'windows-1252'); + CharacterSetECI.Cp1256 = new CharacterSetECI(CharacterSetValueIdentifiers.Cp1256, 24, 'Cp1256', 'windows-1256'); + CharacterSetECI.UnicodeBigUnmarked = new CharacterSetECI(CharacterSetValueIdentifiers.UnicodeBigUnmarked, 25, 'UnicodeBigUnmarked', 'UTF-16BE', 'UnicodeBig'); + CharacterSetECI.UTF8 = new CharacterSetECI(CharacterSetValueIdentifiers.UTF8, 26, 'UTF8', 'UTF-8'); + CharacterSetECI.ASCII = new CharacterSetECI(CharacterSetValueIdentifiers.ASCII, Int32Array.from([27, 170]), 'ASCII', 'US-ASCII'); + CharacterSetECI.Big5 = new CharacterSetECI(CharacterSetValueIdentifiers.Big5, 28, 'Big5'); + CharacterSetECI.GB18030 = new CharacterSetECI(CharacterSetValueIdentifiers.GB18030, 29, 'GB18030', 'GB2312', 'EUC_CN', 'GBK'); + CharacterSetECI.EUC_KR = new CharacterSetECI(CharacterSetValueIdentifiers.EUC_KR, 30, 'EUC_KR', 'EUC-KR'); + + /** + * Custom Error class of type Exception. + */ + class UnsupportedOperationException extends Exception { + } + UnsupportedOperationException.kind = 'UnsupportedOperationException'; + + /** + * Responsible for en/decoding strings. + */ + class StringEncoding { + /** + * Decodes some Uint8Array to a string format. + */ + static decode(bytes, encoding) { + const encodingName = this.encodingName(encoding); + if (this.customDecoder) { + return this.customDecoder(bytes, encodingName); + } + // Increases browser support. + if (typeof TextDecoder === 'undefined' || this.shouldDecodeOnFallback(encodingName)) { + return this.decodeFallback(bytes, encodingName); + } + return new TextDecoder(encodingName).decode(bytes); + } + /** + * Checks if the decoding method should use the fallback for decoding + * once Node TextDecoder doesn't support all encoding formats. + * + * @param encodingName + */ + static shouldDecodeOnFallback(encodingName) { + return !StringEncoding.isBrowser() && encodingName === 'ISO-8859-1'; + } + /** + * Encodes some string into a Uint8Array. + */ + static encode(s, encoding) { + const encodingName = this.encodingName(encoding); + if (this.customEncoder) { + return this.customEncoder(s, encodingName); + } + // Increases browser support. + if (typeof TextEncoder === 'undefined') { + return this.encodeFallback(s); + } + // TextEncoder only encodes to UTF8 by default as specified by encoding.spec.whatwg.org + return new TextEncoder().encode(s); + } + static isBrowser() { + return (typeof window !== 'undefined' && {}.toString.call(window) === '[object Window]'); + } + /** + * Returns the string value from some encoding character set. + */ + static encodingName(encoding) { + return typeof encoding === 'string' + ? encoding + : encoding.getName(); + } + /** + * Returns character set from some encoding character set. + */ + static encodingCharacterSet(encoding) { + if (encoding instanceof CharacterSetECI) { + return encoding; + } + return CharacterSetECI.getCharacterSetECIByName(encoding); + } + /** + * Runs a fallback for the native decoding funcion. + */ + static decodeFallback(bytes, encoding) { + const characterSet = this.encodingCharacterSet(encoding); + if (StringEncoding.isDecodeFallbackSupported(characterSet)) { + let s = ''; + for (let i = 0, length = bytes.length; i < length; i++) { + let h = bytes[i].toString(16); + if (h.length < 2) { + h = '0' + h; + } + s += '%' + h; + } + return decodeURIComponent(s); + } + if (characterSet.equals(CharacterSetECI.UnicodeBigUnmarked)) { + return String.fromCharCode.apply(null, new Uint16Array(bytes.buffer)); + } + throw new UnsupportedOperationException(`Encoding ${this.encodingName(encoding)} not supported by fallback.`); + } + static isDecodeFallbackSupported(characterSet) { + return characterSet.equals(CharacterSetECI.UTF8) || + characterSet.equals(CharacterSetECI.ISO8859_1) || + characterSet.equals(CharacterSetECI.ASCII); + } + /** + * Runs a fallback for the native encoding funcion. + * + * @see https://stackoverflow.com/a/17192845/4367683 + */ + static encodeFallback(s) { + const encodedURIstring = btoa(unescape(encodeURIComponent(s))); + const charList = encodedURIstring.split(''); + const uintArray = []; + for (let i = 0; i < charList.length; i++) { + uintArray.push(charList[i].charCodeAt(0)); + } + return new Uint8Array(uintArray); + } + } + + /* + * Copyright (C) 2010 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. + */ + /** + * Common string-related functions. + * + * @author Sean Owen + * @author Alex Dupre + */ + class StringUtils { + // SHIFT_JIS.equalsIgnoreCase(PLATFORM_DEFAULT_ENCODING) || + // EUC_JP.equalsIgnoreCase(PLATFORM_DEFAULT_ENCODING); + static castAsNonUtf8Char(code, encoding = null) { + // ISO 8859-1 is the Java default as UTF-8 is JavaScripts + // you can see this method as a Java version of String.fromCharCode + const e = encoding ? encoding.getName() : this.ISO88591; + // use passed format (fromCharCode will return UTF8 encoding) + return StringEncoding.decode(new Uint8Array([code]), e); + } + /** + * @param bytes bytes encoding a string, whose encoding should be guessed + * @param hints decode hints if applicable + * @return name of guessed encoding; at the moment will only guess one of: + * {@link #SHIFT_JIS}, {@link #UTF8}, {@link #ISO88591}, or the platform + * default encoding if none of these can possibly be correct + */ + static guessEncoding(bytes, hints) { + if (hints !== null && hints !== undefined && undefined !== hints.get(DecodeHintType$1.CHARACTER_SET)) { + return hints.get(DecodeHintType$1.CHARACTER_SET).toString(); + } + // For now, merely tries to distinguish ISO-8859-1, UTF-8 and Shift_JIS, + // which should be by far the most common encodings. + const length = bytes.length; + let canBeISO88591 = true; + let canBeShiftJIS = true; + let canBeUTF8 = true; + let utf8BytesLeft = 0; + // int utf8LowChars = 0 + let utf2BytesChars = 0; + let utf3BytesChars = 0; + let utf4BytesChars = 0; + let sjisBytesLeft = 0; + // int sjisLowChars = 0 + let sjisKatakanaChars = 0; + // int sjisDoubleBytesChars = 0 + let sjisCurKatakanaWordLength = 0; + let sjisCurDoubleBytesWordLength = 0; + let sjisMaxKatakanaWordLength = 0; + let sjisMaxDoubleBytesWordLength = 0; + // int isoLowChars = 0 + // int isoHighChars = 0 + let isoHighOther = 0; + const utf8bom = bytes.length > 3 && + bytes[0] === /*(byte) */ 0xEF && + bytes[1] === /*(byte) */ 0xBB && + bytes[2] === /*(byte) */ 0xBF; + for (let i = 0; i < length && (canBeISO88591 || canBeShiftJIS || canBeUTF8); i++) { + const value = bytes[i] & 0xFF; + // UTF-8 stuff + if (canBeUTF8) { + if (utf8BytesLeft > 0) { + if ((value & 0x80) === 0) { + canBeUTF8 = false; + } + else { + utf8BytesLeft--; + } + } + else if ((value & 0x80) !== 0) { + if ((value & 0x40) === 0) { + canBeUTF8 = false; + } + else { + utf8BytesLeft++; + if ((value & 0x20) === 0) { + utf2BytesChars++; + } + else { + utf8BytesLeft++; + if ((value & 0x10) === 0) { + utf3BytesChars++; + } + else { + utf8BytesLeft++; + if ((value & 0x08) === 0) { + utf4BytesChars++; + } + else { + canBeUTF8 = false; + } + } + } + } + } // else { + // utf8LowChars++ + // } + } + // ISO-8859-1 stuff + if (canBeISO88591) { + if (value > 0x7F && value < 0xA0) { + canBeISO88591 = false; + } + else if (value > 0x9F) { + if (value < 0xC0 || value === 0xD7 || value === 0xF7) { + isoHighOther++; + } // else { + // isoHighChars++ + // } + } // else { + // isoLowChars++ + // } + } + // Shift_JIS stuff + if (canBeShiftJIS) { + if (sjisBytesLeft > 0) { + if (value < 0x40 || value === 0x7F || value > 0xFC) { + canBeShiftJIS = false; + } + else { + sjisBytesLeft--; + } + } + else if (value === 0x80 || value === 0xA0 || value > 0xEF) { + canBeShiftJIS = false; + } + else if (value > 0xA0 && value < 0xE0) { + sjisKatakanaChars++; + sjisCurDoubleBytesWordLength = 0; + sjisCurKatakanaWordLength++; + if (sjisCurKatakanaWordLength > sjisMaxKatakanaWordLength) { + sjisMaxKatakanaWordLength = sjisCurKatakanaWordLength; + } + } + else if (value > 0x7F) { + sjisBytesLeft++; + // sjisDoubleBytesChars++ + sjisCurKatakanaWordLength = 0; + sjisCurDoubleBytesWordLength++; + if (sjisCurDoubleBytesWordLength > sjisMaxDoubleBytesWordLength) { + sjisMaxDoubleBytesWordLength = sjisCurDoubleBytesWordLength; + } + } + else { + // sjisLowChars++ + sjisCurKatakanaWordLength = 0; + sjisCurDoubleBytesWordLength = 0; + } + } + } + if (canBeUTF8 && utf8BytesLeft > 0) { + canBeUTF8 = false; + } + if (canBeShiftJIS && sjisBytesLeft > 0) { + canBeShiftJIS = false; + } + // Easy -- if there is BOM or at least 1 valid not-single byte character (and no evidence it can't be UTF-8), done + if (canBeUTF8 && (utf8bom || utf2BytesChars + utf3BytesChars + utf4BytesChars > 0)) { + return StringUtils.UTF8; + } + // Easy -- if assuming Shift_JIS or at least 3 valid consecutive not-ascii characters (and no evidence it can't be), done + if (canBeShiftJIS && (StringUtils.ASSUME_SHIFT_JIS || sjisMaxKatakanaWordLength >= 3 || sjisMaxDoubleBytesWordLength >= 3)) { + return StringUtils.SHIFT_JIS; + } + // Distinguishing Shift_JIS and ISO-8859-1 can be a little tough for short words. The crude heuristic is: + // - If we saw + // - only two consecutive katakana chars in the whole text, or + // - at least 10% of bytes that could be "upper" not-alphanumeric Latin1, + // - then we conclude Shift_JIS, else ISO-8859-1 + if (canBeISO88591 && canBeShiftJIS) { + return (sjisMaxKatakanaWordLength === 2 && sjisKatakanaChars === 2) || isoHighOther * 10 >= length + ? StringUtils.SHIFT_JIS : StringUtils.ISO88591; + } + // Otherwise, try in order ISO-8859-1, Shift JIS, UTF-8 and fall back to default platform encoding + if (canBeISO88591) { + return StringUtils.ISO88591; + } + if (canBeShiftJIS) { + return StringUtils.SHIFT_JIS; + } + if (canBeUTF8) { + return StringUtils.UTF8; + } + // Otherwise, we take a wild guess with platform encoding + return StringUtils.PLATFORM_DEFAULT_ENCODING; + } + /** + * + * @see https://stackoverflow.com/a/13439711/4367683 + * + * @param append The new string to append. + * @param args Argumets values to be formated. + */ + static format(append, ...args) { + let i = -1; + function callback(exp, p0, p1, p2, p3, p4) { + if (exp === '%%') + return '%'; + if (args[++i] === undefined) + return undefined; + exp = p2 ? parseInt(p2.substr(1)) : undefined; + let base = p3 ? parseInt(p3.substr(1)) : undefined; + let val; + switch (p4) { + case 's': + val = args[i]; + break; + case 'c': + val = args[i][0]; + break; + case 'f': + val = parseFloat(args[i]).toFixed(exp); + break; + case 'p': + val = parseFloat(args[i]).toPrecision(exp); + break; + case 'e': + val = parseFloat(args[i]).toExponential(exp); + break; + case 'x': + val = parseInt(args[i]).toString(base ? base : 16); + break; + case 'd': + val = parseFloat(parseInt(args[i], base ? base : 10).toPrecision(exp)).toFixed(0); + break; + } + val = typeof val === 'object' ? JSON.stringify(val) : (+val).toString(base); + let size = parseInt(p1); /* padding size */ + let ch = p1 && (p1[0] + '') === '0' ? '0' : ' '; /* isnull? */ + while (val.length < size) + val = p0 !== undefined ? val + ch : ch + val; /* isminus? */ + return val; + } + let regex = /%(-)?(0?[0-9]+)?([.][0-9]+)?([#][0-9]+)?([scfpexd%])/g; + return append.replace(regex, callback); + } + /** + * + */ + static getBytes(str, encoding) { + return StringEncoding.encode(str, encoding); + } + /** + * Returns the charcode at the specified index or at index zero. + */ + static getCharCode(str, index = 0) { + return str.charCodeAt(index); + } + /** + * Returns char for given charcode + */ + static getCharAt(charCode) { + return String.fromCharCode(charCode); + } + } + StringUtils.SHIFT_JIS = CharacterSetECI.SJIS.getName(); // "SJIS" + StringUtils.GB2312 = 'GB2312'; + StringUtils.ISO88591 = CharacterSetECI.ISO8859_1.getName(); // "ISO8859_1" + StringUtils.EUC_JP = 'EUC_JP'; + StringUtils.UTF8 = CharacterSetECI.UTF8.getName(); // "UTF8" + StringUtils.PLATFORM_DEFAULT_ENCODING = StringUtils.UTF8; // "UTF8"//Charset.defaultCharset().name() + StringUtils.ASSUME_SHIFT_JIS = false; + + class StringBuilder { + constructor(value = '') { + this.value = value; + } + enableDecoding(encoding) { + this.encoding = encoding; + return this; + } + append(s) { + if (typeof s === 'string') { + this.value += s.toString(); + } + else if (this.encoding) { + // use passed format (fromCharCode will return UTF8 encoding) + this.value += StringUtils.castAsNonUtf8Char(s, this.encoding); + } + else { + // correctly converts from UTF-8, but not other encodings + this.value += String.fromCharCode(s); + } + return this; + } + appendChars(str, offset, len) { + for (let i = offset; offset < offset + len; i++) { + this.append(str[i]); + } + return this; + } + length() { + return this.value.length; + } + charAt(n) { + return this.value.charAt(n); + } + deleteCharAt(n) { + this.value = this.value.substr(0, n) + this.value.substring(n + 1); + } + setCharAt(n, c) { + this.value = this.value.substr(0, n) + c + this.value.substr(n + 1); + } + substring(start, end) { + return this.value.substring(start, end); + } + /** + * @note helper method for RSS Expanded + */ + setLengthToZero() { + this.value = ''; + } + toString() { + return this.value; + } + insert(n, c) { + this.value = this.value.substr(0, n) + c + this.value.substr(n + c.length); + } + } + + /* + * 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. + */ + /** + *Represents a 2D matrix of bits. In function arguments below, and throughout the common + * module, x is the column position, and y is the row position. The ordering is always x, y. + * The origin is at the top-left.
+ * + *Internally the bits are represented in a 1-D array of 32-bit ints. However, each row begins + * with a new int. This is done intentionally so that we can copy out a row into a BitArray very + * efficiently.
+ * + *The ordering of bits is row-major. Within each int, the least significant bits are used first, + * meaning they represent lower x values. This is compatible with BitArray's implementation.
+ * + * @author Sean Owen + * @author dswitkin@google.com (Daniel Switkin) + */ + class BitMatrix /*implements Cloneable*/ { + /** + * Creates an empty square {@link BitMatrix}. + * + * @param dimension height and width + */ + // public constructor(dimension: number /*int*/) { + // this(dimension, dimension) + // } + /** + * Creates an empty {@link BitMatrix}. + * + * @param width bit matrix width + * @param height bit matrix height + */ + // public constructor(width: number /*int*/, height: number /*int*/) { + // if (width < 1 || height < 1) { + // throw new IllegalArgumentException("Both dimensions must be greater than 0") + // } + // this.width = width + // this.height = height + // this.rowSize = (width + 31) / 32 + // bits = new int[rowSize * height]; + // } + constructor(width /*int*/, height /*int*/, rowSize /*int*/, bits) { + this.width = width; + this.height = height; + this.rowSize = rowSize; + this.bits = bits; + if (undefined === height || null === height) { + height = width; + } + this.height = height; + if (width < 1 || height < 1) { + throw new IllegalArgumentException('Both dimensions must be greater than 0'); + } + if (undefined === rowSize || null === rowSize) { + rowSize = Math.floor((width + 31) / 32); + } + this.rowSize = rowSize; + if (undefined === bits || null === bits) { + this.bits = new Int32Array(this.rowSize * this.height); + } + } + /** + * Interprets a 2D array of booleans as a {@link BitMatrix}, where "true" means an "on" bit. + * + * @function parse + * @param image bits of the image, as a row-major 2D array. Elements are arrays representing rows + * @return {@link BitMatrix} representation of image + */ + static parseFromBooleanArray(image) { + const height = image.length; + const width = image[0].length; + const bits = new BitMatrix(width, height); + for (let i = 0; i < height; i++) { + const imageI = image[i]; + for (let j = 0; j < width; j++) { + if (imageI[j]) { + bits.set(j, i); + } + } + } + return bits; + } + /** + * + * @function parse + * @param stringRepresentation + * @param setString + * @param unsetString + */ + static parseFromString(stringRepresentation, setString, unsetString) { + if (stringRepresentation === null) { + throw new IllegalArgumentException('stringRepresentation cannot be null'); + } + const bits = new Array(stringRepresentation.length); + let bitsPos = 0; + let rowStartPos = 0; + let rowLength = -1; + let nRows = 0; + let pos = 0; + while (pos < stringRepresentation.length) { + if (stringRepresentation.charAt(pos) === '\n' || + stringRepresentation.charAt(pos) === '\r') { + if (bitsPos > rowStartPos) { + if (rowLength === -1) { + rowLength = bitsPos - rowStartPos; + } + else if (bitsPos - rowStartPos !== rowLength) { + throw new IllegalArgumentException('row lengths do not match'); + } + rowStartPos = bitsPos; + nRows++; + } + pos++; + } + else if (stringRepresentation.substring(pos, pos + setString.length) === setString) { + pos += setString.length; + bits[bitsPos] = true; + bitsPos++; + } + else if (stringRepresentation.substring(pos, pos + unsetString.length) === unsetString) { + pos += unsetString.length; + bits[bitsPos] = false; + bitsPos++; + } + else { + throw new IllegalArgumentException('illegal character encountered: ' + stringRepresentation.substring(pos)); + } + } + // no EOL at end? + if (bitsPos > rowStartPos) { + if (rowLength === -1) { + rowLength = bitsPos - rowStartPos; + } + else if (bitsPos - rowStartPos !== rowLength) { + throw new IllegalArgumentException('row lengths do not match'); + } + nRows++; + } + const matrix = new BitMatrix(rowLength, nRows); + for (let i = 0; i < bitsPos; i++) { + if (bits[i]) { + matrix.set(Math.floor(i % rowLength), Math.floor(i / rowLength)); + } + } + return matrix; + } + /** + *Gets the requested bit, where true means black.
+ * + * @param x The horizontal component (i.e. which column) + * @param y The vertical component (i.e. which row) + * @return value of given bit in matrix + */ + get(x /*int*/, y /*int*/) { + const offset = y * this.rowSize + Math.floor(x / 32); + return ((this.bits[offset] >>> (x & 0x1f)) & 1) !== 0; + } + /** + *Sets the given bit to true.
+ * + * @param x The horizontal component (i.e. which column) + * @param y The vertical component (i.e. which row) + */ + set(x /*int*/, y /*int*/) { + const offset = y * this.rowSize + Math.floor(x / 32); + this.bits[offset] |= (1 << (x & 0x1f)) & 0xFFFFFFFF; + } + unset(x /*int*/, y /*int*/) { + const offset = y * this.rowSize + Math.floor(x / 32); + this.bits[offset] &= ~((1 << (x & 0x1f)) & 0xFFFFFFFF); + } + /** + *Flips the given bit.
+ * + * @param x The horizontal component (i.e. which column) + * @param y The vertical component (i.e. which row) + */ + flip(x /*int*/, y /*int*/) { + const offset = y * this.rowSize + Math.floor(x / 32); + this.bits[offset] ^= ((1 << (x & 0x1f)) & 0xFFFFFFFF); + } + /** + * Exclusive-or (XOR): Flip the bit in this {@code BitMatrix} if the corresponding + * mask bit is set. + * + * @param mask XOR mask + */ + xor(mask) { + if (this.width !== mask.getWidth() || this.height !== mask.getHeight() + || this.rowSize !== mask.getRowSize()) { + throw new IllegalArgumentException('input matrix dimensions do not match'); + } + const rowArray = new BitArray(Math.floor(this.width / 32) + 1); + const rowSize = this.rowSize; + const bits = this.bits; + for (let y = 0, height = this.height; y < height; y++) { + const offset = y * rowSize; + const row = mask.getRow(y, rowArray).getBitArray(); + for (let x = 0; x < rowSize; x++) { + bits[offset + x] ^= row[x]; + } + } + } + /** + * Clears all bits (sets to false). + */ + clear() { + const bits = this.bits; + const max = bits.length; + for (let i = 0; i < max; i++) { + bits[i] = 0; + } + } + /** + *Sets a square region of the bit matrix to true.
+ * + * @param left The horizontal position to begin at (inclusive) + * @param top The vertical position to begin at (inclusive) + * @param width The width of the region + * @param height The height of the region + */ + setRegion(left /*int*/, top /*int*/, width /*int*/, height /*int*/) { + if (top < 0 || left < 0) { + throw new IllegalArgumentException('Left and top must be nonnegative'); + } + if (height < 1 || width < 1) { + throw new IllegalArgumentException('Height and width must be at least 1'); + } + const right = left + width; + const bottom = top + height; + if (bottom > this.height || right > this.width) { + throw new IllegalArgumentException('The region must fit inside the matrix'); + } + const rowSize = this.rowSize; + const bits = this.bits; + for (let y = top; y < bottom; y++) { + const offset = y * rowSize; + for (let x = left; x < right; x++) { + bits[offset + Math.floor(x / 32)] |= ((1 << (x & 0x1f)) & 0xFFFFFFFF); + } + } + } + /** + * A fast method to retrieve one row of data from the matrix as a BitArray. + * + * @param y The row to retrieve + * @param row An optional caller-allocated BitArray, will be allocated if null or too small + * @return The resulting BitArray - this reference should always be used even when passing + * your own row + */ + getRow(y /*int*/, row) { + if (row === null || row === undefined || row.getSize() < this.width) { + row = new BitArray(this.width); + } + else { + row.clear(); + } + const rowSize = this.rowSize; + const bits = this.bits; + const offset = y * rowSize; + for (let x = 0; x < rowSize; x++) { + row.setBulk(x * 32, bits[offset + x]); + } + return row; + } + /** + * @param y row to set + * @param row {@link BitArray} to copy from + */ + setRow(y /*int*/, row) { + System.arraycopy(row.getBitArray(), 0, this.bits, y * this.rowSize, this.rowSize); + } + /** + * Modifies this {@code BitMatrix} to represent the same but rotated 180 degrees + */ + rotate180() { + const width = this.getWidth(); + const height = this.getHeight(); + let topRow = new BitArray(width); + let bottomRow = new BitArray(width); + for (let i = 0, length = Math.floor((height + 1) / 2); i < length; i++) { + topRow = this.getRow(i, topRow); + bottomRow = this.getRow(height - 1 - i, bottomRow); + topRow.reverse(); + bottomRow.reverse(); + this.setRow(i, bottomRow); + this.setRow(height - 1 - i, topRow); + } + } + /** + * This is useful in detecting the enclosing rectangle of a 'pure' barcode. + * + * @return {@code left,top,width,height} enclosing rectangle of all 1 bits, or null if it is all white + */ + getEnclosingRectangle() { + const width = this.width; + const height = this.height; + const rowSize = this.rowSize; + const bits = this.bits; + let left = width; + let top = height; + let right = -1; + let bottom = -1; + for (let y = 0; y < height; y++) { + for (let x32 = 0; x32 < rowSize; x32++) { + const theBits = bits[y * rowSize + x32]; + if (theBits !== 0) { + if (y < top) { + top = y; + } + if (y > bottom) { + bottom = y; + } + if (x32 * 32 < left) { + let bit = 0; + while (((theBits << (31 - bit)) & 0xFFFFFFFF) === 0) { + bit++; + } + if ((x32 * 32 + bit) < left) { + left = x32 * 32 + bit; + } + } + if (x32 * 32 + 31 > right) { + let bit = 31; + while ((theBits >>> bit) === 0) { + bit--; + } + if ((x32 * 32 + bit) > right) { + right = x32 * 32 + bit; + } + } + } + } + } + if (right < left || bottom < top) { + return null; + } + return Int32Array.from([left, top, right - left + 1, bottom - top + 1]); + } + /** + * This is useful in detecting a corner of a 'pure' barcode. + * + * @return {@code x,y} coordinate of top-left-most 1 bit, or null if it is all white + */ + getTopLeftOnBit() { + const rowSize = this.rowSize; + const bits = this.bits; + let bitsOffset = 0; + while (bitsOffset < bits.length && bits[bitsOffset] === 0) { + bitsOffset++; + } + if (bitsOffset === bits.length) { + return null; + } + const y = bitsOffset / rowSize; + let x = (bitsOffset % rowSize) * 32; + const theBits = bits[bitsOffset]; + let bit = 0; + while (((theBits << (31 - bit)) & 0xFFFFFFFF) === 0) { + bit++; + } + x += bit; + return Int32Array.from([x, y]); + } + getBottomRightOnBit() { + const rowSize = this.rowSize; + const bits = this.bits; + let bitsOffset = bits.length - 1; + while (bitsOffset >= 0 && bits[bitsOffset] === 0) { + bitsOffset--; + } + if (bitsOffset < 0) { + return null; + } + const y = Math.floor(bitsOffset / rowSize); + let x = Math.floor(bitsOffset % rowSize) * 32; + const theBits = bits[bitsOffset]; + let bit = 31; + while ((theBits >>> bit) === 0) { + bit--; + } + x += bit; + return Int32Array.from([x, y]); + } + /** + * @return The width of the matrix + */ + getWidth() { + return this.width; + } + /** + * @return The height of the matrix + */ + getHeight() { + return this.height; + } + /** + * @return The row size of the matrix + */ + getRowSize() { + return this.rowSize; + } + /*@Override*/ + equals(o) { + if (!(o instanceof BitMatrix)) { + return false; + } + const other = o; + return this.width === other.width && this.height === other.height && this.rowSize === other.rowSize && + Arrays.equals(this.bits, other.bits); + } + /*@Override*/ + hashCode() { + let hash = this.width; + hash = 31 * hash + this.width; + hash = 31 * hash + this.height; + hash = 31 * hash + this.rowSize; + hash = 31 * hash + Arrays.hashCode(this.bits); + return hash; + } + /** + * @return string representation using "X" for set and " " for unset bits + */ + /*@Override*/ + // public toString(): string { + // return toString(": "X, " ") + // } + /** + * @param setString representation of a set bit + * @param unsetString representation of an unset bit + * @return string representation of entire matrix utilizing given strings + */ + // public toString(setString: string = "X ", unsetString: string = " "): string { + // return this.buildToString(setString, unsetString, "\n") + // } + /** + * @param setString representation of a set bit + * @param unsetString representation of an unset bit + * @param lineSeparator newline character in string representation + * @return string representation of entire matrix utilizing given strings and line separator + * @deprecated call {@link #toString(String,String)} only, which uses \n line separator always + */ + // @Deprecated + toString(setString = 'X ', unsetString = ' ', lineSeparator = '\n') { + return this.buildToString(setString, unsetString, lineSeparator); + } + buildToString(setString, unsetString, lineSeparator) { + let result = new StringBuilder(); + // result.append(lineSeparator); + for (let y = 0, height = this.height; y < height; y++) { + for (let x = 0, width = this.width; x < width; x++) { + result.append(this.get(x, y) ? setString : unsetString); + } + result.append(lineSeparator); + } + return result.toString(); + } + /*@Override*/ + clone() { + return new BitMatrix(this.width, this.height, this.rowSize, this.bits.slice()); + } + } + + /** + * Custom Error class of type Exception. + */ + class NotFoundException extends Exception { + static getNotFoundInstance() { + return new NotFoundException(); + } + } + NotFoundException.kind = 'NotFoundException'; + + /* + * Copyright 2009 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. + */ + /** + * This Binarizer implementation uses the old ZXing global histogram approach. It is suitable + * for low-end mobile devices which don't have enough CPU or memory to use a local thresholding + * algorithm. However, because it picks a global black point, it cannot handle difficult shadows + * and gradients. + * + * Faster mobile devices and all desktop applications should probably use HybridBinarizer instead. + * + * @author dswitkin@google.com (Daniel Switkin) + * @author Sean Owen + */ + class GlobalHistogramBinarizer extends Binarizer { + constructor(source) { + super(source); + this.luminances = GlobalHistogramBinarizer.EMPTY; + this.buckets = new Int32Array(GlobalHistogramBinarizer.LUMINANCE_BUCKETS); + } + // Applies simple sharpening to the row data to improve performance of the 1D Readers. + /*@Override*/ + getBlackRow(y /*int*/, row) { + const source = this.getLuminanceSource(); + const width = source.getWidth(); + if (row === undefined || row === null || row.getSize() < width) { + row = new BitArray(width); + } + else { + row.clear(); + } + this.initArrays(width); + const localLuminances = source.getRow(y, this.luminances); + const localBuckets = this.buckets; + for (let x = 0; x < width; x++) { + localBuckets[(localLuminances[x] & 0xff) >> GlobalHistogramBinarizer.LUMINANCE_SHIFT]++; + } + const blackPoint = GlobalHistogramBinarizer.estimateBlackPoint(localBuckets); + if (width < 3) { + // Special case for very small images + for (let x = 0; x < width; x++) { + if ((localLuminances[x] & 0xff) < blackPoint) { + row.set(x); + } + } + } + else { + let left = localLuminances[0] & 0xff; + let center = localLuminances[1] & 0xff; + for (let x = 1; x < width - 1; x++) { + const right = localLuminances[x + 1] & 0xff; + // A simple -1 4 -1 box filter with a weight of 2. + if (((center * 4) - left - right) / 2 < blackPoint) { + row.set(x); + } + left = center; + center = right; + } + } + return row; + } + // Does not sharpen the data, as this call is intended to only be used by 2D Readers. + /*@Override*/ + getBlackMatrix() { + const source = this.getLuminanceSource(); + const width = source.getWidth(); + const height = source.getHeight(); + const matrix = new BitMatrix(width, height); + // Quickly calculates the histogram by sampling four rows from the image. This proved to be + // more robust on the blackbox tests than sampling a diagonal as we used to do. + this.initArrays(width); + const localBuckets = this.buckets; + for (let y = 1; y < 5; y++) { + const row = Math.floor((height * y) / 5); + const localLuminances = source.getRow(row, this.luminances); + const right = Math.floor((width * 4) / 5); + for (let x = Math.floor(width / 5); x < right; x++) { + const pixel = localLuminances[x] & 0xff; + localBuckets[pixel >> GlobalHistogramBinarizer.LUMINANCE_SHIFT]++; + } + } + const blackPoint = GlobalHistogramBinarizer.estimateBlackPoint(localBuckets); + // We delay reading the entire image luminance until the black point estimation succeeds. + // Although we end up reading four rows twice, it is consistent with our motto of + // "fail quickly" which is necessary for continuous scanning. + const localLuminances = source.getMatrix(); + for (let y = 0; y < height; y++) { + const offset = y * width; + for (let x = 0; x < width; x++) { + const pixel = localLuminances[offset + x] & 0xff; + if (pixel < blackPoint) { + matrix.set(x, y); + } + } + } + return matrix; + } + /*@Override*/ + createBinarizer(source) { + return new GlobalHistogramBinarizer(source); + } + initArrays(luminanceSize /*int*/) { + if (this.luminances.length < luminanceSize) { + this.luminances = new Uint8ClampedArray(luminanceSize); + } + const buckets = this.buckets; + for (let x = 0; x < GlobalHistogramBinarizer.LUMINANCE_BUCKETS; x++) { + buckets[x] = 0; + } + } + static estimateBlackPoint(buckets) { + // Find the tallest peak in the histogram. + const numBuckets = buckets.length; + let maxBucketCount = 0; + let firstPeak = 0; + let firstPeakSize = 0; + for (let x = 0; x < numBuckets; x++) { + if (buckets[x] > firstPeakSize) { + firstPeak = x; + firstPeakSize = buckets[x]; + } + if (buckets[x] > maxBucketCount) { + maxBucketCount = buckets[x]; + } + } + // Find the second-tallest peak which is somewhat far from the tallest peak. + let secondPeak = 0; + let secondPeakScore = 0; + for (let x = 0; x < numBuckets; x++) { + const distanceToBiggest = x - firstPeak; + // Encourage more distant second peaks by multiplying by square of distance. + const score = buckets[x] * distanceToBiggest * distanceToBiggest; + if (score > secondPeakScore) { + secondPeak = x; + secondPeakScore = score; + } + } + // Make sure firstPeak corresponds to the black peak. + if (firstPeak > secondPeak) { + const temp = firstPeak; + firstPeak = secondPeak; + secondPeak = temp; + } + // If there is too little contrast in the image to pick a meaningful black point, throw rather + // than waste time trying to decode the image, and risk false positives. + if (secondPeak - firstPeak <= numBuckets / 16) { + throw new NotFoundException(); + } + // Find a valley between them that is low and closer to the white peak. + let bestValley = secondPeak - 1; + let bestValleyScore = -1; + for (let x = secondPeak - 1; x > firstPeak; x--) { + const fromFirst = x - firstPeak; + const score = fromFirst * fromFirst * (secondPeak - x) * (maxBucketCount - buckets[x]); + if (score > bestValleyScore) { + bestValley = x; + bestValleyScore = score; + } + } + return bestValley << GlobalHistogramBinarizer.LUMINANCE_SHIFT; + } + } + GlobalHistogramBinarizer.LUMINANCE_BITS = 5; + GlobalHistogramBinarizer.LUMINANCE_SHIFT = 8 - GlobalHistogramBinarizer.LUMINANCE_BITS; + GlobalHistogramBinarizer.LUMINANCE_BUCKETS = 1 << GlobalHistogramBinarizer.LUMINANCE_BITS; + GlobalHistogramBinarizer.EMPTY = Uint8ClampedArray.from([0]); + + /* + * Copyright 2009 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. + */ + /** + * This class implements a local thresholding algorithm, which while slower than the + * GlobalHistogramBinarizer, is fairly efficient for what it does. It is designed for + * high frequency images of barcodes with black data on white backgrounds. For this application, + * it does a much better job than a global blackpoint with severe shadows and gradients. + * However it tends to produce artifacts on lower frequency images and is therefore not + * a good general purpose binarizer for uses outside ZXing. + * + * This class extends GlobalHistogramBinarizer, using the older histogram approach for 1D readers, + * and the newer local approach for 2D readers. 1D decoding using a per-row histogram is already + * inherently local, and only fails for horizontal gradients. We can revisit that problem later, + * but for now it was not a win to use local blocks for 1D. + * + * This Binarizer is the default for the unit tests and the recommended class for library users. + * + * @author dswitkin@google.com (Daniel Switkin) + */ + class HybridBinarizer extends GlobalHistogramBinarizer { + constructor(source) { + super(source); + this.matrix = null; + } + /** + * Calculates the final BitMatrix once for all requests. This could be called once from the + * constructor instead, but there are some advantages to doing it lazily, such as making + * profiling easier, and not doing heavy lifting when callers don't expect it. + */ + /*@Override*/ + getBlackMatrix() { + if (this.matrix !== null) { + return this.matrix; + } + const source = this.getLuminanceSource(); + const width = source.getWidth(); + const height = source.getHeight(); + if (width >= HybridBinarizer.MINIMUM_DIMENSION && height >= HybridBinarizer.MINIMUM_DIMENSION) { + const luminances = source.getMatrix(); + let subWidth = width >> HybridBinarizer.BLOCK_SIZE_POWER; + if ((width & HybridBinarizer.BLOCK_SIZE_MASK) !== 0) { + subWidth++; + } + let subHeight = height >> HybridBinarizer.BLOCK_SIZE_POWER; + if ((height & HybridBinarizer.BLOCK_SIZE_MASK) !== 0) { + subHeight++; + } + const blackPoints = HybridBinarizer.calculateBlackPoints(luminances, subWidth, subHeight, width, height); + const newMatrix = new BitMatrix(width, height); + HybridBinarizer.calculateThresholdForBlock(luminances, subWidth, subHeight, width, height, blackPoints, newMatrix); + this.matrix = newMatrix; + } + else { + // If the image is too small, fall back to the global histogram approach. + this.matrix = super.getBlackMatrix(); + } + return this.matrix; + } + /*@Override*/ + createBinarizer(source) { + return new HybridBinarizer(source); + } + /** + * For each block in the image, calculate the average black point using a 5x5 grid + * of the blocks around it. Also handles the corner cases (fractional blocks are computed based + * on the last pixels in the row/column which are also used in the previous block). + */ + static calculateThresholdForBlock(luminances, subWidth /*int*/, subHeight /*int*/, width /*int*/, height /*int*/, blackPoints, matrix) { + const maxYOffset = height - HybridBinarizer.BLOCK_SIZE; + const maxXOffset = width - HybridBinarizer.BLOCK_SIZE; + for (let y = 0; y < subHeight; y++) { + let yoffset = y << HybridBinarizer.BLOCK_SIZE_POWER; + if (yoffset > maxYOffset) { + yoffset = maxYOffset; + } + const top = HybridBinarizer.cap(y, 2, subHeight - 3); + for (let x = 0; x < subWidth; x++) { + let xoffset = x << HybridBinarizer.BLOCK_SIZE_POWER; + if (xoffset > maxXOffset) { + xoffset = maxXOffset; + } + const left = HybridBinarizer.cap(x, 2, subWidth - 3); + let sum = 0; + for (let z = -2; z <= 2; z++) { + const blackRow = blackPoints[top + z]; + sum += blackRow[left - 2] + blackRow[left - 1] + blackRow[left] + blackRow[left + 1] + blackRow[left + 2]; + } + const average = sum / 25; + HybridBinarizer.thresholdBlock(luminances, xoffset, yoffset, average, width, matrix); + } + } + } + static cap(value /*int*/, min /*int*/, max /*int*/) { + return value < min ? min : value > max ? max : value; + } + /** + * Applies a single threshold to a block of pixels. + */ + static thresholdBlock(luminances, xoffset /*int*/, yoffset /*int*/, threshold /*int*/, stride /*int*/, matrix) { + for (let y = 0, offset = yoffset * stride + xoffset; y < HybridBinarizer.BLOCK_SIZE; y++, offset += stride) { + for (let x = 0; x < HybridBinarizer.BLOCK_SIZE; x++) { + // Comparison needs to be <= so that black == 0 pixels are black even if the threshold is 0. + if ((luminances[offset + x] & 0xFF) <= threshold) { + matrix.set(xoffset + x, yoffset + y); + } + } + } + } + /** + * Calculates a single black point for each block of pixels and saves it away. + * See the following thread for a discussion of this algorithm: + * http://groups.google.com/group/zxing/browse_thread/thread/d06efa2c35a7ddc0 + */ + static calculateBlackPoints(luminances, subWidth /*int*/, subHeight /*int*/, width /*int*/, height /*int*/) { + const maxYOffset = height - HybridBinarizer.BLOCK_SIZE; + const maxXOffset = width - HybridBinarizer.BLOCK_SIZE; + // tslint:disable-next-line:whitespace + const blackPoints = new Array(subHeight); // subWidth + for (let y = 0; y < subHeight; y++) { + blackPoints[y] = new Int32Array(subWidth); + let yoffset = y << HybridBinarizer.BLOCK_SIZE_POWER; + if (yoffset > maxYOffset) { + yoffset = maxYOffset; + } + for (let x = 0; x < subWidth; x++) { + let xoffset = x << HybridBinarizer.BLOCK_SIZE_POWER; + if (xoffset > maxXOffset) { + xoffset = maxXOffset; + } + let sum = 0; + let min = 0xFF; + let max = 0; + for (let yy = 0, offset = yoffset * width + xoffset; yy < HybridBinarizer.BLOCK_SIZE; yy++, offset += width) { + for (let xx = 0; xx < HybridBinarizer.BLOCK_SIZE; xx++) { + const pixel = luminances[offset + xx] & 0xFF; + sum += pixel; + // still looking for good contrast + if (pixel < min) { + min = pixel; + } + if (pixel > max) { + max = pixel; + } + } + // short-circuit min/max tests once dynamic range is met + if (max - min > HybridBinarizer.MIN_DYNAMIC_RANGE) { + // finish the rest of the rows quickly + for (yy++, offset += width; yy < HybridBinarizer.BLOCK_SIZE; yy++, offset += width) { + for (let xx = 0; xx < HybridBinarizer.BLOCK_SIZE; xx++) { + sum += luminances[offset + xx] & 0xFF; + } + } + } + } + // The default estimate is the average of the values in the block. + let average = sum >> (HybridBinarizer.BLOCK_SIZE_POWER * 2); + if (max - min <= HybridBinarizer.MIN_DYNAMIC_RANGE) { + // If variation within the block is low, assume this is a block with only light or only + // dark pixels. In that case we do not want to use the average, as it would divide this + // low contrast area into black and white pixels, essentially creating data out of noise. + // + // The default assumption is that the block is light/background. Since no estimate for + // the level of dark pixels exists locally, use half the min for the block. + average = min / 2; + if (y > 0 && x > 0) { + // Correct the "white background" assumption for blocks that have neighbors by comparing + // the pixels in this block to the previously calculated black points. This is based on + // the fact that dark barcode symbology is always surrounded by some amount of light + // background for which reasonable black point estimates were made. The bp estimated at + // the boundaries is used for the interior. + // The (min < bp) is arbitrary but works better than other heuristics that were tried. + const averageNeighborBlackPoint = (blackPoints[y - 1][x] + (2 * blackPoints[y][x - 1]) + blackPoints[y - 1][x - 1]) / 4; + if (min < averageNeighborBlackPoint) { + average = averageNeighborBlackPoint; + } + } + } + blackPoints[y][x] = average; + } + } + return blackPoints; + } + } + // This class uses 5x5 blocks to compute local luminance, where each block is 8x8 pixels. + // So this is the smallest dimension in each axis we can accept. + HybridBinarizer.BLOCK_SIZE_POWER = 3; + HybridBinarizer.BLOCK_SIZE = 1 << HybridBinarizer.BLOCK_SIZE_POWER; // ...0100...00 + HybridBinarizer.BLOCK_SIZE_MASK = HybridBinarizer.BLOCK_SIZE - 1; // ...0011...11 + HybridBinarizer.MINIMUM_DIMENSION = HybridBinarizer.BLOCK_SIZE * 5; + HybridBinarizer.MIN_DYNAMIC_RANGE = 24; + + /* + * Copyright 2009 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. + */ + /*namespace com.google.zxing {*/ + /** + * The purpose of this class hierarchy is to abstract different bitmap implementations across + * platforms into a standard interface for requesting greyscale luminance values. The interface + * only provides immutable methods; therefore crop and rotation create copies. This is to ensure + * that one Reader does not modify the original luminance source and leave it in an unknown state + * for other Readers in the chain. + * + * @author dswitkin@google.com (Daniel Switkin) + */ + class LuminanceSource { + constructor(width /*int*/, height /*int*/) { + this.width = width; + this.height = height; + } + /** + * @return The width of the bitmap. + */ + getWidth() { + return this.width; + } + /** + * @return The height of the bitmap. + */ + getHeight() { + return this.height; + } + /** + * @return Whether this subclass supports cropping. + */ + isCropSupported() { + return false; + } + /** + * Returns a new object with cropped image data. Implementations may keep a reference to the + * original data rather than a copy. Only callable if isCropSupported() is true. + * + * @param left The left coordinate, which must be in [0,getWidth()) + * @param top The top coordinate, which must be in [0,getHeight()) + * @param width The width of the rectangle to crop. + * @param height The height of the rectangle to crop. + * @return A cropped version of this object. + */ + crop(left /*int*/, top /*int*/, width /*int*/, height /*int*/) { + throw new UnsupportedOperationException('This luminance source does not support cropping.'); + } + /** + * @return Whether this subclass supports counter-clockwise rotation. + */ + isRotateSupported() { + return false; + } + /** + * Returns a new object with rotated image data by 90 degrees counterclockwise. + * Only callable if {@link #isRotateSupported()} is true. + * + * @return A rotated version of this object. + */ + rotateCounterClockwise() { + throw new UnsupportedOperationException('This luminance source does not support rotation by 90 degrees.'); + } + /** + * Returns a new object with rotated image data by 45 degrees counterclockwise. + * Only callable if {@link #isRotateSupported()} is true. + * + * @return A rotated version of this object. + */ + rotateCounterClockwise45() { + throw new UnsupportedOperationException('This luminance source does not support rotation by 45 degrees.'); + } + /*@Override*/ + toString() { + const row = new Uint8ClampedArray(this.width); + let result = new StringBuilder(); + for (let y = 0; y < this.height; y++) { + const sourceRow = this.getRow(y, row); + for (let x = 0; x < this.width; x++) { + const luminance = sourceRow[x] & 0xFF; + let c; + if (luminance < 0x40) { + c = '#'; + } + else if (luminance < 0x80) { + c = '+'; + } + else if (luminance < 0xC0) { + c = '.'; + } + else { + c = ' '; + } + result.append(c); + } + result.append('\n'); + } + return result.toString(); + } + } + + /* + * Copyright 2009 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. + */ + /*namespace com.google.zxing {*/ + /** + * A wrapper implementation of {@link LuminanceSource} which inverts the luminances it returns -- black becomes + * white and vice versa, and each value becomes (255-value). + * + * @author Sean Owen + */ + class InvertedLuminanceSource extends LuminanceSource { + constructor(delegate) { + super(delegate.getWidth(), delegate.getHeight()); + this.delegate = delegate; + } + /*@Override*/ + getRow(y /*int*/, row) { + const sourceRow = this.delegate.getRow(y, row); + const width = this.getWidth(); + for (let i = 0; i < width; i++) { + sourceRow[i] = /*(byte)*/ (255 - (sourceRow[i] & 0xFF)); + } + return sourceRow; + } + /*@Override*/ + getMatrix() { + const matrix = this.delegate.getMatrix(); + const length = this.getWidth() * this.getHeight(); + const invertedMatrix = new Uint8ClampedArray(length); + for (let i = 0; i < length; i++) { + invertedMatrix[i] = /*(byte)*/ (255 - (matrix[i] & 0xFF)); + } + return invertedMatrix; + } + /*@Override*/ + isCropSupported() { + return this.delegate.isCropSupported(); + } + /*@Override*/ + crop(left /*int*/, top /*int*/, width /*int*/, height /*int*/) { + return new InvertedLuminanceSource(this.delegate.crop(left, top, width, height)); + } + /*@Override*/ + isRotateSupported() { + return this.delegate.isRotateSupported(); + } + /** + * @return original delegate {@link LuminanceSource} since invert undoes itself + */ + /*@Override*/ + invert() { + return this.delegate; + } + /*@Override*/ + rotateCounterClockwise() { + return new InvertedLuminanceSource(this.delegate.rotateCounterClockwise()); + } + /*@Override*/ + rotateCounterClockwise45() { + return new InvertedLuminanceSource(this.delegate.rotateCounterClockwise45()); + } + } + + /** + * @deprecated Moving to @zxing/browser + */ + class HTMLCanvasElementLuminanceSource extends LuminanceSource { + constructor(canvas) { + super(canvas.width, canvas.height); + this.canvas = canvas; + this.tempCanvasElement = null; + this.buffer = HTMLCanvasElementLuminanceSource.makeBufferFromCanvasImageData(canvas); + } + static makeBufferFromCanvasImageData(canvas) { + const imageData = canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height); + return HTMLCanvasElementLuminanceSource.toGrayscaleBuffer(imageData.data, canvas.width, canvas.height); + } + static toGrayscaleBuffer(imageBuffer, width, height) { + const grayscaleBuffer = new Uint8ClampedArray(width * height); + for (let i = 0, j = 0, length = imageBuffer.length; i < length; i += 4, j++) { + let gray; + const alpha = imageBuffer[i + 3]; + // The color of fully-transparent pixels is irrelevant. They are often, technically, fully-transparent + // black (0 alpha, and then 0 RGB). They are often used, of course as the "white" area in a + // barcode image. Force any such pixel to be white: + if (alpha === 0) { + gray = 0xFF; + } + else { + const pixelR = imageBuffer[i]; + const pixelG = imageBuffer[i + 1]; + const pixelB = imageBuffer[i + 2]; + // .299R + 0.587G + 0.114B (YUV/YIQ for PAL and NTSC), + // (306*R) >> 10 is approximately equal to R*0.299, and so on. + // 0x200 >> 10 is 0.5, it implements rounding. + gray = (306 * pixelR + + 601 * pixelG + + 117 * pixelB + + 0x200) >> 10; + } + grayscaleBuffer[j] = gray; + } + return grayscaleBuffer; + } + getRow(y /*int*/, row) { + if (y < 0 || y >= this.getHeight()) { + throw new IllegalArgumentException('Requested row is outside the image: ' + y); + } + const width = this.getWidth(); + const start = y * width; + if (row === null) { + row = this.buffer.slice(start, start + width); + } + else { + if (row.length < width) { + row = new Uint8ClampedArray(width); + } + // The underlying raster of image consists of bytes with the luminance values + // TODO: can avoid set/slice? + row.set(this.buffer.slice(start, start + width)); + } + return row; + } + getMatrix() { + return this.buffer; + } + isCropSupported() { + return true; + } + crop(left /*int*/, top /*int*/, width /*int*/, height /*int*/) { + super.crop(left, top, width, height); + return this; + } + /** + * This is always true, since the image is a gray-scale image. + * + * @return true + */ + isRotateSupported() { + return true; + } + rotateCounterClockwise() { + this.rotate(-90); + return this; + } + rotateCounterClockwise45() { + this.rotate(-45); + return this; + } + getTempCanvasElement() { + if (null === this.tempCanvasElement) { + const tempCanvasElement = this.canvas.ownerDocument.createElement('canvas'); + tempCanvasElement.width = this.canvas.width; + tempCanvasElement.height = this.canvas.height; + this.tempCanvasElement = tempCanvasElement; + } + return this.tempCanvasElement; + } + rotate(angle) { + const tempCanvasElement = this.getTempCanvasElement(); + const tempContext = tempCanvasElement.getContext('2d'); + const angleRadians = angle * HTMLCanvasElementLuminanceSource.DEGREE_TO_RADIANS; + // Calculate and set new dimensions for temp canvas + const width = this.canvas.width; + const height = this.canvas.height; + const newWidth = Math.ceil(Math.abs(Math.cos(angleRadians)) * width + Math.abs(Math.sin(angleRadians)) * height); + const newHeight = Math.ceil(Math.abs(Math.sin(angleRadians)) * width + Math.abs(Math.cos(angleRadians)) * height); + tempCanvasElement.width = newWidth; + tempCanvasElement.height = newHeight; + // Draw at center of temp canvas to prevent clipping of image data + tempContext.translate(newWidth / 2, newHeight / 2); + tempContext.rotate(angleRadians); + tempContext.drawImage(this.canvas, width / -2, height / -2); + this.buffer = HTMLCanvasElementLuminanceSource.makeBufferFromCanvasImageData(tempCanvasElement); + return this; + } + invert() { + return new InvertedLuminanceSource(this); + } + } + HTMLCanvasElementLuminanceSource.DEGREE_TO_RADIANS = Math.PI / 180; + + /** + * @deprecated Moving to @zxing/browser + * + * Video input device metadata containing the id and label of the device if available. + */ + class VideoInputDevice { + /** + * Creates an instance of VideoInputDevice. + * + * @param {string} deviceId the video input device id + * @param {string} label the label of the device if available + */ + constructor(deviceId, label, groupId) { + this.deviceId = deviceId; + this.label = label; + /** @inheritdoc */ + this.kind = 'videoinput'; + this.groupId = groupId || undefined; + } + /** @inheritdoc */ + toJSON() { + return { + kind: this.kind, + groupId: this.groupId, + deviceId: this.deviceId, + label: this.label, + }; + } + } + + var __awaiter = ((globalThis || global || self || window || undefined) && (globalThis || global || self || window || undefined).__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); + }; + /** + * @deprecated Moving to @zxing/browser + * + * Base class for browser code reader. + */ + class BrowserCodeReader { + /** + * Creates an instance of BrowserCodeReader. + * @param {Reader} reader The reader instance to decode the barcode + * @param {number} [timeBetweenScansMillis=500] the time delay between subsequent successful decode tries + * + * @memberOf BrowserCodeReader + */ + constructor(reader, timeBetweenScansMillis = 500, _hints) { + this.reader = reader; + this.timeBetweenScansMillis = timeBetweenScansMillis; + this._hints = _hints; + /** + * This will break the loop. + */ + this._stopContinuousDecode = false; + /** + * This will break the loop. + */ + this._stopAsyncDecode = false; + /** + * Delay time between decode attempts made by the scanner. + */ + this._timeBetweenDecodingAttempts = 0; + } + /** + * If navigator is present. + */ + get hasNavigator() { + return typeof navigator !== 'undefined'; + } + /** + * If mediaDevices under navigator is supported. + */ + get isMediaDevicesSuported() { + return this.hasNavigator && !!navigator.mediaDevices; + } + /** + * If enumerateDevices under navigator is supported. + */ + get canEnumerateDevices() { + return !!(this.isMediaDevicesSuported && navigator.mediaDevices.enumerateDevices); + } + /** Time between two decoding tries in milli seconds. */ + get timeBetweenDecodingAttempts() { + return this._timeBetweenDecodingAttempts; + } + /** + * Change the time span the decoder waits between two decoding tries. + * + * @param {number} millis Time between two decoding tries in milli seconds. + */ + set timeBetweenDecodingAttempts(millis) { + this._timeBetweenDecodingAttempts = millis < 0 ? 0 : millis; + } + /** + * Sets the hints. + */ + set hints(hints) { + this._hints = hints || null; + } + /** + * Sets the hints. + */ + get hints() { + return this._hints; + } + /** + * Lists all the available video input devices. + */ + listVideoInputDevices() { + return __awaiter(this, void 0, void 0, function* () { + if (!this.hasNavigator) { + throw new Error('Can\'t enumerate devices, navigator is not present.'); + } + if (!this.canEnumerateDevices) { + throw new Error('Can\'t enumerate devices, method not supported.'); + } + const devices = yield navigator.mediaDevices.enumerateDevices(); + const videoDevices = []; + for (const device of devices) { + const kind = device.kind === 'video' ? 'videoinput' : device.kind; + if (kind !== 'videoinput') { + continue; + } + const deviceId = device.deviceId || device.id; + const label = device.label || `Video device ${videoDevices.length + 1}`; + const groupId = device.groupId; + const videoDevice = { deviceId, label, kind, groupId }; + videoDevices.push(videoDevice); + } + return videoDevices; + }); + } + /** + * Obtain the list of available devices with type 'videoinput'. + * + * @returns {PromiseEncapsulates the result of decoding a barcode within an image.
+ * + * @author Sean Owen + */ + class Result { + // public constructor(private text: string, + // Uint8Array rawBytes, + // ResultPoconst resultPoints: Int32Array, + // BarcodeFormat format) { + // this(text, rawBytes, resultPoints, format, System.currentTimeMillis()) + // } + // public constructor(text: string, + // Uint8Array rawBytes, + // ResultPoconst resultPoints: Int32Array, + // BarcodeFormat format, + // long timestamp) { + // this(text, rawBytes, rawBytes == null ? 0 : 8 * rawBytes.length, + // resultPoints, format, timestamp) + // } + constructor(text, rawBytes, numBits = rawBytes == null ? 0 : 8 * rawBytes.length, resultPoints, format, timestamp = System.currentTimeMillis()) { + this.text = text; + this.rawBytes = rawBytes; + this.numBits = numBits; + this.resultPoints = resultPoints; + this.format = format; + this.timestamp = timestamp; + this.text = text; + this.rawBytes = rawBytes; + if (undefined === numBits || null === numBits) { + this.numBits = (rawBytes === null || rawBytes === undefined) ? 0 : 8 * rawBytes.length; + } + else { + this.numBits = numBits; + } + this.resultPoints = resultPoints; + this.format = format; + this.resultMetadata = null; + if (undefined === timestamp || null === timestamp) { + this.timestamp = System.currentTimeMillis(); + } + else { + this.timestamp = timestamp; + } + } + /** + * @return raw text encoded by the barcode + */ + getText() { + return this.text; + } + /** + * @return raw bytes encoded by the barcode, if applicable, otherwise {@code null} + */ + getRawBytes() { + return this.rawBytes; + } + /** + * @return how many bits of {@link #getRawBytes()} are valid; typically 8 times its length + * @since 3.3.0 + */ + getNumBits() { + return this.numBits; + } + /** + * @return points related to the barcode in the image. These are typically points + * identifying finder patterns or the corners of the barcode. The exact meaning is + * specific to the type of barcode that was decoded. + */ + getResultPoints() { + return this.resultPoints; + } + /** + * @return {@link BarcodeFormat} representing the format of the barcode that was decoded + */ + getBarcodeFormat() { + return this.format; + } + /** + * @return {@link Map} mapping {@link ResultMetadataType} keys to values. May be + * {@code null}. This contains optional metadata about what was detected about the barcode, + * like orientation. + */ + getResultMetadata() { + return this.resultMetadata; + } + putMetadata(type, value) { + if (this.resultMetadata === null) { + this.resultMetadata = new Map(); + } + this.resultMetadata.set(type, value); + } + putAllMetadata(metadata) { + if (metadata !== null) { + if (this.resultMetadata === null) { + this.resultMetadata = metadata; + } + else { + this.resultMetadata = new Map(metadata); + } + } + } + addResultPoints(newPoints) { + const oldPoints = this.resultPoints; + if (oldPoints === null) { + this.resultPoints = newPoints; + } + else if (newPoints !== null && newPoints.length > 0) { + const allPoints = new Array(oldPoints.length + newPoints.length); + System.arraycopy(oldPoints, 0, allPoints, 0, oldPoints.length); + System.arraycopy(newPoints, 0, allPoints, oldPoints.length, newPoints.length); + this.resultPoints = allPoints; + } + } + getTimestamp() { + return this.timestamp; + } + /*@Override*/ + toString() { + return this.text; + } + } + + /* + * Direct port to TypeScript of ZXing by Adrian Toșcă + */ + /* + * Copyright 2009 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. + */ + /*namespace com.google.zxing {*/ + /** + * Enumerates barcode formats known to this package. Please keep alphabetized. + * + * @author Sean Owen + */ + var BarcodeFormat; + (function (BarcodeFormat) { + /** Aztec 2D barcode format. */ + BarcodeFormat[BarcodeFormat["AZTEC"] = 0] = "AZTEC"; + /** CODABAR 1D format. */ + BarcodeFormat[BarcodeFormat["CODABAR"] = 1] = "CODABAR"; + /** Code 39 1D format. */ + BarcodeFormat[BarcodeFormat["CODE_39"] = 2] = "CODE_39"; + /** Code 93 1D format. */ + BarcodeFormat[BarcodeFormat["CODE_93"] = 3] = "CODE_93"; + /** Code 128 1D format. */ + BarcodeFormat[BarcodeFormat["CODE_128"] = 4] = "CODE_128"; + /** Data Matrix 2D barcode format. */ + BarcodeFormat[BarcodeFormat["DATA_MATRIX"] = 5] = "DATA_MATRIX"; + /** EAN-8 1D format. */ + BarcodeFormat[BarcodeFormat["EAN_8"] = 6] = "EAN_8"; + /** EAN-13 1D format. */ + BarcodeFormat[BarcodeFormat["EAN_13"] = 7] = "EAN_13"; + /** ITF (Interleaved Two of Five) 1D format. */ + BarcodeFormat[BarcodeFormat["ITF"] = 8] = "ITF"; + /** MaxiCode 2D barcode format. */ + BarcodeFormat[BarcodeFormat["MAXICODE"] = 9] = "MAXICODE"; + /** PDF417 format. */ + BarcodeFormat[BarcodeFormat["PDF_417"] = 10] = "PDF_417"; + /** QR Code 2D barcode format. */ + BarcodeFormat[BarcodeFormat["QR_CODE"] = 11] = "QR_CODE"; + /** RSS 14 */ + BarcodeFormat[BarcodeFormat["RSS_14"] = 12] = "RSS_14"; + /** RSS EXPANDED */ + BarcodeFormat[BarcodeFormat["RSS_EXPANDED"] = 13] = "RSS_EXPANDED"; + /** UPC-A 1D format. */ + BarcodeFormat[BarcodeFormat["UPC_A"] = 14] = "UPC_A"; + /** UPC-E 1D format. */ + BarcodeFormat[BarcodeFormat["UPC_E"] = 15] = "UPC_E"; + /** UPC/EAN extension format. Not a stand-alone format. */ + BarcodeFormat[BarcodeFormat["UPC_EAN_EXTENSION"] = 16] = "UPC_EAN_EXTENSION"; + })(BarcodeFormat || (BarcodeFormat = {})); + var BarcodeFormat$1 = BarcodeFormat; + + /* + * Copyright 2008 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. + */ + /*namespace com.google.zxing {*/ + /** + * Represents some type of metadata about the result of the decoding that the decoder + * wishes to communicate back to the caller. + * + * @author Sean Owen + */ + var ResultMetadataType; + (function (ResultMetadataType) { + /** + * Unspecified, application-specific metadata. Maps to an unspecified {@link Object}. + */ + ResultMetadataType[ResultMetadataType["OTHER"] = 0] = "OTHER"; + /** + * Denotes the likely approximate orientation of the barcode in the image. This value + * is given as degrees rotated clockwise from the normal, upright orientation. + * For example a 1D barcode which was found by reading top-to-bottom would be + * said to have orientation "90". This key maps to an {@link Integer} whose + * value is in the range [0,360). + */ + ResultMetadataType[ResultMetadataType["ORIENTATION"] = 1] = "ORIENTATION"; + /** + *2D barcode formats typically encode text, but allow for a sort of 'byte mode' + * which is sometimes used to encode binary data. While {@link Result} makes available + * the complete raw bytes in the barcode for these formats, it does not offer the bytes + * from the byte segments alone.
+ * + *This maps to a {@link java.util.List} of byte arrays corresponding to the + * raw bytes in the byte segments in the barcode, in order.
+ */ + ResultMetadataType[ResultMetadataType["BYTE_SEGMENTS"] = 2] = "BYTE_SEGMENTS"; + /** + * Error correction level used, if applicable. The value type depends on the + * format, but is typically a String. + */ + ResultMetadataType[ResultMetadataType["ERROR_CORRECTION_LEVEL"] = 3] = "ERROR_CORRECTION_LEVEL"; + /** + * For some periodicals, indicates the issue number as an {@link Integer}. + */ + ResultMetadataType[ResultMetadataType["ISSUE_NUMBER"] = 4] = "ISSUE_NUMBER"; + /** + * For some products, indicates the suggested retail price in the barcode as a + * formatted {@link String}. + */ + ResultMetadataType[ResultMetadataType["SUGGESTED_PRICE"] = 5] = "SUGGESTED_PRICE"; + /** + * For some products, the possible country of manufacture as a {@link String} denoting the + * ISO country code. Some map to multiple possible countries, like "US/CA". + */ + ResultMetadataType[ResultMetadataType["POSSIBLE_COUNTRY"] = 6] = "POSSIBLE_COUNTRY"; + /** + * For some products, the extension text + */ + ResultMetadataType[ResultMetadataType["UPC_EAN_EXTENSION"] = 7] = "UPC_EAN_EXTENSION"; + /** + * PDF417-specific metadata + */ + ResultMetadataType[ResultMetadataType["PDF417_EXTRA_METADATA"] = 8] = "PDF417_EXTRA_METADATA"; + /** + * If the code format supports structured append and the current scanned code is part of one then the + * sequence number is given with it. + */ + ResultMetadataType[ResultMetadataType["STRUCTURED_APPEND_SEQUENCE"] = 9] = "STRUCTURED_APPEND_SEQUENCE"; + /** + * If the code format supports structured append and the current scanned code is part of one then the + * parity is given with it. + */ + ResultMetadataType[ResultMetadataType["STRUCTURED_APPEND_PARITY"] = 10] = "STRUCTURED_APPEND_PARITY"; + })(ResultMetadataType || (ResultMetadataType = {})); + var ResultMetadataType$1 = ResultMetadataType; + + /* + * 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. + */ + /*namespace com.google.zxing.common {*/ + /*import java.util.List;*/ + /** + *Encapsulates the result of decoding a matrix of bits. This typically + * applies to 2D barcode formats. For now it contains the raw bytes obtained, + * as well as a String interpretation of those bytes, if applicable.
+ * + * @author Sean Owen + */ + class DecoderResult { + // public constructor(rawBytes: Uint8Array, + // text: string, + // ListThis class contains utility methods for performing mathematical operations over + * the Galois Fields. Operations use a given primitive polynomial in calculations.
+ * + *Throughout this package, elements of the GF are represented as an {@code int} + * for convenience and speed (but at the cost of memory). + *
+ * + * @author Sean Owen + * @author David Olivier + */ + class AbstractGenericGF { + /** + * @return 2 to the power of a in GF(size) + */ + exp(a) { + return this.expTable[a]; + } + /** + * @return base 2 log of a in GF(size) + */ + log(a /*int*/) { + if (a === 0) { + throw new IllegalArgumentException(); + } + return this.logTable[a]; + } + /** + * Implements both addition and subtraction -- they are the same in GF(size). + * + * @return sum/difference of a and b + */ + static addOrSubtract(a /*int*/, b /*int*/) { + return a ^ b; + } + } + + /* + * 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. + */ + /** + *Represents a polynomial whose coefficients are elements of a GF. + * Instances of this class are immutable.
+ * + *Much credit is due to William Rucklidge since portions of this code are an indirect + * port of his C++ Reed-Solomon implementation.
+ * + * @author Sean Owen + */ + class GenericGFPoly { + /** + * @param field the {@link GenericGF} instance representing the field to use + * to perform computations + * @param coefficients coefficients as ints representing elements of GF(size), arranged + * from most significant (highest-power term) coefficient to least significant + * @throws IllegalArgumentException if argument is null or empty, + * or if leading coefficient is 0 and this is not a + * constant polynomial (that is, it is not the monomial "0") + */ + constructor(field, coefficients) { + if (coefficients.length === 0) { + throw new IllegalArgumentException(); + } + this.field = field; + const coefficientsLength = coefficients.length; + if (coefficientsLength > 1 && coefficients[0] === 0) { + // Leading term must be non-zero for anything except the constant polynomial "0" + let firstNonZero = 1; + while (firstNonZero < coefficientsLength && coefficients[firstNonZero] === 0) { + firstNonZero++; + } + if (firstNonZero === coefficientsLength) { + this.coefficients = Int32Array.from([0]); + } + else { + this.coefficients = new Int32Array(coefficientsLength - firstNonZero); + System.arraycopy(coefficients, firstNonZero, this.coefficients, 0, this.coefficients.length); + } + } + else { + this.coefficients = coefficients; + } + } + getCoefficients() { + return this.coefficients; + } + /** + * @return degree of this polynomial + */ + getDegree() { + return this.coefficients.length - 1; + } + /** + * @return true iff this polynomial is the monomial "0" + */ + isZero() { + return this.coefficients[0] === 0; + } + /** + * @return coefficient of x^degree term in this polynomial + */ + getCoefficient(degree /*int*/) { + return this.coefficients[this.coefficients.length - 1 - degree]; + } + /** + * @return evaluation of this polynomial at a given point + */ + evaluateAt(a /*int*/) { + if (a === 0) { + // Just return the x^0 coefficient + return this.getCoefficient(0); + } + const coefficients = this.coefficients; + let result; + if (a === 1) { + // Just the sum of the coefficients + result = 0; + for (let i = 0, length = coefficients.length; i !== length; i++) { + const coefficient = coefficients[i]; + result = AbstractGenericGF.addOrSubtract(result, coefficient); + } + return result; + } + result = coefficients[0]; + const size = coefficients.length; + const field = this.field; + for (let i = 1; i < size; i++) { + result = AbstractGenericGF.addOrSubtract(field.multiply(a, result), coefficients[i]); + } + return result; + } + addOrSubtract(other) { + if (!this.field.equals(other.field)) { + throw new IllegalArgumentException('GenericGFPolys do not have same GenericGF field'); + } + if (this.isZero()) { + return other; + } + if (other.isZero()) { + return this; + } + let smallerCoefficients = this.coefficients; + let largerCoefficients = other.coefficients; + if (smallerCoefficients.length > largerCoefficients.length) { + const temp = smallerCoefficients; + smallerCoefficients = largerCoefficients; + largerCoefficients = temp; + } + let sumDiff = new Int32Array(largerCoefficients.length); + const lengthDiff = largerCoefficients.length - smallerCoefficients.length; + // Copy high-order terms only found in higher-degree polynomial's coefficients + System.arraycopy(largerCoefficients, 0, sumDiff, 0, lengthDiff); + for (let i = lengthDiff; i < largerCoefficients.length; i++) { + sumDiff[i] = AbstractGenericGF.addOrSubtract(smallerCoefficients[i - lengthDiff], largerCoefficients[i]); + } + return new GenericGFPoly(this.field, sumDiff); + } + multiply(other) { + if (!this.field.equals(other.field)) { + throw new IllegalArgumentException('GenericGFPolys do not have same GenericGF field'); + } + if (this.isZero() || other.isZero()) { + return this.field.getZero(); + } + const aCoefficients = this.coefficients; + const aLength = aCoefficients.length; + const bCoefficients = other.coefficients; + const bLength = bCoefficients.length; + const product = new Int32Array(aLength + bLength - 1); + const field = this.field; + for (let i = 0; i < aLength; i++) { + const aCoeff = aCoefficients[i]; + for (let j = 0; j < bLength; j++) { + product[i + j] = AbstractGenericGF.addOrSubtract(product[i + j], field.multiply(aCoeff, bCoefficients[j])); + } + } + return new GenericGFPoly(field, product); + } + multiplyScalar(scalar /*int*/) { + if (scalar === 0) { + return this.field.getZero(); + } + if (scalar === 1) { + return this; + } + const size = this.coefficients.length; + const field = this.field; + const product = new Int32Array(size); + const coefficients = this.coefficients; + for (let i = 0; i < size; i++) { + product[i] = field.multiply(coefficients[i], scalar); + } + return new GenericGFPoly(field, product); + } + multiplyByMonomial(degree /*int*/, coefficient /*int*/) { + if (degree < 0) { + throw new IllegalArgumentException(); + } + if (coefficient === 0) { + return this.field.getZero(); + } + const coefficients = this.coefficients; + const size = coefficients.length; + const product = new Int32Array(size + degree); + const field = this.field; + for (let i = 0; i < size; i++) { + product[i] = field.multiply(coefficients[i], coefficient); + } + return new GenericGFPoly(field, product); + } + divide(other) { + if (!this.field.equals(other.field)) { + throw new IllegalArgumentException('GenericGFPolys do not have same GenericGF field'); + } + if (other.isZero()) { + throw new IllegalArgumentException('Divide by 0'); + } + const field = this.field; + let quotient = field.getZero(); + let remainder = this; + const denominatorLeadingTerm = other.getCoefficient(other.getDegree()); + const inverseDenominatorLeadingTerm = field.inverse(denominatorLeadingTerm); + while (remainder.getDegree() >= other.getDegree() && !remainder.isZero()) { + const degreeDifference = remainder.getDegree() - other.getDegree(); + const scale = field.multiply(remainder.getCoefficient(remainder.getDegree()), inverseDenominatorLeadingTerm); + const term = other.multiplyByMonomial(degreeDifference, scale); + const iterationQuotient = field.buildMonomial(degreeDifference, scale); + quotient = quotient.addOrSubtract(iterationQuotient); + remainder = remainder.addOrSubtract(term); + } + return [quotient, remainder]; + } + /*@Override*/ + toString() { + let result = ''; + for (let degree = this.getDegree(); degree >= 0; degree--) { + let coefficient = this.getCoefficient(degree); + if (coefficient !== 0) { + if (coefficient < 0) { + result += ' - '; + coefficient = -coefficient; + } + else { + if (result.length > 0) { + result += ' + '; + } + } + if (degree === 0 || coefficient !== 1) { + const alphaPower = this.field.log(coefficient); + if (alphaPower === 0) { + result += '1'; + } + else if (alphaPower === 1) { + result += 'a'; + } + else { + result += 'a^'; + result += alphaPower; + } + } + if (degree !== 0) { + if (degree === 1) { + result += 'x'; + } + else { + result += 'x^'; + result += degree; + } + } + } + } + return result; + } + } + + /** + * Custom Error class of type Exception. + */ + class ArithmeticException extends Exception { + } + ArithmeticException.kind = 'ArithmeticException'; + + /* + * 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. + */ + /** + *This class contains utility methods for performing mathematical operations over + * the Galois Fields. Operations use a given primitive polynomial in calculations.
+ * + *Throughout this package, elements of the GF are represented as an {@code int} + * for convenience and speed (but at the cost of memory). + *
+ * + * @author Sean Owen + * @author David Olivier + */ + class GenericGF extends AbstractGenericGF { + /** + * Create a representation of GF(size) using the given primitive polynomial. + * + * @param primitive irreducible polynomial whose coefficients are represented by + * the bits of an int, where the least-significant bit represents the constant + * coefficient + * @param size the size of the field + * @param b the factor b in the generator polynomial can be 0- or 1-based + * (g(x) = (x+a^b)(x+a^(b+1))...(x+a^(b+2t-1))). + * In most cases it should be 1, but for QR code it is 0. + */ + constructor(primitive /*int*/, size /*int*/, generatorBase /*int*/) { + super(); + this.primitive = primitive; + this.size = size; + this.generatorBase = generatorBase; + const expTable = new Int32Array(size); + let x = 1; + for (let i = 0; i < size; i++) { + expTable[i] = x; + x *= 2; // we're assuming the generator alpha is 2 + if (x >= size) { + x ^= primitive; + x &= size - 1; + } + } + this.expTable = expTable; + const logTable = new Int32Array(size); + for (let i = 0; i < size - 1; i++) { + logTable[expTable[i]] = i; + } + this.logTable = logTable; + // logTable[0] == 0 but this should never be used + this.zero = new GenericGFPoly(this, Int32Array.from([0])); + this.one = new GenericGFPoly(this, Int32Array.from([1])); + } + getZero() { + return this.zero; + } + getOne() { + return this.one; + } + /** + * @return the monomial representing coefficient * x^degree + */ + buildMonomial(degree /*int*/, coefficient /*int*/) { + if (degree < 0) { + throw new IllegalArgumentException(); + } + if (coefficient === 0) { + return this.zero; + } + const coefficients = new Int32Array(degree + 1); + coefficients[0] = coefficient; + return new GenericGFPoly(this, coefficients); + } + /** + * @return multiplicative inverse of a + */ + inverse(a /*int*/) { + if (a === 0) { + throw new ArithmeticException(); + } + return this.expTable[this.size - this.logTable[a] - 1]; + } + /** + * @return product of a and b in GF(size) + */ + multiply(a /*int*/, b /*int*/) { + if (a === 0 || b === 0) { + return 0; + } + return this.expTable[(this.logTable[a] + this.logTable[b]) % (this.size - 1)]; + } + getSize() { + return this.size; + } + getGeneratorBase() { + return this.generatorBase; + } + /*@Override*/ + toString() { + return ('GF(0x' + Integer.toHexString(this.primitive) + ',' + this.size + ')'); + } + equals(o) { + return o === this; + } + } + GenericGF.AZTEC_DATA_12 = new GenericGF(0x1069, 4096, 1); // x^12 + x^6 + x^5 + x^3 + 1 + GenericGF.AZTEC_DATA_10 = new GenericGF(0x409, 1024, 1); // x^10 + x^3 + 1 + GenericGF.AZTEC_DATA_6 = new GenericGF(0x43, 64, 1); // x^6 + x + 1 + GenericGF.AZTEC_PARAM = new GenericGF(0x13, 16, 1); // x^4 + x + 1 + GenericGF.QR_CODE_FIELD_256 = new GenericGF(0x011d, 256, 0); // x^8 + x^4 + x^3 + x^2 + 1 + GenericGF.DATA_MATRIX_FIELD_256 = new GenericGF(0x012d, 256, 1); // x^8 + x^5 + x^3 + x^2 + 1 + GenericGF.AZTEC_DATA_8 = GenericGF.DATA_MATRIX_FIELD_256; + GenericGF.MAXICODE_FIELD_64 = GenericGF.AZTEC_DATA_6; + + /** + * Custom Error class of type Exception. + */ + class ReedSolomonException extends Exception { + } + ReedSolomonException.kind = 'ReedSolomonException'; + + /** + * Custom Error class of type Exception. + */ + class IllegalStateException extends Exception { + } + IllegalStateException.kind = 'IllegalStateException'; + + /* + * 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. + */ + /** + *Implements Reed-Solomon decoding, as the name implies.
+ * + *The algorithm will not be explained here, but the following references were helpful + * in creating this implementation:
+ * + *Much credit is due to William Rucklidge since portions of this code are an indirect + * port of his C++ Reed-Solomon implementation.
+ * + * @author Sean Owen + * @author William Rucklidge + * @author sanfordsquires + */ + class ReedSolomonDecoder { + constructor(field) { + this.field = field; + } + /** + *Decodes given set of received codewords, which include both data and error-correction + * codewords. Really, this means it uses Reed-Solomon to detect and correct errors, in-place, + * in the input.
+ * + * @param received data and error-correction codewords + * @param twoS number of error-correction codewords available + * @throws ReedSolomonException if decoding fails for any reason + */ + decode(received, twoS /*int*/) { + const field = this.field; + const poly = new GenericGFPoly(field, received); + const syndromeCoefficients = new Int32Array(twoS); + let noError = true; + for (let i = 0; i < twoS; i++) { + const evalResult = poly.evaluateAt(field.exp(i + field.getGeneratorBase())); + syndromeCoefficients[syndromeCoefficients.length - 1 - i] = evalResult; + if (evalResult !== 0) { + noError = false; + } + } + if (noError) { + return; + } + const syndrome = new GenericGFPoly(field, syndromeCoefficients); + const sigmaOmega = this.runEuclideanAlgorithm(field.buildMonomial(twoS, 1), syndrome, twoS); + const sigma = sigmaOmega[0]; + const omega = sigmaOmega[1]; + const errorLocations = this.findErrorLocations(sigma); + const errorMagnitudes = this.findErrorMagnitudes(omega, errorLocations); + for (let i = 0; i < errorLocations.length; i++) { + const position = received.length - 1 - field.log(errorLocations[i]); + if (position < 0) { + throw new ReedSolomonException('Bad error location'); + } + received[position] = GenericGF.addOrSubtract(received[position], errorMagnitudes[i]); + } + } + runEuclideanAlgorithm(a, b, R /*int*/) { + // Assume a's degree is >= b's + if (a.getDegree() < b.getDegree()) { + const temp = a; + a = b; + b = temp; + } + const field = this.field; + let rLast = a; + let r = b; + let tLast = field.getZero(); + let t = field.getOne(); + // Run Euclidean algorithm until r's degree is less than R/2 + while (r.getDegree() >= (R / 2 | 0)) { + let rLastLast = rLast; + let tLastLast = tLast; + rLast = r; + tLast = t; + // Divide rLastLast by rLast, with quotient in q and remainder in r + if (rLast.isZero()) { + // Oops, Euclidean algorithm already terminated? + throw new ReedSolomonException('r_{i-1} was zero'); + } + r = rLastLast; + let q = field.getZero(); + const denominatorLeadingTerm = rLast.getCoefficient(rLast.getDegree()); + const dltInverse = field.inverse(denominatorLeadingTerm); + while (r.getDegree() >= rLast.getDegree() && !r.isZero()) { + const degreeDiff = r.getDegree() - rLast.getDegree(); + const scale = field.multiply(r.getCoefficient(r.getDegree()), dltInverse); + q = q.addOrSubtract(field.buildMonomial(degreeDiff, scale)); + r = r.addOrSubtract(rLast.multiplyByMonomial(degreeDiff, scale)); + } + t = q.multiply(tLast).addOrSubtract(tLastLast); + if (r.getDegree() >= rLast.getDegree()) { + throw new IllegalStateException('Division algorithm failed to reduce polynomial?'); + } + } + const sigmaTildeAtZero = t.getCoefficient(0); + if (sigmaTildeAtZero === 0) { + throw new ReedSolomonException('sigmaTilde(0) was zero'); + } + const inverse = field.inverse(sigmaTildeAtZero); + const sigma = t.multiplyScalar(inverse); + const omega = r.multiplyScalar(inverse); + return [sigma, omega]; + } + findErrorLocations(errorLocator) { + // This is a direct application of Chien's search + const numErrors = errorLocator.getDegree(); + if (numErrors === 1) { // shortcut + return Int32Array.from([errorLocator.getCoefficient(1)]); + } + const result = new Int32Array(numErrors); + let e = 0; + const field = this.field; + for (let i = 1; i < field.getSize() && e < numErrors; i++) { + if (errorLocator.evaluateAt(i) === 0) { + result[e] = field.inverse(i); + e++; + } + } + if (e !== numErrors) { + throw new ReedSolomonException('Error locator degree does not match number of roots'); + } + return result; + } + findErrorMagnitudes(errorEvaluator, errorLocations) { + // This is directly applying Forney's Formula + const s = errorLocations.length; + const result = new Int32Array(s); + const field = this.field; + for (let i = 0; i < s; i++) { + const xiInverse = field.inverse(errorLocations[i]); + let denominator = 1; + for (let j = 0; j < s; j++) { + if (i !== j) { + // denominator = field.multiply(denominator, + // GenericGF.addOrSubtract(1, field.multiply(errorLocations[j], xiInverse))) + // Above should work but fails on some Apple and Linux JDKs due to a Hotspot bug. + // Below is a funny-looking workaround from Steven Parkes + const term = field.multiply(errorLocations[j], xiInverse); + const termPlus1 = (term & 0x1) === 0 ? term | 1 : term & ~1; + denominator = field.multiply(denominator, termPlus1); + } + } + result[i] = field.multiply(errorEvaluator.evaluateAt(xiInverse), field.inverse(denominator)); + if (field.getGeneratorBase() !== 0) { + result[i] = field.multiply(result[i], xiInverse); + } + } + return result; + } + } + + /* + * Copyright 2010 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. + */ + // import java.util.Arrays; + var Table; + (function (Table) { + Table[Table["UPPER"] = 0] = "UPPER"; + Table[Table["LOWER"] = 1] = "LOWER"; + Table[Table["MIXED"] = 2] = "MIXED"; + Table[Table["DIGIT"] = 3] = "DIGIT"; + Table[Table["PUNCT"] = 4] = "PUNCT"; + Table[Table["BINARY"] = 5] = "BINARY"; + })(Table || (Table = {})); + /** + *The main class which implements Aztec Code decoding -- as opposed to locating and extracting + * the Aztec Code from an image.
+ * + * @author David Olivier + */ + class Decoder { + decode(detectorResult) { + this.ddata = detectorResult; + let matrix = detectorResult.getBits(); + let rawbits = this.extractBits(matrix); + let correctedBits = this.correctBits(rawbits); + let rawBytes = Decoder.convertBoolArrayToByteArray(correctedBits); + let result = Decoder.getEncodedData(correctedBits); + let decoderResult = new DecoderResult(rawBytes, result, null, null); + decoderResult.setNumBits(correctedBits.length); + return decoderResult; + } + // This method is used for testing the high-level encoder + static highLevelDecode(correctedBits) { + return this.getEncodedData(correctedBits); + } + /** + * Gets the string encoded in the aztec code bits + * + * @return the decoded string + */ + static getEncodedData(correctedBits) { + let endIndex = correctedBits.length; + let latchTable = Table.UPPER; // table most recently latched to + let shiftTable = Table.UPPER; // table to use for the next read + let result = ''; + let index = 0; + while (index < endIndex) { + if (shiftTable === Table.BINARY) { + if (endIndex - index < 5) { + break; + } + let length = Decoder.readCode(correctedBits, index, 5); + index += 5; + if (length === 0) { + if (endIndex - index < 11) { + break; + } + length = Decoder.readCode(correctedBits, index, 11) + 31; + index += 11; + } + for (let charCount = 0; charCount < length; charCount++) { + if (endIndex - index < 8) { + index = endIndex; // Force outer loop to exit + break; + } + const code = Decoder.readCode(correctedBits, index, 8); + result += /*(char)*/ StringUtils.castAsNonUtf8Char(code); + index += 8; + } + // Go back to whatever mode we had been in + shiftTable = latchTable; + } + else { + let size = shiftTable === Table.DIGIT ? 4 : 5; + if (endIndex - index < size) { + break; + } + let code = Decoder.readCode(correctedBits, index, size); + index += size; + let str = Decoder.getCharacter(shiftTable, code); + if (str.startsWith('CTRL_')) { + // Table changes + // ISO/IEC 24778:2008 prescribes ending a shift sequence in the mode from which it was invoked. + // That's including when that mode is a shift. + // Our test case dlusbs.png for issue #642 exercises that. + latchTable = shiftTable; // Latch the current mode, so as to return to Upper after U/S B/S + shiftTable = Decoder.getTable(str.charAt(5)); + if (str.charAt(6) === 'L') { + latchTable = shiftTable; + } + } + else { + result += str; + // Go back to whatever mode we had been in + shiftTable = latchTable; + } + } + } + return result; + } + /** + * gets the table corresponding to the char passed + */ + static getTable(t) { + switch (t) { + case 'L': + return Table.LOWER; + case 'P': + return Table.PUNCT; + case 'M': + return Table.MIXED; + case 'D': + return Table.DIGIT; + case 'B': + return Table.BINARY; + case 'U': + default: + return Table.UPPER; + } + } + /** + * Gets the character (or string) corresponding to the passed code in the given table + * + * @param table the table used + * @param code the code of the character + */ + static getCharacter(table, code) { + switch (table) { + case Table.UPPER: + return Decoder.UPPER_TABLE[code]; + case Table.LOWER: + return Decoder.LOWER_TABLE[code]; + case Table.MIXED: + return Decoder.MIXED_TABLE[code]; + case Table.PUNCT: + return Decoder.PUNCT_TABLE[code]; + case Table.DIGIT: + return Decoder.DIGIT_TABLE[code]; + default: + // Should not reach here. + throw new IllegalStateException('Bad table'); + } + } + /** + *Performs RS error correction on an array of bits.
+ * + * @return the corrected array + * @throws FormatException if the input contains too many errors + */ + correctBits(rawbits) { + let gf; + let codewordSize; + if (this.ddata.getNbLayers() <= 2) { + codewordSize = 6; + gf = GenericGF.AZTEC_DATA_6; + } + else if (this.ddata.getNbLayers() <= 8) { + codewordSize = 8; + gf = GenericGF.AZTEC_DATA_8; + } + else if (this.ddata.getNbLayers() <= 22) { + codewordSize = 10; + gf = GenericGF.AZTEC_DATA_10; + } + else { + codewordSize = 12; + gf = GenericGF.AZTEC_DATA_12; + } + let numDataCodewords = this.ddata.getNbDatablocks(); + let numCodewords = rawbits.length / codewordSize; + if (numCodewords < numDataCodewords) { + throw new FormatException(); + } + let offset = rawbits.length % codewordSize; + let dataWords = new Int32Array(numCodewords); + for (let i = 0; i < numCodewords; i++, offset += codewordSize) { + dataWords[i] = Decoder.readCode(rawbits, offset, codewordSize); + } + try { + let rsDecoder = new ReedSolomonDecoder(gf); + rsDecoder.decode(dataWords, numCodewords - numDataCodewords); + } + catch (ex) { + throw new FormatException(ex); + } + // Now perform the unstuffing operation. + // First, count how many bits are going to be thrown out as stuffing + let mask = (1 << codewordSize) - 1; + let stuffedBits = 0; + for (let i = 0; i < numDataCodewords; i++) { + let dataWord = dataWords[i]; + if (dataWord === 0 || dataWord === mask) { + throw new FormatException(); + } + else if (dataWord === 1 || dataWord === mask - 1) { + stuffedBits++; + } + } + // Now, actually unpack the bits and remove the stuffing + let correctedBits = new Array(numDataCodewords * codewordSize - stuffedBits); + let index = 0; + for (let i = 0; i < numDataCodewords; i++) { + let dataWord = dataWords[i]; + if (dataWord === 1 || dataWord === mask - 1) { + // next codewordSize-1 bits are all zeros or all ones + correctedBits.fill(dataWord > 1, index, index + codewordSize - 1); + // Arrays.fill(correctedBits, index, index + codewordSize - 1, dataWord > 1); + index += codewordSize - 1; + } + else { + for (let bit = codewordSize - 1; bit >= 0; --bit) { + correctedBits[index++] = (dataWord & (1 << bit)) !== 0; + } + } + } + return correctedBits; + } + /** + * Gets the array of bits from an Aztec Code matrix + * + * @return the array of bits + */ + extractBits(matrix) { + let compact = this.ddata.isCompact(); + let layers = this.ddata.getNbLayers(); + let baseMatrixSize = (compact ? 11 : 14) + layers * 4; // not including alignment lines + let alignmentMap = new Int32Array(baseMatrixSize); + let rawbits = new Array(this.totalBitsInLayer(layers, compact)); + if (compact) { + for (let i = 0; i < alignmentMap.length; i++) { + alignmentMap[i] = i; + } + } + else { + let matrixSize = baseMatrixSize + 1 + 2 * Integer.truncDivision((Integer.truncDivision(baseMatrixSize, 2) - 1), 15); + let origCenter = baseMatrixSize / 2; + let center = Integer.truncDivision(matrixSize, 2); + for (let i = 0; i < origCenter; i++) { + let newOffset = i + Integer.truncDivision(i, 15); + alignmentMap[origCenter - i - 1] = center - newOffset - 1; + alignmentMap[origCenter + i] = center + newOffset + 1; + } + } + for (let i = 0, rowOffset = 0; i < layers; i++) { + let rowSize = (layers - i) * 4 + (compact ? 9 : 12); + // The top-left most point of this layer isEncapsulates a point of interest in an image containing a barcode. Typically, this + * would be the location of a finder pattern or the corner of the barcode, for example.
+ * + * @author Sean Owen + */ + class ResultPoint { + constructor(x, y) { + this.x = x; + this.y = y; + } + getX() { + return this.x; + } + getY() { + return this.y; + } + /*@Override*/ + equals(other) { + if (other instanceof ResultPoint) { + const otherPoint = other; + return this.x === otherPoint.x && this.y === otherPoint.y; + } + return false; + } + /*@Override*/ + hashCode() { + return 31 * Float.floatToIntBits(this.x) + Float.floatToIntBits(this.y); + } + /*@Override*/ + toString() { + return '(' + this.x + ',' + this.y + ')'; + } + /** + * Orders an array of three ResultPoints in an order [A,B,C] such that AB is less than AC + * and BC is less than AC, and the angle between BC and BA is less than 180 degrees. + * + * @param patterns array of three {@code ResultPoint} to order + */ + static orderBestPatterns(patterns) { + // Find distances between pattern centers + const zeroOneDistance = this.distance(patterns[0], patterns[1]); + const oneTwoDistance = this.distance(patterns[1], patterns[2]); + const zeroTwoDistance = this.distance(patterns[0], patterns[2]); + let pointA; + let pointB; + let pointC; + // Assume one closest to other two is B; A and C will just be guesses at first + 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]; + } + // Use cross product to figure out whether A and C are correct or flipped. + // This asks whether BC x BA has a positive z component, which is the arrangement + // we want for A, B, C. If it's negative, then we've got it flipped around and + // should swap A and C. + if (this.crossProductZ(pointA, pointB, pointC) < 0.0) { + const temp = pointA; + pointA = pointC; + pointC = temp; + } + patterns[0] = pointA; + patterns[1] = pointB; + patterns[2] = pointC; + } + /** + * @param pattern1 first pattern + * @param pattern2 second pattern + * @return distance between two points + */ + static distance(pattern1, pattern2) { + return MathUtils.distance(pattern1.x, pattern1.y, pattern2.x, pattern2.y); + } + /** + * Returns the z component of the cross product between vectors BC and BA. + */ + static crossProductZ(pointA, pointB, pointC) { + const bX = pointB.x; + const bY = pointB.y; + return ((pointC.x - bX) * (pointA.y - bY)) - ((pointC.y - bY) * (pointA.x - bX)); + } + } + + /* + * 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. + */ + /** + *Encapsulates the result of detecting a barcode in an image. This includes the raw + * matrix of black/white pixels corresponding to the barcode, and possibly points of interest + * in the image, like the location of finder patterns or corners of the barcode in the image.
+ * + * @author Sean Owen + */ + class DetectorResult { + constructor(bits, points) { + this.bits = bits; + this.points = points; + } + getBits() { + return this.bits; + } + getPoints() { + return this.points; + } + } + + /* + * Copyright 2010 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. + */ + /** + *Extends {@link DetectorResult} with more information specific to the Aztec format, + * like the number of layers and whether it's compact.
+ * + * @author Sean Owen + */ + class AztecDetectorResult extends DetectorResult { + constructor(bits, points, compact, nbDatablocks, nbLayers) { + super(bits, points); + this.compact = compact; + this.nbDatablocks = nbDatablocks; + this.nbLayers = nbLayers; + } + getNbLayers() { + return this.nbLayers; + } + getNbDatablocks() { + return this.nbDatablocks; + } + isCompact() { + return this.compact; + } + } + + /* + * Copyright 2010 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. + */ + /** + *+ * Detects a candidate barcode-like rectangular region within an image. It + * starts around the center of the image, increases the size of the candidate + * region until it finds a white rectangular region. By keeping track of the + * last black points it encountered, it determines the corners of the barcode. + *
+ * + * @author David Olivier + */ + class WhiteRectangleDetector { + // public constructor(private image: BitMatrix) /*throws NotFoundException*/ { + // this(image, INIT_SIZE, image.getWidth() / 2, image.getHeight() / 2) + // } + /** + * @param image barcode image to find a rectangle in + * @param initSize initial size of search area around center + * @param x x position of search center + * @param y y position of search center + * @throws NotFoundException if image is too small to accommodate {@code initSize} + */ + constructor(image, initSize /*int*/, x /*int*/, y /*int*/) { + this.image = image; + this.height = image.getHeight(); + this.width = image.getWidth(); + if (undefined === initSize || null === initSize) { + initSize = WhiteRectangleDetector.INIT_SIZE; + } + if (undefined === x || null === x) { + x = image.getWidth() / 2 | 0; + } + if (undefined === y || null === y) { + y = image.getHeight() / 2 | 0; + } + const halfsize = initSize / 2 | 0; + this.leftInit = x - halfsize; + this.rightInit = x + halfsize; + this.upInit = y - halfsize; + this.downInit = y + halfsize; + if (this.upInit < 0 || this.leftInit < 0 || this.downInit >= this.height || this.rightInit >= this.width) { + throw new NotFoundException(); + } + } + /** + *+ * Detects a candidate barcode-like rectangular region within an image. It + * starts around the center of the image, increases the size of the candidate + * region until it finds a white rectangular region. + *
+ * + * @return {@link ResultPoint}[] describing the corners of the rectangular + * region. The first and last points are opposed on the diagonal, as + * are the second and third. The first point will be the topmost + * point and the last, the bottommost. The second point will be + * leftmost and the third, the rightmost + * @throws NotFoundException if no Data Matrix Code can be found + */ + detect() { + let left = this.leftInit; + let right = this.rightInit; + let up = this.upInit; + let down = this.downInit; + let sizeExceeded = false; + let aBlackPointFoundOnBorder = true; + let atLeastOneBlackPointFoundOnBorder = false; + let atLeastOneBlackPointFoundOnRight = false; + let atLeastOneBlackPointFoundOnBottom = false; + let atLeastOneBlackPointFoundOnLeft = false; + let atLeastOneBlackPointFoundOnTop = false; + const width = this.width; + const height = this.height; + while (aBlackPointFoundOnBorder) { + aBlackPointFoundOnBorder = false; + // ..... + // . | + // ..... + let rightBorderNotWhite = true; + while ((rightBorderNotWhite || !atLeastOneBlackPointFoundOnRight) && right < width) { + rightBorderNotWhite = this.containsBlackPoint(up, down, right, false); + if (rightBorderNotWhite) { + right++; + aBlackPointFoundOnBorder = true; + atLeastOneBlackPointFoundOnRight = true; + } + else if (!atLeastOneBlackPointFoundOnRight) { + right++; + } + } + if (right >= width) { + sizeExceeded = true; + break; + } + // ..... + // . . + // .___. + let bottomBorderNotWhite = true; + while ((bottomBorderNotWhite || !atLeastOneBlackPointFoundOnBottom) && down < height) { + bottomBorderNotWhite = this.containsBlackPoint(left, right, down, true); + if (bottomBorderNotWhite) { + down++; + aBlackPointFoundOnBorder = true; + atLeastOneBlackPointFoundOnBottom = true; + } + else if (!atLeastOneBlackPointFoundOnBottom) { + down++; + } + } + if (down >= height) { + sizeExceeded = true; + break; + } + // ..... + // | . + // ..... + let leftBorderNotWhite = true; + while ((leftBorderNotWhite || !atLeastOneBlackPointFoundOnLeft) && left >= 0) { + leftBorderNotWhite = this.containsBlackPoint(up, down, left, false); + if (leftBorderNotWhite) { + left--; + aBlackPointFoundOnBorder = true; + atLeastOneBlackPointFoundOnLeft = true; + } + else if (!atLeastOneBlackPointFoundOnLeft) { + left--; + } + } + if (left < 0) { + sizeExceeded = true; + break; + } + // .___. + // . . + // ..... + let topBorderNotWhite = true; + while ((topBorderNotWhite || !atLeastOneBlackPointFoundOnTop) && up >= 0) { + topBorderNotWhite = this.containsBlackPoint(left, right, up, true); + if (topBorderNotWhite) { + up--; + aBlackPointFoundOnBorder = true; + atLeastOneBlackPointFoundOnTop = true; + } + else if (!atLeastOneBlackPointFoundOnTop) { + up--; + } + } + if (up < 0) { + sizeExceeded = true; + break; + } + if (aBlackPointFoundOnBorder) { + atLeastOneBlackPointFoundOnBorder = true; + } + } + if (!sizeExceeded && atLeastOneBlackPointFoundOnBorder) { + const maxSize = right - left; + let z = null; + for (let i = 1; z === null && i < maxSize; i++) { + z = this.getBlackPointOnSegment(left, down - i, left + i, down); + } + if (z == null) { + throw new NotFoundException(); + } + let t = null; + // go down right + for (let i = 1; t === null && i < maxSize; i++) { + t = this.getBlackPointOnSegment(left, up + i, left + i, up); + } + if (t == null) { + throw new NotFoundException(); + } + let x = null; + // go down left + for (let i = 1; x === null && i < maxSize; i++) { + x = this.getBlackPointOnSegment(right, up + i, right - i, up); + } + if (x == null) { + throw new NotFoundException(); + } + let y = null; + // go up left + for (let i = 1; y === null && i < maxSize; i++) { + y = this.getBlackPointOnSegment(right, down - i, right - i, down); + } + if (y == null) { + throw new NotFoundException(); + } + return this.centerEdges(y, z, x, t); + } + else { + throw new NotFoundException(); + } + } + getBlackPointOnSegment(aX /*float*/, aY /*float*/, bX /*float*/, bY /*float*/) { + const dist = MathUtils.round(MathUtils.distance(aX, aY, bX, bY)); + const xStep = (bX - aX) / dist; + const yStep = (bY - aY) / dist; + const image = this.image; + for (let i = 0; i < dist; i++) { + const x = MathUtils.round(aX + i * xStep); + const y = MathUtils.round(aY + i * yStep); + if (image.get(x, y)) { + return new ResultPoint(x, y); + } + } + return null; + } + /** + * recenters the points of a constant distance towards the center + * + * @param y bottom most point + * @param z left most point + * @param x right most point + * @param t top most point + * @return {@link ResultPoint}[] describing the corners of the rectangular + * region. The first and last points are opposed on the diagonal, as + * are the second and third. The first point will be the topmost + * point and the last, the bottommost. The second point will be + * leftmost and the third, the rightmost + */ + centerEdges(y, z, x, t) { + // + // t t + // z x + // x OR z + // y y + // + const yi = y.getX(); + const yj = y.getY(); + const zi = z.getX(); + const zj = z.getY(); + const xi = x.getX(); + const xj = x.getY(); + const ti = t.getX(); + const tj = t.getY(); + const CORR = WhiteRectangleDetector.CORR; + if (yi < this.width / 2.0) { + return [ + new ResultPoint(ti - CORR, tj + CORR), + new ResultPoint(zi + CORR, zj + CORR), + new ResultPoint(xi - CORR, xj - CORR), + new ResultPoint(yi + CORR, yj - CORR) + ]; + } + else { + return [ + new ResultPoint(ti + CORR, tj + CORR), + new ResultPoint(zi + CORR, zj - CORR), + new ResultPoint(xi - CORR, xj + CORR), + new ResultPoint(yi - CORR, yj - CORR) + ]; + } + } + /** + * Determines whether a segment contains a black point + * + * @param a min value of the scanned coordinate + * @param b max value of the scanned coordinate + * @param fixed value of fixed coordinate + * @param horizontal set to true if scan must be horizontal, false if vertical + * @return true if a black point has been found, else false. + */ + containsBlackPoint(a /*int*/, b /*int*/, fixed /*int*/, horizontal) { + const image = this.image; + if (horizontal) { + for (let x = a; x <= b; x++) { + if (image.get(x, fixed)) { + return true; + } + } + } + else { + for (let y = a; y <= b; y++) { + if (image.get(fixed, y)) { + return true; + } + } + } + return false; + } + } + WhiteRectangleDetector.INIT_SIZE = 10; + WhiteRectangleDetector.CORR = 1; + + /* + * 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. + */ + /** + * Implementations of this class can, given locations of finder patterns for a QR code in an + * image, sample the right points in the image to reconstruct the QR code, accounting for + * perspective distortion. It is abstracted since it is relatively expensive and should be allowed + * to take advantage of platform-specific optimized implementations, like Sun's Java Advanced + * Imaging library, but which may not be available in other environments such as J2ME, and vice + * versa. + * + * The implementation used can be controlled by calling {@link #setGridSampler(GridSampler)} + * with an instance of a class which implements this interface. + * + * @author Sean Owen + */ + class GridSampler { + /** + *Checks a set of points that have been transformed to sample points on an image against + * the image's dimensions to see if the point are even within the image.
+ * + *This method will actually "nudge" the endpoints back onto the image if they are found to be + * barely (less than 1 pixel) off the image. This accounts for imperfect detection of finder + * patterns in an image where the QR Code runs all the way to the image border.
+ * + *For efficiency, the method will check points from either end of the line until one is found + * to be within the image. Because the set of points are assumed to be linear, this is valid.
+ * + * @param image image into which the points should map + * @param points actual points in x1,y1,...,xn,yn form + * @throws NotFoundException if an endpoint is lies outside the image boundaries + */ + static checkAndNudgePoints(image, points) { + const width = image.getWidth(); + const height = image.getHeight(); + // Check and nudge points from start until we see some that are OK: + let nudged = true; + for (let offset = 0; offset < points.length && nudged; offset += 2) { + const x = Math.floor(points[offset]); + const y = Math.floor(points[offset + 1]); + if (x < -1 || x > width || y < -1 || y > height) { + throw new NotFoundException(); + } + nudged = false; + if (x === -1) { + points[offset] = 0.0; + nudged = true; + } + else if (x === width) { + points[offset] = width - 1; + nudged = true; + } + if (y === -1) { + points[offset + 1] = 0.0; + nudged = true; + } + else if (y === height) { + points[offset + 1] = height - 1; + nudged = true; + } + } + // Check and nudge points from end: + nudged = true; + for (let offset = points.length - 2; offset >= 0 && nudged; offset -= 2) { + const x = Math.floor(points[offset]); + const y = Math.floor(points[offset + 1]); + if (x < -1 || x > width || y < -1 || y > height) { + throw new NotFoundException(); + } + nudged = false; + if (x === -1) { + points[offset] = 0.0; + nudged = true; + } + else if (x === width) { + points[offset] = width - 1; + nudged = true; + } + if (y === -1) { + points[offset + 1] = 0.0; + nudged = true; + } + else if (y === height) { + points[offset + 1] = height - 1; + nudged = true; + } + } + } + } + + /* + * 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. + */ + /*namespace com.google.zxing.common {*/ + /** + *This class implements a perspective transform in two dimensions. Given four source and four + * destination points, it will compute the transformation implied between them. The code is based + * directly upon section 3.4.2 of George Wolberg's "Digital Image Warping"; see pages 54-56.
+ * + * @author Sean Owen + */ + class PerspectiveTransform { + constructor(a11 /*float*/, a21 /*float*/, a31 /*float*/, a12 /*float*/, a22 /*float*/, a32 /*float*/, a13 /*float*/, a23 /*float*/, a33 /*float*/) { + this.a11 = a11; + this.a21 = a21; + this.a31 = a31; + this.a12 = a12; + this.a22 = a22; + this.a32 = a32; + this.a13 = a13; + this.a23 = a23; + this.a33 = a33; + } + static quadrilateralToQuadrilateral(x0 /*float*/, y0 /*float*/, x1 /*float*/, y1 /*float*/, x2 /*float*/, y2 /*float*/, x3 /*float*/, y3 /*float*/, x0p /*float*/, y0p /*float*/, x1p /*float*/, y1p /*float*/, x2p /*float*/, y2p /*float*/, x3p /*float*/, y3p /*float*/) { + const qToS = PerspectiveTransform.quadrilateralToSquare(x0, y0, x1, y1, x2, y2, x3, y3); + const sToQ = PerspectiveTransform.squareToQuadrilateral(x0p, y0p, x1p, y1p, x2p, y2p, x3p, y3p); + return sToQ.times(qToS); + } + transformPoints(points) { + const max = points.length; + const a11 = this.a11; + const a12 = this.a12; + const a13 = this.a13; + const a21 = this.a21; + const a22 = this.a22; + const a23 = this.a23; + const a31 = this.a31; + const a32 = this.a32; + const a33 = this.a33; + for (let i = 0; i < max; i += 2) { + const x = points[i]; + const y = points[i + 1]; + const denominator = a13 * x + a23 * y + a33; + points[i] = (a11 * x + a21 * y + a31) / denominator; + points[i + 1] = (a12 * x + a22 * y + a32) / denominator; + } + } + transformPointsWithValues(xValues, yValues) { + const a11 = this.a11; + const a12 = this.a12; + const a13 = this.a13; + const a21 = this.a21; + const a22 = this.a22; + const a23 = this.a23; + const a31 = this.a31; + const a32 = this.a32; + const a33 = this.a33; + const n = xValues.length; + for (let i = 0; i < n; i++) { + const x = xValues[i]; + const y = yValues[i]; + const denominator = a13 * x + a23 * y + a33; + xValues[i] = (a11 * x + a21 * y + a31) / denominator; + yValues[i] = (a12 * x + a22 * y + a32) / denominator; + } + } + static squareToQuadrilateral(x0 /*float*/, y0 /*float*/, x1 /*float*/, y1 /*float*/, x2 /*float*/, y2 /*float*/, x3 /*float*/, y3 /*float*/) { + const dx3 = x0 - x1 + x2 - x3; + const dy3 = y0 - y1 + y2 - y3; + if (dx3 === 0.0 && dy3 === 0.0) { + // Affine + return new PerspectiveTransform(x1 - x0, x2 - x1, x0, y1 - y0, y2 - y1, y0, 0.0, 0.0, 1.0); + } + else { + const dx1 = x1 - x2; + const dx2 = x3 - x2; + const dy1 = y1 - y2; + const dy2 = y3 - y2; + const denominator = dx1 * dy2 - dx2 * dy1; + const a13 = (dx3 * dy2 - dx2 * dy3) / denominator; + const 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.0); + } + } + static quadrilateralToSquare(x0 /*float*/, y0 /*float*/, x1 /*float*/, y1 /*float*/, x2 /*float*/, y2 /*float*/, x3 /*float*/, y3 /*float*/) { + // Here, the adjoint serves as the inverse: + return PerspectiveTransform.squareToQuadrilateral(x0, y0, x1, y1, x2, y2, x3, y3).buildAdjoint(); + } + buildAdjoint() { + // Adjoint is the transpose of the cofactor matrix: + 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); + } + times(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); + } + } + + /* + * 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. + */ + /** + * @author Sean Owen + */ + class DefaultGridSampler extends GridSampler { + /*@Override*/ + sampleGrid(image, dimensionX /*int*/, dimensionY /*int*/, p1ToX /*float*/, p1ToY /*float*/, p2ToX /*float*/, p2ToY /*float*/, p3ToX /*float*/, p3ToY /*float*/, p4ToX /*float*/, p4ToY /*float*/, p1FromX /*float*/, p1FromY /*float*/, p2FromX /*float*/, p2FromY /*float*/, p3FromX /*float*/, p3FromY /*float*/, p4FromX /*float*/, p4FromY /*float*/) { + const transform = PerspectiveTransform.quadrilateralToQuadrilateral(p1ToX, p1ToY, p2ToX, p2ToY, p3ToX, p3ToY, p4ToX, p4ToY, p1FromX, p1FromY, p2FromX, p2FromY, p3FromX, p3FromY, p4FromX, p4FromY); + return this.sampleGridWithTransform(image, dimensionX, dimensionY, transform); + } + /*@Override*/ + sampleGridWithTransform(image, dimensionX /*int*/, dimensionY /*int*/, transform) { + if (dimensionX <= 0 || dimensionY <= 0) { + throw new NotFoundException(); + } + const bits = new BitMatrix(dimensionX, dimensionY); + const points = new Float32Array(2 * dimensionX); + for (let y = 0; y < dimensionY; y++) { + const max = points.length; + const iValue = y + 0.5; + for (let x = 0; x < max; x += 2) { + points[x] = (x / 2) + 0.5; + points[x + 1] = iValue; + } + transform.transformPoints(points); + // Quick check to see if points transformed to something inside the image + // sufficient to check the endpoints + GridSampler.checkAndNudgePoints(image, points); + try { + for (let x = 0; x < max; x += 2) { + if (image.get(Math.floor(points[x]), Math.floor(points[x + 1]))) { + // Black(-ish) pixel + bits.set(x / 2, y); + } + } + } + catch (aioobe /*: ArrayIndexOutOfBoundsException*/) { + // This feels wrong, but, sometimes if the finder patterns are misidentified, the resulting + // transform gets "twisted" such that it maps a straight line of points to a set of points + // whose endpoints are in bounds, but others are not. There is probably some mathematical + // way to detect this about the transformation that I don't know yet. + // This results in an ugly runtime exception despite our clever checks above -- can't have + // that. We could check each point's coordinates but that feels duplicative. We settle for + // catching and wrapping ArrayIndexOutOfBoundsException. + throw new NotFoundException(); + } + } + return bits; + } + } + + class GridSamplerInstance { + /** + * Sets the implementation of GridSampler used by the library. One global + * instance is stored, which may sound problematic. But, the implementation provided + * ought to be appropriate for the entire platform, and all uses of this library + * in the whole lifetime of the JVM. For instance, an Android activity can swap in + * an implementation that takes advantage of native platform libraries. + * + * @param newGridSampler The platform-specific object to install. + */ + static setGridSampler(newGridSampler) { + GridSamplerInstance.gridSampler = newGridSampler; + } + /** + * @return the current implementation of GridSampler + */ + static getInstance() { + return GridSamplerInstance.gridSampler; + } + } + GridSamplerInstance.gridSampler = new DefaultGridSampler(); + + /* + * Copyright 2010 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. + */ + class Point { + constructor(x, y) { + this.x = x; + this.y = y; + } + toResultPoint() { + return new ResultPoint(this.getX(), this.getY()); + } + getX() { + return this.x; + } + getY() { + return this.y; + } + } + /** + * Encapsulates logic that can detect an Aztec Code in an image, even if the Aztec Code + * is rotated or skewed, or partially obscured. + * + * @author David Olivier + * @author Frank Yellin + */ + class Detector { + constructor(image) { + this.EXPECTED_CORNER_BITS = new Int32Array([ + 0xee0, + 0x1dc, + 0x83b, + 0x707, + ]); + this.image = image; + } + detect() { + return this.detectMirror(false); + } + /** + * Detects an Aztec Code in an image. + * + * @param isMirror if true, image is a mirror-image of original + * @return {@link AztecDetectorResult} encapsulating results of detecting an Aztec Code + * @throws NotFoundException if no Aztec Code can be found + */ + detectMirror(isMirror) { + // 1. Get the center of the aztec matrix + let pCenter = this.getMatrixCenter(); + // 2. Get the center points of the four diagonal points just outside the bull's eye + // [topRight, bottomRight, bottomLeft, topLeft] + let bullsEyeCorners = this.getBullsEyeCorners(pCenter); + if (isMirror) { + let temp = bullsEyeCorners[0]; + bullsEyeCorners[0] = bullsEyeCorners[2]; + bullsEyeCorners[2] = temp; + } + // 3. Get the size of the matrix and other parameters from the bull's eye + this.extractParameters(bullsEyeCorners); + // 4. Sample the grid + let bits = this.sampleGrid(this.image, bullsEyeCorners[this.shift % 4], bullsEyeCorners[(this.shift + 1) % 4], bullsEyeCorners[(this.shift + 2) % 4], bullsEyeCorners[(this.shift + 3) % 4]); + // 5. Get the corners of the matrix. + let corners = this.getMatrixCornerPoints(bullsEyeCorners); + return new AztecDetectorResult(bits, corners, this.compact, this.nbDataBlocks, this.nbLayers); + } + /** + * Extracts the number of data layers and data blocks from the layer around the bull's eye. + * + * @param bullsEyeCorners the array of bull's eye corners + * @throws NotFoundException in case of too many errors or invalid parameters + */ + extractParameters(bullsEyeCorners) { + if (!this.isValidPoint(bullsEyeCorners[0]) || !this.isValidPoint(bullsEyeCorners[1]) || + !this.isValidPoint(bullsEyeCorners[2]) || !this.isValidPoint(bullsEyeCorners[3])) { + throw new NotFoundException(); + } + let length = 2 * this.nbCenterLayers; + // Get the bits around the bull's eye + let sides = new Int32Array([ + this.sampleLine(bullsEyeCorners[0], bullsEyeCorners[1], length), + this.sampleLine(bullsEyeCorners[1], bullsEyeCorners[2], length), + this.sampleLine(bullsEyeCorners[2], bullsEyeCorners[3], length), + this.sampleLine(bullsEyeCorners[3], bullsEyeCorners[0], length) // Top + ]); + // bullsEyeCorners[shift] is the corner of the bulls'eye that has three + // orientation marks. + // sides[shift] is the row/column that goes from the corner with three + // orientation marks to the corner with two. + this.shift = this.getRotation(sides, length); + // Flatten the parameter bits into a single 28- or 40-bit long + let parameterData = 0; + for (let i = 0; i < 4; i++) { + let side = sides[(this.shift + i) % 4]; + if (this.compact) { + // Each side of the form ..XXXXXXX. where Xs are parameter data + parameterData <<= 7; + parameterData += (side >> 1) & 0x7F; + } + else { + // Each side of the form ..XXXXX.XXXXX. where Xs are parameter data + parameterData <<= 10; + parameterData += ((side >> 2) & (0x1f << 5)) + ((side >> 1) & 0x1F); + } + } + // Corrects parameter data using RS. Returns just the data portion + // without the error correction. + let correctedData = this.getCorrectedParameterData(parameterData, this.compact); + if (this.compact) { + // 8 bits: 2 bits layers and 6 bits data blocks + this.nbLayers = (correctedData >> 6) + 1; + this.nbDataBlocks = (correctedData & 0x3F) + 1; + } + else { + // 16 bits: 5 bits layers and 11 bits data blocks + this.nbLayers = (correctedData >> 11) + 1; + this.nbDataBlocks = (correctedData & 0x7FF) + 1; + } + } + getRotation(sides, length) { + // In a normal pattern, we expect to See + // ** .* D A + // * * + // + // . * + // .. .. C B + // + // Grab the 3 bits from each of the sides the form the locator pattern and concatenate + // into a 12-bit integer. Start with the bit at A + let cornerBits = 0; + sides.forEach((side, idx, arr) => { + // XX......X where X's are orientation marks + let t = ((side >> (length - 2)) << 1) + (side & 1); + cornerBits = (cornerBits << 3) + t; + }); + // for (var side in sides) { + // // XX......X where X's are orientation marks + // var t = ((side >> (length - 2)) << 1) + (side & 1); + // cornerBits = (cornerBits << 3) + t; + // } + // Mov the bottom bit to the top, so that the three bits of the locator pattern at A are + // together. cornerBits is now: + // 3 orientation bits at A || 3 orientation bits at B || ... || 3 orientation bits at D + cornerBits = ((cornerBits & 1) << 11) + (cornerBits >> 1); + // The result shift indicates which element of BullsEyeCorners[] goes into the top-left + // corner. Since the four rotation values have a Hamming distance of 8, we + // can easily tolerate two errors. + for (let shift = 0; shift < 4; shift++) { + if (Integer.bitCount(cornerBits ^ this.EXPECTED_CORNER_BITS[shift]) <= 2) { + return shift; + } + } + throw new NotFoundException(); + } + /** + * Corrects the parameter bits using Reed-Solomon algorithm. + * + * @param parameterData parameter bits + * @param compact true if this is a compact Aztec code + * @throws NotFoundException if the array contains too many errors + */ + getCorrectedParameterData(parameterData, compact) { + let numCodewords; + let numDataCodewords; + if (compact) { + numCodewords = 7; + numDataCodewords = 2; + } + else { + numCodewords = 10; + numDataCodewords = 4; + } + let numECCodewords = numCodewords - numDataCodewords; + let parameterWords = new Int32Array(numCodewords); + for (let i = numCodewords - 1; i >= 0; --i) { + parameterWords[i] = parameterData & 0xF; + parameterData >>= 4; + } + try { + let rsDecoder = new ReedSolomonDecoder(GenericGF.AZTEC_PARAM); + rsDecoder.decode(parameterWords, numECCodewords); + } + catch (ignored) { + throw new NotFoundException(); + } + // Toss the error correction. Just return the data as an integer + let result = 0; + for (let i = 0; i < numDataCodewords; i++) { + result = (result << 4) + parameterWords[i]; + } + return result; + } + /** + * Finds the corners of a bull-eye centered on the passed point. + * This returns the centers of the diagonal points just outside the bull's eye + * Returns [topRight, bottomRight, bottomLeft, topLeft] + * + * @param pCenter Center point + * @return The corners of the bull-eye + * @throws NotFoundException If no valid bull-eye can be found + */ + getBullsEyeCorners(pCenter) { + let pina = pCenter; + let pinb = pCenter; + let pinc = pCenter; + let pind = pCenter; + let color = true; + for (this.nbCenterLayers = 1; this.nbCenterLayers < 9; this.nbCenterLayers++) { + let pouta = this.getFirstDifferent(pina, color, 1, -1); + let poutb = this.getFirstDifferent(pinb, color, 1, 1); + let poutc = this.getFirstDifferent(pinc, color, -1, 1); + let poutd = this.getFirstDifferent(pind, color, -1, -1); + // d a + // + // c b + if (this.nbCenterLayers > 2) { + let q = (this.distancePoint(poutd, pouta) * this.nbCenterLayers) / (this.distancePoint(pind, pina) * (this.nbCenterLayers + 2)); + if (q < 0.75 || q > 1.25 || !this.isWhiteOrBlackRectangle(pouta, poutb, poutc, poutd)) { + break; + } + } + pina = pouta; + pinb = poutb; + pinc = poutc; + pind = poutd; + color = !color; + } + if (this.nbCenterLayers !== 5 && this.nbCenterLayers !== 7) { + throw new NotFoundException(); + } + this.compact = this.nbCenterLayers === 5; + // Expand the square by .5 pixel in each direction so that we're on the border + // between the white square and the black square + let pinax = new ResultPoint(pina.getX() + 0.5, pina.getY() - 0.5); + let pinbx = new ResultPoint(pinb.getX() + 0.5, pinb.getY() + 0.5); + let pincx = new ResultPoint(pinc.getX() - 0.5, pinc.getY() + 0.5); + let pindx = new ResultPoint(pind.getX() - 0.5, pind.getY() - 0.5); + // Expand the square so that its corners are the centers of the points + // just outside the bull's eye. + return this.expandSquare([pinax, pinbx, pincx, pindx], 2 * this.nbCenterLayers - 3, 2 * this.nbCenterLayers); + } + /** + * Finds a candidate center point of an Aztec code from an image + * + * @return the center point + */ + getMatrixCenter() { + let pointA; + let pointB; + let pointC; + let pointD; + // Get a white rectangle that can be the border of the matrix in center bull's eye or + try { + let cornerPoints = new WhiteRectangleDetector(this.image).detect(); + pointA = cornerPoints[0]; + pointB = cornerPoints[1]; + pointC = cornerPoints[2]; + pointD = cornerPoints[3]; + } + catch (e) { + // This exception can be in case the initial rectangle is white + // In that case, surely in the bull's eye, we try to expand the rectangle. + let cx = this.image.getWidth() / 2; + let cy = this.image.getHeight() / 2; + pointA = this.getFirstDifferent(new Point(cx + 7, cy - 7), false, 1, -1).toResultPoint(); + pointB = this.getFirstDifferent(new Point(cx + 7, cy + 7), false, 1, 1).toResultPoint(); + pointC = this.getFirstDifferent(new Point(cx - 7, cy + 7), false, -1, 1).toResultPoint(); + pointD = this.getFirstDifferent(new Point(cx - 7, cy - 7), false, -1, -1).toResultPoint(); + } + // Compute the center of the rectangle + let cx = MathUtils.round((pointA.getX() + pointD.getX() + pointB.getX() + pointC.getX()) / 4.0); + let cy = MathUtils.round((pointA.getY() + pointD.getY() + pointB.getY() + pointC.getY()) / 4.0); + // Redetermine the white rectangle starting from previously computed center. + // This will ensure that we end up with a white rectangle in center bull's eye + // in order to compute a more accurate center. + try { + let cornerPoints = new WhiteRectangleDetector(this.image, 15, cx, cy).detect(); + pointA = cornerPoints[0]; + pointB = cornerPoints[1]; + pointC = cornerPoints[2]; + pointD = cornerPoints[3]; + } + catch (e) { + // This exception can be in case the initial rectangle is white + // In that case we try to expand the rectangle. + pointA = this.getFirstDifferent(new Point(cx + 7, cy - 7), false, 1, -1).toResultPoint(); + pointB = this.getFirstDifferent(new Point(cx + 7, cy + 7), false, 1, 1).toResultPoint(); + pointC = this.getFirstDifferent(new Point(cx - 7, cy + 7), false, -1, 1).toResultPoint(); + pointD = this.getFirstDifferent(new Point(cx - 7, cy - 7), false, -1, -1).toResultPoint(); + } + // Recompute the center of the rectangle + cx = MathUtils.round((pointA.getX() + pointD.getX() + pointB.getX() + pointC.getX()) / 4.0); + cy = MathUtils.round((pointA.getY() + pointD.getY() + pointB.getY() + pointC.getY()) / 4.0); + return new Point(cx, cy); + } + /** + * Gets the Aztec code corners from the bull's eye corners and the parameters. + * + * @param bullsEyeCorners the array of bull's eye corners + * @return the array of aztec code corners + */ + getMatrixCornerPoints(bullsEyeCorners) { + return this.expandSquare(bullsEyeCorners, 2 * this.nbCenterLayers, this.getDimension()); + } + /** + * Creates a BitMatrix by sampling the provided image. + * topLeft, topRight, bottomRight, and bottomLeft are the centers of the squares on the + * diagonal just outside the bull's eye. + */ + sampleGrid(image, topLeft, topRight, bottomRight, bottomLeft) { + let sampler = GridSamplerInstance.getInstance(); + let dimension = this.getDimension(); + let low = dimension / 2 - this.nbCenterLayers; + let high = dimension / 2 + this.nbCenterLayers; + return sampler.sampleGrid(image, dimension, dimension, low, low, // topleft + high, low, // topright + high, high, // bottomright + low, high, // bottomleft + topLeft.getX(), topLeft.getY(), topRight.getX(), topRight.getY(), bottomRight.getX(), bottomRight.getY(), bottomLeft.getX(), bottomLeft.getY()); + } + /** + * Samples a line. + * + * @param p1 start point (inclusive) + * @param p2 end point (exclusive) + * @param size number of bits + * @return the array of bits as an int (first bit is high-order bit of result) + */ + sampleLine(p1, p2, size) { + let result = 0; + let d = this.distanceResultPoint(p1, p2); + let moduleSize = d / size; + let px = p1.getX(); + let py = p1.getY(); + let dx = moduleSize * (p2.getX() - p1.getX()) / d; + let dy = moduleSize * (p2.getY() - p1.getY()) / d; + for (let i = 0; i < size; i++) { + if (this.image.get(MathUtils.round(px + i * dx), MathUtils.round(py + i * dy))) { + result |= 1 << (size - i - 1); + } + } + return result; + } + /** + * @return true if the border of the rectangle passed in parameter is compound of white points only + * or black points only + */ + isWhiteOrBlackRectangle(p1, p2, p3, p4) { + let corr = 3; + p1 = new Point(p1.getX() - corr, p1.getY() + corr); + p2 = new Point(p2.getX() - corr, p2.getY() - corr); + p3 = new Point(p3.getX() + corr, p3.getY() - corr); + p4 = new Point(p4.getX() + corr, p4.getY() + corr); + let cInit = this.getColor(p4, p1); + if (cInit === 0) { + return false; + } + let c = this.getColor(p1, p2); + if (c !== cInit) { + return false; + } + c = this.getColor(p2, p3); + if (c !== cInit) { + return false; + } + c = this.getColor(p3, p4); + return c === cInit; + } + /** + * Gets the color of a segment + * + * @return 1 if segment more than 90% black, -1 if segment is more than 90% white, 0 else + */ + getColor(p1, p2) { + let d = this.distancePoint(p1, p2); + let dx = (p2.getX() - p1.getX()) / d; + let dy = (p2.getY() - p1.getY()) / d; + let error = 0; + let px = p1.getX(); + let py = p1.getY(); + let colorModel = this.image.get(p1.getX(), p1.getY()); + let iMax = Math.ceil(d); + for (let i = 0; i < iMax; i++) { + px += dx; + py += dy; + if (this.image.get(MathUtils.round(px), MathUtils.round(py)) !== colorModel) { + error++; + } + } + let errRatio = error / d; + if (errRatio > 0.1 && errRatio < 0.9) { + return 0; + } + return (errRatio <= 0.1) === colorModel ? 1 : -1; + } + /** + * Gets the coordinate of the first point with a different color in the given direction + */ + getFirstDifferent(init, color, dx, dy) { + let x = init.getX() + dx; + let y = init.getY() + dy; + while (this.isValid(x, y) && this.image.get(x, y) === color) { + x += dx; + y += dy; + } + x -= dx; + y -= dy; + while (this.isValid(x, y) && this.image.get(x, y) === color) { + x += dx; + } + x -= dx; + while (this.isValid(x, y) && this.image.get(x, y) === color) { + y += dy; + } + y -= dy; + return new Point(x, y); + } + /** + * Expand the square represented by the corner points by pushing out equally in all directions + * + * @param cornerPoints the corners of the square, which has the bull's eye at its center + * @param oldSide the original length of the side of the square in the target bit matrix + * @param newSide the new length of the size of the square in the target bit matrix + * @return the corners of the expanded square + */ + expandSquare(cornerPoints, oldSide, newSide) { + let ratio = newSide / (2.0 * oldSide); + let dx = cornerPoints[0].getX() - cornerPoints[2].getX(); + let dy = cornerPoints[0].getY() - cornerPoints[2].getY(); + let centerx = (cornerPoints[0].getX() + cornerPoints[2].getX()) / 2.0; + let centery = (cornerPoints[0].getY() + cornerPoints[2].getY()) / 2.0; + let result0 = new ResultPoint(centerx + ratio * dx, centery + ratio * dy); + let result2 = new ResultPoint(centerx - ratio * dx, centery - ratio * dy); + dx = cornerPoints[1].getX() - cornerPoints[3].getX(); + dy = cornerPoints[1].getY() - cornerPoints[3].getY(); + centerx = (cornerPoints[1].getX() + cornerPoints[3].getX()) / 2.0; + centery = (cornerPoints[1].getY() + cornerPoints[3].getY()) / 2.0; + let result1 = new ResultPoint(centerx + ratio * dx, centery + ratio * dy); + let result3 = new ResultPoint(centerx - ratio * dx, centery - ratio * dy); + let results = [result0, result1, result2, result3]; + return results; + } + isValid(x, y) { + return x >= 0 && x < this.image.getWidth() && y > 0 && y < this.image.getHeight(); + } + isValidPoint(point) { + let x = MathUtils.round(point.getX()); + let y = MathUtils.round(point.getY()); + return this.isValid(x, y); + } + distancePoint(a, b) { + return MathUtils.distance(a.getX(), a.getY(), b.getX(), b.getY()); + } + distanceResultPoint(a, b) { + return MathUtils.distance(a.getX(), a.getY(), b.getX(), b.getY()); + } + getDimension() { + if (this.compact) { + return 4 * this.nbLayers + 11; + } + if (this.nbLayers <= 4) { + return 4 * this.nbLayers + 15; + } + return 4 * this.nbLayers + 2 * (Integer.truncDivision((this.nbLayers - 4), 8) + 1) + 15; + } + } + + /* + * Copyright 2010 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. + */ + // import java.util.List; + // import java.util.Map; + /** + * This implementation can detect and decode Aztec codes in an image. + * + * @author David Olivier + */ + class AztecReader { + /** + * Locates and decodes a Data Matrix code in an image. + * + * @return a String representing the content encoded by the Data Matrix code + * @throws NotFoundException if a Data Matrix code cannot be found + * @throws FormatException if a Data Matrix code cannot be decoded + */ + decode(image, hints = null) { + let exception = null; + let detector = new Detector(image.getBlackMatrix()); + let points = null; + let decoderResult = null; + try { + let detectorResult = detector.detectMirror(false); + points = detectorResult.getPoints(); + this.reportFoundResultPoints(hints, points); + decoderResult = new Decoder().decode(detectorResult); + } + catch (e) { + exception = e; + } + if (decoderResult == null) { + try { + let detectorResult = detector.detectMirror(true); + points = detectorResult.getPoints(); + this.reportFoundResultPoints(hints, points); + decoderResult = new Decoder().decode(detectorResult); + } + catch (e) { + if (exception != null) { + throw exception; + } + throw e; + } + } + let result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), decoderResult.getNumBits(), points, BarcodeFormat$1.AZTEC, System.currentTimeMillis()); + let byteSegments = decoderResult.getByteSegments(); + if (byteSegments != null) { + result.putMetadata(ResultMetadataType$1.BYTE_SEGMENTS, byteSegments); + } + let ecLevel = decoderResult.getECLevel(); + if (ecLevel != null) { + result.putMetadata(ResultMetadataType$1.ERROR_CORRECTION_LEVEL, ecLevel); + } + return result; + } + reportFoundResultPoints(hints, points) { + if (hints != null) { + let rpcb = hints.get(DecodeHintType$1.NEED_RESULT_POINT_CALLBACK); + if (rpcb != null) { + points.forEach((point, idx, arr) => { + rpcb.foundPossibleResultPoint(point); + }); + } + } + } + // @Override + reset() { + // do nothing + } + } + + /** + * Aztec Code reader to use from browser. + * + * @class BrowserAztecCodeReader + * @extends {BrowserCodeReader} + */ + class BrowserAztecCodeReader extends BrowserCodeReader { + /** + * Creates an instance of BrowserAztecCodeReader. + * @param {number} [timeBetweenScansMillis=500] the time delay between subsequent decode tries + * + * @memberOf BrowserAztecCodeReader + */ + constructor(timeBetweenScansMillis = 500) { + super(new AztecReader(), timeBetweenScansMillis); + } + } + + /* + * Copyright 2008 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. + */ + /** + * Encapsulates functionality and implementation that is common to all families + * of one-dimensional barcodes. + * + * @author dswitkin@google.com (Daniel Switkin) + * @author Sean Owen + */ + class OneDReader { + /* + @Override + public Result decode(BinaryBitmap image) throws NotFoundException, FormatException { + return decode(image, null); + } + */ + // Note that we don't try rotation without the try harder flag, even if rotation was supported. + // @Override + decode(image, hints) { + try { + return this.doDecode(image, hints); + } + catch (nfe) { + const tryHarder = hints && (hints.get(DecodeHintType$1.TRY_HARDER) === true); + if (tryHarder && image.isRotateSupported()) { + const rotatedImage = image.rotateCounterClockwise(); + const result = this.doDecode(rotatedImage, hints); + // Record that we found it rotated 90 degrees CCW / 270 degrees CW + const metadata = result.getResultMetadata(); + let orientation = 270; + if (metadata !== null && (metadata.get(ResultMetadataType$1.ORIENTATION) === true)) { + // But if we found it reversed in doDecode(), add in that result here: + orientation = (orientation + metadata.get(ResultMetadataType$1.ORIENTATION) % 360); + } + result.putMetadata(ResultMetadataType$1.ORIENTATION, orientation); + // Update result points + const points = result.getResultPoints(); + if (points !== null) { + const height = rotatedImage.getHeight(); + for (let i = 0; i < points.length; i++) { + points[i] = new ResultPoint(height - points[i].getY() - 1, points[i].getX()); + } + } + return result; + } + else { + throw new NotFoundException(); + } + } + } + // @Override + reset() { + // do nothing + } + /** + * We're going to examine rows from the middle outward, searching alternately above and below the + * middle, and farther out each time. rowStep is the number of rows between each successive + * attempt above and below the middle. So we'd scan row middle, then middle - rowStep, then + * middle + rowStep, then middle - (2 * rowStep), etc. + * rowStep is bigger as the image is taller, but is always at least 1. We've somewhat arbitrarily + * decided that moving up and down by about 1/16 of the image is pretty good; we try more of the + * image if "trying harder". + * + * @param image The image to decode + * @param hints Any hints that were requested + * @return The contents of the decoded barcode + * @throws NotFoundException Any spontaneous errors which occur + */ + doDecode(image, hints) { + const width = image.getWidth(); + const height = image.getHeight(); + let row = new BitArray(width); + const tryHarder = hints && (hints.get(DecodeHintType$1.TRY_HARDER) === true); + const rowStep = Math.max(1, height >> (tryHarder ? 8 : 5)); + let maxLines; + if (tryHarder) { + maxLines = height; // Look at the whole image, not just the center + } + else { + maxLines = 15; // 15 rows spaced 1/32 apart is roughly the middle half of the image + } + const middle = Math.trunc(height / 2); + for (let x = 0; x < maxLines; x++) { + // Scanning from the middle out. Determine which row we're looking at next: + const rowStepsAboveOrBelow = Math.trunc((x + 1) / 2); + const isAbove = (x & 0x01) === 0; // i.e. is x even? + const rowNumber = middle + rowStep * (isAbove ? rowStepsAboveOrBelow : -rowStepsAboveOrBelow); + if (rowNumber < 0 || rowNumber >= height) { + // Oops, if we run off the top or bottom, stop + break; + } + // Estimate black point for this row and load it: + try { + row = image.getBlackRow(rowNumber, row); + } + catch (ignored) { + continue; + } + // While we have the image data in a BitArray, it's fairly cheap to reverse it in place to + // handle decoding upside down barcodes. + for (let attempt = 0; attempt < 2; attempt++) { + if (attempt === 1) { // trying again? + row.reverse(); // reverse the row and continue + // This means we will only ever draw result points *once* in the life of this method + // since we want to avoid drawing the wrong points after flipping the row, and, + // don't want to clutter with noise from every single row scan -- just the scans + // that start on the center line. + if (hints && (hints.get(DecodeHintType$1.NEED_RESULT_POINT_CALLBACK) === true)) { + const newHints = new Map(); + hints.forEach((hint, key) => newHints.set(key, hint)); + newHints.delete(DecodeHintType$1.NEED_RESULT_POINT_CALLBACK); + hints = newHints; + } + } + try { + // Look for a barcode + const result = this.decodeRow(rowNumber, row, hints); + // We found our barcode + if (attempt === 1) { + // But it was upside down, so note that + result.putMetadata(ResultMetadataType$1.ORIENTATION, 180); + // And remember to flip the result points horizontally. + const points = result.getResultPoints(); + if (points !== null) { + points[0] = new ResultPoint(width - points[0].getX() - 1, points[0].getY()); + points[1] = new ResultPoint(width - points[1].getX() - 1, points[1].getY()); + } + } + return result; + } + catch (re) { + // continue -- just couldn't decode this row + } + } + } + throw new NotFoundException(); + } + /** + * Records the size of successive runs of white and black pixels in a row, starting at a given point. + * The values are recorded in the given array, and the number of runs recorded is equal to the size + * of the array. If the row starts on a white pixel at the given start point, then the first count + * recorded is the run of white pixels starting from that point; likewise it is the count of a run + * of black pixels if the row begin on a black pixels at that point. + * + * @param row row to count from + * @param start offset into row to start at + * @param counters array into which to record counts + * @throws NotFoundException if counters cannot be filled entirely from row before running out + * of pixels + */ + static recordPattern(row, start, counters) { + const numCounters = counters.length; + for (let index = 0; index < numCounters; index++) + counters[index] = 0; + const end = row.getSize(); + if (start >= end) { + throw new NotFoundException(); + } + let isWhite = !row.get(start); + let counterPosition = 0; + let i = start; + while (i < end) { + if (row.get(i) !== isWhite) { + counters[counterPosition]++; + } + else { + if (++counterPosition === numCounters) { + break; + } + else { + counters[counterPosition] = 1; + isWhite = !isWhite; + } + } + i++; + } + // If we read fully the last section of pixels and filled up our counters -- or filled + // the last counter but ran off the side of the image, OK. Otherwise, a problem. + if (!(counterPosition === numCounters || (counterPosition === numCounters - 1 && i === end))) { + throw new NotFoundException(); + } + } + static recordPatternInReverse(row, start, counters) { + // This could be more efficient I guess + let numTransitionsLeft = counters.length; + let last = row.get(start); + while (start > 0 && numTransitionsLeft >= 0) { + if (row.get(--start) !== last) { + numTransitionsLeft--; + last = !last; + } + } + if (numTransitionsLeft >= 0) { + throw new NotFoundException(); + } + OneDReader.recordPattern(row, start + 1, counters); + } + /** + * Determines how closely a set of observed counts of runs of black/white values matches a given + * target pattern. This is reported as the ratio of the total variance from the expected pattern + * proportions across all pattern elements, to the length of the pattern. + * + * @param counters observed counters + * @param pattern expected pattern + * @param maxIndividualVariance The most any counter can differ before we give up + * @return ratio of total variance between counters and pattern compared to total pattern size + */ + static patternMatchVariance(counters, pattern, maxIndividualVariance) { + const numCounters = counters.length; + let total = 0; + let patternLength = 0; + for (let i = 0; i < numCounters; i++) { + total += counters[i]; + patternLength += pattern[i]; + } + if (total < patternLength) { + // If we don't even have one pixel per unit of bar width, assume this is too small + // to reliably match, so fail: + return Number.POSITIVE_INFINITY; + } + const unitBarWidth = total / patternLength; + maxIndividualVariance *= unitBarWidth; + let totalVariance = 0.0; + for (let x = 0; x < numCounters; x++) { + const counter = counters[x]; + const scaledPattern = pattern[x] * unitBarWidth; + const variance = counter > scaledPattern ? counter - scaledPattern : scaledPattern - counter; + if (variance > maxIndividualVariance) { + return Number.POSITIVE_INFINITY; + } + totalVariance += variance; + } + return totalVariance / total; + } + } + + /* + * Copyright 2008 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. + */ + /** + *Decodes Code 128 barcodes.
+ * + * @author Sean Owen + */ + class Code128Reader extends OneDReader { + static findStartPattern(row) { + const width = row.getSize(); + const rowOffset = row.getNextSet(0); + let counterPosition = 0; + let counters = Int32Array.from([0, 0, 0, 0, 0, 0]); + let patternStart = rowOffset; + let isWhite = false; + const patternLength = 6; + for (let i = rowOffset; i < width; i++) { + if (row.get(i) !== isWhite) { + counters[counterPosition]++; + } + else { + if (counterPosition === (patternLength - 1)) { + let bestVariance = Code128Reader.MAX_AVG_VARIANCE; + let bestMatch = -1; + for (let startCode = Code128Reader.CODE_START_A; startCode <= Code128Reader.CODE_START_C; startCode++) { + const variance = OneDReader.patternMatchVariance(counters, Code128Reader.CODE_PATTERNS[startCode], Code128Reader.MAX_INDIVIDUAL_VARIANCE); + if (variance < bestVariance) { + bestVariance = variance; + bestMatch = startCode; + } + } + // Look for whitespace before start pattern, >= 50% of width of start pattern + if (bestMatch >= 0 && + row.isRange(Math.max(0, patternStart - (i - patternStart) / 2), patternStart, false)) { + return Int32Array.from([patternStart, i, bestMatch]); + } + patternStart += counters[0] + counters[1]; + counters = counters.slice(2, counters.length - 1); + counters[counterPosition - 1] = 0; + counters[counterPosition] = 0; + counterPosition--; + } + else { + counterPosition++; + } + counters[counterPosition] = 1; + isWhite = !isWhite; + } + } + throw new NotFoundException(); + } + static decodeCode(row, counters, rowOffset) { + OneDReader.recordPattern(row, rowOffset, counters); + let bestVariance = Code128Reader.MAX_AVG_VARIANCE; // worst variance we'll accept + let bestMatch = -1; + for (let d = 0; d < Code128Reader.CODE_PATTERNS.length; d++) { + const pattern = Code128Reader.CODE_PATTERNS[d]; + const variance = this.patternMatchVariance(counters, pattern, Code128Reader.MAX_INDIVIDUAL_VARIANCE); + if (variance < bestVariance) { + bestVariance = variance; + bestMatch = d; + } + } + // TODO We're overlooking the fact that the STOP pattern has 7 values, not 6. + if (bestMatch >= 0) { + return bestMatch; + } + else { + throw new NotFoundException(); + } + } + decodeRow(rowNumber, row, hints) { + const convertFNC1 = hints && (hints.get(DecodeHintType$1.ASSUME_GS1) === true); + const startPatternInfo = Code128Reader.findStartPattern(row); + const startCode = startPatternInfo[2]; + let currentRawCodesIndex = 0; + const rawCodes = new Uint8Array(20); + rawCodes[currentRawCodesIndex++] = startCode; + let codeSet; + switch (startCode) { + case Code128Reader.CODE_START_A: + codeSet = Code128Reader.CODE_CODE_A; + break; + case Code128Reader.CODE_START_B: + codeSet = Code128Reader.CODE_CODE_B; + break; + case Code128Reader.CODE_START_C: + codeSet = Code128Reader.CODE_CODE_C; + break; + default: + throw new FormatException(); + } + let done = false; + let isNextShifted = false; + let result = ''; + let lastStart = startPatternInfo[0]; + let nextStart = startPatternInfo[1]; + const counters = Int32Array.from([0, 0, 0, 0, 0, 0]); + let lastCode = 0; + let code = 0; + let checksumTotal = startCode; + let multiplier = 0; + let lastCharacterWasPrintable = true; + let upperMode = false; + let shiftUpperMode = false; + while (!done) { + const unshift = isNextShifted; + isNextShifted = false; + // Save off last code + lastCode = code; + // Decode another code from image + code = Code128Reader.decodeCode(row, counters, nextStart); + rawCodes[currentRawCodesIndex++] = code; + // Remember whether the last code was printable or not (excluding CODE_STOP) + if (code !== Code128Reader.CODE_STOP) { + lastCharacterWasPrintable = true; + } + // Add to checksum computation (if not CODE_STOP of course) + if (code !== Code128Reader.CODE_STOP) { + multiplier++; + checksumTotal += multiplier * code; + } + // Advance to where the next code will to start + lastStart = nextStart; + nextStart += counters.reduce((previous, current) => previous + current, 0); + // Take care of illegal start codes + switch (code) { + case Code128Reader.CODE_START_A: + case Code128Reader.CODE_START_B: + case Code128Reader.CODE_START_C: + throw new FormatException(); + } + switch (codeSet) { + case Code128Reader.CODE_CODE_A: + if (code < 64) { + if (shiftUpperMode === upperMode) { + result += String.fromCharCode((' '.charCodeAt(0) + code)); + } + else { + result += String.fromCharCode((' '.charCodeAt(0) + code + 128)); + } + shiftUpperMode = false; + } + else if (code < 96) { + if (shiftUpperMode === upperMode) { + result += String.fromCharCode((code - 64)); + } + else { + result += String.fromCharCode((code + 64)); + } + shiftUpperMode = false; + } + else { + // Don't let CODE_STOP, which always appears, affect whether whether we think the last + // code was printable or not. + if (code !== Code128Reader.CODE_STOP) { + lastCharacterWasPrintable = false; + } + switch (code) { + case Code128Reader.CODE_FNC_1: + if (convertFNC1) { + if (result.length === 0) { + // GS1 specification 5.4.3.7. and 5.4.6.4. If the first char after the start code + // is FNC1 then this is GS1-128. We add the symbology identifier. + result += ']C1'; + } + else { + // GS1 specification 5.4.7.5. Every subsequent FNC1 is returned as ASCII 29 (GS) + result += String.fromCharCode(29); + } + } + break; + case Code128Reader.CODE_FNC_2: + case Code128Reader.CODE_FNC_3: + // do nothing? + break; + case Code128Reader.CODE_FNC_4_A: + if (!upperMode && shiftUpperMode) { + upperMode = true; + shiftUpperMode = false; + } + else if (upperMode && shiftUpperMode) { + upperMode = false; + shiftUpperMode = false; + } + else { + shiftUpperMode = true; + } + break; + case Code128Reader.CODE_SHIFT: + isNextShifted = true; + codeSet = Code128Reader.CODE_CODE_B; + break; + case Code128Reader.CODE_CODE_B: + codeSet = Code128Reader.CODE_CODE_B; + break; + case Code128Reader.CODE_CODE_C: + codeSet = Code128Reader.CODE_CODE_C; + break; + case Code128Reader.CODE_STOP: + done = true; + break; + } + } + break; + case Code128Reader.CODE_CODE_B: + if (code < 96) { + if (shiftUpperMode === upperMode) { + result += String.fromCharCode((' '.charCodeAt(0) + code)); + } + else { + result += String.fromCharCode((' '.charCodeAt(0) + code + 128)); + } + shiftUpperMode = false; + } + else { + if (code !== Code128Reader.CODE_STOP) { + lastCharacterWasPrintable = false; + } + switch (code) { + case Code128Reader.CODE_FNC_1: + if (convertFNC1) { + if (result.length === 0) { + // GS1 specification 5.4.3.7. and 5.4.6.4. If the first char after the start code + // is FNC1 then this is GS1-128. We add the symbology identifier. + result += ']C1'; + } + else { + // GS1 specification 5.4.7.5. Every subsequent FNC1 is returned as ASCII 29 (GS) + result += String.fromCharCode(29); + } + } + break; + case Code128Reader.CODE_FNC_2: + case Code128Reader.CODE_FNC_3: + // do nothing? + break; + case Code128Reader.CODE_FNC_4_B: + if (!upperMode && shiftUpperMode) { + upperMode = true; + shiftUpperMode = false; + } + else if (upperMode && shiftUpperMode) { + upperMode = false; + shiftUpperMode = false; + } + else { + shiftUpperMode = true; + } + break; + case Code128Reader.CODE_SHIFT: + isNextShifted = true; + codeSet = Code128Reader.CODE_CODE_A; + break; + case Code128Reader.CODE_CODE_A: + codeSet = Code128Reader.CODE_CODE_A; + break; + case Code128Reader.CODE_CODE_C: + codeSet = Code128Reader.CODE_CODE_C; + break; + case Code128Reader.CODE_STOP: + done = true; + break; + } + } + break; + case Code128Reader.CODE_CODE_C: + if (code < 100) { + if (code < 10) { + result += '0'; + } + result += code; + } + else { + if (code !== Code128Reader.CODE_STOP) { + lastCharacterWasPrintable = false; + } + switch (code) { + case Code128Reader.CODE_FNC_1: + if (convertFNC1) { + if (result.length === 0) { + // GS1 specification 5.4.3.7. and 5.4.6.4. If the first char after the start code + // is FNC1 then this is GS1-128. We add the symbology identifier. + result += ']C1'; + } + else { + // GS1 specification 5.4.7.5. Every subsequent FNC1 is returned as ASCII 29 (GS) + result += String.fromCharCode(29); + } + } + break; + case Code128Reader.CODE_CODE_A: + codeSet = Code128Reader.CODE_CODE_A; + break; + case Code128Reader.CODE_CODE_B: + codeSet = Code128Reader.CODE_CODE_B; + break; + case Code128Reader.CODE_STOP: + done = true; + break; + } + } + break; + } + // Unshift back to another code set if we were shifted + if (unshift) { + codeSet = codeSet === Code128Reader.CODE_CODE_A ? Code128Reader.CODE_CODE_B : Code128Reader.CODE_CODE_A; + } + } + const lastPatternSize = nextStart - lastStart; + // Check for ample whitespace following pattern, but, to do this we first need to remember that + // we fudged decoding CODE_STOP since it actually has 7 bars, not 6. There is a black bar left + // to read off. Would be slightly better to properly read. Here we just skip it: + nextStart = row.getNextUnset(nextStart); + if (!row.isRange(nextStart, Math.min(row.getSize(), nextStart + (nextStart - lastStart) / 2), false)) { + throw new NotFoundException(); + } + // Pull out from sum the value of the penultimate check code + checksumTotal -= multiplier * lastCode; + // lastCode is the checksum then: + if (checksumTotal % 103 !== lastCode) { + throw new ChecksumException(); + } + // Need to pull out the check digits from string + const resultLength = result.length; + if (resultLength === 0) { + // false positive + throw new NotFoundException(); + } + // Only bother if the result had at least one character, and if the checksum digit happened to + // be a printable character. If it was just interpreted as a control code, nothing to remove. + if (resultLength > 0 && lastCharacterWasPrintable) { + if (codeSet === Code128Reader.CODE_CODE_C) { + result = result.substring(0, resultLength - 2); + } + else { + result = result.substring(0, resultLength - 1); + } + } + const left = (startPatternInfo[1] + startPatternInfo[0]) / 2.0; + const right = lastStart + lastPatternSize / 2.0; + const rawCodesSize = rawCodes.length; + const rawBytes = new Uint8Array(rawCodesSize); + for (let i = 0; i < rawCodesSize; i++) { + rawBytes[i] = rawCodes[i]; + } + const points = [new ResultPoint(left, rowNumber), new ResultPoint(right, rowNumber)]; + return new Result(result, rawBytes, 0, points, BarcodeFormat$1.CODE_128, new Date().getTime()); + } + } + Code128Reader.CODE_PATTERNS = [ + Int32Array.from([2, 1, 2, 2, 2, 2]), + Int32Array.from([2, 2, 2, 1, 2, 2]), + Int32Array.from([2, 2, 2, 2, 2, 1]), + Int32Array.from([1, 2, 1, 2, 2, 3]), + Int32Array.from([1, 2, 1, 3, 2, 2]), + Int32Array.from([1, 3, 1, 2, 2, 2]), + Int32Array.from([1, 2, 2, 2, 1, 3]), + Int32Array.from([1, 2, 2, 3, 1, 2]), + Int32Array.from([1, 3, 2, 2, 1, 2]), + Int32Array.from([2, 2, 1, 2, 1, 3]), + Int32Array.from([2, 2, 1, 3, 1, 2]), + Int32Array.from([2, 3, 1, 2, 1, 2]), + Int32Array.from([1, 1, 2, 2, 3, 2]), + Int32Array.from([1, 2, 2, 1, 3, 2]), + Int32Array.from([1, 2, 2, 2, 3, 1]), + Int32Array.from([1, 1, 3, 2, 2, 2]), + Int32Array.from([1, 2, 3, 1, 2, 2]), + Int32Array.from([1, 2, 3, 2, 2, 1]), + Int32Array.from([2, 2, 3, 2, 1, 1]), + Int32Array.from([2, 2, 1, 1, 3, 2]), + Int32Array.from([2, 2, 1, 2, 3, 1]), + Int32Array.from([2, 1, 3, 2, 1, 2]), + Int32Array.from([2, 2, 3, 1, 1, 2]), + Int32Array.from([3, 1, 2, 1, 3, 1]), + Int32Array.from([3, 1, 1, 2, 2, 2]), + Int32Array.from([3, 2, 1, 1, 2, 2]), + Int32Array.from([3, 2, 1, 2, 2, 1]), + Int32Array.from([3, 1, 2, 2, 1, 2]), + Int32Array.from([3, 2, 2, 1, 1, 2]), + Int32Array.from([3, 2, 2, 2, 1, 1]), + Int32Array.from([2, 1, 2, 1, 2, 3]), + Int32Array.from([2, 1, 2, 3, 2, 1]), + Int32Array.from([2, 3, 2, 1, 2, 1]), + Int32Array.from([1, 1, 1, 3, 2, 3]), + Int32Array.from([1, 3, 1, 1, 2, 3]), + Int32Array.from([1, 3, 1, 3, 2, 1]), + Int32Array.from([1, 1, 2, 3, 1, 3]), + Int32Array.from([1, 3, 2, 1, 1, 3]), + Int32Array.from([1, 3, 2, 3, 1, 1]), + Int32Array.from([2, 1, 1, 3, 1, 3]), + Int32Array.from([2, 3, 1, 1, 1, 3]), + Int32Array.from([2, 3, 1, 3, 1, 1]), + Int32Array.from([1, 1, 2, 1, 3, 3]), + Int32Array.from([1, 1, 2, 3, 3, 1]), + Int32Array.from([1, 3, 2, 1, 3, 1]), + Int32Array.from([1, 1, 3, 1, 2, 3]), + Int32Array.from([1, 1, 3, 3, 2, 1]), + Int32Array.from([1, 3, 3, 1, 2, 1]), + Int32Array.from([3, 1, 3, 1, 2, 1]), + Int32Array.from([2, 1, 1, 3, 3, 1]), + Int32Array.from([2, 3, 1, 1, 3, 1]), + Int32Array.from([2, 1, 3, 1, 1, 3]), + Int32Array.from([2, 1, 3, 3, 1, 1]), + Int32Array.from([2, 1, 3, 1, 3, 1]), + Int32Array.from([3, 1, 1, 1, 2, 3]), + Int32Array.from([3, 1, 1, 3, 2, 1]), + Int32Array.from([3, 3, 1, 1, 2, 1]), + Int32Array.from([3, 1, 2, 1, 1, 3]), + Int32Array.from([3, 1, 2, 3, 1, 1]), + Int32Array.from([3, 3, 2, 1, 1, 1]), + Int32Array.from([3, 1, 4, 1, 1, 1]), + Int32Array.from([2, 2, 1, 4, 1, 1]), + Int32Array.from([4, 3, 1, 1, 1, 1]), + Int32Array.from([1, 1, 1, 2, 2, 4]), + Int32Array.from([1, 1, 1, 4, 2, 2]), + Int32Array.from([1, 2, 1, 1, 2, 4]), + Int32Array.from([1, 2, 1, 4, 2, 1]), + Int32Array.from([1, 4, 1, 1, 2, 2]), + Int32Array.from([1, 4, 1, 2, 2, 1]), + Int32Array.from([1, 1, 2, 2, 1, 4]), + Int32Array.from([1, 1, 2, 4, 1, 2]), + Int32Array.from([1, 2, 2, 1, 1, 4]), + Int32Array.from([1, 2, 2, 4, 1, 1]), + Int32Array.from([1, 4, 2, 1, 1, 2]), + Int32Array.from([1, 4, 2, 2, 1, 1]), + Int32Array.from([2, 4, 1, 2, 1, 1]), + Int32Array.from([2, 2, 1, 1, 1, 4]), + Int32Array.from([4, 1, 3, 1, 1, 1]), + Int32Array.from([2, 4, 1, 1, 1, 2]), + Int32Array.from([1, 3, 4, 1, 1, 1]), + Int32Array.from([1, 1, 1, 2, 4, 2]), + Int32Array.from([1, 2, 1, 1, 4, 2]), + Int32Array.from([1, 2, 1, 2, 4, 1]), + Int32Array.from([1, 1, 4, 2, 1, 2]), + Int32Array.from([1, 2, 4, 1, 1, 2]), + Int32Array.from([1, 2, 4, 2, 1, 1]), + Int32Array.from([4, 1, 1, 2, 1, 2]), + Int32Array.from([4, 2, 1, 1, 1, 2]), + Int32Array.from([4, 2, 1, 2, 1, 1]), + Int32Array.from([2, 1, 2, 1, 4, 1]), + Int32Array.from([2, 1, 4, 1, 2, 1]), + Int32Array.from([4, 1, 2, 1, 2, 1]), + Int32Array.from([1, 1, 1, 1, 4, 3]), + Int32Array.from([1, 1, 1, 3, 4, 1]), + Int32Array.from([1, 3, 1, 1, 4, 1]), + Int32Array.from([1, 1, 4, 1, 1, 3]), + Int32Array.from([1, 1, 4, 3, 1, 1]), + Int32Array.from([4, 1, 1, 1, 1, 3]), + Int32Array.from([4, 1, 1, 3, 1, 1]), + Int32Array.from([1, 1, 3, 1, 4, 1]), + Int32Array.from([1, 1, 4, 1, 3, 1]), + Int32Array.from([3, 1, 1, 1, 4, 1]), + Int32Array.from([4, 1, 1, 1, 3, 1]), + Int32Array.from([2, 1, 1, 4, 1, 2]), + Int32Array.from([2, 1, 1, 2, 1, 4]), + Int32Array.from([2, 1, 1, 2, 3, 2]), + Int32Array.from([2, 3, 3, 1, 1, 1, 2]), + ]; + Code128Reader.MAX_AVG_VARIANCE = 0.25; + Code128Reader.MAX_INDIVIDUAL_VARIANCE = 0.7; + Code128Reader.CODE_SHIFT = 98; + Code128Reader.CODE_CODE_C = 99; + Code128Reader.CODE_CODE_B = 100; + Code128Reader.CODE_CODE_A = 101; + Code128Reader.CODE_FNC_1 = 102; + Code128Reader.CODE_FNC_2 = 97; + Code128Reader.CODE_FNC_3 = 96; + Code128Reader.CODE_FNC_4_A = 101; + Code128Reader.CODE_FNC_4_B = 100; + Code128Reader.CODE_START_A = 103; + Code128Reader.CODE_START_B = 104; + Code128Reader.CODE_START_C = 105; + Code128Reader.CODE_STOP = 106; + + /* + * Copyright 2008 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. + */ + /** + *Decodes Code 39 barcodes. Supports "Full ASCII Code 39" if USE_CODE_39_EXTENDED_MODE is set.
+ * + * @author Sean Owen + * @see Code93Reader + */ + class Code39Reader extends OneDReader { + /** + * Creates a reader that assumes all encoded data is data, and does not treat the final + * character as a check digit. It will not decoded "extended Code 39" sequences. + */ + // public Code39Reader() { + // this(false); + // } + /** + * Creates a reader that can be configured to check the last character as a check digit. + * It will not decoded "extended Code 39" sequences. + * + * @param usingCheckDigit if true, treat the last data character as a check digit, not + * data, and verify that the checksum passes. + */ + // public Code39Reader(boolean usingCheckDigit) { + // this(usingCheckDigit, false); + // } + /** + * Creates a reader that can be configured to check the last character as a check digit, + * or optionally attempt to decode "extended Code 39" sequences that are used to encode + * the full ASCII character set. + * + * @param usingCheckDigit if true, treat the last data character as a check digit, not + * data, and verify that the checksum passes. + * @param extendedMode if true, will attempt to decode extended Code 39 sequences in the + * text. + */ + constructor(usingCheckDigit = false, extendedMode = false) { + super(); + this.usingCheckDigit = usingCheckDigit; + this.extendedMode = extendedMode; + this.decodeRowResult = ''; + this.counters = new Int32Array(9); + } + decodeRow(rowNumber, row, hints) { + let theCounters = this.counters; + theCounters.fill(0); + this.decodeRowResult = ''; + let start = Code39Reader.findAsteriskPattern(row, theCounters); + // Read off white space + let nextStart = row.getNextSet(start[1]); + let end = row.getSize(); + let decodedChar; + let lastStart; + do { + Code39Reader.recordPattern(row, nextStart, theCounters); + let pattern = Code39Reader.toNarrowWidePattern(theCounters); + if (pattern < 0) { + throw new NotFoundException(); + } + decodedChar = Code39Reader.patternToChar(pattern); + this.decodeRowResult += decodedChar; + lastStart = nextStart; + for (let counter of theCounters) { + nextStart += counter; + } + // Read off white space + nextStart = row.getNextSet(nextStart); + } while (decodedChar !== '*'); + this.decodeRowResult = this.decodeRowResult.substring(0, this.decodeRowResult.length - 1); // remove asterisk + // Look for whitespace after pattern: + let lastPatternSize = 0; + for (let counter of theCounters) { + lastPatternSize += counter; + } + let whiteSpaceAfterEnd = nextStart - lastStart - lastPatternSize; + // If 50% of last pattern size, following last pattern, is not whitespace, fail + // (but if it's whitespace to the very end of the image, that's OK) + if (nextStart !== end && (whiteSpaceAfterEnd * 2) < lastPatternSize) { + throw new NotFoundException(); + } + if (this.usingCheckDigit) { + let max = this.decodeRowResult.length - 1; + let total = 0; + for (let i = 0; i < max; i++) { + total += Code39Reader.ALPHABET_STRING.indexOf(this.decodeRowResult.charAt(i)); + } + if (this.decodeRowResult.charAt(max) !== Code39Reader.ALPHABET_STRING.charAt(total % 43)) { + throw new ChecksumException(); + } + this.decodeRowResult = this.decodeRowResult.substring(0, max); + } + if (this.decodeRowResult.length === 0) { + // false positive + throw new NotFoundException(); + } + let resultString; + if (this.extendedMode) { + resultString = Code39Reader.decodeExtended(this.decodeRowResult); + } + else { + resultString = this.decodeRowResult; + } + let left = (start[1] + start[0]) / 2.0; + let right = lastStart + lastPatternSize / 2.0; + return new Result(resultString, null, 0, [new ResultPoint(left, rowNumber), new ResultPoint(right, rowNumber)], BarcodeFormat$1.CODE_39, new Date().getTime()); + } + static findAsteriskPattern(row, counters) { + let width = row.getSize(); + let rowOffset = row.getNextSet(0); + let counterPosition = 0; + let patternStart = rowOffset; + let isWhite = false; + let patternLength = counters.length; + for (let i = rowOffset; i < width; i++) { + if (row.get(i) !== isWhite) { + counters[counterPosition]++; + } + else { + if (counterPosition === patternLength - 1) { + // Look for whitespace before start pattern, >= 50% of width of start pattern + if (this.toNarrowWidePattern(counters) === Code39Reader.ASTERISK_ENCODING && + row.isRange(Math.max(0, patternStart - Math.floor((i - patternStart) / 2)), patternStart, false)) { + return [patternStart, i]; + } + patternStart += counters[0] + counters[1]; + counters.copyWithin(0, 2, 2 + counterPosition - 1); + counters[counterPosition - 1] = 0; + counters[counterPosition] = 0; + counterPosition--; + } + else { + counterPosition++; + } + counters[counterPosition] = 1; + isWhite = !isWhite; + } + } + throw new NotFoundException(); + } + // For efficiency, returns -1 on failure. Not throwing here saved as many as 700 exceptions + // per image when using some of our blackbox images. + static toNarrowWidePattern(counters) { + let numCounters = counters.length; + let maxNarrowCounter = 0; + let wideCounters; + do { + let minCounter = 0x7fffffff; + for (let counter of counters) { + if (counter < minCounter && counter > maxNarrowCounter) { + minCounter = counter; + } + } + maxNarrowCounter = minCounter; + wideCounters = 0; + let totalWideCountersWidth = 0; + let pattern = 0; + for (let i = 0; i < numCounters; i++) { + let counter = counters[i]; + if (counter > maxNarrowCounter) { + pattern |= 1 << (numCounters - 1 - i); + wideCounters++; + totalWideCountersWidth += counter; + } + } + if (wideCounters === 3) { + // Found 3 wide counters, but are they close enough in width? + // We can perform a cheap, conservative check to see if any individual + // counter is more than 1.5 times the average: + for (let i = 0; i < numCounters && wideCounters > 0; i++) { + let counter = counters[i]; + if (counter > maxNarrowCounter) { + wideCounters--; + // totalWideCountersWidth = 3 * average, so this checks if counter >= 3/2 * average + if ((counter * 2) >= totalWideCountersWidth) { + return -1; + } + } + } + return pattern; + } + } while (wideCounters > 3); + return -1; + } + static patternToChar(pattern) { + for (let i = 0; i < Code39Reader.CHARACTER_ENCODINGS.length; i++) { + if (Code39Reader.CHARACTER_ENCODINGS[i] === pattern) { + return Code39Reader.ALPHABET_STRING.charAt(i); + } + } + if (pattern === Code39Reader.ASTERISK_ENCODING) { + return '*'; + } + throw new NotFoundException(); + } + static decodeExtended(encoded) { + let length = encoded.length; + let decoded = ''; + for (let i = 0; i < length; i++) { + let c = encoded.charAt(i); + if (c === '+' || c === '$' || c === '%' || c === '/') { + let next = encoded.charAt(i + 1); + let decodedChar = '\0'; + switch (c) { + case '+': + // +A to +Z map to a to z + if (next >= 'A' && next <= 'Z') { + decodedChar = String.fromCharCode(next.charCodeAt(0) + 32); + } + else { + throw new FormatException(); + } + break; + case '$': + // $A to $Z map to control codes SH to SB + if (next >= 'A' && next <= 'Z') { + decodedChar = String.fromCharCode(next.charCodeAt(0) - 64); + } + else { + throw new FormatException(); + } + break; + case '%': + // %A to %E map to control codes ESC to US + if (next >= 'A' && next <= 'E') { + decodedChar = String.fromCharCode(next.charCodeAt(0) - 38); + } + else if (next >= 'F' && next <= 'J') { + decodedChar = String.fromCharCode(next.charCodeAt(0) - 11); + } + else if (next >= 'K' && next <= 'O') { + decodedChar = String.fromCharCode(next.charCodeAt(0) + 16); + } + else if (next >= 'P' && next <= 'T') { + decodedChar = String.fromCharCode(next.charCodeAt(0) + 43); + } + else if (next === 'U') { + decodedChar = '\0'; + } + else if (next === 'V') { + decodedChar = '@'; + } + else if (next === 'W') { + decodedChar = '`'; + } + else if (next === 'X' || next === 'Y' || next === 'Z') { + decodedChar = '\x7f'; + } + else { + throw new FormatException(); + } + break; + case '/': + // /A to /O map to ! to , and /Z maps to : + if (next >= 'A' && next <= 'O') { + decodedChar = String.fromCharCode(next.charCodeAt(0) - 32); + } + else if (next === 'Z') { + decodedChar = ':'; + } + else { + throw new FormatException(); + } + break; + } + decoded += decodedChar; + // bump up i again since we read two characters + i++; + } + else { + decoded += c; + } + } + return decoded; + } + } + Code39Reader.ALPHABET_STRING = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%'; + /** + * These represent the encodings of characters, as patterns of wide and narrow bars. + * The 9 least-significant bits of each int correspond to the pattern of wide and narrow, + * with 1s representing "wide" and 0s representing narrow. + */ + Code39Reader.CHARACTER_ENCODINGS = [ + 0x034, 0x121, 0x061, 0x160, 0x031, 0x130, 0x070, 0x025, 0x124, 0x064, + 0x109, 0x049, 0x148, 0x019, 0x118, 0x058, 0x00D, 0x10C, 0x04C, 0x01C, + 0x103, 0x043, 0x142, 0x013, 0x112, 0x052, 0x007, 0x106, 0x046, 0x016, + 0x181, 0x0C1, 0x1C0, 0x091, 0x190, 0x0D0, 0x085, 0x184, 0x0C4, 0x0A8, + 0x0A2, 0x08A, 0x02A // /-% + ]; + Code39Reader.ASTERISK_ENCODING = 0x094; + + /* + * Copyright 2008 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. + */ + /** + *Decodes ITF barcodes.
+ * + * @author Tjieco + */ + class ITFReader extends OneDReader { + constructor() { + // private static W = 3; // Pixel width of a 3x wide line + // private static w = 2; // Pixel width of a 2x wide line + // private static N = 1; // Pixed width of a narrow line + super(...arguments); + // Stores the actual narrow line width of the image being decoded. + this.narrowLineWidth = -1; + } + // See ITFWriter.PATTERNS + /* + + /!** + * Patterns of Wide / Narrow lines to indicate each digit + *!/ + */ + decodeRow(rowNumber, row, hints) { + // Find out where the Middle section (payload) starts & ends + let startRange = this.decodeStart(row); + let endRange = this.decodeEnd(row); + let result = new StringBuilder(); + ITFReader.decodeMiddle(row, startRange[1], endRange[0], result); + let resultString = result.toString(); + let allowedLengths = null; + if (hints != null) { + allowedLengths = hints.get(DecodeHintType$1.ALLOWED_LENGTHS); + } + if (allowedLengths == null) { + allowedLengths = ITFReader.DEFAULT_ALLOWED_LENGTHS; + } + // To avoid false positives with 2D barcodes (and other patterns), make + // an assumption that the decoded string must be a 'standard' length if it's short + let length = resultString.length; + let lengthOK = false; + let maxAllowedLength = 0; + for (let value of allowedLengths) { + if (length === value) { + lengthOK = true; + break; + } + if (value > maxAllowedLength) { + maxAllowedLength = value; + } + } + if (!lengthOK && length > maxAllowedLength) { + lengthOK = true; + } + if (!lengthOK) { + throw new FormatException(); + } + const points = [new ResultPoint(startRange[1], rowNumber), new ResultPoint(endRange[0], rowNumber)]; + let resultReturn = new Result(resultString, null, // no natural byte representation for these barcodes + 0, points, BarcodeFormat$1.ITF, new Date().getTime()); + return resultReturn; + } + /* + /!** + * @param row row of black/white values to search + * @param payloadStart offset of start pattern + * @param resultString {@link StringBuilder} to append decoded chars to + * @throws NotFoundException if decoding could not complete successfully + *!/*/ + static decodeMiddle(row, payloadStart, payloadEnd, resultString) { + // Digits are interleaved in pairs - 5 black lines for one digit, and the + // 5 + // interleaved white lines for the second digit. + // Therefore, need to scan 10 lines and then + // split these into two arrays + let counterDigitPair = new Int32Array(10); // 10 + let counterBlack = new Int32Array(5); // 5 + let counterWhite = new Int32Array(5); // 5 + counterDigitPair.fill(0); + counterBlack.fill(0); + counterWhite.fill(0); + while (payloadStart < payloadEnd) { + // Get 10 runs of black/white. + OneDReader.recordPattern(row, payloadStart, counterDigitPair); + // Split them into each array + for (let k = 0; k < 5; k++) { + let twoK = 2 * k; + counterBlack[k] = counterDigitPair[twoK]; + counterWhite[k] = counterDigitPair[twoK + 1]; + } + let bestMatch = ITFReader.decodeDigit(counterBlack); + resultString.append(bestMatch.toString()); + bestMatch = this.decodeDigit(counterWhite); + resultString.append(bestMatch.toString()); + counterDigitPair.forEach(function (counterDigit) { + payloadStart += counterDigit; + }); + } + } + /*/!** + * Identify where the start of the middle / payload section starts. + * + * @param row row of black/white values to search + * @return Array, containing index of start of 'start block' and end of + * 'start block' + *!/*/ + decodeStart(row) { + let endStart = ITFReader.skipWhiteSpace(row); + let startPattern = ITFReader.findGuardPattern(row, endStart, ITFReader.START_PATTERN); + // Determine the width of a narrow line in pixels. We can do this by + // getting the width of the start pattern and dividing by 4 because its + // made up of 4 narrow lines. + this.narrowLineWidth = (startPattern[1] - startPattern[0]) / 4; + this.validateQuietZone(row, startPattern[0]); + return startPattern; + } + /*/!** + * The start & end patterns must be pre/post fixed by a quiet zone. This + * zone must be at least 10 times the width of a narrow line. Scan back until + * we either get to the start of the barcode or match the necessary number of + * quiet zone pixels. + * + * Note: Its assumed the row is reversed when using this method to find + * quiet zone after the end pattern. + * + * ref: http://www.barcode-1.net/i25code.html + * + * @param row bit array representing the scanned barcode. + * @param startPattern index into row of the start or end pattern. + * @throws NotFoundException if the quiet zone cannot be found + *!/*/ + validateQuietZone(row, startPattern) { + let quietCount = this.narrowLineWidth * 10; // expect to find this many pixels of quiet zone + // if there are not so many pixel at all let's try as many as possible + quietCount = quietCount < startPattern ? quietCount : startPattern; + for (let i = startPattern - 1; quietCount > 0 && i >= 0; i--) { + if (row.get(i)) { + break; + } + quietCount--; + } + if (quietCount !== 0) { + // Unable to find the necessary number of quiet zone pixels. + throw new NotFoundException(); + } + } + /* + /!** + * Skip all whitespace until we get to the first black line. + * + * @param row row of black/white values to search + * @return index of the first black line. + * @throws NotFoundException Throws exception if no black lines are found in the row + *!/*/ + static skipWhiteSpace(row) { + const width = row.getSize(); + const endStart = row.getNextSet(0); + if (endStart === width) { + throw new NotFoundException(); + } + return endStart; + } + /*/!** + * Identify where the end of the middle / payload section ends. + * + * @param row row of black/white values to search + * @return Array, containing index of start of 'end block' and end of 'end + * block' + *!/*/ + decodeEnd(row) { + // For convenience, reverse the row and then + // search from 'the start' for the end block + row.reverse(); + try { + let endStart = ITFReader.skipWhiteSpace(row); + let endPattern; + try { + endPattern = ITFReader.findGuardPattern(row, endStart, ITFReader.END_PATTERN_REVERSED[0]); + } + catch (error) { + if (error instanceof NotFoundException) { + endPattern = ITFReader.findGuardPattern(row, endStart, ITFReader.END_PATTERN_REVERSED[1]); + } + } + // The start & end patterns must be pre/post fixed by a quiet zone. This + // zone must be at least 10 times the width of a narrow line. + // ref: http://www.barcode-1.net/i25code.html + this.validateQuietZone(row, endPattern[0]); + // Now recalculate the indices of where the 'endblock' starts & stops to + // accommodate + // the reversed nature of the search + let temp = endPattern[0]; + endPattern[0] = row.getSize() - endPattern[1]; + endPattern[1] = row.getSize() - temp; + return endPattern; + } + finally { + // Put the row back the right way. + row.reverse(); + } + } + /* + /!** + * @param row row of black/white values to search + * @param rowOffset position to start search + * @param pattern pattern of counts of number of black and white pixels that are + * being searched for as a pattern + * @return start/end horizontal offset of guard pattern, as an array of two + * ints + * @throws NotFoundException if pattern is not found + *!/*/ + static findGuardPattern(row, rowOffset, pattern) { + let patternLength = pattern.length; + let counters = new Int32Array(patternLength); + let width = row.getSize(); + let isWhite = false; + let counterPosition = 0; + let patternStart = rowOffset; + counters.fill(0); + for (let x = rowOffset; x < width; x++) { + if (row.get(x) !== isWhite) { + counters[counterPosition]++; + } + else { + if (counterPosition === patternLength - 1) { + if (OneDReader.patternMatchVariance(counters, pattern, ITFReader.MAX_INDIVIDUAL_VARIANCE) < ITFReader.MAX_AVG_VARIANCE) { + return [patternStart, x]; + } + patternStart += counters[0] + counters[1]; + System.arraycopy(counters, 2, counters, 0, counterPosition - 1); + counters[counterPosition - 1] = 0; + counters[counterPosition] = 0; + counterPosition--; + } + else { + counterPosition++; + } + counters[counterPosition] = 1; + isWhite = !isWhite; + } + } + throw new NotFoundException(); + } + /*/!** + * Attempts to decode a sequence of ITF black/white lines into single + * digit. + * + * @param counters the counts of runs of observed black/white/black/... values + * @return The decoded digit + * @throws NotFoundException if digit cannot be decoded + *!/*/ + static decodeDigit(counters) { + let bestVariance = ITFReader.MAX_AVG_VARIANCE; // worst variance we'll accept + let bestMatch = -1; + let max = ITFReader.PATTERNS.length; + for (let i = 0; i < max; i++) { + let pattern = ITFReader.PATTERNS[i]; + let variance = OneDReader.patternMatchVariance(counters, pattern, ITFReader.MAX_INDIVIDUAL_VARIANCE); + if (variance < bestVariance) { + bestVariance = variance; + bestMatch = i; + } + else if (variance === bestVariance) { + // if we find a second 'best match' with the same variance, we can not reliably report to have a suitable match + bestMatch = -1; + } + } + if (bestMatch >= 0) { + return bestMatch % 10; + } + else { + throw new NotFoundException(); + } + } + } + ITFReader.PATTERNS = [ + Int32Array.from([1, 1, 2, 2, 1]), + Int32Array.from([2, 1, 1, 1, 2]), + Int32Array.from([1, 2, 1, 1, 2]), + Int32Array.from([2, 2, 1, 1, 1]), + Int32Array.from([1, 1, 2, 1, 2]), + Int32Array.from([2, 1, 2, 1, 1]), + Int32Array.from([1, 2, 2, 1, 1]), + Int32Array.from([1, 1, 1, 2, 2]), + Int32Array.from([2, 1, 1, 2, 1]), + Int32Array.from([1, 2, 1, 2, 1]), + Int32Array.from([1, 1, 3, 3, 1]), + Int32Array.from([3, 1, 1, 1, 3]), + Int32Array.from([1, 3, 1, 1, 3]), + Int32Array.from([3, 3, 1, 1, 1]), + Int32Array.from([1, 1, 3, 1, 3]), + Int32Array.from([3, 1, 3, 1, 1]), + Int32Array.from([1, 3, 3, 1, 1]), + Int32Array.from([1, 1, 1, 3, 3]), + Int32Array.from([3, 1, 1, 3, 1]), + Int32Array.from([1, 3, 1, 3, 1]) // 9 + ]; + ITFReader.MAX_AVG_VARIANCE = 0.38; + ITFReader.MAX_INDIVIDUAL_VARIANCE = 0.5; + /* /!** Valid ITF lengths. Anything longer than the largest value is also allowed. *!/*/ + ITFReader.DEFAULT_ALLOWED_LENGTHS = [6, 8, 10, 12, 14]; + /*/!** + * Start/end guard pattern. + * + * Note: The end pattern is reversed because the row is reversed before + * searching for the END_PATTERN + *!/*/ + ITFReader.START_PATTERN = Int32Array.from([1, 1, 1, 1]); + ITFReader.END_PATTERN_REVERSED = [ + Int32Array.from([1, 1, 2]), + Int32Array.from([1, 1, 3]) // 3x + ]; + + /* + * Copyright 2008 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. + */ + /** + *Encapsulates functionality and implementation that is common to UPC and EAN families + * of one-dimensional barcodes.
+ * + * @author dswitkin@google.com (Daniel Switkin) + * @author Sean Owen + * @author alasdair@google.com (Alasdair Mackintosh) + */ + class AbstractUPCEANReader extends OneDReader { + constructor() { + super(...arguments); + this.decodeRowStringBuffer = ''; + } + // private final UPCEANExtensionSupport extensionReader; + // private final EANManufacturerOrgSupport eanManSupport; + /* + protected UPCEANReader() { + decodeRowStringBuffer = new StringBuilder(20); + extensionReader = new UPCEANExtensionSupport(); + eanManSupport = new EANManufacturerOrgSupport(); + } + */ + static findStartGuardPattern(row) { + let foundStart = false; + let startRange; + let nextStart = 0; + let counters = Int32Array.from([0, 0, 0]); + while (!foundStart) { + counters = Int32Array.from([0, 0, 0]); + startRange = AbstractUPCEANReader.findGuardPattern(row, nextStart, false, this.START_END_PATTERN, counters); + let start = startRange[0]; + nextStart = startRange[1]; + let quietStart = start - (nextStart - start); + if (quietStart >= 0) { + foundStart = row.isRange(quietStart, start, false); + } + } + return startRange; + } + static checkChecksum(s) { + return AbstractUPCEANReader.checkStandardUPCEANChecksum(s); + } + static checkStandardUPCEANChecksum(s) { + let length = s.length; + if (length === 0) + return false; + let check = parseInt(s.charAt(length - 1), 10); + return AbstractUPCEANReader.getStandardUPCEANChecksum(s.substring(0, length - 1)) === check; + } + static getStandardUPCEANChecksum(s) { + let length = s.length; + let sum = 0; + for (let i = length - 1; i >= 0; i -= 2) { + let digit = s.charAt(i).charCodeAt(0) - '0'.charCodeAt(0); + if (digit < 0 || digit > 9) { + throw new FormatException(); + } + sum += digit; + } + sum *= 3; + for (let i = length - 2; i >= 0; i -= 2) { + let digit = s.charAt(i).charCodeAt(0) - '0'.charCodeAt(0); + if (digit < 0 || digit > 9) { + throw new FormatException(); + } + sum += digit; + } + return (1000 - sum) % 10; + } + static decodeEnd(row, endStart) { + return AbstractUPCEANReader.findGuardPattern(row, endStart, false, AbstractUPCEANReader.START_END_PATTERN, new Int32Array(AbstractUPCEANReader.START_END_PATTERN.length).fill(0)); + } + /** + * @throws NotFoundException + */ + static findGuardPatternWithoutCounters(row, rowOffset, whiteFirst, pattern) { + return this.findGuardPattern(row, rowOffset, whiteFirst, pattern, new Int32Array(pattern.length)); + } + /** + * @param row row of black/white values to search + * @param rowOffset position to start search + * @param whiteFirst if true, indicates that the pattern specifies white/black/white/... + * pixel counts, otherwise, it is interpreted as black/white/black/... + * @param pattern pattern of counts of number of black and white pixels that are being + * searched for as a pattern + * @param counters array of counters, as long as pattern, to re-use + * @return start/end horizontal offset of guard pattern, as an array of two ints + * @throws NotFoundException if pattern is not found + */ + static findGuardPattern(row, rowOffset, whiteFirst, pattern, counters) { + let width = row.getSize(); + rowOffset = whiteFirst ? row.getNextUnset(rowOffset) : row.getNextSet(rowOffset); + let counterPosition = 0; + let patternStart = rowOffset; + let patternLength = pattern.length; + let isWhite = whiteFirst; + for (let x = rowOffset; x < width; x++) { + if (row.get(x) !== isWhite) { + counters[counterPosition]++; + } + else { + if (counterPosition === patternLength - 1) { + if (OneDReader.patternMatchVariance(counters, pattern, AbstractUPCEANReader.MAX_INDIVIDUAL_VARIANCE) < AbstractUPCEANReader.MAX_AVG_VARIANCE) { + return Int32Array.from([patternStart, x]); + } + patternStart += counters[0] + counters[1]; + let slice = counters.slice(2, counters.length - 1); + for (let i = 0; i < counterPosition - 1; i++) { + counters[i] = slice[i]; + } + counters[counterPosition - 1] = 0; + counters[counterPosition] = 0; + counterPosition--; + } + else { + counterPosition++; + } + counters[counterPosition] = 1; + isWhite = !isWhite; + } + } + throw new NotFoundException(); + } + static decodeDigit(row, counters, rowOffset, patterns) { + this.recordPattern(row, rowOffset, counters); + let bestVariance = this.MAX_AVG_VARIANCE; + let bestMatch = -1; + let max = patterns.length; + for (let i = 0; i < max; i++) { + let pattern = patterns[i]; + let variance = OneDReader.patternMatchVariance(counters, pattern, AbstractUPCEANReader.MAX_INDIVIDUAL_VARIANCE); + if (variance < bestVariance) { + bestVariance = variance; + bestMatch = i; + } + } + if (bestMatch >= 0) { + return bestMatch; + } + else { + throw new NotFoundException(); + } + } + } + // These two values are critical for determining how permissive the decoding will be. + // We've arrived at these values through a lot of trial and error. Setting them any higher + // lets false positives creep in quickly. + AbstractUPCEANReader.MAX_AVG_VARIANCE = 0.48; + AbstractUPCEANReader.MAX_INDIVIDUAL_VARIANCE = 0.7; + /** + * Start/end guard pattern. + */ + AbstractUPCEANReader.START_END_PATTERN = Int32Array.from([1, 1, 1]); + /** + * Pattern marking the middle of a UPC/EAN pattern, separating the two halves. + */ + AbstractUPCEANReader.MIDDLE_PATTERN = Int32Array.from([1, 1, 1, 1, 1]); + /** + * end guard pattern. + */ + AbstractUPCEANReader.END_PATTERN = Int32Array.from([1, 1, 1, 1, 1, 1]); + /** + * "Odd", or "L" patterns used to encode UPC/EAN digits. + */ + AbstractUPCEANReader.L_PATTERNS = [ + Int32Array.from([3, 2, 1, 1]), + Int32Array.from([2, 2, 2, 1]), + Int32Array.from([2, 1, 2, 2]), + Int32Array.from([1, 4, 1, 1]), + Int32Array.from([1, 1, 3, 2]), + Int32Array.from([1, 2, 3, 1]), + Int32Array.from([1, 1, 1, 4]), + Int32Array.from([1, 3, 1, 2]), + Int32Array.from([1, 2, 1, 3]), + Int32Array.from([3, 1, 1, 2]), + ]; + + /* + * Copyright (C) 2010 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. + */ + /** + * @see UPCEANExtension2Support + */ + class UPCEANExtension5Support { + constructor() { + this.CHECK_DIGIT_ENCODINGS = [0x18, 0x14, 0x12, 0x11, 0x0C, 0x06, 0x03, 0x0A, 0x09, 0x05]; + this.decodeMiddleCounters = Int32Array.from([0, 0, 0, 0]); + this.decodeRowStringBuffer = ''; + } + decodeRow(rowNumber, row, extensionStartRange) { + let result = this.decodeRowStringBuffer; + let end = this.decodeMiddle(row, extensionStartRange, result); + let resultString = result.toString(); + let extensionData = UPCEANExtension5Support.parseExtensionString(resultString); + let resultPoints = [ + new ResultPoint((extensionStartRange[0] + extensionStartRange[1]) / 2.0, rowNumber), + new ResultPoint(end, rowNumber) + ]; + let extensionResult = new Result(resultString, null, 0, resultPoints, BarcodeFormat$1.UPC_EAN_EXTENSION, new Date().getTime()); + if (extensionData != null) { + extensionResult.putAllMetadata(extensionData); + } + return extensionResult; + } + decodeMiddle(row, startRange, resultString) { + let counters = this.decodeMiddleCounters; + counters[0] = 0; + counters[1] = 0; + counters[2] = 0; + counters[3] = 0; + let end = row.getSize(); + let rowOffset = startRange[1]; + let lgPatternFound = 0; + for (let x = 0; x < 5 && rowOffset < end; x++) { + let bestMatch = AbstractUPCEANReader.decodeDigit(row, counters, rowOffset, AbstractUPCEANReader.L_AND_G_PATTERNS); + resultString += String.fromCharCode(('0'.charCodeAt(0) + bestMatch % 10)); + for (let counter of counters) { + rowOffset += counter; + } + if (bestMatch >= 10) { + lgPatternFound |= 1 << (4 - x); + } + if (x !== 4) { + // Read off separator if not last + rowOffset = row.getNextSet(rowOffset); + rowOffset = row.getNextUnset(rowOffset); + } + } + if (resultString.length !== 5) { + throw new NotFoundException(); + } + let checkDigit = this.determineCheckDigit(lgPatternFound); + if (UPCEANExtension5Support.extensionChecksum(resultString.toString()) !== checkDigit) { + throw new NotFoundException(); + } + return rowOffset; + } + static extensionChecksum(s) { + let length = s.length; + let sum = 0; + for (let i = length - 2; i >= 0; i -= 2) { + sum += s.charAt(i).charCodeAt(0) - '0'.charCodeAt(0); + } + sum *= 3; + for (let i = length - 1; i >= 0; i -= 2) { + sum += s.charAt(i).charCodeAt(0) - '0'.charCodeAt(0); + } + sum *= 3; + return sum % 10; + } + determineCheckDigit(lgPatternFound) { + for (let d = 0; d < 10; d++) { + if (lgPatternFound === this.CHECK_DIGIT_ENCODINGS[d]) { + return d; + } + } + throw new NotFoundException(); + } + static parseExtensionString(raw) { + if (raw.length !== 5) { + return null; + } + let value = UPCEANExtension5Support.parseExtension5String(raw); + if (value == null) { + return null; + } + return new Map([[ResultMetadataType$1.SUGGESTED_PRICE, value]]); + } + static parseExtension5String(raw) { + let currency; + switch (raw.charAt(0)) { + case '0': + currency = '£'; + break; + case '5': + currency = '$'; + break; + case '9': + // Reference: http://www.jollytech.com + switch (raw) { + case '90000': + // No suggested retail price + return null; + case '99991': + // Complementary + return '0.00'; + case '99990': + return 'Used'; + } + // Otherwise... unknown currency? + currency = ''; + break; + default: + currency = ''; + break; + } + let rawAmount = parseInt(raw.substring(1)); + let unitsString = (rawAmount / 100).toString(); + let hundredths = rawAmount % 100; + let hundredthsString = hundredths < 10 ? '0' + hundredths : hundredths.toString(); // fixme + return currency + unitsString + '.' + hundredthsString; + } + } + + /* + * Copyright (C) 2012 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. + */ + /** + * @see UPCEANExtension5Support + */ + class UPCEANExtension2Support { + constructor() { + this.decodeMiddleCounters = Int32Array.from([0, 0, 0, 0]); + this.decodeRowStringBuffer = ''; + } + decodeRow(rowNumber, row, extensionStartRange) { + let result = this.decodeRowStringBuffer; + let end = this.decodeMiddle(row, extensionStartRange, result); + let resultString = result.toString(); + let extensionData = UPCEANExtension2Support.parseExtensionString(resultString); + let resultPoints = [ + new ResultPoint((extensionStartRange[0] + extensionStartRange[1]) / 2.0, rowNumber), + new ResultPoint(end, rowNumber) + ]; + let extensionResult = new Result(resultString, null, 0, resultPoints, BarcodeFormat$1.UPC_EAN_EXTENSION, new Date().getTime()); + if (extensionData != null) { + extensionResult.putAllMetadata(extensionData); + } + return extensionResult; + } + decodeMiddle(row, startRange, resultString) { + let counters = this.decodeMiddleCounters; + counters[0] = 0; + counters[1] = 0; + counters[2] = 0; + counters[3] = 0; + let end = row.getSize(); + let rowOffset = startRange[1]; + let checkParity = 0; + for (let x = 0; x < 2 && rowOffset < end; x++) { + let bestMatch = AbstractUPCEANReader.decodeDigit(row, counters, rowOffset, AbstractUPCEANReader.L_AND_G_PATTERNS); + resultString += String.fromCharCode(('0'.charCodeAt(0) + bestMatch % 10)); + for (let counter of counters) { + rowOffset += counter; + } + if (bestMatch >= 10) { + checkParity |= 1 << (1 - x); + } + if (x !== 1) { + // Read off separator if not last + rowOffset = row.getNextSet(rowOffset); + rowOffset = row.getNextUnset(rowOffset); + } + } + if (resultString.length !== 2) { + throw new NotFoundException(); + } + if (parseInt(resultString.toString()) % 4 !== checkParity) { + throw new NotFoundException(); + } + return rowOffset; + } + static parseExtensionString(raw) { + if (raw.length !== 2) { + return null; + } + return new Map([[ResultMetadataType$1.ISSUE_NUMBER, parseInt(raw)]]); + } + } + + /* + * Copyright (C) 2010 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. + */ + class UPCEANExtensionSupport { + static decodeRow(rowNumber, row, rowOffset) { + let extensionStartRange = AbstractUPCEANReader.findGuardPattern(row, rowOffset, false, this.EXTENSION_START_PATTERN, new Int32Array(this.EXTENSION_START_PATTERN.length).fill(0)); + try { + // return null; + let fiveSupport = new UPCEANExtension5Support(); + return fiveSupport.decodeRow(rowNumber, row, extensionStartRange); + } + catch (err) { + // return null; + let twoSupport = new UPCEANExtension2Support(); + return twoSupport.decodeRow(rowNumber, row, extensionStartRange); + } + } + } + UPCEANExtensionSupport.EXTENSION_START_PATTERN = Int32Array.from([1, 1, 2]); + + /* + * Copyright 2008 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. + */ + /** + *Encapsulates functionality and implementation that is common to UPC and EAN families + * of one-dimensional barcodes.
+ * + * @author dswitkin@google.com (Daniel Switkin) + * @author Sean Owen + * @author alasdair@google.com (Alasdair Mackintosh) + */ + class UPCEANReader extends AbstractUPCEANReader { + constructor() { + super(); + this.decodeRowStringBuffer = ''; + UPCEANReader.L_AND_G_PATTERNS = UPCEANReader.L_PATTERNS.map(arr => Int32Array.from(arr)); + for (let i = 10; i < 20; i++) { + let widths = UPCEANReader.L_PATTERNS[i - 10]; + let reversedWidths = new Int32Array(widths.length); + for (let j = 0; j < widths.length; j++) { + reversedWidths[j] = widths[widths.length - j - 1]; + } + UPCEANReader.L_AND_G_PATTERNS[i] = reversedWidths; + } + } + decodeRow(rowNumber, row, hints) { + let startGuardRange = UPCEANReader.findStartGuardPattern(row); + let resultPointCallback = hints == null ? null : hints.get(DecodeHintType$1.NEED_RESULT_POINT_CALLBACK); + if (resultPointCallback != null) { + const resultPoint = new ResultPoint((startGuardRange[0] + startGuardRange[1]) / 2.0, rowNumber); + resultPointCallback.foundPossibleResultPoint(resultPoint); + } + let budello = this.decodeMiddle(row, startGuardRange, this.decodeRowStringBuffer); + let endStart = budello.rowOffset; + let result = budello.resultString; + if (resultPointCallback != null) { + const resultPoint = new ResultPoint(endStart, rowNumber); + resultPointCallback.foundPossibleResultPoint(resultPoint); + } + let endRange = UPCEANReader.decodeEnd(row, endStart); + if (resultPointCallback != null) { + const resultPoint = new ResultPoint((endRange[0] + endRange[1]) / 2.0, rowNumber); + resultPointCallback.foundPossibleResultPoint(resultPoint); + } + // Make sure there is a quiet zone at least as big as the end pattern after the barcode. The + // spec might want more whitespace, but in practice this is the maximum we can count on. + let end = endRange[1]; + let quietEnd = end + (end - endRange[0]); + if (quietEnd >= row.getSize() || !row.isRange(end, quietEnd, false)) { + throw new NotFoundException(); + } + let resultString = result.toString(); + // UPC/EAN should never be less than 8 chars anyway + if (resultString.length < 8) { + throw new FormatException(); + } + if (!UPCEANReader.checkChecksum(resultString)) { + throw new ChecksumException(); + } + let left = (startGuardRange[1] + startGuardRange[0]) / 2.0; + let right = (endRange[1] + endRange[0]) / 2.0; + let format = this.getBarcodeFormat(); + let resultPoint = [new ResultPoint(left, rowNumber), new ResultPoint(right, rowNumber)]; + let decodeResult = new Result(resultString, null, 0, resultPoint, format, new Date().getTime()); + let extensionLength = 0; + try { + let extensionResult = UPCEANExtensionSupport.decodeRow(rowNumber, row, endRange[1]); + decodeResult.putMetadata(ResultMetadataType$1.UPC_EAN_EXTENSION, extensionResult.getText()); + decodeResult.putAllMetadata(extensionResult.getResultMetadata()); + decodeResult.addResultPoints(extensionResult.getResultPoints()); + extensionLength = extensionResult.getText().length; + } + catch (err) { + } + let allowedExtensions = hints == null ? null : hints.get(DecodeHintType$1.ALLOWED_EAN_EXTENSIONS); + if (allowedExtensions != null) { + let valid = false; + for (let length in allowedExtensions) { + if (extensionLength.toString() === length) { // check me + valid = true; + break; + } + } + if (!valid) { + throw new NotFoundException(); + } + } + if (format === BarcodeFormat$1.EAN_13 || format === BarcodeFormat$1.UPC_A) ; + return decodeResult; + } + static checkChecksum(s) { + return UPCEANReader.checkStandardUPCEANChecksum(s); + } + static checkStandardUPCEANChecksum(s) { + let length = s.length; + if (length === 0) + return false; + let check = parseInt(s.charAt(length - 1), 10); + return UPCEANReader.getStandardUPCEANChecksum(s.substring(0, length - 1)) === check; + } + static getStandardUPCEANChecksum(s) { + let length = s.length; + let sum = 0; + for (let i = length - 1; i >= 0; i -= 2) { + let digit = s.charAt(i).charCodeAt(0) - '0'.charCodeAt(0); + if (digit < 0 || digit > 9) { + throw new FormatException(); + } + sum += digit; + } + sum *= 3; + for (let i = length - 2; i >= 0; i -= 2) { + let digit = s.charAt(i).charCodeAt(0) - '0'.charCodeAt(0); + if (digit < 0 || digit > 9) { + throw new FormatException(); + } + sum += digit; + } + return (1000 - sum) % 10; + } + static decodeEnd(row, endStart) { + return UPCEANReader.findGuardPattern(row, endStart, false, UPCEANReader.START_END_PATTERN, new Int32Array(UPCEANReader.START_END_PATTERN.length).fill(0)); + } + } + + /* + * Copyright 2008 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. + */ + /** + *Implements decoding of the EAN-13 format.
+ * + * @author dswitkin@google.com (Daniel Switkin) + * @author Sean Owen + * @author alasdair@google.com (Alasdair Mackintosh) + */ + class EAN13Reader extends UPCEANReader { + constructor() { + super(); + this.decodeMiddleCounters = Int32Array.from([0, 0, 0, 0]); + } + decodeMiddle(row, startRange, resultString) { + let counters = this.decodeMiddleCounters; + counters[0] = 0; + counters[1] = 0; + counters[2] = 0; + counters[3] = 0; + let end = row.getSize(); + let rowOffset = startRange[1]; + let lgPatternFound = 0; + for (let x = 0; x < 6 && rowOffset < end; x++) { + let bestMatch = UPCEANReader.decodeDigit(row, counters, rowOffset, UPCEANReader.L_AND_G_PATTERNS); + resultString += String.fromCharCode(('0'.charCodeAt(0) + bestMatch % 10)); + for (let counter of counters) { + rowOffset += counter; + } + if (bestMatch >= 10) { + lgPatternFound |= 1 << (5 - x); + } + } + resultString = EAN13Reader.determineFirstDigit(resultString, lgPatternFound); + let middleRange = UPCEANReader.findGuardPattern(row, rowOffset, true, UPCEANReader.MIDDLE_PATTERN, new Int32Array(UPCEANReader.MIDDLE_PATTERN.length).fill(0)); + rowOffset = middleRange[1]; + for (let x = 0; x < 6 && rowOffset < end; x++) { + let bestMatch = UPCEANReader.decodeDigit(row, counters, rowOffset, UPCEANReader.L_PATTERNS); + resultString += String.fromCharCode(('0'.charCodeAt(0) + bestMatch)); + for (let counter of counters) { + rowOffset += counter; + } + } + return { rowOffset, resultString }; + } + getBarcodeFormat() { + return BarcodeFormat$1.EAN_13; + } + static determineFirstDigit(resultString, lgPatternFound) { + for (let d = 0; d < 10; d++) { + if (lgPatternFound === this.FIRST_DIGIT_ENCODINGS[d]) { + resultString = String.fromCharCode(('0'.charCodeAt(0) + d)) + resultString; + return resultString; + } + } + throw new NotFoundException(); + } + } + EAN13Reader.FIRST_DIGIT_ENCODINGS = [0x00, 0x0B, 0x0D, 0xE, 0x13, 0x19, 0x1C, 0x15, 0x16, 0x1A]; + + /* + * Copyright 2008 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. + */ + /** + *Implements decoding of the EAN-8 format.
+ * + * @author Sean Owen + */ + class EAN8Reader extends UPCEANReader { + constructor() { + super(); + this.decodeMiddleCounters = Int32Array.from([0, 0, 0, 0]); + } + decodeMiddle(row, startRange, resultString) { + const counters = this.decodeMiddleCounters; + counters[0] = 0; + counters[1] = 0; + counters[2] = 0; + counters[3] = 0; + let end = row.getSize(); + let rowOffset = startRange[1]; + for (let x = 0; x < 4 && rowOffset < end; x++) { + let bestMatch = UPCEANReader.decodeDigit(row, counters, rowOffset, UPCEANReader.L_PATTERNS); + resultString += String.fromCharCode(('0'.charCodeAt(0) + bestMatch)); + for (let counter of counters) { + rowOffset += counter; + } + } + let middleRange = UPCEANReader.findGuardPattern(row, rowOffset, true, UPCEANReader.MIDDLE_PATTERN, new Int32Array(UPCEANReader.MIDDLE_PATTERN.length).fill(0)); + rowOffset = middleRange[1]; + for (let x = 0; x < 4 && rowOffset < end; x++) { + let bestMatch = UPCEANReader.decodeDigit(row, counters, rowOffset, UPCEANReader.L_PATTERNS); + resultString += String.fromCharCode(('0'.charCodeAt(0) + bestMatch)); + for (let counter of counters) { + rowOffset += counter; + } + } + return { rowOffset, resultString }; + } + getBarcodeFormat() { + return BarcodeFormat$1.EAN_8; + } + } + + /* + * Copyright 2008 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. + */ + /** + * Encapsulates functionality and implementation that is common to all families + * of one-dimensional barcodes. + * + * @author dswitkin@google.com (Daniel Switkin) + * @author Sean Owen + * @author sam2332 (Sam Rudloff) + * + * @source https://github.com/zxing/zxing/blob/3c96923276dd5785d58eb970b6ba3f80d36a9505/core/src/main/java/com/google/zxing/oned/UPCAReader.java + * + * @experimental + */ + class UPCAReader extends UPCEANReader { + constructor() { + super(...arguments); + this.ean13Reader = new EAN13Reader(); + } + // @Override + getBarcodeFormat() { + return BarcodeFormat$1.UPC_A; + } + // Note that we don't try rotation without the try harder flag, even if rotation was supported. + // @Override + decode(image, hints) { + return this.maybeReturnResult(this.ean13Reader.decode(image)); + } + // @Override + decodeRow(rowNumber, row, hints) { + return this.maybeReturnResult(this.ean13Reader.decodeRow(rowNumber, row, hints)); + } + // @Override + decodeMiddle(row, startRange, resultString) { + return this.ean13Reader.decodeMiddle(row, startRange, resultString); + } + maybeReturnResult(result) { + let text = result.getText(); + if (text.charAt(0) === '0') { + let upcaResult = new Result(text.substring(1), null, null, result.getResultPoints(), BarcodeFormat$1.UPC_A); + if (result.getResultMetadata() != null) { + upcaResult.putAllMetadata(result.getResultMetadata()); + } + return upcaResult; + } + else { + throw new NotFoundException(); + } + } + reset() { + this.ean13Reader.reset(); + } + } + + /* + * Copyright 2008 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. + */ + // package com.google.zxing.oned; + // import com.google.zxing.BarcodeFormat; + // import com.google.zxing.FormatException; + // import com.google.zxing.NotFoundException; + // import com.google.zxing.common.BitArray; + /** + *Implements decoding of the UPC-E format.
+ *This is a great reference for + * UPC-E information.
+ * + * @author Sean Owen + * + * @source https://github.com/zxing/zxing/blob/3c96923276dd5785d58eb970b6ba3f80d36a9505/core/src/main/java/com/google/zxing/oned/UPCEReader.java + * + * @experimental + */ + /* final */ class UPCEReader extends UPCEANReader { + constructor() { + super(); + this.decodeMiddleCounters = new Int32Array(4); + } + /** + * @throws NotFoundException + */ + // @Override + decodeMiddle(row, startRange, result) { + const counters = this.decodeMiddleCounters.map(x => x); + counters[0] = 0; + counters[1] = 0; + counters[2] = 0; + counters[3] = 0; + const end = row.getSize(); + let rowOffset = startRange[1]; + let lgPatternFound = 0; + for (let x = 0; x < 6 && rowOffset < end; x++) { + const bestMatch = UPCEReader.decodeDigit(row, counters, rowOffset, UPCEReader.L_AND_G_PATTERNS); + result += String.fromCharCode(('0'.charCodeAt(0) + bestMatch % 10)); + for (let counter of counters) { + rowOffset += counter; + } + if (bestMatch >= 10) { + lgPatternFound |= 1 << (5 - x); + } + } + UPCEReader.determineNumSysAndCheckDigit(new StringBuilder(result), lgPatternFound); + return rowOffset; + } + /** + * @throws NotFoundException + */ + // @Override + decodeEnd(row, endStart) { + return UPCEReader.findGuardPatternWithoutCounters(row, endStart, true, UPCEReader.MIDDLE_END_PATTERN); + } + /** + * @throws FormatException + */ + // @Override + checkChecksum(s) { + return UPCEANReader.checkChecksum(UPCEReader.convertUPCEtoUPCA(s)); + } + /** + * @throws NotFoundException + */ + static determineNumSysAndCheckDigit(resultString, lgPatternFound) { + for (let numSys = 0; numSys <= 1; numSys++) { + for (let d = 0; d < 10; d++) { + if (lgPatternFound === this.NUMSYS_AND_CHECK_DIGIT_PATTERNS[numSys][d]) { + resultString.insert(0, /*(char)*/ ('0' + numSys)); + resultString.append(/*(char)*/ ('0' + d)); + return; + } + } + } + throw NotFoundException.getNotFoundInstance(); + } + // @Override + getBarcodeFormat() { + return BarcodeFormat$1.UPC_E; + } + /** + * Expands a UPC-E value back into its full, equivalent UPC-A code value. + * + * @param upce UPC-E code as string of digits + * @return equivalent UPC-A code as string of digits + */ + static convertUPCEtoUPCA(upce) { + // the following line is equivalent to upce.getChars(1, 7, upceChars, 0); + const upceChars = upce.slice(1, 7).split('').map(x => x.charCodeAt(0)); + const result = new StringBuilder( /*12*/); + result.append(upce.charAt(0)); + let lastChar = upceChars[5]; + switch (lastChar) { + case 0: + case 1: + case 2: + result.appendChars(upceChars, 0, 2); + result.append(lastChar); + result.append('0000'); + result.appendChars(upceChars, 2, 3); + break; + case 3: + result.appendChars(upceChars, 0, 3); + result.append('00000'); + result.appendChars(upceChars, 3, 2); + break; + case 4: + result.appendChars(upceChars, 0, 4); + result.append('00000'); + result.append(upceChars[4]); + break; + default: + result.appendChars(upceChars, 0, 5); + result.append('0000'); + result.append(lastChar); + break; + } + // Only append check digit in conversion if supplied + if (upce.length >= 8) { + result.append(upce.charAt(7)); + } + return result.toString(); + } + } + /** + * The pattern that marks the middle, and end, of a UPC-E pattern. + * There is no "second half" to a UPC-E barcode. + */ + UPCEReader.MIDDLE_END_PATTERN = Int32Array.from([1, 1, 1, 1, 1, 1]); + // For an UPC-E barcode, the final digit is represented by the parities used + // to encode the middle six digits, according to the table below. + // + // Parity of next 6 digits + // Digit 0 1 2 3 4 5 + // 0 Even Even Even Odd Odd Odd + // 1 Even Even Odd Even Odd Odd + // 2 Even Even Odd Odd Even Odd + // 3 Even Even Odd Odd Odd Even + // 4 Even Odd Even Even Odd Odd + // 5 Even Odd Odd Even Even Odd + // 6 Even Odd Odd Odd Even Even + // 7 Even Odd Even Odd Even Odd + // 8 Even Odd Even Odd Odd Even + // 9 Even Odd Odd Even Odd Even + // + // The encoding is represented by the following array, which is a bit pattern + // using Odd = 0 and Even = 1. For example, 5 is represented by: + // + // Odd Even Even Odd Odd Even + // in binary: + // 0 1 1 0 0 1 == 0x19 + // + /** + * See {@link #L_AND_G_PATTERNS}; these values similarly represent patterns of + * even-odd parity encodings of digits that imply both the number system (0 or 1) + * used, and the check digit. + */ + UPCEReader.NUMSYS_AND_CHECK_DIGIT_PATTERNS = [ + Int32Array.from([0x38, 0x34, 0x32, 0x31, 0x2C, 0x26, 0x23, 0x2A, 0x29, 0x25]), + Int32Array.from([0x07, 0x0B, 0x0D, 0x0E, 0x13, 0x19, 0x1C, 0x15, 0x16, 0x1]), + ]; + + /* + * Copyright 2008 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. + */ + /** + *A reader that can read all available UPC/EAN formats. If a caller wants to try to + * read all such formats, it is most efficient to use this implementation rather than invoke + * individual readers.
+ * + * @author Sean Owen + */ + class MultiFormatUPCEANReader extends OneDReader { + constructor(hints) { + super(); + let possibleFormats = hints == null ? null : hints.get(DecodeHintType$1.POSSIBLE_FORMATS); + let readers = []; + if (possibleFormats != null) { + if (possibleFormats.indexOf(BarcodeFormat$1.EAN_13) > -1) { + readers.push(new EAN13Reader()); + } + else if (possibleFormats.indexOf(BarcodeFormat$1.UPC_A) > -1) { + readers.push(new UPCAReader()); + } + if (possibleFormats.indexOf(BarcodeFormat$1.EAN_8) > -1) { + readers.push(new EAN8Reader()); + } + if (possibleFormats.indexOf(BarcodeFormat$1.UPC_E) > -1) { + readers.push(new UPCEReader()); + } + } + if (readers.length === 0) { + readers.push(new EAN13Reader()); + // UPC-A is covered by EAN-13 + readers.push(new EAN8Reader()); + readers.push(new UPCEReader()); + } + this.readers = readers; + } + decodeRow(rowNumber, row, hints) { + for (let reader of this.readers) { + try { + // const result: Result = reader.decodeRow(rowNumber, row, startGuardPattern, hints); + const result = reader.decodeRow(rowNumber, row, hints); + // Special case: a 12-digit code encoded in UPC-A is identical to a "0" + // followed by those 12 digits encoded as EAN-13. Each will recognize such a code, + // UPC-A as a 12-digit string and EAN-13 as a 13-digit string starting with "0". + // Individually these are correct and their readers will both read such a code + // and correctly call it EAN-13, or UPC-A, respectively. + // + // In this case, if we've been looking for both types, we'd like to call it + // a UPC-A code. But for efficiency we only run the EAN-13 decoder to also read + // UPC-A. So we special case it here, and convert an EAN-13 result to a UPC-A + // result if appropriate. + // + // But, don't return UPC-A if UPC-A was not a requested format! + const ean13MayBeUPCA = result.getBarcodeFormat() === BarcodeFormat$1.EAN_13 && + result.getText().charAt(0) === '0'; + // @SuppressWarnings("unchecked") + const possibleFormats = hints == null ? null : hints.get(DecodeHintType$1.POSSIBLE_FORMATS); + const canReturnUPCA = possibleFormats == null || possibleFormats.includes(BarcodeFormat$1.UPC_A); + if (ean13MayBeUPCA && canReturnUPCA) { + const rawBytes = result.getRawBytes(); + // Transfer the metadata across + const resultUPCA = new Result(result.getText().substring(1), rawBytes, rawBytes.length, result.getResultPoints(), BarcodeFormat$1.UPC_A); + resultUPCA.putAllMetadata(result.getResultMetadata()); + return resultUPCA; + } + return result; + } + catch (err) { + // continue; + } + } + throw new NotFoundException(); + } + reset() { + for (let reader of this.readers) { + reader.reset(); + } + } + } + + // import Integer from '../../util/Integer'; + // import Float from '../../util/Float'; + class AbstractRSSReader extends OneDReader { + constructor() { + super(); + this.decodeFinderCounters = new Int32Array(4); + this.dataCharacterCounters = new Int32Array(8); + this.oddRoundingErrors = new Array(4); + this.evenRoundingErrors = new Array(4); + this.oddCounts = new Array(this.dataCharacterCounters.length / 2); + this.evenCounts = new Array(this.dataCharacterCounters.length / 2); + } + getDecodeFinderCounters() { + return this.decodeFinderCounters; + } + getDataCharacterCounters() { + return this.dataCharacterCounters; + } + getOddRoundingErrors() { + return this.oddRoundingErrors; + } + getEvenRoundingErrors() { + return this.evenRoundingErrors; + } + getOddCounts() { + return this.oddCounts; + } + getEvenCounts() { + return this.evenCounts; + } + parseFinderValue(counters, finderPatterns) { + for (let value = 0; value < finderPatterns.length; value++) { + if (OneDReader.patternMatchVariance(counters, finderPatterns[value], AbstractRSSReader.MAX_INDIVIDUAL_VARIANCE) < AbstractRSSReader.MAX_AVG_VARIANCE) { + return value; + } + } + throw new NotFoundException(); + } + /** + * @param array values to sum + * @return sum of values + * @deprecated call {@link MathUtils#sum(int[])} + */ + static count(array) { + return MathUtils.sum(new Int32Array(array)); + } + static increment(array, errors) { + let index = 0; + let biggestError = errors[0]; + for (let i = 1; i < array.length; i++) { + if (errors[i] > biggestError) { + biggestError = errors[i]; + index = i; + } + } + array[index]++; + } + static decrement(array, errors) { + let index = 0; + let biggestError = errors[0]; + for (let i = 1; i < array.length; i++) { + if (errors[i] < biggestError) { + biggestError = errors[i]; + index = i; + } + } + array[index]--; + } + static isFinderPattern(counters) { + let firstTwoSum = counters[0] + counters[1]; + let sum = firstTwoSum + counters[2] + counters[3]; + let ratio = firstTwoSum / sum; + if (ratio >= AbstractRSSReader.MIN_FINDER_PATTERN_RATIO && ratio <= AbstractRSSReader.MAX_FINDER_PATTERN_RATIO) { + // passes ratio test in spec, but see if the counts are unreasonable + let minCounter = Number.MAX_SAFE_INTEGER; + let maxCounter = Number.MIN_SAFE_INTEGER; + for (let counter of counters) { + if (counter > maxCounter) { + maxCounter = counter; + } + if (counter < minCounter) { + minCounter = counter; + } + } + return maxCounter < 10 * minCounter; + } + return false; + } + } + AbstractRSSReader.MAX_AVG_VARIANCE = 0.2; + AbstractRSSReader.MAX_INDIVIDUAL_VARIANCE = 0.45; + AbstractRSSReader.MIN_FINDER_PATTERN_RATIO = 9.5 / 12.0; + AbstractRSSReader.MAX_FINDER_PATTERN_RATIO = 12.5 / 14.0; + + class DataCharacter { + constructor(value, checksumPortion) { + this.value = value; + this.checksumPortion = checksumPortion; + } + getValue() { + return this.value; + } + getChecksumPortion() { + return this.checksumPortion; + } + toString() { + return this.value + '(' + this.checksumPortion + ')'; + } + equals(o) { + if (!(o instanceof DataCharacter)) { + return false; + } + const that = o; + return this.value === that.value && this.checksumPortion === that.checksumPortion; + } + hashCode() { + return this.value ^ this.checksumPortion; + } + } + + class FinderPattern { + constructor(value, startEnd, start, end, rowNumber) { + this.value = value; + this.startEnd = startEnd; + this.value = value; + this.startEnd = startEnd; + this.resultPoints = new Array(); + this.resultPoints.push(new ResultPoint(start, rowNumber)); + this.resultPoints.push(new ResultPoint(end, rowNumber)); + } + getValue() { + return this.value; + } + getStartEnd() { + return this.startEnd; + } + getResultPoints() { + return this.resultPoints; + } + equals(o) { + if (!(o instanceof FinderPattern)) { + return false; + } + const that = o; + return this.value === that.value; + } + hashCode() { + return this.value; + } + } + + /** + * RSS util functions. + */ + class RSSUtils { + constructor() { } + static getRSSvalue(widths, maxWidth, noNarrow) { + let n = 0; + for (let width of widths) { + n += width; + } + let val = 0; + let narrowMask = 0; + let elements = widths.length; + for (let bar = 0; bar < elements - 1; bar++) { + let elmWidth; + for (elmWidth = 1, narrowMask |= 1 << bar; elmWidth < widths[bar]; elmWidth++, narrowMask &= ~(1 << bar)) { + let subVal = RSSUtils.combins(n - elmWidth - 1, elements - bar - 2); + if (noNarrow && (narrowMask === 0) && (n - elmWidth - (elements - bar - 1) >= elements - bar - 1)) { + subVal -= RSSUtils.combins(n - elmWidth - (elements - bar), elements - bar - 2); + } + if (elements - bar - 1 > 1) { + let lessVal = 0; + for (let mxwElement = n - elmWidth - (elements - bar - 2); mxwElement > maxWidth; mxwElement--) { + lessVal += RSSUtils.combins(n - elmWidth - mxwElement - 1, elements - bar - 3); + } + subVal -= lessVal * (elements - 1 - bar); + } + else if (n - elmWidth > maxWidth) { + subVal--; + } + val += subVal; + } + n -= elmWidth; + } + return val; + } + static combins(n, r) { + let maxDenom; + let minDenom; + if (n - r > r) { + minDenom = r; + maxDenom = n - r; + } + else { + minDenom = n - r; + maxDenom = r; + } + let val = 1; + let j = 1; + for (let i = n; i > maxDenom; i--) { + val *= i; + if (j <= minDenom) { + val /= j; + j++; + } + } + while ((j <= minDenom)) { + val /= j; + j++; + } + return val; + } + } + + class BitArrayBuilder { + static buildBitArray(pairs) { + let charNumber = (pairs.length * 2) - 1; + if (pairs[pairs.length - 1].getRightChar() == null) { + charNumber -= 1; + } + let size = 12 * charNumber; + let binary = new BitArray(size); + let accPos = 0; + let firstPair = pairs[0]; + let firstValue = firstPair.getRightChar().getValue(); + for (let i = 11; i >= 0; --i) { + if ((firstValue & (1 << i)) != 0) { + binary.set(accPos); + } + accPos++; + } + for (let i = 1; i < pairs.length; ++i) { + let currentPair = pairs[i]; + let leftValue = currentPair.getLeftChar().getValue(); + for (let j = 11; j >= 0; --j) { + if ((leftValue & (1 << j)) != 0) { + binary.set(accPos); + } + accPos++; + } + if (currentPair.getRightChar() != null) { + let rightValue = currentPair.getRightChar().getValue(); + for (let j = 11; j >= 0; --j) { + if ((rightValue & (1 << j)) != 0) { + binary.set(accPos); + } + accPos++; + } + } + } + return binary; + } + } + + class BlockParsedResult { + constructor(finished, decodedInformation) { + if (decodedInformation) { + this.decodedInformation = null; + } + else { + this.finished = finished; + this.decodedInformation = decodedInformation; + } + } + getDecodedInformation() { + return this.decodedInformation; + } + isFinished() { + return this.finished; + } + } + + class DecodedObject { + constructor(newPosition) { + this.newPosition = newPosition; + } + getNewPosition() { + return this.newPosition; + } + } + + class DecodedChar extends DecodedObject { + constructor(newPosition, value) { + super(newPosition); + this.value = value; + } + getValue() { + return this.value; + } + isFNC1() { + return this.value === DecodedChar.FNC1; + } + } + DecodedChar.FNC1 = '$'; + + class DecodedInformation extends DecodedObject { + constructor(newPosition, newString, remainingValue) { + super(newPosition); + if (remainingValue) { + this.remaining = true; + this.remainingValue = this.remainingValue; + } + else { + this.remaining = false; + this.remainingValue = 0; + } + this.newString = newString; + } + getNewString() { + return this.newString; + } + isRemaining() { + return this.remaining; + } + getRemainingValue() { + return this.remainingValue; + } + } + + class DecodedNumeric extends DecodedObject { + constructor(newPosition, firstDigit, secondDigit) { + super(newPosition); + if (firstDigit < 0 || firstDigit > 10 || secondDigit < 0 || secondDigit > 10) { + throw new FormatException(); + } + this.firstDigit = firstDigit; + this.secondDigit = secondDigit; + } + getFirstDigit() { + return this.firstDigit; + } + getSecondDigit() { + return this.secondDigit; + } + getValue() { + return this.firstDigit * 10 + this.secondDigit; + } + isFirstDigitFNC1() { + return this.firstDigit === DecodedNumeric.FNC1; + } + isSecondDigitFNC1() { + return this.secondDigit === DecodedNumeric.FNC1; + } + isAnyFNC1() { + return this.firstDigit === DecodedNumeric.FNC1 || this.secondDigit === DecodedNumeric.FNC1; + } + } + DecodedNumeric.FNC1 = 10; + + class FieldParser { + constructor() { + } + static parseFieldsInGeneralPurpose(rawInformation) { + if (!rawInformation) { + return null; + } + // Processing 2-digit AIs + if (rawInformation.length < 2) { + throw new NotFoundException(); + } + let firstTwoDigits = rawInformation.substring(0, 2); + for (let dataLength of FieldParser.TWO_DIGIT_DATA_LENGTH) { + if (dataLength[0] === firstTwoDigits) { + if (dataLength[1] === FieldParser.VARIABLE_LENGTH) { + return FieldParser.processVariableAI(2, dataLength[2], rawInformation); + } + return FieldParser.processFixedAI(2, dataLength[1], rawInformation); + } + } + if (rawInformation.length < 3) { + throw new NotFoundException(); + } + let firstThreeDigits = rawInformation.substring(0, 3); + for (let dataLength of FieldParser.THREE_DIGIT_DATA_LENGTH) { + if (dataLength[0] === firstThreeDigits) { + if (dataLength[1] === FieldParser.VARIABLE_LENGTH) { + return FieldParser.processVariableAI(3, dataLength[2], rawInformation); + } + return FieldParser.processFixedAI(3, dataLength[1], rawInformation); + } + } + for (let dataLength of FieldParser.THREE_DIGIT_PLUS_DIGIT_DATA_LENGTH) { + if (dataLength[0] === firstThreeDigits) { + if (dataLength[1] === FieldParser.VARIABLE_LENGTH) { + return FieldParser.processVariableAI(4, dataLength[2], rawInformation); + } + return FieldParser.processFixedAI(4, dataLength[1], rawInformation); + } + } + if (rawInformation.length < 4) { + throw new NotFoundException(); + } + let firstFourDigits = rawInformation.substring(0, 4); + for (let dataLength of FieldParser.FOUR_DIGIT_DATA_LENGTH) { + if (dataLength[0] === firstFourDigits) { + if (dataLength[1] === FieldParser.VARIABLE_LENGTH) { + return FieldParser.processVariableAI(4, dataLength[2], rawInformation); + } + return FieldParser.processFixedAI(4, dataLength[1], rawInformation); + } + } + throw new NotFoundException(); + } + static processFixedAI(aiSize, fieldSize, rawInformation) { + if (rawInformation.length < aiSize) { + throw new NotFoundException(); + } + let ai = rawInformation.substring(0, aiSize); + if (rawInformation.length < aiSize + fieldSize) { + throw new NotFoundException(); + } + let field = rawInformation.substring(aiSize, aiSize + fieldSize); + let remaining = rawInformation.substring(aiSize + fieldSize); + let result = '(' + ai + ')' + field; + let parsedAI = FieldParser.parseFieldsInGeneralPurpose(remaining); + return parsedAI == null ? result : result + parsedAI; + } + static processVariableAI(aiSize, variableFieldSize, rawInformation) { + let ai = rawInformation.substring(0, aiSize); + let maxSize; + if (rawInformation.length < aiSize + variableFieldSize) { + maxSize = rawInformation.length; + } + else { + maxSize = aiSize + variableFieldSize; + } + let field = rawInformation.substring(aiSize, maxSize); + let remaining = rawInformation.substring(maxSize); + let result = '(' + ai + ')' + field; + let parsedAI = FieldParser.parseFieldsInGeneralPurpose(remaining); + return parsedAI == null ? result : result + parsedAI; + } + } + FieldParser.VARIABLE_LENGTH = []; + FieldParser.TWO_DIGIT_DATA_LENGTH = [ + ['00', 18], + ['01', 14], + ['02', 14], + ['10', FieldParser.VARIABLE_LENGTH, 20], + ['11', 6], + ['12', 6], + ['13', 6], + ['15', 6], + ['17', 6], + ['20', 2], + ['21', FieldParser.VARIABLE_LENGTH, 20], + ['22', FieldParser.VARIABLE_LENGTH, 29], + ['30', FieldParser.VARIABLE_LENGTH, 8], + ['37', FieldParser.VARIABLE_LENGTH, 8], + // internal company codes + ['90', FieldParser.VARIABLE_LENGTH, 30], + ['91', FieldParser.VARIABLE_LENGTH, 30], + ['92', FieldParser.VARIABLE_LENGTH, 30], + ['93', FieldParser.VARIABLE_LENGTH, 30], + ['94', FieldParser.VARIABLE_LENGTH, 30], + ['95', FieldParser.VARIABLE_LENGTH, 30], + ['96', FieldParser.VARIABLE_LENGTH, 30], + ['97', FieldParser.VARIABLE_LENGTH, 3], + ['98', FieldParser.VARIABLE_LENGTH, 30], + ['99', FieldParser.VARIABLE_LENGTH, 30], + ]; + FieldParser.THREE_DIGIT_DATA_LENGTH = [ + // Same format as above + ['240', FieldParser.VARIABLE_LENGTH, 30], + ['241', FieldParser.VARIABLE_LENGTH, 30], + ['242', FieldParser.VARIABLE_LENGTH, 6], + ['250', FieldParser.VARIABLE_LENGTH, 30], + ['251', FieldParser.VARIABLE_LENGTH, 30], + ['253', FieldParser.VARIABLE_LENGTH, 17], + ['254', FieldParser.VARIABLE_LENGTH, 20], + ['400', FieldParser.VARIABLE_LENGTH, 30], + ['401', FieldParser.VARIABLE_LENGTH, 30], + ['402', 17], + ['403', FieldParser.VARIABLE_LENGTH, 30], + ['410', 13], + ['411', 13], + ['412', 13], + ['413', 13], + ['414', 13], + ['420', FieldParser.VARIABLE_LENGTH, 20], + ['421', FieldParser.VARIABLE_LENGTH, 15], + ['422', 3], + ['423', FieldParser.VARIABLE_LENGTH, 15], + ['424', 3], + ['425', 3], + ['426', 3], + ]; + FieldParser.THREE_DIGIT_PLUS_DIGIT_DATA_LENGTH = [ + // Same format as above + ['310', 6], + ['311', 6], + ['312', 6], + ['313', 6], + ['314', 6], + ['315', 6], + ['316', 6], + ['320', 6], + ['321', 6], + ['322', 6], + ['323', 6], + ['324', 6], + ['325', 6], + ['326', 6], + ['327', 6], + ['328', 6], + ['329', 6], + ['330', 6], + ['331', 6], + ['332', 6], + ['333', 6], + ['334', 6], + ['335', 6], + ['336', 6], + ['340', 6], + ['341', 6], + ['342', 6], + ['343', 6], + ['344', 6], + ['345', 6], + ['346', 6], + ['347', 6], + ['348', 6], + ['349', 6], + ['350', 6], + ['351', 6], + ['352', 6], + ['353', 6], + ['354', 6], + ['355', 6], + ['356', 6], + ['357', 6], + ['360', 6], + ['361', 6], + ['362', 6], + ['363', 6], + ['364', 6], + ['365', 6], + ['366', 6], + ['367', 6], + ['368', 6], + ['369', 6], + ['390', FieldParser.VARIABLE_LENGTH, 15], + ['391', FieldParser.VARIABLE_LENGTH, 18], + ['392', FieldParser.VARIABLE_LENGTH, 15], + ['393', FieldParser.VARIABLE_LENGTH, 18], + ['703', FieldParser.VARIABLE_LENGTH, 30], + ]; + FieldParser.FOUR_DIGIT_DATA_LENGTH = [ + // Same format as above + ['7001', 13], + ['7002', FieldParser.VARIABLE_LENGTH, 30], + ['7003', 10], + ['8001', 14], + ['8002', FieldParser.VARIABLE_LENGTH, 20], + ['8003', FieldParser.VARIABLE_LENGTH, 30], + ['8004', FieldParser.VARIABLE_LENGTH, 30], + ['8005', 6], + ['8006', 18], + ['8007', FieldParser.VARIABLE_LENGTH, 30], + ['8008', FieldParser.VARIABLE_LENGTH, 12], + ['8018', 18], + ['8020', FieldParser.VARIABLE_LENGTH, 25], + ['8100', 6], + ['8101', 10], + ['8102', 2], + ['8110', FieldParser.VARIABLE_LENGTH, 70], + ['8200', FieldParser.VARIABLE_LENGTH, 70], + ]; + + class GeneralAppIdDecoder { + constructor(information) { + this.buffer = new StringBuilder(); + this.information = information; + } + decodeAllCodes(buff, initialPosition) { + let currentPosition = initialPosition; + let remaining = null; + do { + let info = this.decodeGeneralPurposeField(currentPosition, remaining); + let parsedFields = FieldParser.parseFieldsInGeneralPurpose(info.getNewString()); + if (parsedFields != null) { + buff.append(parsedFields); + } + if (info.isRemaining()) { + remaining = '' + info.getRemainingValue(); + } + else { + remaining = null; + } + if (currentPosition === info.getNewPosition()) { // No step forward! + break; + } + currentPosition = info.getNewPosition(); + } while (true); + return buff.toString(); + } + isStillNumeric(pos) { + // It's numeric if it still has 7 positions + // and one of the first 4 bits is "1". + if (pos + 7 > this.information.getSize()) { + return pos + 4 <= this.information.getSize(); + } + for (let i = pos; i < pos + 3; ++i) { + if (this.information.get(i)) { + return true; + } + } + return this.information.get(pos + 3); + } + decodeNumeric(pos) { + if (pos + 7 > this.information.getSize()) { + let numeric = this.extractNumericValueFromBitArray(pos, 4); + if (numeric === 0) { + return new DecodedNumeric(this.information.getSize(), DecodedNumeric.FNC1, DecodedNumeric.FNC1); + } + return new DecodedNumeric(this.information.getSize(), numeric - 1, DecodedNumeric.FNC1); + } + let numeric = this.extractNumericValueFromBitArray(pos, 7); + let digit1 = (numeric - 8) / 11; + let digit2 = (numeric - 8) % 11; + return new DecodedNumeric(pos + 7, digit1, digit2); + } + extractNumericValueFromBitArray(pos, bits) { + return GeneralAppIdDecoder.extractNumericValueFromBitArray(this.information, pos, bits); + } + static extractNumericValueFromBitArray(information, pos, bits) { + let value = 0; + for (let i = 0; i < bits; ++i) { + if (information.get(pos + i)) { + value |= 1 << (bits - i - 1); + } + } + return value; + } + decodeGeneralPurposeField(pos, remaining) { + // this.buffer.setLength(0); + this.buffer.setLengthToZero(); + if (remaining != null) { + this.buffer.append(remaining); + } + this.current.setPosition(pos); + let lastDecoded = this.parseBlocks(); + if (lastDecoded != null && lastDecoded.isRemaining()) { + return new DecodedInformation(this.current.getPosition(), this.buffer.toString(), lastDecoded.getRemainingValue()); + } + return new DecodedInformation(this.current.getPosition(), this.buffer.toString()); + } + parseBlocks() { + let isFinished; + let result; + do { + let initialPosition = this.current.getPosition(); + if (this.current.isAlpha()) { + result = this.parseAlphaBlock(); + isFinished = result.isFinished(); + } + else if (this.current.isIsoIec646()) { + result = this.parseIsoIec646Block(); + isFinished = result.isFinished(); + } + else { // it must be numeric + result = this.parseNumericBlock(); + isFinished = result.isFinished(); + } + let positionChanged = initialPosition !== this.current.getPosition(); + if (!positionChanged && !isFinished) { + break; + } + } while (!isFinished); + return result.getDecodedInformation(); + } + parseNumericBlock() { + while (this.isStillNumeric(this.current.getPosition())) { + let numeric = this.decodeNumeric(this.current.getPosition()); + this.current.setPosition(numeric.getNewPosition()); + if (numeric.isFirstDigitFNC1()) { + let information; + if (numeric.isSecondDigitFNC1()) { + information = new DecodedInformation(this.current.getPosition(), this.buffer.toString()); + } + else { + information = new DecodedInformation(this.current.getPosition(), this.buffer.toString(), numeric.getSecondDigit()); + } + return new BlockParsedResult(true, information); + } + this.buffer.append(numeric.getFirstDigit()); + if (numeric.isSecondDigitFNC1()) { + let information = new DecodedInformation(this.current.getPosition(), this.buffer.toString()); + return new BlockParsedResult(true, information); + } + this.buffer.append(numeric.getSecondDigit()); + } + if (this.isNumericToAlphaNumericLatch(this.current.getPosition())) { + this.current.setAlpha(); + this.current.incrementPosition(4); + } + return new BlockParsedResult(false); + } + parseIsoIec646Block() { + while (this.isStillIsoIec646(this.current.getPosition())) { + let iso = this.decodeIsoIec646(this.current.getPosition()); + this.current.setPosition(iso.getNewPosition()); + if (iso.isFNC1()) { + let information = new DecodedInformation(this.current.getPosition(), this.buffer.toString()); + return new BlockParsedResult(true, information); + } + this.buffer.append(iso.getValue()); + } + if (this.isAlphaOr646ToNumericLatch(this.current.getPosition())) { + this.current.incrementPosition(3); + this.current.setNumeric(); + } + else if (this.isAlphaTo646ToAlphaLatch(this.current.getPosition())) { + if (this.current.getPosition() + 5 < this.information.getSize()) { + this.current.incrementPosition(5); + } + else { + this.current.setPosition(this.information.getSize()); + } + this.current.setAlpha(); + } + return new BlockParsedResult(false); + } + parseAlphaBlock() { + while (this.isStillAlpha(this.current.getPosition())) { + let alpha = this.decodeAlphanumeric(this.current.getPosition()); + this.current.setPosition(alpha.getNewPosition()); + if (alpha.isFNC1()) { + let information = new DecodedInformation(this.current.getPosition(), this.buffer.toString()); + return new BlockParsedResult(true, information); // end of the char block + } + this.buffer.append(alpha.getValue()); + } + if (this.isAlphaOr646ToNumericLatch(this.current.getPosition())) { + this.current.incrementPosition(3); + this.current.setNumeric(); + } + else if (this.isAlphaTo646ToAlphaLatch(this.current.getPosition())) { + if (this.current.getPosition() + 5 < this.information.getSize()) { + this.current.incrementPosition(5); + } + else { + this.current.setPosition(this.information.getSize()); + } + this.current.setIsoIec646(); + } + return new BlockParsedResult(false); + } + isStillIsoIec646(pos) { + if (pos + 5 > this.information.getSize()) { + return false; + } + let fiveBitValue = this.extractNumericValueFromBitArray(pos, 5); + if (fiveBitValue >= 5 && fiveBitValue < 16) { + return true; + } + if (pos + 7 > this.information.getSize()) { + return false; + } + let sevenBitValue = this.extractNumericValueFromBitArray(pos, 7); + if (sevenBitValue >= 64 && sevenBitValue < 116) { + return true; + } + if (pos + 8 > this.information.getSize()) { + return false; + } + let eightBitValue = this.extractNumericValueFromBitArray(pos, 8); + return eightBitValue >= 232 && eightBitValue < 253; + } + decodeIsoIec646(pos) { + let fiveBitValue = this.extractNumericValueFromBitArray(pos, 5); + if (fiveBitValue === 15) { + return new DecodedChar(pos + 5, DecodedChar.FNC1); + } + if (fiveBitValue >= 5 && fiveBitValue < 15) { + return new DecodedChar(pos + 5, ('0' + (fiveBitValue - 5))); + } + let sevenBitValue = this.extractNumericValueFromBitArray(pos, 7); + if (sevenBitValue >= 64 && sevenBitValue < 90) { + return new DecodedChar(pos + 7, ('' + (sevenBitValue + 1))); + } + if (sevenBitValue >= 90 && sevenBitValue < 116) { + return new DecodedChar(pos + 7, ('' + (sevenBitValue + 7))); + } + let eightBitValue = this.extractNumericValueFromBitArray(pos, 8); + let c; + switch (eightBitValue) { + case 232: + c = '!'; + break; + case 233: + c = '"'; + break; + case 234: + c = '%'; + break; + case 235: + c = '&'; + break; + case 236: + c = '\''; + break; + case 237: + c = '('; + break; + case 238: + c = ')'; + break; + case 239: + c = '*'; + break; + case 240: + c = '+'; + break; + case 241: + c = ','; + break; + case 242: + c = '-'; + break; + case 243: + c = '.'; + break; + case 244: + c = '/'; + break; + case 245: + c = ':'; + break; + case 246: + c = ';'; + break; + case 247: + c = '<'; + break; + case 248: + c = '='; + break; + case 249: + c = '>'; + break; + case 250: + c = '?'; + break; + case 251: + c = '_'; + break; + case 252: + c = ' '; + break; + default: + throw new FormatException(); + } + return new DecodedChar(pos + 8, c); + } + isStillAlpha(pos) { + if (pos + 5 > this.information.getSize()) { + return false; + } + // We now check if it's a valid 5-bit value (0..9 and FNC1) + let fiveBitValue = this.extractNumericValueFromBitArray(pos, 5); + if (fiveBitValue >= 5 && fiveBitValue < 16) { + return true; + } + if (pos + 6 > this.information.getSize()) { + return false; + } + let sixBitValue = this.extractNumericValueFromBitArray(pos, 6); + return sixBitValue >= 16 && sixBitValue < 63; // 63 not included + } + decodeAlphanumeric(pos) { + let fiveBitValue = this.extractNumericValueFromBitArray(pos, 5); + if (fiveBitValue === 15) { + return new DecodedChar(pos + 5, DecodedChar.FNC1); + } + if (fiveBitValue >= 5 && fiveBitValue < 15) { + return new DecodedChar(pos + 5, ('0' + (fiveBitValue - 5))); + } + let sixBitValue = this.extractNumericValueFromBitArray(pos, 6); + if (sixBitValue >= 32 && sixBitValue < 58) { + return new DecodedChar(pos + 6, ('' + (sixBitValue + 33))); + } + let c; + switch (sixBitValue) { + case 58: + c = '*'; + break; + case 59: + c = ','; + break; + case 60: + c = '-'; + break; + case 61: + c = '.'; + break; + case 62: + c = '/'; + break; + default: + throw new IllegalStateException('Decoding invalid alphanumeric value: ' + sixBitValue); + } + return new DecodedChar(pos + 6, c); + } + isAlphaTo646ToAlphaLatch(pos) { + if (pos + 1 > this.information.getSize()) { + return false; + } + for (let i = 0; i < 5 && i + pos < this.information.getSize(); ++i) { + if (i === 2) { + if (!this.information.get(pos + 2)) { + return false; + } + } + else if (this.information.get(pos + i)) { + return false; + } + } + return true; + } + isAlphaOr646ToNumericLatch(pos) { + // Next is alphanumeric if there are 3 positions and they are all zeros + if (pos + 3 > this.information.getSize()) { + return false; + } + for (let i = pos; i < pos + 3; ++i) { + if (this.information.get(i)) { + return false; + } + } + return true; + } + isNumericToAlphaNumericLatch(pos) { + // Next is alphanumeric if there are 4 positions and they are all zeros, or + // if there is a subset of this just before the end of the symbol + if (pos + 1 > this.information.getSize()) { + return false; + } + for (let i = 0; i < 4 && i + pos < this.information.getSize(); ++i) { + if (this.information.get(pos + i)) { + return false; + } + } + return true; + } + } + + class AbstractExpandedDecoder { + constructor(information) { + this.information = information; + this.generalDecoder = new GeneralAppIdDecoder(information); + } + getInformation() { + return this.information; + } + getGeneralDecoder() { + return this.generalDecoder; + } + } + + class AI01decoder extends AbstractExpandedDecoder { + constructor(information) { + super(information); + } + encodeCompressedGtin(buf, currentPos) { + buf.append('(01)'); + let initialPosition = buf.length(); + buf.append('9'); + this.encodeCompressedGtinWithoutAI(buf, currentPos, initialPosition); + } + encodeCompressedGtinWithoutAI(buf, currentPos, initialBufferPosition) { + for (let i = 0; i < 4; ++i) { + let currentBlock = this.getGeneralDecoder().extractNumericValueFromBitArray(currentPos + 10 * i, 10); + if (currentBlock / 100 === 0) { + buf.append('0'); + } + if (currentBlock / 10 === 0) { + buf.append('0'); + } + buf.append(currentBlock); + } + AI01decoder.appendCheckDigit(buf, initialBufferPosition); + } + static appendCheckDigit(buf, currentPos) { + let checkDigit = 0; + for (let i = 0; i < 13; i++) { + // let digit = buf.charAt(i + currentPos) - '0'; + // To be checked + let digit = buf.charAt(i + currentPos).charCodeAt(0) - '0'.charCodeAt(0); + checkDigit += (i & 0x01) === 0 ? 3 * digit : digit; + } + checkDigit = 10 - (checkDigit % 10); + if (checkDigit === 10) { + checkDigit = 0; + } + buf.append(checkDigit); + } + } + AI01decoder.GTIN_SIZE = 40; + + class AI01AndOtherAIs extends AI01decoder { + // the second one is the encodation method, and the other two are for the variable length + constructor(information) { + super(information); + } + parseInformation() { + let buff = new StringBuilder(); + buff.append('(01)'); + let initialGtinPosition = buff.length(); + let firstGtinDigit = this.getGeneralDecoder().extractNumericValueFromBitArray(AI01AndOtherAIs.HEADER_SIZE, 4); + buff.append(firstGtinDigit); + this.encodeCompressedGtinWithoutAI(buff, AI01AndOtherAIs.HEADER_SIZE + 4, initialGtinPosition); + return this.getGeneralDecoder().decodeAllCodes(buff, AI01AndOtherAIs.HEADER_SIZE + 44); + } + } + AI01AndOtherAIs.HEADER_SIZE = 1 + 1 + 2; // first bit encodes the linkage flag, + + class AnyAIDecoder extends AbstractExpandedDecoder { + constructor(information) { + super(information); + } + parseInformation() { + let buf = new StringBuilder(); + return this.getGeneralDecoder().decodeAllCodes(buf, AnyAIDecoder.HEADER_SIZE); + } + } + AnyAIDecoder.HEADER_SIZE = 2 + 1 + 2; + + class AI01weightDecoder extends AI01decoder { + constructor(information) { + super(information); + } + encodeCompressedWeight(buf, currentPos, weightSize) { + let originalWeightNumeric = this.getGeneralDecoder().extractNumericValueFromBitArray(currentPos, weightSize); + this.addWeightCode(buf, originalWeightNumeric); + let weightNumeric = this.checkWeight(originalWeightNumeric); + let currentDivisor = 100000; + for (let i = 0; i < 5; ++i) { + if (weightNumeric / currentDivisor === 0) { + buf.append('0'); + } + currentDivisor /= 10; + } + buf.append(weightNumeric); + } + } + + class AI013x0xDecoder extends AI01weightDecoder { + constructor(information) { + super(information); + } + parseInformation() { + if (this.getInformation().getSize() != AI013x0xDecoder.HEADER_SIZE + AI01weightDecoder.GTIN_SIZE + AI013x0xDecoder.WEIGHT_SIZE) { + throw new NotFoundException(); + } + let buf = new StringBuilder(); + this.encodeCompressedGtin(buf, AI013x0xDecoder.HEADER_SIZE); + this.encodeCompressedWeight(buf, AI013x0xDecoder.HEADER_SIZE + AI01weightDecoder.GTIN_SIZE, AI013x0xDecoder.WEIGHT_SIZE); + return buf.toString(); + } + } + AI013x0xDecoder.HEADER_SIZE = 4 + 1; + AI013x0xDecoder.WEIGHT_SIZE = 15; + + class AI013103decoder extends AI013x0xDecoder { + constructor(information) { + super(information); + } + addWeightCode(buf, weight) { + buf.append('(3103)'); + } + checkWeight(weight) { + return weight; + } + } + + class AI01320xDecoder extends AI013x0xDecoder { + constructor(information) { + super(information); + } + addWeightCode(buf, weight) { + if (weight < 10000) { + buf.append('(3202)'); + } + else { + buf.append('(3203)'); + } + } + checkWeight(weight) { + if (weight < 10000) { + return weight; + } + return weight - 10000; + } + } + + class AI01392xDecoder extends AI01decoder { + constructor(information) { + super(information); + } + parseInformation() { + if (this.getInformation().getSize() < AI01392xDecoder.HEADER_SIZE + AI01decoder.GTIN_SIZE) { + throw new NotFoundException(); + } + let buf = new StringBuilder(); + this.encodeCompressedGtin(buf, AI01392xDecoder.HEADER_SIZE); + let lastAIdigit = this.getGeneralDecoder().extractNumericValueFromBitArray(AI01392xDecoder.HEADER_SIZE + AI01decoder.GTIN_SIZE, AI01392xDecoder.LAST_DIGIT_SIZE); + buf.append('(392'); + buf.append(lastAIdigit); + buf.append(')'); + let decodedInformation = this.getGeneralDecoder().decodeGeneralPurposeField(AI01392xDecoder.HEADER_SIZE + AI01decoder.GTIN_SIZE + AI01392xDecoder.LAST_DIGIT_SIZE, null); + buf.append(decodedInformation.getNewString()); + return buf.toString(); + } + } + AI01392xDecoder.HEADER_SIZE = 5 + 1 + 2; + AI01392xDecoder.LAST_DIGIT_SIZE = 2; + + class AI01393xDecoder extends AI01decoder { + constructor(information) { + super(information); + } + parseInformation() { + if (this.getInformation().getSize() < AI01393xDecoder.HEADER_SIZE + AI01decoder.GTIN_SIZE) { + throw new NotFoundException(); + } + let buf = new StringBuilder(); + this.encodeCompressedGtin(buf, AI01393xDecoder.HEADER_SIZE); + let lastAIdigit = this.getGeneralDecoder().extractNumericValueFromBitArray(AI01393xDecoder.HEADER_SIZE + AI01decoder.GTIN_SIZE, AI01393xDecoder.LAST_DIGIT_SIZE); + buf.append('(393'); + buf.append(lastAIdigit); + buf.append(')'); + let firstThreeDigits = this.getGeneralDecoder().extractNumericValueFromBitArray(AI01393xDecoder.HEADER_SIZE + AI01decoder.GTIN_SIZE + AI01393xDecoder.LAST_DIGIT_SIZE, AI01393xDecoder.FIRST_THREE_DIGITS_SIZE); + if (firstThreeDigits / 100 == 0) { + buf.append('0'); + } + if (firstThreeDigits / 10 == 0) { + buf.append('0'); + } + buf.append(firstThreeDigits); + let generalInformation = this.getGeneralDecoder().decodeGeneralPurposeField(AI01393xDecoder.HEADER_SIZE + AI01decoder.GTIN_SIZE + AI01393xDecoder.LAST_DIGIT_SIZE + AI01393xDecoder.FIRST_THREE_DIGITS_SIZE, null); + buf.append(generalInformation.getNewString()); + return buf.toString(); + } + } + AI01393xDecoder.HEADER_SIZE = 5 + 1 + 2; + AI01393xDecoder.LAST_DIGIT_SIZE = 2; + AI01393xDecoder.FIRST_THREE_DIGITS_SIZE = 10; + + class AI013x0x1xDecoder extends AI01weightDecoder { + constructor(information, firstAIdigits, dateCode) { + super(information); + this.dateCode = dateCode; + this.firstAIdigits = firstAIdigits; + } + parseInformation() { + if (this.getInformation().getSize() != AI013x0x1xDecoder.HEADER_SIZE + AI013x0x1xDecoder.GTIN_SIZE + AI013x0x1xDecoder.WEIGHT_SIZE + AI013x0x1xDecoder.DATE_SIZE) { + throw new NotFoundException(); + } + let buf = new StringBuilder(); + this.encodeCompressedGtin(buf, AI013x0x1xDecoder.HEADER_SIZE); + this.encodeCompressedWeight(buf, AI013x0x1xDecoder.HEADER_SIZE + AI013x0x1xDecoder.GTIN_SIZE, AI013x0x1xDecoder.WEIGHT_SIZE); + this.encodeCompressedDate(buf, AI013x0x1xDecoder.HEADER_SIZE + AI013x0x1xDecoder.GTIN_SIZE + AI013x0x1xDecoder.WEIGHT_SIZE); + return buf.toString(); + } + encodeCompressedDate(buf, currentPos) { + let numericDate = this.getGeneralDecoder().extractNumericValueFromBitArray(currentPos, AI013x0x1xDecoder.DATE_SIZE); + if (numericDate == 38400) { + return; + } + buf.append('('); + buf.append(this.dateCode); + buf.append(')'); + let day = numericDate % 32; + numericDate /= 32; + let month = numericDate % 12 + 1; + numericDate /= 12; + let year = numericDate; + if (year / 10 == 0) { + buf.append('0'); + } + buf.append(year); + if (month / 10 == 0) { + buf.append('0'); + } + buf.append(month); + if (day / 10 == 0) { + buf.append('0'); + } + buf.append(day); + } + addWeightCode(buf, weight) { + buf.append('('); + buf.append(this.firstAIdigits); + buf.append(weight / 100000); + buf.append(')'); + } + checkWeight(weight) { + return weight % 100000; + } + } + AI013x0x1xDecoder.HEADER_SIZE = 7 + 1; + AI013x0x1xDecoder.WEIGHT_SIZE = 20; + AI013x0x1xDecoder.DATE_SIZE = 16; + + function createDecoder(information) { + try { + if (information.get(1)) { + return new AI01AndOtherAIs(information); + } + if (!information.get(2)) { + return new AnyAIDecoder(information); + } + let fourBitEncodationMethod = GeneralAppIdDecoder.extractNumericValueFromBitArray(information, 1, 4); + switch (fourBitEncodationMethod) { + case 4: return new AI013103decoder(information); + case 5: return new AI01320xDecoder(information); + } + let fiveBitEncodationMethod = GeneralAppIdDecoder.extractNumericValueFromBitArray(information, 1, 5); + switch (fiveBitEncodationMethod) { + case 12: return new AI01392xDecoder(information); + case 13: return new AI01393xDecoder(information); + } + let sevenBitEncodationMethod = GeneralAppIdDecoder.extractNumericValueFromBitArray(information, 1, 7); + switch (sevenBitEncodationMethod) { + case 56: return new AI013x0x1xDecoder(information, '310', '11'); + case 57: return new AI013x0x1xDecoder(information, '320', '11'); + case 58: return new AI013x0x1xDecoder(information, '310', '13'); + case 59: return new AI013x0x1xDecoder(information, '320', '13'); + case 60: return new AI013x0x1xDecoder(information, '310', '15'); + case 61: return new AI013x0x1xDecoder(information, '320', '15'); + case 62: return new AI013x0x1xDecoder(information, '310', '17'); + case 63: return new AI013x0x1xDecoder(information, '320', '17'); + } + } + catch (e) { + console.log(e); + throw new IllegalStateException('unknown decoder: ' + information); + } + } + + class ExpandedPair { + constructor(leftChar, rightChar, finderPatter, mayBeLast) { + this.leftchar = leftChar; + this.rightchar = rightChar; + this.finderpattern = finderPatter; + this.maybeLast = mayBeLast; + } + mayBeLast() { + return this.maybeLast; + } + getLeftChar() { + return this.leftchar; + } + getRightChar() { + return this.rightchar; + } + getFinderPattern() { + return this.finderpattern; + } + mustBeLast() { + return this.rightchar == null; + } + toString() { + return '[ ' + this.leftchar + ', ' + this.rightchar + ' : ' + (this.finderpattern == null ? 'null' : this.finderpattern.getValue()) + ' ]'; + } + static equals(o1, o2) { + if (!(o1 instanceof ExpandedPair)) { + return false; + } + return ExpandedPair.equalsOrNull(o1.leftchar, o2.leftchar) && + ExpandedPair.equalsOrNull(o1.rightchar, o2.rightchar) && + ExpandedPair.equalsOrNull(o1.finderpattern, o2.finderpattern); + } + static equalsOrNull(o1, o2) { + return o1 === null ? o2 === null : ExpandedPair.equals(o1, o2); + } + hashCode() { + // return ExpandedPair.hashNotNull(leftChar) ^ hashNotNull(rightChar) ^ hashNotNull(finderPattern); + let value = this.leftchar.getValue() ^ this.rightchar.getValue() ^ this.finderpattern.getValue(); + return value; + } + } + + class ExpandedRow { + constructor(pairs, rowNumber, wasReversed) { + this.pairs = pairs; + this.rowNumber = rowNumber; + this.wasReversed = wasReversed; + } + getPairs() { + return this.pairs; + } + getRowNumber() { + return this.rowNumber; + } + isReversed() { + return this.wasReversed; + } + // check implementation + isEquivalent(otherPairs) { + return this.checkEqualitity(this, otherPairs); + } + // @Override + toString() { + return '{ ' + this.pairs + ' }'; + } + /** + * Two rows are equal if they contain the same pairs in the same order. + */ + // @Override + // check implementation + equals(o1, o2) { + if (!(o1 instanceof ExpandedRow)) { + return false; + } + return this.checkEqualitity(o1, o2) && o1.wasReversed === o2.wasReversed; + } + checkEqualitity(pair1, pair2) { + if (!pair1 || !pair2) + return; + let result; + pair1.forEach((e1, i) => { + pair2.forEach(e2 => { + if (e1.getLeftChar().getValue() === e2.getLeftChar().getValue() && e1.getRightChar().getValue() === e2.getRightChar().getValue() && e1.getFinderPatter().getValue() === e2.getFinderPatter().getValue()) { + result = true; + } + }); + }); + return result; + } + } + + // import java.util.ArrayList; + // import java.util.Iterator; + // import java.util.List; + // import java.util.Map; + // import java.util.Collections; + class RSSExpandedReader extends AbstractRSSReader { + constructor() { + super(...arguments); + this.pairs = new Array(RSSExpandedReader.MAX_PAIRS); + this.rows = new Array(); + this.startEnd = [2]; + } + decodeRow(rowNumber, row, hints) { + // Rows can start with even pattern in case in prev rows there where odd number of patters. + // So lets try twice + // this.pairs.clear(); + this.pairs.length = 0; + this.startFromEven = false; + try { + return RSSExpandedReader.constructResult(this.decodeRow2pairs(rowNumber, row)); + } + catch (e) { + // OK + console.log(e); + } + this.pairs.length = 0; + this.startFromEven = true; + return RSSExpandedReader.constructResult(this.decodeRow2pairs(rowNumber, row)); + } + reset() { + this.pairs.length = 0; + this.rows.length = 0; + } + // Not private for testing + decodeRow2pairs(rowNumber, row) { + let done = false; + while (!done) { + try { + this.pairs.push(this.retrieveNextPair(row, this.pairs, rowNumber)); + } + catch (error) { + if (error instanceof NotFoundException) { + if (!this.pairs.length) { + throw new NotFoundException(); + } + // exit this loop when retrieveNextPair() fails and throws + done = true; + } + } + } + // TODO: verify sequence of finder patterns as in checkPairSequence() + if (this.checkChecksum()) { + return this.pairs; + } + let tryStackedDecode; + if (this.rows.length) { + tryStackedDecode = true; + } + else { + tryStackedDecode = false; + } + // let tryStackedDecode = !this.rows.isEmpty(); + this.storeRow(rowNumber, false); // TODO: deal with reversed rows + if (tryStackedDecode) { + // When the image is 180-rotated, then rows are sorted in wrong direction. + // Try twice with both the directions. + let ps = this.checkRowsBoolean(false); + if (ps != null) { + return ps; + } + ps = this.checkRowsBoolean(true); + if (ps != null) { + return ps; + } + } + throw new NotFoundException(); + } + // Need to Verify + checkRowsBoolean(reverse) { + // Limit number of rows we are checking + // We use recursive algorithm with pure complexity and don't want it to take forever + // Stacked barcode can have up to 11 rows, so 25 seems reasonable enough + if (this.rows.length > 25) { + this.rows.length = 0; // We will never have a chance to get result, so clear it + return null; + } + this.pairs.length = 0; + if (reverse) { + this.rows = this.rows.reverse(); + // Collections.reverse(this.rows); + } + let ps = null; + try { + ps = this.checkRows(new Array(), 0); + } + catch (e) { + // OK + console.log(e); + } + if (reverse) { + this.rows = this.rows.reverse(); + // Collections.reverse(this.rows); + } + return ps; + } + // Try to construct a valid rows sequence + // Recursion is used to implement backtracking + checkRows(collectedRows, currentRow) { + for (let i = currentRow; i < this.rows.length; i++) { + let row = this.rows[i]; + this.pairs.length = 0; + for (let collectedRow of collectedRows) { + this.pairs.push(collectedRow.getPairs()); + } + this.pairs.push(row.getPairs()); + if (!RSSExpandedReader.isValidSequence(this.pairs)) { + continue; + } + if (this.checkChecksum()) { + return this.pairs; + } + let rs = new Array(collectedRows); + rs.push(row); + try { + // Recursion: try to add more rows + return this.checkRows(rs, i + 1); + } + catch (e) { + // We failed, try the next candidate + console.log(e); + } + } + throw new NotFoundException(); + } + // Whether the pairs form a valid find pattern sequence, + // either complete or a prefix + static isValidSequence(pairs) { + for (let sequence of RSSExpandedReader.FINDER_PATTERN_SEQUENCES) { + if (pairs.length > sequence.length) { + continue; + } + let stop = true; + for (let j = 0; j < pairs.length; j++) { + if (pairs[j].getFinderPattern().getValue() != sequence[j]) { + stop = false; + break; + } + } + if (stop) { + return true; + } + } + return false; + } + storeRow(rowNumber, wasReversed) { + // Discard if duplicate above or below; otherwise insert in order by row number. + let insertPos = 0; + let prevIsSame = false; + let nextIsSame = false; + while (insertPos < this.rows.length) { + let erow = this.rows[insertPos]; + if (erow.getRowNumber() > rowNumber) { + nextIsSame = erow.isEquivalent(this.pairs); + break; + } + prevIsSame = erow.isEquivalent(this.pairs); + insertPos++; + } + if (nextIsSame || prevIsSame) { + return; + } + // When the row was partially decoded (e.g. 2 pairs found instead of 3), + // it will prevent us from detecting the barcode. + // Try to merge partial rows + // Check whether the row is part of an allready detected row + if (RSSExpandedReader.isPartialRow(this.pairs, this.rows)) { + return; + } + this.rows.push(insertPos, new ExpandedRow(this.pairs, rowNumber, wasReversed)); + this.removePartialRows(this.pairs, this.rows); + } + // Remove all the rows that contains only specified pairs + removePartialRows(pairs, rows) { + // for (IteratorEncapsulates a set of error-correction blocks in one symbol version. Most versions will + * use blocks of differing sizes within one version, so, this encapsulates the parameters for + * each set of blocks. It also holds the number of error-correction codewords per block since it + * will be the same across all blocks within one version.
+ */ + class ECBlocks { + constructor(ecCodewords, ecBlocks1, ecBlocks2) { + this.ecCodewords = ecCodewords; + this.ecBlocks = [ecBlocks1]; + ecBlocks2 && this.ecBlocks.push(ecBlocks2); + } + getECCodewords() { + return this.ecCodewords; + } + getECBlocks() { + return this.ecBlocks; + } + } + /** + *Encapsulates the parameters for one error-correction block in one symbol version. + * This includes the number of data codewords, and the number of times a block with these + * parameters is used consecutively in the Data Matrix code version's format.
+ */ + class ECB { + constructor(count, dataCodewords) { + this.count = count; + this.dataCodewords = dataCodewords; + } + getCount() { + return this.count; + } + getDataCodewords() { + return this.dataCodewords; + } + } + /** + * The Version object encapsulates attributes about a particular + * size Data Matrix Code. + * + * @author bbrown@google.com (Brian Brown) + */ + class Version { + constructor(versionNumber, symbolSizeRows, symbolSizeColumns, dataRegionSizeRows, dataRegionSizeColumns, ecBlocks) { + this.versionNumber = versionNumber; + this.symbolSizeRows = symbolSizeRows; + this.symbolSizeColumns = symbolSizeColumns; + this.dataRegionSizeRows = dataRegionSizeRows; + this.dataRegionSizeColumns = dataRegionSizeColumns; + this.ecBlocks = ecBlocks; + // Calculate the total number of codewords + let total = 0; + const ecCodewords = ecBlocks.getECCodewords(); + const ecbArray = ecBlocks.getECBlocks(); + for (let ecBlock of ecbArray) { + total += ecBlock.getCount() * (ecBlock.getDataCodewords() + ecCodewords); + } + this.totalCodewords = total; + } + getVersionNumber() { + return this.versionNumber; + } + getSymbolSizeRows() { + return this.symbolSizeRows; + } + getSymbolSizeColumns() { + return this.symbolSizeColumns; + } + getDataRegionSizeRows() { + return this.dataRegionSizeRows; + } + getDataRegionSizeColumns() { + return this.dataRegionSizeColumns; + } + getTotalCodewords() { + return this.totalCodewords; + } + getECBlocks() { + return this.ecBlocks; + } + /** + *Deduces version information from Data Matrix dimensions.
+ * + * @param numRows Number of rows in modules + * @param numColumns Number of columns in modules + * @return Version for a Data Matrix Code of those dimensions + * @throws FormatException if dimensions do correspond to a valid Data Matrix size + */ + static getVersionForDimensions(numRows, numColumns) { + if ((numRows & 0x01) !== 0 || (numColumns & 0x01) !== 0) { + throw new FormatException(); + } + for (let version of Version.VERSIONS) { + if (version.symbolSizeRows === numRows && version.symbolSizeColumns === numColumns) { + return version; + } + } + throw new FormatException(); + } + // @Override + toString() { + return '' + this.versionNumber; + } + /** + * See ISO 16022:2006 5.5.1 Table 7 + */ + static buildVersions() { + return [ + new Version(1, 10, 10, 8, 8, new ECBlocks(5, new ECB(1, 3))), + new Version(2, 12, 12, 10, 10, new ECBlocks(7, new ECB(1, 5))), + new Version(3, 14, 14, 12, 12, new ECBlocks(10, new ECB(1, 8))), + new Version(4, 16, 16, 14, 14, new ECBlocks(12, new ECB(1, 12))), + new Version(5, 18, 18, 16, 16, new ECBlocks(14, new ECB(1, 18))), + new Version(6, 20, 20, 18, 18, new ECBlocks(18, new ECB(1, 22))), + new Version(7, 22, 22, 20, 20, new ECBlocks(20, new ECB(1, 30))), + new Version(8, 24, 24, 22, 22, new ECBlocks(24, new ECB(1, 36))), + new Version(9, 26, 26, 24, 24, new ECBlocks(28, new ECB(1, 44))), + new Version(10, 32, 32, 14, 14, new ECBlocks(36, new ECB(1, 62))), + new Version(11, 36, 36, 16, 16, new ECBlocks(42, new ECB(1, 86))), + new Version(12, 40, 40, 18, 18, new ECBlocks(48, new ECB(1, 114))), + new Version(13, 44, 44, 20, 20, new ECBlocks(56, new ECB(1, 144))), + new Version(14, 48, 48, 22, 22, new ECBlocks(68, new ECB(1, 174))), + new Version(15, 52, 52, 24, 24, new ECBlocks(42, new ECB(2, 102))), + new Version(16, 64, 64, 14, 14, new ECBlocks(56, new ECB(2, 140))), + new Version(17, 72, 72, 16, 16, new ECBlocks(36, new ECB(4, 92))), + new Version(18, 80, 80, 18, 18, new ECBlocks(48, new ECB(4, 114))), + new Version(19, 88, 88, 20, 20, new ECBlocks(56, new ECB(4, 144))), + new Version(20, 96, 96, 22, 22, new ECBlocks(68, new ECB(4, 174))), + new Version(21, 104, 104, 24, 24, new ECBlocks(56, new ECB(6, 136))), + new Version(22, 120, 120, 18, 18, new ECBlocks(68, new ECB(6, 175))), + new Version(23, 132, 132, 20, 20, new ECBlocks(62, new ECB(8, 163))), + new Version(24, 144, 144, 22, 22, new ECBlocks(62, new ECB(8, 156), new ECB(2, 155))), + new Version(25, 8, 18, 6, 16, new ECBlocks(7, new ECB(1, 5))), + new Version(26, 8, 32, 6, 14, new ECBlocks(11, new ECB(1, 10))), + new Version(27, 12, 26, 10, 24, new ECBlocks(14, new ECB(1, 16))), + new Version(28, 12, 36, 10, 16, new ECBlocks(18, new ECB(1, 22))), + new Version(29, 16, 36, 14, 16, new ECBlocks(24, new ECB(1, 32))), + new Version(30, 16, 48, 14, 22, new ECBlocks(28, new ECB(1, 49))) + ]; + } + } + Version.VERSIONS = Version.buildVersions(); + + /* + * 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. + */ + /** + * @author bbrown@google.com (Brian Brown) + */ + class BitMatrixParser { + /** + * @param bitMatrix {@link BitMatrix} to parse + * @throws FormatException if dimension is < 8 or > 144 or not 0 mod 2 + */ + constructor(bitMatrix) { + const dimension = bitMatrix.getHeight(); + if (dimension < 8 || dimension > 144 || (dimension & 0x01) !== 0) { + throw new FormatException(); + } + this.version = BitMatrixParser.readVersion(bitMatrix); + this.mappingBitMatrix = this.extractDataRegion(bitMatrix); + this.readMappingMatrix = new BitMatrix(this.mappingBitMatrix.getWidth(), this.mappingBitMatrix.getHeight()); + } + getVersion() { + return this.version; + } + /** + *Creates the version object based on the dimension of the original bit matrix from + * the datamatrix code.
+ * + *See ISO 16022:2006 Table 7 - ECC 200 symbol attributes
+ * + * @param bitMatrix Original {@link BitMatrix} including alignment patterns + * @return {@link Version} encapsulating the Data Matrix Code's "version" + * @throws FormatException if the dimensions of the mapping matrix are not valid + * Data Matrix dimensions. + */ + static readVersion(bitMatrix) { + const numRows = bitMatrix.getHeight(); + const numColumns = bitMatrix.getWidth(); + return Version.getVersionForDimensions(numRows, numColumns); + } + /** + *Reads the bits in the {@link BitMatrix} representing the mapping matrix (No alignment patterns) + * in the correct order in order to reconstitute the codewords bytes contained within the + * Data Matrix Code.
+ * + * @return bytes encoded within the Data Matrix Code + * @throws FormatException if the exact number of bytes expected is not read + */ + readCodewords() { + const result = new Int8Array(this.version.getTotalCodewords()); + let resultOffset = 0; + let row = 4; + let column = 0; + const numRows = this.mappingBitMatrix.getHeight(); + const numColumns = this.mappingBitMatrix.getWidth(); + let corner1Read = false; + let corner2Read = false; + let corner3Read = false; + let corner4Read = false; + // Read all of the codewords + do { + // Check the four corner cases + if ((row === numRows) && (column === 0) && !corner1Read) { + result[resultOffset++] = this.readCorner1(numRows, numColumns) & 0xff; + row -= 2; + column += 2; + corner1Read = true; + } + else if ((row === numRows - 2) && (column === 0) && ((numColumns & 0x03) !== 0) && !corner2Read) { + result[resultOffset++] = this.readCorner2(numRows, numColumns) & 0xff; + row -= 2; + column += 2; + corner2Read = true; + } + else if ((row === numRows + 4) && (column === 2) && ((numColumns & 0x07) === 0) && !corner3Read) { + result[resultOffset++] = this.readCorner3(numRows, numColumns) & 0xff; + row -= 2; + column += 2; + corner3Read = true; + } + else if ((row === numRows - 2) && (column === 0) && ((numColumns & 0x07) === 4) && !corner4Read) { + result[resultOffset++] = this.readCorner4(numRows, numColumns) & 0xff; + row -= 2; + column += 2; + corner4Read = true; + } + else { + // Sweep upward diagonally to the right + do { + if ((row < numRows) && (column >= 0) && !this.readMappingMatrix.get(column, row)) { + result[resultOffset++] = this.readUtah(row, column, numRows, numColumns) & 0xff; + } + row -= 2; + column += 2; + } while ((row >= 0) && (column < numColumns)); + row += 1; + column += 3; + // Sweep downward diagonally to the left + do { + if ((row >= 0) && (column < numColumns) && !this.readMappingMatrix.get(column, row)) { + result[resultOffset++] = this.readUtah(row, column, numRows, numColumns) & 0xff; + } + row += 2; + column -= 2; + } while ((row < numRows) && (column >= 0)); + row += 3; + column += 1; + } + } while ((row < numRows) || (column < numColumns)); + if (resultOffset !== this.version.getTotalCodewords()) { + throw new FormatException(); + } + return result; + } + /** + *Reads a bit of the mapping matrix accounting for boundary wrapping.
+ * + * @param row Row to read in the mapping matrix + * @param column Column to read in the mapping matrix + * @param numRows Number of rows in the mapping matrix + * @param numColumns Number of columns in the mapping matrix + * @return value of the given bit in the mapping matrix + */ + readModule(row, column, numRows, numColumns) { + // Adjust the row and column indices based on boundary wrapping + if (row < 0) { + row += numRows; + column += 4 - ((numRows + 4) & 0x07); + } + if (column < 0) { + column += numColumns; + row += 4 - ((numColumns + 4) & 0x07); + } + this.readMappingMatrix.set(column, row); + return this.mappingBitMatrix.get(column, row); + } + /** + *Reads the 8 bits of the standard Utah-shaped pattern.
+ * + *See ISO 16022:2006, 5.8.1 Figure 6
+ * + * @param row Current row in the mapping matrix, anchored at the 8th bit (LSB) of the pattern + * @param column Current column in the mapping matrix, anchored at the 8th bit (LSB) of the pattern + * @param numRows Number of rows in the mapping matrix + * @param numColumns Number of columns in the mapping matrix + * @return byte from the utah shape + */ + readUtah(row, column, numRows, numColumns) { + let currentByte = 0; + if (this.readModule(row - 2, column - 2, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(row - 2, column - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(row - 1, column - 2, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(row - 1, column - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(row - 1, column, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(row, column - 2, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(row, column - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(row, column, numRows, numColumns)) { + currentByte |= 1; + } + return currentByte; + } + /** + *Reads the 8 bits of the special corner condition 1.
+ * + *See ISO 16022:2006, Figure F.3
+ * + * @param numRows Number of rows in the mapping matrix + * @param numColumns Number of columns in the mapping matrix + * @return byte from the Corner condition 1 + */ + readCorner1(numRows, numColumns) { + let currentByte = 0; + if (this.readModule(numRows - 1, 0, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(numRows - 1, 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(numRows - 1, 2, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(0, numColumns - 2, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(0, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(1, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(2, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(3, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + return currentByte; + } + /** + *Reads the 8 bits of the special corner condition 2.
+ * + *See ISO 16022:2006, Figure F.4
+ * + * @param numRows Number of rows in the mapping matrix + * @param numColumns Number of columns in the mapping matrix + * @return byte from the Corner condition 2 + */ + readCorner2(numRows, numColumns) { + let currentByte = 0; + if (this.readModule(numRows - 3, 0, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(numRows - 2, 0, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(numRows - 1, 0, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(0, numColumns - 4, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(0, numColumns - 3, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(0, numColumns - 2, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(0, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(1, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + return currentByte; + } + /** + *Reads the 8 bits of the special corner condition 3.
+ * + *See ISO 16022:2006, Figure F.5
+ * + * @param numRows Number of rows in the mapping matrix + * @param numColumns Number of columns in the mapping matrix + * @return byte from the Corner condition 3 + */ + readCorner3(numRows, numColumns) { + let currentByte = 0; + if (this.readModule(numRows - 1, 0, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(numRows - 1, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(0, numColumns - 3, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(0, numColumns - 2, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(0, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(1, numColumns - 3, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(1, numColumns - 2, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(1, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + return currentByte; + } + /** + *Reads the 8 bits of the special corner condition 4.
+ * + *See ISO 16022:2006, Figure F.6
+ * + * @param numRows Number of rows in the mapping matrix + * @param numColumns Number of columns in the mapping matrix + * @return byte from the Corner condition 4 + */ + readCorner4(numRows, numColumns) { + let currentByte = 0; + if (this.readModule(numRows - 3, 0, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(numRows - 2, 0, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(numRows - 1, 0, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(0, numColumns - 2, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(0, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(1, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(2, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (this.readModule(3, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + return currentByte; + } + /** + *Extracts the data region from a {@link BitMatrix} that contains + * alignment patterns.
+ * + * @param bitMatrix Original {@link BitMatrix} with alignment patterns + * @return BitMatrix that has the alignment patterns removed + */ + extractDataRegion(bitMatrix) { + const symbolSizeRows = this.version.getSymbolSizeRows(); + const symbolSizeColumns = this.version.getSymbolSizeColumns(); + if (bitMatrix.getHeight() !== symbolSizeRows) { + throw new IllegalArgumentException('Dimension of bitMatrix must match the version size'); + } + const dataRegionSizeRows = this.version.getDataRegionSizeRows(); + const dataRegionSizeColumns = this.version.getDataRegionSizeColumns(); + const numDataRegionsRow = symbolSizeRows / dataRegionSizeRows | 0; + const numDataRegionsColumn = symbolSizeColumns / dataRegionSizeColumns | 0; + const sizeDataRegionRow = numDataRegionsRow * dataRegionSizeRows; + const sizeDataRegionColumn = numDataRegionsColumn * dataRegionSizeColumns; + const bitMatrixWithoutAlignment = new BitMatrix(sizeDataRegionColumn, sizeDataRegionRow); + for (let dataRegionRow = 0; dataRegionRow < numDataRegionsRow; ++dataRegionRow) { + const dataRegionRowOffset = dataRegionRow * dataRegionSizeRows; + for (let dataRegionColumn = 0; dataRegionColumn < numDataRegionsColumn; ++dataRegionColumn) { + const dataRegionColumnOffset = dataRegionColumn * dataRegionSizeColumns; + for (let i = 0; i < dataRegionSizeRows; ++i) { + const readRowOffset = dataRegionRow * (dataRegionSizeRows + 2) + 1 + i; + const writeRowOffset = dataRegionRowOffset + i; + for (let j = 0; j < dataRegionSizeColumns; ++j) { + const readColumnOffset = dataRegionColumn * (dataRegionSizeColumns + 2) + 1 + j; + if (bitMatrix.get(readColumnOffset, readRowOffset)) { + const writeColumnOffset = dataRegionColumnOffset + j; + bitMatrixWithoutAlignment.set(writeColumnOffset, writeRowOffset); + } + } + } + } + } + return bitMatrixWithoutAlignment; + } + } + + /* + * Copyright 2008 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. + */ + /** + *Encapsulates a block of data within a Data Matrix Code. Data Matrix Codes may split their data into + * multiple blocks, each of which is a unit of data and error-correction codewords. Each + * is represented by an instance of this class.
+ * + * @author bbrown@google.com (Brian Brown) + */ + class DataBlock { + constructor(numDataCodewords, codewords) { + this.numDataCodewords = numDataCodewords; + this.codewords = codewords; + } + /** + *When Data Matrix Codes use multiple data blocks, they actually interleave the bytes of each of them. + * That is, the first byte of data block 1 to n is written, then the second bytes, and so on. This + * method will separate the data into original blocks.
+ * + * @param rawCodewords bytes as read directly from the Data Matrix Code + * @param version version of the Data Matrix Code + * @return DataBlocks containing original bytes, "de-interleaved" from representation in the + * Data Matrix Code + */ + static getDataBlocks(rawCodewords, version) { + // Figure out the number and size of data blocks used by this version + const ecBlocks = version.getECBlocks(); + // First count the total number of data blocks + let totalBlocks = 0; + const ecBlockArray = ecBlocks.getECBlocks(); + for (let ecBlock of ecBlockArray) { + totalBlocks += ecBlock.getCount(); + } + // Now establish DataBlocks of the appropriate size and number of data codewords + const result = new Array(totalBlocks); + let numResultBlocks = 0; + for (let ecBlock of ecBlockArray) { + for (let i = 0; i < ecBlock.getCount(); i++) { + const numDataCodewords = ecBlock.getDataCodewords(); + const numBlockCodewords = ecBlocks.getECCodewords() + numDataCodewords; + result[numResultBlocks++] = new DataBlock(numDataCodewords, new Uint8Array(numBlockCodewords)); + } + } + // All blocks have the same amount of data, except that the last n + // (where n may be 0) have 1 less byte. Figure out where these start. + // TODO(bbrown): There is only one case where there is a difference for Data Matrix for size 144 + const longerBlocksTotalCodewords = result[0].codewords.length; + // int shorterBlocksTotalCodewords = longerBlocksTotalCodewords - 1; + const longerBlocksNumDataCodewords = longerBlocksTotalCodewords - ecBlocks.getECCodewords(); + const shorterBlocksNumDataCodewords = longerBlocksNumDataCodewords - 1; + // The last elements of result may be 1 element shorter for 144 matrix + // first fill out as many elements as all of them have minus 1 + let rawCodewordsOffset = 0; + for (let i = 0; i < shorterBlocksNumDataCodewords; i++) { + for (let j = 0; j < numResultBlocks; j++) { + result[j].codewords[i] = rawCodewords[rawCodewordsOffset++]; + } + } + // Fill out the last data block in the longer ones + const specialVersion = version.getVersionNumber() === 24; + const numLongerBlocks = specialVersion ? 8 : numResultBlocks; + for (let j = 0; j < numLongerBlocks; j++) { + result[j].codewords[longerBlocksNumDataCodewords - 1] = rawCodewords[rawCodewordsOffset++]; + } + // Now add in error correction blocks + const max = result[0].codewords.length; + for (let i = longerBlocksNumDataCodewords; i < max; i++) { + for (let j = 0; j < numResultBlocks; j++) { + const jOffset = specialVersion ? (j + 8) % numResultBlocks : j; + const iOffset = specialVersion && jOffset > 7 ? i - 1 : i; + result[jOffset].codewords[iOffset] = rawCodewords[rawCodewordsOffset++]; + } + } + if (rawCodewordsOffset !== rawCodewords.length) { + throw new IllegalArgumentException(); + } + return result; + } + getNumDataCodewords() { + return this.numDataCodewords; + } + getCodewords() { + return this.codewords; + } + } + + /* + * 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. + */ + /** + *This provides an easy abstraction to read bits at a time from a sequence of bytes, where the + * number of bits read is not often a multiple of 8.
+ * + *This class is thread-safe but not reentrant -- unless the caller modifies the bytes array + * it passed in, in which case all bets are off.
+ * + * @author Sean Owen + */ + class BitSource { + /** + * @param bytes bytes from which this will read bits. Bits will be read from the first byte first. + * Bits are read within a byte from most-significant to least-significant bit. + */ + constructor(bytes) { + this.bytes = bytes; + this.byteOffset = 0; + this.bitOffset = 0; + } + /** + * @return index of next bit in current byte which would be read by the next call to {@link #readBits(int)}. + */ + getBitOffset() { + return this.bitOffset; + } + /** + * @return index of next byte in input byte array which would be read by the next call to {@link #readBits(int)}. + */ + getByteOffset() { + return this.byteOffset; + } + /** + * @param numBits number of bits to read + * @return int representing the bits read. The bits will appear as the least-significant + * bits of the int + * @throws IllegalArgumentException if numBits isn't in [1,32] or more than is available + */ + readBits(numBits /*int*/) { + if (numBits < 1 || numBits > 32 || numBits > this.available()) { + throw new IllegalArgumentException('' + numBits); + } + let result = 0; + let bitOffset = this.bitOffset; + let byteOffset = this.byteOffset; + const bytes = this.bytes; + // First, read remainder from current byte + if (bitOffset > 0) { + const bitsLeft = 8 - bitOffset; + const toRead = numBits < bitsLeft ? numBits : bitsLeft; + const bitsToNotRead = bitsLeft - toRead; + const mask = (0xFF >> (8 - toRead)) << bitsToNotRead; + result = (bytes[byteOffset] & mask) >> bitsToNotRead; + numBits -= toRead; + bitOffset += toRead; + if (bitOffset === 8) { + bitOffset = 0; + byteOffset++; + } + } + // Next read whole bytes + if (numBits > 0) { + while (numBits >= 8) { + result = (result << 8) | (bytes[byteOffset] & 0xFF); + byteOffset++; + numBits -= 8; + } + // Finally read a partial byte + if (numBits > 0) { + const bitsToNotRead = 8 - numBits; + const mask = (0xFF >> bitsToNotRead) << bitsToNotRead; + result = (result << numBits) | ((bytes[byteOffset] & mask) >> bitsToNotRead); + bitOffset += numBits; + } + } + this.bitOffset = bitOffset; + this.byteOffset = byteOffset; + return result; + } + /** + * @return number of bits that can be read successfully + */ + available() { + return 8 * (this.bytes.length - this.byteOffset) - this.bitOffset; + } + } + + /* + * Copyright 2008 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 Mode; + (function (Mode) { + Mode[Mode["PAD_ENCODE"] = 0] = "PAD_ENCODE"; + Mode[Mode["ASCII_ENCODE"] = 1] = "ASCII_ENCODE"; + Mode[Mode["C40_ENCODE"] = 2] = "C40_ENCODE"; + Mode[Mode["TEXT_ENCODE"] = 3] = "TEXT_ENCODE"; + Mode[Mode["ANSIX12_ENCODE"] = 4] = "ANSIX12_ENCODE"; + Mode[Mode["EDIFACT_ENCODE"] = 5] = "EDIFACT_ENCODE"; + Mode[Mode["BASE256_ENCODE"] = 6] = "BASE256_ENCODE"; + })(Mode || (Mode = {})); + /** + *Data Matrix Codes can encode text as bits in one of several modes, and can use multiple modes + * in one Data Matrix Code. This class decodes the bits back into text.
+ * + *See ISO 16022:2006, 5.2.1 - 5.2.9.2
+ * + * @author bbrown@google.com (Brian Brown) + * @author Sean Owen + */ + class DecodedBitStreamParser { + static decode(bytes) { + const bits = new BitSource(bytes); + const result = new StringBuilder(); + const resultTrailer = new StringBuilder(); + const byteSegments = new Array(); + let mode = Mode.ASCII_ENCODE; + do { + if (mode === Mode.ASCII_ENCODE) { + mode = this.decodeAsciiSegment(bits, result, resultTrailer); + } + else { + switch (mode) { + case Mode.C40_ENCODE: + this.decodeC40Segment(bits, result); + break; + case Mode.TEXT_ENCODE: + this.decodeTextSegment(bits, result); + break; + case Mode.ANSIX12_ENCODE: + this.decodeAnsiX12Segment(bits, result); + break; + case Mode.EDIFACT_ENCODE: + this.decodeEdifactSegment(bits, result); + break; + case Mode.BASE256_ENCODE: + this.decodeBase256Segment(bits, result, byteSegments); + break; + default: + throw new FormatException(); + } + mode = Mode.ASCII_ENCODE; + } + } while (mode !== Mode.PAD_ENCODE && bits.available() > 0); + if (resultTrailer.length() > 0) { + result.append(resultTrailer.toString()); + } + return new DecoderResult(bytes, result.toString(), byteSegments.length === 0 ? null : byteSegments, null); + } + /** + * See ISO 16022:2006, 5.2.3 and Annex C, Table C.2 + */ + static decodeAsciiSegment(bits, result, resultTrailer) { + let upperShift = false; + do { + let oneByte = bits.readBits(8); + if (oneByte === 0) { + throw new FormatException(); + } + else if (oneByte <= 128) { // ASCII data (ASCII value + 1) + if (upperShift) { + oneByte += 128; + // upperShift = false; + } + result.append(String.fromCharCode(oneByte - 1)); + return Mode.ASCII_ENCODE; + } + else if (oneByte === 129) { // Pad + return Mode.PAD_ENCODE; + } + else if (oneByte <= 229) { // 2-digit data 00-99 (Numeric Value + 130) + const value = oneByte - 130; + if (value < 10) { // pad with '0' for single digit values + result.append('0'); + } + result.append('' + value); + } + else { + switch (oneByte) { + case 230: // Latch to C40 encodation + return Mode.C40_ENCODE; + case 231: // Latch to Base 256 encodation + return Mode.BASE256_ENCODE; + case 232: // FNC1 + result.append(String.fromCharCode(29)); // translate as ASCII 29 + break; + case 233: // Structured Append + case 234: // Reader Programming + // Ignore these symbols for now + // throw ReaderException.getInstance(); + break; + case 235: // Upper Shift (shift to Extended ASCII) + upperShift = true; + break; + case 236: // 05 Macro + result.append('[)>\u001E05\u001D'); + resultTrailer.insert(0, '\u001E\u0004'); + break; + case 237: // 06 Macro + result.append('[)>\u001E06\u001D'); + resultTrailer.insert(0, '\u001E\u0004'); + break; + case 238: // Latch to ANSI X12 encodation + return Mode.ANSIX12_ENCODE; + case 239: // Latch to Text encodation + return Mode.TEXT_ENCODE; + case 240: // Latch to EDIFACT encodation + return Mode.EDIFACT_ENCODE; + case 241: // ECI Character + // TODO(bbrown): I think we need to support ECI + // throw ReaderException.getInstance(); + // Ignore this symbol for now + break; + default: + // Not to be used in ASCII encodation + // but work around encoders that end with 254, latch back to ASCII + if (oneByte !== 254 || bits.available() !== 0) { + throw new FormatException(); + } + break; + } + } + } while (bits.available() > 0); + return Mode.ASCII_ENCODE; + } + /** + * See ISO 16022:2006, 5.2.5 and Annex C, Table C.1 + */ + static decodeC40Segment(bits, result) { + // Three C40 values are encoded in a 16-bit value as + // (1600 * C1) + (40 * C2) + C3 + 1 + // TODO(bbrown): The Upper Shift with C40 doesn't work in the 4 value scenario all the time + let upperShift = false; + const cValues = []; + let shift = 0; + do { + // If there is only one byte left then it will be encoded as ASCII + if (bits.available() === 8) { + return; + } + const firstByte = bits.readBits(8); + if (firstByte === 254) { // Unlatch codeword + return; + } + this.parseTwoBytes(firstByte, bits.readBits(8), cValues); + for (let i = 0; i < 3; i++) { + const cValue = cValues[i]; + switch (shift) { + case 0: + if (cValue < 3) { + shift = cValue + 1; + } + else if (cValue < this.C40_BASIC_SET_CHARS.length) { + const c40char = this.C40_BASIC_SET_CHARS[cValue]; + if (upperShift) { + result.append(String.fromCharCode(c40char.charCodeAt(0) + 128)); + upperShift = false; + } + else { + result.append(c40char); + } + } + else { + throw new FormatException(); + } + break; + case 1: + if (upperShift) { + result.append(String.fromCharCode(cValue + 128)); + upperShift = false; + } + else { + result.append(String.fromCharCode(cValue)); + } + shift = 0; + break; + case 2: + if (cValue < this.C40_SHIFT2_SET_CHARS.length) { + const c40char = this.C40_SHIFT2_SET_CHARS[cValue]; + if (upperShift) { + result.append(String.fromCharCode(c40char.charCodeAt(0) + 128)); + upperShift = false; + } + else { + result.append(c40char); + } + } + else { + switch (cValue) { + case 27: // FNC1 + result.append(String.fromCharCode(29)); // translate as ASCII 29 + break; + case 30: // Upper Shift + upperShift = true; + break; + default: + throw new FormatException(); + } + } + shift = 0; + break; + case 3: + if (upperShift) { + result.append(String.fromCharCode(cValue + 224)); + upperShift = false; + } + else { + result.append(String.fromCharCode(cValue + 96)); + } + shift = 0; + break; + default: + throw new FormatException(); + } + } + } while (bits.available() > 0); + } + /** + * See ISO 16022:2006, 5.2.6 and Annex C, Table C.2 + */ + static decodeTextSegment(bits, result) { + // Three Text values are encoded in a 16-bit value as + // (1600 * C1) + (40 * C2) + C3 + 1 + // TODO(bbrown): The Upper Shift with Text doesn't work in the 4 value scenario all the time + let upperShift = false; + let cValues = []; + let shift = 0; + do { + // If there is only one byte left then it will be encoded as ASCII + if (bits.available() === 8) { + return; + } + const firstByte = bits.readBits(8); + if (firstByte === 254) { // Unlatch codeword + return; + } + this.parseTwoBytes(firstByte, bits.readBits(8), cValues); + for (let i = 0; i < 3; i++) { + const cValue = cValues[i]; + switch (shift) { + case 0: + if (cValue < 3) { + shift = cValue + 1; + } + else if (cValue < this.TEXT_BASIC_SET_CHARS.length) { + const textChar = this.TEXT_BASIC_SET_CHARS[cValue]; + if (upperShift) { + result.append(String.fromCharCode(textChar.charCodeAt(0) + 128)); + upperShift = false; + } + else { + result.append(textChar); + } + } + else { + throw new FormatException(); + } + break; + case 1: + if (upperShift) { + result.append(String.fromCharCode(cValue + 128)); + upperShift = false; + } + else { + result.append(String.fromCharCode(cValue)); + } + shift = 0; + break; + case 2: + // Shift 2 for Text is the same encoding as C40 + if (cValue < this.TEXT_SHIFT2_SET_CHARS.length) { + const textChar = this.TEXT_SHIFT2_SET_CHARS[cValue]; + if (upperShift) { + result.append(String.fromCharCode(textChar.charCodeAt(0) + 128)); + upperShift = false; + } + else { + result.append(textChar); + } + } + else { + switch (cValue) { + case 27: // FNC1 + result.append(String.fromCharCode(29)); // translate as ASCII 29 + break; + case 30: // Upper Shift + upperShift = true; + break; + default: + throw new FormatException(); + } + } + shift = 0; + break; + case 3: + if (cValue < this.TEXT_SHIFT3_SET_CHARS.length) { + const textChar = this.TEXT_SHIFT3_SET_CHARS[cValue]; + if (upperShift) { + result.append(String.fromCharCode(textChar.charCodeAt(0) + 128)); + upperShift = false; + } + else { + result.append(textChar); + } + shift = 0; + } + else { + throw new FormatException(); + } + break; + default: + throw new FormatException(); + } + } + } while (bits.available() > 0); + } + /** + * See ISO 16022:2006, 5.2.7 + */ + static decodeAnsiX12Segment(bits, result) { + // Three ANSI X12 values are encoded in a 16-bit value as + // (1600 * C1) + (40 * C2) + C3 + 1 + const cValues = []; + do { + // If there is only one byte left then it will be encoded as ASCII + if (bits.available() === 8) { + return; + } + const firstByte = bits.readBits(8); + if (firstByte === 254) { // Unlatch codeword + return; + } + this.parseTwoBytes(firstByte, bits.readBits(8), cValues); + for (let i = 0; i < 3; i++) { + const cValue = cValues[i]; + switch (cValue) { + case 0: // X12 segment terminatorThe main class which implements Data Matrix Code decoding -- as opposed to locating and extracting + * the Data Matrix Code from an image.
+ * + * @author bbrown@google.com (Brian Brown) + */ + class Decoder$1 { + constructor() { + this.rsDecoder = new ReedSolomonDecoder(GenericGF.DATA_MATRIX_FIELD_256); + } + /** + *Decodes a Data Matrix Code represented as a {@link BitMatrix}. A 1 or "true" is taken + * to mean a black module.
+ * + * @param bits booleans representing white/black Data Matrix Code modules + * @return text and bytes encoded within the Data Matrix Code + * @throws FormatException if the Data Matrix Code cannot be decoded + * @throws ChecksumException if error correction fails + */ + decode(bits) { + // Construct a parser and read version, error-correction level + const parser = new BitMatrixParser(bits); + const version = parser.getVersion(); + // Read codewords + const codewords = parser.readCodewords(); + // Separate into data blocks + const dataBlocks = DataBlock.getDataBlocks(codewords, version); + // Count total number of data bytes + let totalBytes = 0; + for (let db of dataBlocks) { + totalBytes += db.getNumDataCodewords(); + } + const resultBytes = new Uint8Array(totalBytes); + const dataBlocksCount = dataBlocks.length; + // Error-correct and copy data blocks together into a stream of bytes + for (let j = 0; j < dataBlocksCount; j++) { + const dataBlock = dataBlocks[j]; + const codewordBytes = dataBlock.getCodewords(); + const numDataCodewords = dataBlock.getNumDataCodewords(); + this.correctErrors(codewordBytes, numDataCodewords); + for (let i = 0; i < numDataCodewords; i++) { + // De-interlace data blocks. + resultBytes[i * dataBlocksCount + j] = codewordBytes[i]; + } + } + // Decode the contents of that stream of bytes + return DecodedBitStreamParser.decode(resultBytes); + } + /** + *Given data and error-correction codewords received, possibly corrupted by errors, attempts to + * correct the errors in-place using Reed-Solomon error correction.
+ * + * @param codewordBytes data and error correction codewords + * @param numDataCodewords number of codewords that are data bytes + * @throws ChecksumException if error correction fails + */ + correctErrors(codewordBytes, numDataCodewords) { + // const numCodewords = codewordBytes.length; + // First read into an array of ints + const codewordsInts = new Int32Array(codewordBytes); + // for (let i = 0; i < numCodewords; i++) { + // codewordsInts[i] = codewordBytes[i] & 0xFF; + // } + try { + this.rsDecoder.decode(codewordsInts, codewordBytes.length - numDataCodewords); + } + catch (ignored /* ReedSolomonException */) { + throw new ChecksumException(); + } + // Copy back into array of bytes -- only need to worry about the bytes that were data + // We don't care about errors in the error-correction codewords + for (let i = 0; i < numDataCodewords; i++) { + codewordBytes[i] = codewordsInts[i]; + } + } + } + + /* + * Copyright 2008 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. + */ + /** + *Encapsulates logic that can detect a Data Matrix Code in an image, even if the Data Matrix Code + * is rotated or skewed, or partially obscured.
+ * + * @author Sean Owen + */ + class Detector$1 { + constructor(image) { + this.image = image; + this.rectangleDetector = new WhiteRectangleDetector(this.image); + } + /** + *Detects a Data Matrix Code in an image.
+ * + * @return {@link DetectorResult} encapsulating results of detecting a Data Matrix Code + * @throws NotFoundException if no Data Matrix Code can be found + */ + detect() { + const cornerPoints = this.rectangleDetector.detect(); + let points = this.detectSolid1(cornerPoints); + points = this.detectSolid2(points); + points[3] = this.correctTopRight(points); + if (!points[3]) { + throw new NotFoundException(); + } + points = this.shiftToModuleCenter(points); + const topLeft = points[0]; + const bottomLeft = points[1]; + const bottomRight = points[2]; + const topRight = points[3]; + let dimensionTop = this.transitionsBetween(topLeft, topRight) + 1; + let dimensionRight = this.transitionsBetween(bottomRight, topRight) + 1; + if ((dimensionTop & 0x01) === 1) { + dimensionTop += 1; + } + if ((dimensionRight & 0x01) === 1) { + dimensionRight += 1; + } + if (4 * dimensionTop < 7 * dimensionRight && 4 * dimensionRight < 7 * dimensionTop) { + // The matrix is square + dimensionTop = dimensionRight = Math.max(dimensionTop, dimensionRight); + } + let bits = Detector$1.sampleGrid(this.image, topLeft, bottomLeft, bottomRight, topRight, dimensionTop, dimensionRight); + return new DetectorResult(bits, [topLeft, bottomLeft, bottomRight, topRight]); + } + static shiftPoint(point, to, div) { + let x = (to.getX() - point.getX()) / (div + 1); + let y = (to.getY() - point.getY()) / (div + 1); + return new ResultPoint(point.getX() + x, point.getY() + y); + } + static moveAway(point, fromX, fromY) { + let x = point.getX(); + let y = point.getY(); + if (x < fromX) { + x -= 1; + } + else { + x += 1; + } + if (y < fromY) { + y -= 1; + } + else { + y += 1; + } + return new ResultPoint(x, y); + } + /** + * Detect a solid side which has minimum transition. + */ + detectSolid1(cornerPoints) { + // 0 2 + // 1 3 + let pointA = cornerPoints[0]; + let pointB = cornerPoints[1]; + let pointC = cornerPoints[3]; + let pointD = cornerPoints[2]; + let trAB = this.transitionsBetween(pointA, pointB); + let trBC = this.transitionsBetween(pointB, pointC); + let trCD = this.transitionsBetween(pointC, pointD); + let trDA = this.transitionsBetween(pointD, pointA); + // 0..3 + // : : + // 1--2 + let min = trAB; + let points = [pointD, pointA, pointB, pointC]; + if (min > trBC) { + min = trBC; + points[0] = pointA; + points[1] = pointB; + points[2] = pointC; + points[3] = pointD; + } + if (min > trCD) { + min = trCD; + points[0] = pointB; + points[1] = pointC; + points[2] = pointD; + points[3] = pointA; + } + if (min > trDA) { + points[0] = pointC; + points[1] = pointD; + points[2] = pointA; + points[3] = pointB; + } + return points; + } + /** + * Detect a second solid side next to first solid side. + */ + detectSolid2(points) { + // A..D + // : : + // B--C + let pointA = points[0]; + let pointB = points[1]; + let pointC = points[2]; + let pointD = points[3]; + // Transition detection on the edge is not stable. + // To safely detect, shift the points to the module center. + let tr = this.transitionsBetween(pointA, pointD); + let pointBs = Detector$1.shiftPoint(pointB, pointC, (tr + 1) * 4); + let pointCs = Detector$1.shiftPoint(pointC, pointB, (tr + 1) * 4); + let trBA = this.transitionsBetween(pointBs, pointA); + let trCD = this.transitionsBetween(pointCs, pointD); + // 0..3 + // | : + // 1--2 + if (trBA < trCD) { + // solid sides: A-B-C + points[0] = pointA; + points[1] = pointB; + points[2] = pointC; + points[3] = pointD; + } + else { + // solid sides: B-C-D + points[0] = pointB; + points[1] = pointC; + points[2] = pointD; + points[3] = pointA; + } + return points; + } + /** + * Calculates the corner position of the white top right module. + */ + correctTopRight(points) { + // A..D + // | : + // B--C + let pointA = points[0]; + let pointB = points[1]; + let pointC = points[2]; + let pointD = points[3]; + // shift points for safe transition detection. + let trTop = this.transitionsBetween(pointA, pointD); + let trRight = this.transitionsBetween(pointB, pointD); + let pointAs = Detector$1.shiftPoint(pointA, pointB, (trRight + 1) * 4); + let pointCs = Detector$1.shiftPoint(pointC, pointB, (trTop + 1) * 4); + trTop = this.transitionsBetween(pointAs, pointD); + trRight = this.transitionsBetween(pointCs, pointD); + let candidate1 = new ResultPoint(pointD.getX() + (pointC.getX() - pointB.getX()) / (trTop + 1), pointD.getY() + (pointC.getY() - pointB.getY()) / (trTop + 1)); + let candidate2 = new ResultPoint(pointD.getX() + (pointA.getX() - pointB.getX()) / (trRight + 1), pointD.getY() + (pointA.getY() - pointB.getY()) / (trRight + 1)); + if (!this.isValid(candidate1)) { + if (this.isValid(candidate2)) { + return candidate2; + } + return null; + } + if (!this.isValid(candidate2)) { + return candidate1; + } + let sumc1 = this.transitionsBetween(pointAs, candidate1) + this.transitionsBetween(pointCs, candidate1); + let sumc2 = this.transitionsBetween(pointAs, candidate2) + this.transitionsBetween(pointCs, candidate2); + if (sumc1 > sumc2) { + return candidate1; + } + else { + return candidate2; + } + } + /** + * Shift the edge points to the module center. + */ + shiftToModuleCenter(points) { + // A..D + // | : + // B--C + let pointA = points[0]; + let pointB = points[1]; + let pointC = points[2]; + let pointD = points[3]; + // calculate pseudo dimensions + let dimH = this.transitionsBetween(pointA, pointD) + 1; + let dimV = this.transitionsBetween(pointC, pointD) + 1; + // shift points for safe dimension detection + let pointAs = Detector$1.shiftPoint(pointA, pointB, dimV * 4); + let pointCs = Detector$1.shiftPoint(pointC, pointB, dimH * 4); + // calculate more precise dimensions + dimH = this.transitionsBetween(pointAs, pointD) + 1; + dimV = this.transitionsBetween(pointCs, pointD) + 1; + if ((dimH & 0x01) === 1) { + dimH += 1; + } + if ((dimV & 0x01) === 1) { + dimV += 1; + } + // WhiteRectangleDetector returns points inside of the rectangle. + // I want points on the edges. + let centerX = (pointA.getX() + pointB.getX() + pointC.getX() + pointD.getX()) / 4; + let centerY = (pointA.getY() + pointB.getY() + pointC.getY() + pointD.getY()) / 4; + pointA = Detector$1.moveAway(pointA, centerX, centerY); + pointB = Detector$1.moveAway(pointB, centerX, centerY); + pointC = Detector$1.moveAway(pointC, centerX, centerY); + pointD = Detector$1.moveAway(pointD, centerX, centerY); + let pointBs; + let pointDs; + // shift points to the center of each modules + pointAs = Detector$1.shiftPoint(pointA, pointB, dimV * 4); + pointAs = Detector$1.shiftPoint(pointAs, pointD, dimH * 4); + pointBs = Detector$1.shiftPoint(pointB, pointA, dimV * 4); + pointBs = Detector$1.shiftPoint(pointBs, pointC, dimH * 4); + pointCs = Detector$1.shiftPoint(pointC, pointD, dimV * 4); + pointCs = Detector$1.shiftPoint(pointCs, pointB, dimH * 4); + pointDs = Detector$1.shiftPoint(pointD, pointC, dimV * 4); + pointDs = Detector$1.shiftPoint(pointDs, pointA, dimH * 4); + return [pointAs, pointBs, pointCs, pointDs]; + } + isValid(p) { + return p.getX() >= 0 && p.getX() < this.image.getWidth() && p.getY() > 0 && p.getY() < this.image.getHeight(); + } + static sampleGrid(image, topLeft, bottomLeft, bottomRight, topRight, dimensionX, dimensionY) { + const sampler = GridSamplerInstance.getInstance(); + return sampler.sampleGrid(image, dimensionX, dimensionY, 0.5, 0.5, dimensionX - 0.5, 0.5, dimensionX - 0.5, dimensionY - 0.5, 0.5, dimensionY - 0.5, topLeft.getX(), topLeft.getY(), topRight.getX(), topRight.getY(), bottomRight.getX(), bottomRight.getY(), bottomLeft.getX(), bottomLeft.getY()); + } + /** + * Counts the number of black/white transitions between two points, using something like Bresenham's algorithm. + */ + transitionsBetween(from, to) { + // See QR Code Detector, sizeOfBlackWhiteBlackRun() + let fromX = Math.trunc(from.getX()); + let fromY = Math.trunc(from.getY()); + let toX = Math.trunc(to.getX()); + let toY = Math.trunc(to.getY()); + 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 / 2; + let ystep = fromY < toY ? 1 : -1; + let xstep = fromX < toX ? 1 : -1; + let transitions = 0; + let inBlack = this.image.get(steep ? fromY : fromX, steep ? fromX : fromY); + for (let x = fromX, y = fromY; x !== toX; x += xstep) { + let isBlack = this.image.get(steep ? y : x, steep ? x : y); + if (isBlack !== inBlack) { + transitions++; + inBlack = isBlack; + } + error += dy; + if (error > 0) { + if (y === toY) { + break; + } + y += ystep; + error -= dx; + } + } + return transitions; + } + } + + /* + * 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. + */ + /** + * This implementation can detect and decode Data Matrix codes in an image. + * + * @author bbrown@google.com (Brian Brown) + */ + class DataMatrixReader { + constructor() { + this.decoder = new Decoder$1(); + } + /** + * Locates and decodes a Data Matrix code in an image. + * + * @return a String representing the content encoded by the Data Matrix code + * @throws NotFoundException if a Data Matrix code cannot be found + * @throws FormatException if a Data Matrix code cannot be decoded + * @throws ChecksumException if error correction fails + */ + // @Override + // public Result decode(BinaryBitmap image) throws NotFoundException, ChecksumException, FormatException { + // return decode(image, null); + // } + // @Override + decode(image, hints = null) { + let decoderResult; + let points; + if (hints != null && hints.has(DecodeHintType$1.PURE_BARCODE)) { + const bits = DataMatrixReader.extractPureBits(image.getBlackMatrix()); + decoderResult = this.decoder.decode(bits); + points = DataMatrixReader.NO_POINTS; + } + else { + const detectorResult = new Detector$1(image.getBlackMatrix()).detect(); + decoderResult = this.decoder.decode(detectorResult.getBits()); + points = detectorResult.getPoints(); + } + const rawBytes = decoderResult.getRawBytes(); + const result = new Result(decoderResult.getText(), rawBytes, 8 * rawBytes.length, points, BarcodeFormat$1.DATA_MATRIX, System.currentTimeMillis()); + const byteSegments = decoderResult.getByteSegments(); + if (byteSegments != null) { + result.putMetadata(ResultMetadataType$1.BYTE_SEGMENTS, byteSegments); + } + const ecLevel = decoderResult.getECLevel(); + if (ecLevel != null) { + result.putMetadata(ResultMetadataType$1.ERROR_CORRECTION_LEVEL, ecLevel); + } + return result; + } + // @Override + reset() { + // do nothing + } + /** + * This method detects a code in a "pure" image -- that is, pure monochrome image + * which contains only an unrotated, unskewed, image of a code, with some white border + * around it. This is a specialized method that works exceptionally fast in this special + * case. + * + * @see com.google.zxing.qrcode.QRCodeReader#extractPureBits(BitMatrix) + */ + static extractPureBits(image) { + const leftTopBlack = image.getTopLeftOnBit(); + const rightBottomBlack = image.getBottomRightOnBit(); + if (leftTopBlack == null || rightBottomBlack == null) { + throw new NotFoundException(); + } + const moduleSize = this.moduleSize(leftTopBlack, image); + let top = leftTopBlack[1]; + const bottom = rightBottomBlack[1]; + let left = leftTopBlack[0]; + const right = rightBottomBlack[0]; + const matrixWidth = (right - left + 1) / moduleSize; + const matrixHeight = (bottom - top + 1) / moduleSize; + if (matrixWidth <= 0 || matrixHeight <= 0) { + throw new NotFoundException(); + } + // Push in the "border" by half the module width so that we start + // sampling in the middle of the module. Just in case the image is a + // little off, this will help recover. + const nudge = moduleSize / 2; + top += nudge; + left += nudge; + // Now just read off the bits + const bits = new BitMatrix(matrixWidth, matrixHeight); + for (let y = 0; y < matrixHeight; y++) { + const iOffset = top + y * moduleSize; + for (let x = 0; x < matrixWidth; x++) { + if (image.get(left + x * moduleSize, iOffset)) { + bits.set(x, y); + } + } + } + return bits; + } + static moduleSize(leftTopBlack, image) { + const width = image.getWidth(); + let x = leftTopBlack[0]; + const y = leftTopBlack[1]; + while (x < width && image.get(x, y)) { + x++; + } + if (x === width) { + throw new NotFoundException(); + } + const moduleSize = x - leftTopBlack[0]; + if (moduleSize === 0) { + throw new NotFoundException(); + } + return moduleSize; + } + } + DataMatrixReader.NO_POINTS = []; + + /** + * @deprecated Moving to @zxing/browser + * + * QR Code reader to use from browser. + */ + class BrowserDatamatrixCodeReader extends BrowserCodeReader { + /** + * Creates an instance of BrowserQRCodeReader. + * @param {number} [timeBetweenScansMillis=500] the time delay between subsequent decode tries + */ + constructor(timeBetweenScansMillis = 500) { + super(new DataMatrixReader(), timeBetweenScansMillis); + } + } + + /* + * 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 ErrorCorrectionLevelValues; + (function (ErrorCorrectionLevelValues) { + ErrorCorrectionLevelValues[ErrorCorrectionLevelValues["L"] = 0] = "L"; + ErrorCorrectionLevelValues[ErrorCorrectionLevelValues["M"] = 1] = "M"; + ErrorCorrectionLevelValues[ErrorCorrectionLevelValues["Q"] = 2] = "Q"; + ErrorCorrectionLevelValues[ErrorCorrectionLevelValues["H"] = 3] = "H"; + })(ErrorCorrectionLevelValues || (ErrorCorrectionLevelValues = {})); + /** + *See ISO 18004:2006, 6.5.1. This enum encapsulates the four error correction levels + * defined by the QR code standard.
+ * + * @author Sean Owen + */ + class ErrorCorrectionLevel { + constructor(value, stringValue, bits /*int*/) { + this.value = value; + this.stringValue = stringValue; + this.bits = bits; + ErrorCorrectionLevel.FOR_BITS.set(bits, this); + ErrorCorrectionLevel.FOR_VALUE.set(value, this); + } + getValue() { + return this.value; + } + getBits() { + return this.bits; + } + static fromString(s) { + switch (s) { + case 'L': return ErrorCorrectionLevel.L; + case 'M': return ErrorCorrectionLevel.M; + case 'Q': return ErrorCorrectionLevel.Q; + case 'H': return ErrorCorrectionLevel.H; + default: throw new ArgumentException(s + 'not available'); + } + } + toString() { + return this.stringValue; + } + equals(o) { + if (!(o instanceof ErrorCorrectionLevel)) { + return false; + } + const other = o; + return this.value === other.value; + } + /** + * @param bits int containing the two bits encoding a QR Code's error correction level + * @return ErrorCorrectionLevel representing the encoded error correction level + */ + static forBits(bits /*int*/) { + if (bits < 0 || bits >= ErrorCorrectionLevel.FOR_BITS.size) { + throw new IllegalArgumentException(); + } + return ErrorCorrectionLevel.FOR_BITS.get(bits); + } + } + ErrorCorrectionLevel.FOR_BITS = new Map(); + ErrorCorrectionLevel.FOR_VALUE = new Map(); + /** L = ~7% correction */ + ErrorCorrectionLevel.L = new ErrorCorrectionLevel(ErrorCorrectionLevelValues.L, 'L', 0x01); + /** M = ~15% correction */ + ErrorCorrectionLevel.M = new ErrorCorrectionLevel(ErrorCorrectionLevelValues.M, 'M', 0x00); + /** Q = ~25% correction */ + ErrorCorrectionLevel.Q = new ErrorCorrectionLevel(ErrorCorrectionLevelValues.Q, 'Q', 0x03); + /** H = ~30% correction */ + ErrorCorrectionLevel.H = new ErrorCorrectionLevel(ErrorCorrectionLevelValues.H, 'H', 0x02); + + /* + * 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. + */ + /** + *Encapsulates a QR Code's format information, including the data mask used and + * error correction level.
+ * + * @author Sean Owen + * @see DataMask + * @see ErrorCorrectionLevel + */ + class FormatInformation { + constructor(formatInfo /*int*/) { + // Bits 3,4 + this.errorCorrectionLevel = ErrorCorrectionLevel.forBits((formatInfo >> 3) & 0x03); + // Bottom 3 bits + this.dataMask = /*(byte) */ (formatInfo & 0x07); + } + static numBitsDiffering(a /*int*/, b /*int*/) { + return Integer.bitCount(a ^ b); + } + /** + * @param maskedFormatInfo1 format info indicator, with mask still applied + * @param maskedFormatInfo2 second copy of same info; both are checked at the same time + * to establish best match + * @return information about the format it specifies, or {@code null} + * if doesn't seem to match any known pattern + */ + static decodeFormatInformation(maskedFormatInfo1 /*int*/, maskedFormatInfo2 /*int*/) { + const formatInfo = FormatInformation.doDecodeFormatInformation(maskedFormatInfo1, maskedFormatInfo2); + if (formatInfo !== null) { + return formatInfo; + } + // Should return null, but, some QR codes apparently + // do not mask this info. Try again by actually masking the pattern + // first + return FormatInformation.doDecodeFormatInformation(maskedFormatInfo1 ^ FormatInformation.FORMAT_INFO_MASK_QR, maskedFormatInfo2 ^ FormatInformation.FORMAT_INFO_MASK_QR); + } + static doDecodeFormatInformation(maskedFormatInfo1 /*int*/, maskedFormatInfo2 /*int*/) { + // Find the int in FORMAT_INFO_DECODE_LOOKUP with fewest bits differing + let bestDifference = Number.MAX_SAFE_INTEGER; + let bestFormatInfo = 0; + for (const decodeInfo of FormatInformation.FORMAT_INFO_DECODE_LOOKUP) { + const targetInfo = decodeInfo[0]; + if (targetInfo === maskedFormatInfo1 || targetInfo === maskedFormatInfo2) { + // Found an exact match + return new FormatInformation(decodeInfo[1]); + } + let bitsDifference = FormatInformation.numBitsDiffering(maskedFormatInfo1, targetInfo); + if (bitsDifference < bestDifference) { + bestFormatInfo = decodeInfo[1]; + bestDifference = bitsDifference; + } + if (maskedFormatInfo1 !== maskedFormatInfo2) { + // also try the other option + bitsDifference = FormatInformation.numBitsDiffering(maskedFormatInfo2, targetInfo); + if (bitsDifference < bestDifference) { + bestFormatInfo = decodeInfo[1]; + bestDifference = bitsDifference; + } + } + } + // Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits + // differing means we found a match + if (bestDifference <= 3) { + return new FormatInformation(bestFormatInfo); + } + return null; + } + getErrorCorrectionLevel() { + return this.errorCorrectionLevel; + } + getDataMask() { + return this.dataMask; + } + /*@Override*/ + hashCode() { + return (this.errorCorrectionLevel.getBits() << 3) | this.dataMask; + } + /*@Override*/ + equals(o) { + if (!(o instanceof FormatInformation)) { + return false; + } + const other = o; + return this.errorCorrectionLevel === other.errorCorrectionLevel && + this.dataMask === other.dataMask; + } + } + FormatInformation.FORMAT_INFO_MASK_QR = 0x5412; + /** + * See ISO 18004:2006, Annex C, Table C.1 + */ + FormatInformation.FORMAT_INFO_DECODE_LOOKUP = [ + Int32Array.from([0x5412, 0x00]), + Int32Array.from([0x5125, 0x01]), + Int32Array.from([0x5E7C, 0x02]), + Int32Array.from([0x5B4B, 0x03]), + Int32Array.from([0x45F9, 0x04]), + Int32Array.from([0x40CE, 0x05]), + Int32Array.from([0x4F97, 0x06]), + Int32Array.from([0x4AA0, 0x07]), + Int32Array.from([0x77C4, 0x08]), + Int32Array.from([0x72F3, 0x09]), + Int32Array.from([0x7DAA, 0x0A]), + Int32Array.from([0x789D, 0x0B]), + Int32Array.from([0x662F, 0x0C]), + Int32Array.from([0x6318, 0x0D]), + Int32Array.from([0x6C41, 0x0E]), + Int32Array.from([0x6976, 0x0F]), + Int32Array.from([0x1689, 0x10]), + Int32Array.from([0x13BE, 0x11]), + Int32Array.from([0x1CE7, 0x12]), + Int32Array.from([0x19D0, 0x13]), + Int32Array.from([0x0762, 0x14]), + Int32Array.from([0x0255, 0x15]), + Int32Array.from([0x0D0C, 0x16]), + Int32Array.from([0x083B, 0x17]), + Int32Array.from([0x355F, 0x18]), + Int32Array.from([0x3068, 0x19]), + Int32Array.from([0x3F31, 0x1A]), + Int32Array.from([0x3A06, 0x1B]), + Int32Array.from([0x24B4, 0x1C]), + Int32Array.from([0x2183, 0x1D]), + Int32Array.from([0x2EDA, 0x1E]), + Int32Array.from([0x2BED, 0x1F]), + ]; + + /** + *Encapsulates a set of error-correction blocks in one symbol version. Most versions will + * use blocks of differing sizes within one version, so, this encapsulates the parameters for + * each set of blocks. It also holds the number of error-correction codewords per block since it + * will be the same across all blocks within one version.
+ */ + class ECBlocks$1 { + constructor(ecCodewordsPerBlock /*int*/, ...ecBlocks) { + this.ecCodewordsPerBlock = ecCodewordsPerBlock; + this.ecBlocks = ecBlocks; + } + getECCodewordsPerBlock() { + return this.ecCodewordsPerBlock; + } + getNumBlocks() { + let total = 0; + const ecBlocks = this.ecBlocks; + for (const ecBlock of ecBlocks) { + total += ecBlock.getCount(); + } + return total; + } + getTotalECCodewords() { + return this.ecCodewordsPerBlock * this.getNumBlocks(); + } + getECBlocks() { + return this.ecBlocks; + } + } + + /** + *Encapsulates the parameters for one error-correction block in one symbol version. + * This includes the number of data codewords, and the number of times a block with these + * parameters is used consecutively in the QR code version's format.
+ */ + class ECB$1 { + constructor(count /*int*/, dataCodewords /*int*/) { + this.count = count; + this.dataCodewords = dataCodewords; + } + getCount() { + return this.count; + } + getDataCodewords() { + return this.dataCodewords; + } + } + + /* + * 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. + */ + /** + * See ISO 18004:2006 Annex D + * + * @author Sean Owen + */ + class Version$1 { + constructor(versionNumber /*int*/, alignmentPatternCenters, ...ecBlocks) { + this.versionNumber = versionNumber; + this.alignmentPatternCenters = alignmentPatternCenters; + this.ecBlocks = ecBlocks; + let total = 0; + const ecCodewords = ecBlocks[0].getECCodewordsPerBlock(); + const ecbArray = ecBlocks[0].getECBlocks(); + for (const ecBlock of ecbArray) { + total += ecBlock.getCount() * (ecBlock.getDataCodewords() + ecCodewords); + } + this.totalCodewords = total; + } + getVersionNumber() { + return this.versionNumber; + } + getAlignmentPatternCenters() { + return this.alignmentPatternCenters; + } + getTotalCodewords() { + return this.totalCodewords; + } + getDimensionForVersion() { + return 17 + 4 * this.versionNumber; + } + getECBlocksForLevel(ecLevel) { + return this.ecBlocks[ecLevel.getValue()]; + // TYPESCRIPTPORT: original was using ordinal, and using the order of levels as defined in ErrorCorrectionLevel enum (LMQH) + // I will use the direct value from ErrorCorrectionLevelValues enum which in typescript goes to a number + } + /** + *Deduces version information purely from QR Code dimensions.
+ * + * @param dimension dimension in modules + * @return Version for a QR Code of that dimension + * @throws FormatException if dimension is not 1 mod 4 + */ + static getProvisionalVersionForDimension(dimension /*int*/) { + if (dimension % 4 !== 1) { + throw new FormatException(); + } + try { + return this.getVersionForNumber((dimension - 17) / 4); + } + catch (ignored /*: IllegalArgumentException*/) { + throw new FormatException(); + } + } + static getVersionForNumber(versionNumber /*int*/) { + if (versionNumber < 1 || versionNumber > 40) { + throw new IllegalArgumentException(); + } + return Version$1.VERSIONS[versionNumber - 1]; + } + static decodeVersionInformation(versionBits /*int*/) { + let bestDifference = Number.MAX_SAFE_INTEGER; + let bestVersion = 0; + for (let i = 0; i < Version$1.VERSION_DECODE_INFO.length; i++) { + const targetVersion = Version$1.VERSION_DECODE_INFO[i]; + // Do the version info bits match exactly? done. + if (targetVersion === versionBits) { + return Version$1.getVersionForNumber(i + 7); + } + // Otherwise see if this is the closest to a real version info bit string + // we have seen so far + const bitsDifference = FormatInformation.numBitsDiffering(versionBits, targetVersion); + if (bitsDifference < bestDifference) { + bestVersion = i + 7; + bestDifference = bitsDifference; + } + } + // We can tolerate up to 3 bits of error since no two version info codewords will + // differ in less than 8 bits. + if (bestDifference <= 3) { + return Version$1.getVersionForNumber(bestVersion); + } + // If we didn't find a close enough match, fail + return null; + } + /** + * See ISO 18004:2006 Annex E + */ + buildFunctionPattern() { + const dimension = this.getDimensionForVersion(); + const bitMatrix = new BitMatrix(dimension); + // Top left finder pattern + separator + format + bitMatrix.setRegion(0, 0, 9, 9); + // Top right finder pattern + separator + format + bitMatrix.setRegion(dimension - 8, 0, 8, 9); + // Bottom left finder pattern + separator + format + bitMatrix.setRegion(0, dimension - 8, 9, 8); + // Alignment patterns + const max = this.alignmentPatternCenters.length; + for (let x = 0; x < max; x++) { + const 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)) { + // No alignment patterns near the three finder patterns + continue; + } + bitMatrix.setRegion(this.alignmentPatternCenters[y] - 2, i, 5, 5); + } + } + // Vertical timing pattern + bitMatrix.setRegion(6, 9, 1, dimension - 17); + // Horizontal timing pattern + bitMatrix.setRegion(9, 6, dimension - 17, 1); + if (this.versionNumber > 6) { + // Version info, top right + bitMatrix.setRegion(dimension - 11, 0, 3, 6); + // Version info, bottom left + bitMatrix.setRegion(0, dimension - 11, 6, 3); + } + return bitMatrix; + } + /*@Override*/ + toString() { + return '' + this.versionNumber; + } + } + /** + * See ISO 18004:2006 Annex D. + * Element i represents the raw version bits that specify version i + 7 + */ + Version$1.VERSION_DECODE_INFO = Int32Array.from([ + 0x07C94, 0x085BC, 0x09A99, 0x0A4D3, 0x0BBF6, + 0x0C762, 0x0D847, 0x0E60D, 0x0F928, 0x10B78, + 0x1145D, 0x12A17, 0x13532, 0x149A6, 0x15683, + 0x168C9, 0x177EC, 0x18EC4, 0x191E1, 0x1AFAB, + 0x1B08E, 0x1CC1A, 0x1D33F, 0x1ED75, 0x1F250, + 0x209D5, 0x216F0, 0x228BA, 0x2379F, 0x24B0B, + 0x2542E, 0x26A64, 0x27541, 0x28C69 + ]); + /** + * See ISO 18004:2006 6.5.1 Table 9 + */ + Version$1.VERSIONS = [ + new Version$1(1, new Int32Array(0), new ECBlocks$1(7, new ECB$1(1, 19)), new ECBlocks$1(10, new ECB$1(1, 16)), new ECBlocks$1(13, new ECB$1(1, 13)), new ECBlocks$1(17, new ECB$1(1, 9))), + new Version$1(2, Int32Array.from([6, 18]), new ECBlocks$1(10, new ECB$1(1, 34)), new ECBlocks$1(16, new ECB$1(1, 28)), new ECBlocks$1(22, new ECB$1(1, 22)), new ECBlocks$1(28, new ECB$1(1, 16))), + new Version$1(3, Int32Array.from([6, 22]), new ECBlocks$1(15, new ECB$1(1, 55)), new ECBlocks$1(26, new ECB$1(1, 44)), new ECBlocks$1(18, new ECB$1(2, 17)), new ECBlocks$1(22, new ECB$1(2, 13))), + new Version$1(4, Int32Array.from([6, 26]), new ECBlocks$1(20, new ECB$1(1, 80)), new ECBlocks$1(18, new ECB$1(2, 32)), new ECBlocks$1(26, new ECB$1(2, 24)), new ECBlocks$1(16, new ECB$1(4, 9))), + new Version$1(5, Int32Array.from([6, 30]), new ECBlocks$1(26, new ECB$1(1, 108)), new ECBlocks$1(24, new ECB$1(2, 43)), new ECBlocks$1(18, new ECB$1(2, 15), new ECB$1(2, 16)), new ECBlocks$1(22, new ECB$1(2, 11), new ECB$1(2, 12))), + new Version$1(6, Int32Array.from([6, 34]), new ECBlocks$1(18, new ECB$1(2, 68)), new ECBlocks$1(16, new ECB$1(4, 27)), new ECBlocks$1(24, new ECB$1(4, 19)), new ECBlocks$1(28, new ECB$1(4, 15))), + new Version$1(7, Int32Array.from([6, 22, 38]), new ECBlocks$1(20, new ECB$1(2, 78)), new ECBlocks$1(18, new ECB$1(4, 31)), new ECBlocks$1(18, new ECB$1(2, 14), new ECB$1(4, 15)), new ECBlocks$1(26, new ECB$1(4, 13), new ECB$1(1, 14))), + new Version$1(8, Int32Array.from([6, 24, 42]), new ECBlocks$1(24, new ECB$1(2, 97)), new ECBlocks$1(22, new ECB$1(2, 38), new ECB$1(2, 39)), new ECBlocks$1(22, new ECB$1(4, 18), new ECB$1(2, 19)), new ECBlocks$1(26, new ECB$1(4, 14), new ECB$1(2, 15))), + new Version$1(9, Int32Array.from([6, 26, 46]), new ECBlocks$1(30, new ECB$1(2, 116)), new ECBlocks$1(22, new ECB$1(3, 36), new ECB$1(2, 37)), new ECBlocks$1(20, new ECB$1(4, 16), new ECB$1(4, 17)), new ECBlocks$1(24, new ECB$1(4, 12), new ECB$1(4, 13))), + new Version$1(10, Int32Array.from([6, 28, 50]), new ECBlocks$1(18, new ECB$1(2, 68), new ECB$1(2, 69)), new ECBlocks$1(26, new ECB$1(4, 43), new ECB$1(1, 44)), new ECBlocks$1(24, new ECB$1(6, 19), new ECB$1(2, 20)), new ECBlocks$1(28, new ECB$1(6, 15), new ECB$1(2, 16))), + new Version$1(11, Int32Array.from([6, 30, 54]), new ECBlocks$1(20, new ECB$1(4, 81)), new ECBlocks$1(30, new ECB$1(1, 50), new ECB$1(4, 51)), new ECBlocks$1(28, new ECB$1(4, 22), new ECB$1(4, 23)), new ECBlocks$1(24, new ECB$1(3, 12), new ECB$1(8, 13))), + new Version$1(12, Int32Array.from([6, 32, 58]), new ECBlocks$1(24, new ECB$1(2, 92), new ECB$1(2, 93)), new ECBlocks$1(22, new ECB$1(6, 36), new ECB$1(2, 37)), new ECBlocks$1(26, new ECB$1(4, 20), new ECB$1(6, 21)), new ECBlocks$1(28, new ECB$1(7, 14), new ECB$1(4, 15))), + new Version$1(13, Int32Array.from([6, 34, 62]), new ECBlocks$1(26, new ECB$1(4, 107)), new ECBlocks$1(22, new ECB$1(8, 37), new ECB$1(1, 38)), new ECBlocks$1(24, new ECB$1(8, 20), new ECB$1(4, 21)), new ECBlocks$1(22, new ECB$1(12, 11), new ECB$1(4, 12))), + new Version$1(14, Int32Array.from([6, 26, 46, 66]), new ECBlocks$1(30, new ECB$1(3, 115), new ECB$1(1, 116)), new ECBlocks$1(24, new ECB$1(4, 40), new ECB$1(5, 41)), new ECBlocks$1(20, new ECB$1(11, 16), new ECB$1(5, 17)), new ECBlocks$1(24, new ECB$1(11, 12), new ECB$1(5, 13))), + new Version$1(15, Int32Array.from([6, 26, 48, 70]), new ECBlocks$1(22, new ECB$1(5, 87), new ECB$1(1, 88)), new ECBlocks$1(24, new ECB$1(5, 41), new ECB$1(5, 42)), new ECBlocks$1(30, new ECB$1(5, 24), new ECB$1(7, 25)), new ECBlocks$1(24, new ECB$1(11, 12), new ECB$1(7, 13))), + new Version$1(16, Int32Array.from([6, 26, 50, 74]), new ECBlocks$1(24, new ECB$1(5, 98), new ECB$1(1, 99)), new ECBlocks$1(28, new ECB$1(7, 45), new ECB$1(3, 46)), new ECBlocks$1(24, new ECB$1(15, 19), new ECB$1(2, 20)), new ECBlocks$1(30, new ECB$1(3, 15), new ECB$1(13, 16))), + new Version$1(17, Int32Array.from([6, 30, 54, 78]), new ECBlocks$1(28, new ECB$1(1, 107), new ECB$1(5, 108)), new ECBlocks$1(28, new ECB$1(10, 46), new ECB$1(1, 47)), new ECBlocks$1(28, new ECB$1(1, 22), new ECB$1(15, 23)), new ECBlocks$1(28, new ECB$1(2, 14), new ECB$1(17, 15))), + new Version$1(18, Int32Array.from([6, 30, 56, 82]), new ECBlocks$1(30, new ECB$1(5, 120), new ECB$1(1, 121)), new ECBlocks$1(26, new ECB$1(9, 43), new ECB$1(4, 44)), new ECBlocks$1(28, new ECB$1(17, 22), new ECB$1(1, 23)), new ECBlocks$1(28, new ECB$1(2, 14), new ECB$1(19, 15))), + new Version$1(19, Int32Array.from([6, 30, 58, 86]), new ECBlocks$1(28, new ECB$1(3, 113), new ECB$1(4, 114)), new ECBlocks$1(26, new ECB$1(3, 44), new ECB$1(11, 45)), new ECBlocks$1(26, new ECB$1(17, 21), new ECB$1(4, 22)), new ECBlocks$1(26, new ECB$1(9, 13), new ECB$1(16, 14))), + new Version$1(20, Int32Array.from([6, 34, 62, 90]), new ECBlocks$1(28, new ECB$1(3, 107), new ECB$1(5, 108)), new ECBlocks$1(26, new ECB$1(3, 41), new ECB$1(13, 42)), new ECBlocks$1(30, new ECB$1(15, 24), new ECB$1(5, 25)), new ECBlocks$1(28, new ECB$1(15, 15), new ECB$1(10, 16))), + new Version$1(21, Int32Array.from([6, 28, 50, 72, 94]), new ECBlocks$1(28, new ECB$1(4, 116), new ECB$1(4, 117)), new ECBlocks$1(26, new ECB$1(17, 42)), new ECBlocks$1(28, new ECB$1(17, 22), new ECB$1(6, 23)), new ECBlocks$1(30, new ECB$1(19, 16), new ECB$1(6, 17))), + new Version$1(22, Int32Array.from([6, 26, 50, 74, 98]), new ECBlocks$1(28, new ECB$1(2, 111), new ECB$1(7, 112)), new ECBlocks$1(28, new ECB$1(17, 46)), new ECBlocks$1(30, new ECB$1(7, 24), new ECB$1(16, 25)), new ECBlocks$1(24, new ECB$1(34, 13))), + new Version$1(23, Int32Array.from([6, 30, 54, 78, 102]), new ECBlocks$1(30, new ECB$1(4, 121), new ECB$1(5, 122)), new ECBlocks$1(28, new ECB$1(4, 47), new ECB$1(14, 48)), new ECBlocks$1(30, new ECB$1(11, 24), new ECB$1(14, 25)), new ECBlocks$1(30, new ECB$1(16, 15), new ECB$1(14, 16))), + new Version$1(24, Int32Array.from([6, 28, 54, 80, 106]), new ECBlocks$1(30, new ECB$1(6, 117), new ECB$1(4, 118)), new ECBlocks$1(28, new ECB$1(6, 45), new ECB$1(14, 46)), new ECBlocks$1(30, new ECB$1(11, 24), new ECB$1(16, 25)), new ECBlocks$1(30, new ECB$1(30, 16), new ECB$1(2, 17))), + new Version$1(25, Int32Array.from([6, 32, 58, 84, 110]), new ECBlocks$1(26, new ECB$1(8, 106), new ECB$1(4, 107)), new ECBlocks$1(28, new ECB$1(8, 47), new ECB$1(13, 48)), new ECBlocks$1(30, new ECB$1(7, 24), new ECB$1(22, 25)), new ECBlocks$1(30, new ECB$1(22, 15), new ECB$1(13, 16))), + new Version$1(26, Int32Array.from([6, 30, 58, 86, 114]), new ECBlocks$1(28, new ECB$1(10, 114), new ECB$1(2, 115)), new ECBlocks$1(28, new ECB$1(19, 46), new ECB$1(4, 47)), new ECBlocks$1(28, new ECB$1(28, 22), new ECB$1(6, 23)), new ECBlocks$1(30, new ECB$1(33, 16), new ECB$1(4, 17))), + new Version$1(27, Int32Array.from([6, 34, 62, 90, 118]), new ECBlocks$1(30, new ECB$1(8, 122), new ECB$1(4, 123)), new ECBlocks$1(28, new ECB$1(22, 45), new ECB$1(3, 46)), new ECBlocks$1(30, new ECB$1(8, 23), new ECB$1(26, 24)), new ECBlocks$1(30, new ECB$1(12, 15), new ECB$1(28, 16))), + new Version$1(28, Int32Array.from([6, 26, 50, 74, 98, 122]), new ECBlocks$1(30, new ECB$1(3, 117), new ECB$1(10, 118)), new ECBlocks$1(28, new ECB$1(3, 45), new ECB$1(23, 46)), new ECBlocks$1(30, new ECB$1(4, 24), new ECB$1(31, 25)), new ECBlocks$1(30, new ECB$1(11, 15), new ECB$1(31, 16))), + new Version$1(29, Int32Array.from([6, 30, 54, 78, 102, 126]), new ECBlocks$1(30, new ECB$1(7, 116), new ECB$1(7, 117)), new ECBlocks$1(28, new ECB$1(21, 45), new ECB$1(7, 46)), new ECBlocks$1(30, new ECB$1(1, 23), new ECB$1(37, 24)), new ECBlocks$1(30, new ECB$1(19, 15), new ECB$1(26, 16))), + new Version$1(30, Int32Array.from([6, 26, 52, 78, 104, 130]), new ECBlocks$1(30, new ECB$1(5, 115), new ECB$1(10, 116)), new ECBlocks$1(28, new ECB$1(19, 47), new ECB$1(10, 48)), new ECBlocks$1(30, new ECB$1(15, 24), new ECB$1(25, 25)), new ECBlocks$1(30, new ECB$1(23, 15), new ECB$1(25, 16))), + new Version$1(31, Int32Array.from([6, 30, 56, 82, 108, 134]), new ECBlocks$1(30, new ECB$1(13, 115), new ECB$1(3, 116)), new ECBlocks$1(28, new ECB$1(2, 46), new ECB$1(29, 47)), new ECBlocks$1(30, new ECB$1(42, 24), new ECB$1(1, 25)), new ECBlocks$1(30, new ECB$1(23, 15), new ECB$1(28, 16))), + new Version$1(32, Int32Array.from([6, 34, 60, 86, 112, 138]), new ECBlocks$1(30, new ECB$1(17, 115)), new ECBlocks$1(28, new ECB$1(10, 46), new ECB$1(23, 47)), new ECBlocks$1(30, new ECB$1(10, 24), new ECB$1(35, 25)), new ECBlocks$1(30, new ECB$1(19, 15), new ECB$1(35, 16))), + new Version$1(33, Int32Array.from([6, 30, 58, 86, 114, 142]), new ECBlocks$1(30, new ECB$1(17, 115), new ECB$1(1, 116)), new ECBlocks$1(28, new ECB$1(14, 46), new ECB$1(21, 47)), new ECBlocks$1(30, new ECB$1(29, 24), new ECB$1(19, 25)), new ECBlocks$1(30, new ECB$1(11, 15), new ECB$1(46, 16))), + new Version$1(34, Int32Array.from([6, 34, 62, 90, 118, 146]), new ECBlocks$1(30, new ECB$1(13, 115), new ECB$1(6, 116)), new ECBlocks$1(28, new ECB$1(14, 46), new ECB$1(23, 47)), new ECBlocks$1(30, new ECB$1(44, 24), new ECB$1(7, 25)), new ECBlocks$1(30, new ECB$1(59, 16), new ECB$1(1, 17))), + new Version$1(35, Int32Array.from([6, 30, 54, 78, 102, 126, 150]), new ECBlocks$1(30, new ECB$1(12, 121), new ECB$1(7, 122)), new ECBlocks$1(28, new ECB$1(12, 47), new ECB$1(26, 48)), new ECBlocks$1(30, new ECB$1(39, 24), new ECB$1(14, 25)), new ECBlocks$1(30, new ECB$1(22, 15), new ECB$1(41, 16))), + new Version$1(36, Int32Array.from([6, 24, 50, 76, 102, 128, 154]), new ECBlocks$1(30, new ECB$1(6, 121), new ECB$1(14, 122)), new ECBlocks$1(28, new ECB$1(6, 47), new ECB$1(34, 48)), new ECBlocks$1(30, new ECB$1(46, 24), new ECB$1(10, 25)), new ECBlocks$1(30, new ECB$1(2, 15), new ECB$1(64, 16))), + new Version$1(37, Int32Array.from([6, 28, 54, 80, 106, 132, 158]), new ECBlocks$1(30, new ECB$1(17, 122), new ECB$1(4, 123)), new ECBlocks$1(28, new ECB$1(29, 46), new ECB$1(14, 47)), new ECBlocks$1(30, new ECB$1(49, 24), new ECB$1(10, 25)), new ECBlocks$1(30, new ECB$1(24, 15), new ECB$1(46, 16))), + new Version$1(38, Int32Array.from([6, 32, 58, 84, 110, 136, 162]), new ECBlocks$1(30, new ECB$1(4, 122), new ECB$1(18, 123)), new ECBlocks$1(28, new ECB$1(13, 46), new ECB$1(32, 47)), new ECBlocks$1(30, new ECB$1(48, 24), new ECB$1(14, 25)), new ECBlocks$1(30, new ECB$1(42, 15), new ECB$1(32, 16))), + new Version$1(39, Int32Array.from([6, 26, 54, 82, 110, 138, 166]), new ECBlocks$1(30, new ECB$1(20, 117), new ECB$1(4, 118)), new ECBlocks$1(28, new ECB$1(40, 47), new ECB$1(7, 48)), new ECBlocks$1(30, new ECB$1(43, 24), new ECB$1(22, 25)), new ECBlocks$1(30, new ECB$1(10, 15), new ECB$1(67, 16))), + new Version$1(40, Int32Array.from([6, 30, 58, 86, 114, 142, 170]), new ECBlocks$1(30, new ECB$1(19, 118), new ECB$1(6, 119)), new ECBlocks$1(28, new ECB$1(18, 47), new ECB$1(31, 48)), new ECBlocks$1(30, new ECB$1(34, 24), new ECB$1(34, 25)), new ECBlocks$1(30, new ECB$1(20, 15), new ECB$1(61, 16))) + ]; + + /* + * 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 DataMaskValues; + (function (DataMaskValues) { + DataMaskValues[DataMaskValues["DATA_MASK_000"] = 0] = "DATA_MASK_000"; + DataMaskValues[DataMaskValues["DATA_MASK_001"] = 1] = "DATA_MASK_001"; + DataMaskValues[DataMaskValues["DATA_MASK_010"] = 2] = "DATA_MASK_010"; + DataMaskValues[DataMaskValues["DATA_MASK_011"] = 3] = "DATA_MASK_011"; + DataMaskValues[DataMaskValues["DATA_MASK_100"] = 4] = "DATA_MASK_100"; + DataMaskValues[DataMaskValues["DATA_MASK_101"] = 5] = "DATA_MASK_101"; + DataMaskValues[DataMaskValues["DATA_MASK_110"] = 6] = "DATA_MASK_110"; + DataMaskValues[DataMaskValues["DATA_MASK_111"] = 7] = "DATA_MASK_111"; + })(DataMaskValues || (DataMaskValues = {})); + /** + *Encapsulates data masks for the data bits in a QR code, per ISO 18004:2006 6.8. Implementations + * of this class can un-mask a raw BitMatrix. For simplicity, they will unmask the entire BitMatrix, + * including areas used for finder patterns, timing patterns, etc. These areas should be unused + * after the point they are unmasked anyway.
+ * + *Note that the diagram in section 6.8.1 is misleading since it indicates that i is column position + * and j is row position. In fact, as the text says, i is row position and j is column position.
+ * + * @author Sean Owen + */ + class DataMask { + // See ISO 18004:2006 6.8.1 + constructor(value, isMasked) { + this.value = value; + this.isMasked = isMasked; + } + // End of enum constants. + /** + *Implementations of this method reverse the data masking process applied to a QR Code and + * make its bits ready to read.
+ * + * @param bits representation of QR Code bits + * @param dimension dimension of QR Code, represented by bits, being unmasked + */ + unmaskBitMatrix(bits, dimension /*int*/) { + for (let i = 0; i < dimension; i++) { + for (let j = 0; j < dimension; j++) { + if (this.isMasked(i, j)) { + bits.flip(j, i); + } + } + } + } + } + DataMask.values = new Map([ + /** + * 000: mask bits for which (x + y) mod 2 == 0 + */ + [DataMaskValues.DATA_MASK_000, new DataMask(DataMaskValues.DATA_MASK_000, (i /*int*/, j /*int*/) => { return ((i + j) & 0x01) === 0; })], + /** + * 001: mask bits for which x mod 2 == 0 + */ + [DataMaskValues.DATA_MASK_001, new DataMask(DataMaskValues.DATA_MASK_001, (i /*int*/, j /*int*/) => { return (i & 0x01) === 0; })], + /** + * 010: mask bits for which y mod 3 == 0 + */ + [DataMaskValues.DATA_MASK_010, new DataMask(DataMaskValues.DATA_MASK_010, (i /*int*/, j /*int*/) => { return j % 3 === 0; })], + /** + * 011: mask bits for which (x + y) mod 3 == 0 + */ + [DataMaskValues.DATA_MASK_011, new DataMask(DataMaskValues.DATA_MASK_011, (i /*int*/, j /*int*/) => { return (i + j) % 3 === 0; })], + /** + * 100: mask bits for which (x/2 + y/3) mod 2 == 0 + */ + [DataMaskValues.DATA_MASK_100, new DataMask(DataMaskValues.DATA_MASK_100, (i /*int*/, j /*int*/) => { return ((Math.floor(i / 2) + Math.floor(j / 3)) & 0x01) === 0; })], + /** + * 101: mask bits for which xy mod 2 + xy mod 3 == 0 + * equivalently, such that xy mod 6 == 0 + */ + [DataMaskValues.DATA_MASK_101, new DataMask(DataMaskValues.DATA_MASK_101, (i /*int*/, j /*int*/) => { return (i * j) % 6 === 0; })], + /** + * 110: mask bits for which (xy mod 2 + xy mod 3) mod 2 == 0 + * equivalently, such that xy mod 6 < 3 + */ + [DataMaskValues.DATA_MASK_110, new DataMask(DataMaskValues.DATA_MASK_110, (i /*int*/, j /*int*/) => { return ((i * j) % 6) < 3; })], + /** + * 111: mask bits for which ((x+y)mod 2 + xy mod 3) mod 2 == 0 + * equivalently, such that (x + y + xy mod 3) mod 2 == 0 + */ + [DataMaskValues.DATA_MASK_111, new DataMask(DataMaskValues.DATA_MASK_111, (i /*int*/, j /*int*/) => { return ((i + j + ((i * j) % 3)) & 0x01) === 0; })], + ]); + + /* + * 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. + */ + /** + * @author Sean Owen + */ + class BitMatrixParser$1 { + /** + * @param bitMatrix {@link BitMatrix} to parse + * @throws FormatException if dimension is not >= 21 and 1 mod 4 + */ + constructor(bitMatrix) { + const dimension = bitMatrix.getHeight(); + if (dimension < 21 || (dimension & 0x03) !== 1) { + throw new FormatException(); + } + this.bitMatrix = bitMatrix; + } + /** + *Reads format information from one of its two locations within the QR Code.
+ * + * @return {@link FormatInformation} encapsulating the QR Code's format info + * @throws FormatException if both format information locations cannot be parsed as + * the valid encoding of format information + */ + readFormatInformation() { + if (this.parsedFormatInfo !== null && this.parsedFormatInfo !== undefined) { + return this.parsedFormatInfo; + } + // Read top-left format info bits + let formatInfoBits1 = 0; + for (let i = 0; i < 6; i++) { + formatInfoBits1 = this.copyBit(i, 8, formatInfoBits1); + } + // .. and skip a bit in the timing pattern ... + formatInfoBits1 = this.copyBit(7, 8, formatInfoBits1); + formatInfoBits1 = this.copyBit(8, 8, formatInfoBits1); + formatInfoBits1 = this.copyBit(8, 7, formatInfoBits1); + // .. and skip a bit in the timing pattern ... + for (let j = 5; j >= 0; j--) { + formatInfoBits1 = this.copyBit(8, j, formatInfoBits1); + } + // Read the top-right/bottom-left pattern too + const dimension = this.bitMatrix.getHeight(); + let formatInfoBits2 = 0; + const jMin = dimension - 7; + for (let j = dimension - 1; j >= jMin; j--) { + formatInfoBits2 = this.copyBit(8, j, formatInfoBits2); + } + for (let i = dimension - 8; i < dimension; i++) { + formatInfoBits2 = this.copyBit(i, 8, formatInfoBits2); + } + this.parsedFormatInfo = FormatInformation.decodeFormatInformation(formatInfoBits1, formatInfoBits2); + if (this.parsedFormatInfo !== null) { + return this.parsedFormatInfo; + } + throw new FormatException(); + } + /** + *Reads version information from one of its two locations within the QR Code.
+ * + * @return {@link Version} encapsulating the QR Code's version + * @throws FormatException if both version information locations cannot be parsed as + * the valid encoding of version information + */ + readVersion() { + if (this.parsedVersion !== null && this.parsedVersion !== undefined) { + return this.parsedVersion; + } + const dimension = this.bitMatrix.getHeight(); + const provisionalVersion = Math.floor((dimension - 17) / 4); + if (provisionalVersion <= 6) { + return Version$1.getVersionForNumber(provisionalVersion); + } + // Read top-right version info: 3 wide by 6 tall + let versionBits = 0; + const ijMin = dimension - 11; + for (let j = 5; j >= 0; j--) { + for (let i = dimension - 9; i >= ijMin; i--) { + versionBits = this.copyBit(i, j, versionBits); + } + } + let theParsedVersion = Version$1.decodeVersionInformation(versionBits); + if (theParsedVersion !== null && theParsedVersion.getDimensionForVersion() === dimension) { + this.parsedVersion = theParsedVersion; + return theParsedVersion; + } + // Hmm, failed. Try bottom left: 6 wide by 3 tall + versionBits = 0; + for (let i = 5; i >= 0; i--) { + for (let j = dimension - 9; j >= ijMin; j--) { + versionBits = this.copyBit(i, j, versionBits); + } + } + theParsedVersion = Version$1.decodeVersionInformation(versionBits); + if (theParsedVersion !== null && theParsedVersion.getDimensionForVersion() === dimension) { + this.parsedVersion = theParsedVersion; + return theParsedVersion; + } + throw new FormatException(); + } + copyBit(i /*int*/, j /*int*/, versionBits /*int*/) { + const bit = this.isMirror ? this.bitMatrix.get(j, i) : this.bitMatrix.get(i, j); + return bit ? (versionBits << 1) | 0x1 : versionBits << 1; + } + /** + *Reads the bits in the {@link BitMatrix} representing the finder pattern in the + * correct order in order to reconstruct the codewords bytes contained within the + * QR Code.
+ * + * @return bytes encoded within the QR Code + * @throws FormatException if the exact number of bytes expected is not read + */ + readCodewords() { + const formatInfo = this.readFormatInformation(); + const version = this.readVersion(); + // Get the data mask for the format used in this QR Code. This will exclude + // some bits from reading as we wind through the bit matrix. + const dataMask = DataMask.values.get(formatInfo.getDataMask()); + const dimension = this.bitMatrix.getHeight(); + dataMask.unmaskBitMatrix(this.bitMatrix, dimension); + const functionPattern = version.buildFunctionPattern(); + let readingUp = true; + const result = new Uint8Array(version.getTotalCodewords()); + let resultOffset = 0; + let currentByte = 0; + let bitsRead = 0; + // Read columns in pairs, from right to left + for (let j = dimension - 1; j > 0; j -= 2) { + if (j === 6) { + // Skip whole column with vertical alignment pattern + // saves time and makes the other code proceed more cleanly + j--; + } + // Read alternatingly from bottom to top then top to bottom + for (let count = 0; count < dimension; count++) { + const i = readingUp ? dimension - 1 - count : count; + for (let col = 0; col < 2; col++) { + // Ignore bits covered by the function pattern + if (!functionPattern.get(j - col, i)) { + // Read a bit + bitsRead++; + currentByte <<= 1; + if (this.bitMatrix.get(j - col, i)) { + currentByte |= 1; + } + // If we've made a whole byte, save it off + if (bitsRead === 8) { + result[resultOffset++] = /*(byte) */ currentByte; + bitsRead = 0; + currentByte = 0; + } + } + } + } + readingUp = !readingUp; // readingUp ^= true; // readingUp = !readingUp; // switch directions + } + if (resultOffset !== version.getTotalCodewords()) { + throw new FormatException(); + } + return result; + } + /** + * Revert the mask removal done while reading the code words. The bit matrix should revert to its original state. + */ + remask() { + if (this.parsedFormatInfo === null) { + return; // We have no format information, and have no data mask + } + const dataMask = DataMask.values[this.parsedFormatInfo.getDataMask()]; + const dimension = this.bitMatrix.getHeight(); + dataMask.unmaskBitMatrix(this.bitMatrix, dimension); + } + /** + * Prepare the parser for a mirrored operation. + * This flag has effect only on the {@link #readFormatInformation()} and the + * {@link #readVersion()}. Before proceeding with {@link #readCodewords()} the + * {@link #mirror()} method should be called. + * + * @param mirror Whether to read version and format information mirrored. + */ + setMirror(isMirror) { + this.parsedVersion = null; + this.parsedFormatInfo = null; + this.isMirror = isMirror; + } + /** Mirror the bit matrix in order to attempt a second reading. */ + mirror() { + const bitMatrix = this.bitMatrix; + for (let x = 0, width = bitMatrix.getWidth(); x < width; x++) { + for (let y = x + 1, height = bitMatrix.getHeight(); y < height; y++) { + if (bitMatrix.get(x, y) !== bitMatrix.get(y, x)) { + bitMatrix.flip(y, x); + bitMatrix.flip(x, y); + } + } + } + } + } + + /* + * 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. + */ + /** + *Encapsulates a block of data within a QR Code. QR Codes may split their data into + * multiple blocks, each of which is a unit of data and error-correction codewords. Each + * is represented by an instance of this class.
+ * + * @author Sean Owen + */ + class DataBlock$1 { + constructor(numDataCodewords /*int*/, codewords) { + this.numDataCodewords = numDataCodewords; + this.codewords = codewords; + } + /** + *When QR Codes use multiple data blocks, they are actually interleaved. + * That is, the first byte of data block 1 to n is written, then the second bytes, and so on. This + * method will separate the data into original blocks.
+ * + * @param rawCodewords bytes as read directly from the QR Code + * @param version version of the QR Code + * @param ecLevel error-correction level of the QR Code + * @return DataBlocks containing original bytes, "de-interleaved" from representation in the + * QR Code + */ + static getDataBlocks(rawCodewords, version, ecLevel) { + if (rawCodewords.length !== version.getTotalCodewords()) { + throw new IllegalArgumentException(); + } + // Figure out the number and size of data blocks used by this version and + // error correction level + const ecBlocks = version.getECBlocksForLevel(ecLevel); + // First count the total number of data blocks + let totalBlocks = 0; + const ecBlockArray = ecBlocks.getECBlocks(); + for (const ecBlock of ecBlockArray) { + totalBlocks += ecBlock.getCount(); + } + // Now establish DataBlocks of the appropriate size and number of data codewords + const result = new Array(totalBlocks); + let numResultBlocks = 0; + for (const ecBlock of ecBlockArray) { + for (let i = 0; i < ecBlock.getCount(); i++) { + const numDataCodewords = ecBlock.getDataCodewords(); + const numBlockCodewords = ecBlocks.getECCodewordsPerBlock() + numDataCodewords; + result[numResultBlocks++] = new DataBlock$1(numDataCodewords, new Uint8Array(numBlockCodewords)); + } + } + // All blocks have the same amount of data, except that the last n + // (where n may be 0) have 1 more byte. Figure out where these start. + const shorterBlocksTotalCodewords = result[0].codewords.length; + let longerBlocksStartAt = result.length - 1; + // TYPESCRIPTPORT: check length is correct here + while (longerBlocksStartAt >= 0) { + const numCodewords = result[longerBlocksStartAt].codewords.length; + if (numCodewords === shorterBlocksTotalCodewords) { + break; + } + longerBlocksStartAt--; + } + longerBlocksStartAt++; + const shorterBlocksNumDataCodewords = shorterBlocksTotalCodewords - ecBlocks.getECCodewordsPerBlock(); + // The last elements of result may be 1 element longer + // first fill out as many elements as all of them have + let rawCodewordsOffset = 0; + for (let i = 0; i < shorterBlocksNumDataCodewords; i++) { + for (let j = 0; j < numResultBlocks; j++) { + result[j].codewords[i] = rawCodewords[rawCodewordsOffset++]; + } + } + // Fill out the last data block in the longer ones + for (let j = longerBlocksStartAt; j < numResultBlocks; j++) { + result[j].codewords[shorterBlocksNumDataCodewords] = rawCodewords[rawCodewordsOffset++]; + } + // Now add in error correction blocks + const max = result[0].codewords.length; + for (let i = shorterBlocksNumDataCodewords; i < max; i++) { + for (let j = 0; j < numResultBlocks; j++) { + const iOffset = j < longerBlocksStartAt ? i : i + 1; + result[j].codewords[iOffset] = rawCodewords[rawCodewordsOffset++]; + } + } + return result; + } + getNumDataCodewords() { + return this.numDataCodewords; + } + getCodewords() { + return this.codewords; + } + } + + /* + * 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 ModeValues; + (function (ModeValues) { + ModeValues[ModeValues["TERMINATOR"] = 0] = "TERMINATOR"; + ModeValues[ModeValues["NUMERIC"] = 1] = "NUMERIC"; + ModeValues[ModeValues["ALPHANUMERIC"] = 2] = "ALPHANUMERIC"; + ModeValues[ModeValues["STRUCTURED_APPEND"] = 3] = "STRUCTURED_APPEND"; + ModeValues[ModeValues["BYTE"] = 4] = "BYTE"; + ModeValues[ModeValues["ECI"] = 5] = "ECI"; + ModeValues[ModeValues["KANJI"] = 6] = "KANJI"; + ModeValues[ModeValues["FNC1_FIRST_POSITION"] = 7] = "FNC1_FIRST_POSITION"; + ModeValues[ModeValues["FNC1_SECOND_POSITION"] = 8] = "FNC1_SECOND_POSITION"; + /** See GBT 18284-2000; "Hanzi" is a transliteration of this mode name. */ + ModeValues[ModeValues["HANZI"] = 9] = "HANZI"; + })(ModeValues || (ModeValues = {})); + /** + *See ISO 18004:2006, 6.4.1, Tables 2 and 3. This enum encapsulates the various modes in which + * data can be encoded to bits in the QR code standard.
+ * + * @author Sean Owen + */ + class Mode$1 { + constructor(value, stringValue, characterCountBitsForVersions, bits /*int*/) { + this.value = value; + this.stringValue = stringValue; + this.characterCountBitsForVersions = characterCountBitsForVersions; + this.bits = bits; + Mode$1.FOR_BITS.set(bits, this); + Mode$1.FOR_VALUE.set(value, this); + } + /** + * @param bits four bits encoding a QR Code data mode + * @return Mode encoded by these bits + * @throws IllegalArgumentException if bits do not correspond to a known mode + */ + static forBits(bits /*int*/) { + const mode = Mode$1.FOR_BITS.get(bits); + if (undefined === mode) { + throw new IllegalArgumentException(); + } + return mode; + } + /** + * @param version version in question + * @return number of bits used, in this QR Code symbol {@link Version}, to encode the + * count of characters that will follow encoded in this Mode + */ + getCharacterCountBits(version) { + const versionNumber = version.getVersionNumber(); + let offset; + if (versionNumber <= 9) { + offset = 0; + } + else if (versionNumber <= 26) { + offset = 1; + } + else { + offset = 2; + } + return this.characterCountBitsForVersions[offset]; + } + getValue() { + return this.value; + } + getBits() { + return this.bits; + } + equals(o) { + if (!(o instanceof Mode$1)) { + return false; + } + const other = o; + return this.value === other.value; + } + toString() { + return this.stringValue; + } + } + Mode$1.FOR_BITS = new Map(); + Mode$1.FOR_VALUE = new Map(); + Mode$1.TERMINATOR = new Mode$1(ModeValues.TERMINATOR, 'TERMINATOR', Int32Array.from([0, 0, 0]), 0x00); // Not really a mode... + Mode$1.NUMERIC = new Mode$1(ModeValues.NUMERIC, 'NUMERIC', Int32Array.from([10, 12, 14]), 0x01); + Mode$1.ALPHANUMERIC = new Mode$1(ModeValues.ALPHANUMERIC, 'ALPHANUMERIC', Int32Array.from([9, 11, 13]), 0x02); + Mode$1.STRUCTURED_APPEND = new Mode$1(ModeValues.STRUCTURED_APPEND, 'STRUCTURED_APPEND', Int32Array.from([0, 0, 0]), 0x03); // Not supported + Mode$1.BYTE = new Mode$1(ModeValues.BYTE, 'BYTE', Int32Array.from([8, 16, 16]), 0x04); + Mode$1.ECI = new Mode$1(ModeValues.ECI, 'ECI', Int32Array.from([0, 0, 0]), 0x07); // character counts don't apply + Mode$1.KANJI = new Mode$1(ModeValues.KANJI, 'KANJI', Int32Array.from([8, 10, 12]), 0x08); + Mode$1.FNC1_FIRST_POSITION = new Mode$1(ModeValues.FNC1_FIRST_POSITION, 'FNC1_FIRST_POSITION', Int32Array.from([0, 0, 0]), 0x05); + Mode$1.FNC1_SECOND_POSITION = new Mode$1(ModeValues.FNC1_SECOND_POSITION, 'FNC1_SECOND_POSITION', Int32Array.from([0, 0, 0]), 0x09); + /** See GBT 18284-2000; "Hanzi" is a transliteration of this mode name. */ + Mode$1.HANZI = new Mode$1(ModeValues.HANZI, 'HANZI', Int32Array.from([8, 10, 12]), 0x0D); + + /* + * 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. + */ + /*import java.io.UnsupportedEncodingException;*/ + /*import java.util.ArrayList;*/ + /*import java.util.Collection;*/ + /*import java.util.List;*/ + /*import java.util.Map;*/ + /** + *QR Codes can encode text as bits in one of several modes, and can use multiple modes + * in one QR Code. This class decodes the bits back into text.
+ * + *See ISO 18004:2006, 6.4.3 - 6.4.7
+ * + * @author Sean Owen + */ + class DecodedBitStreamParser$1 { + static decode(bytes, version, ecLevel, hints) { + const bits = new BitSource(bytes); + let result = new StringBuilder(); + const byteSegments = new Array(); // 1 + // TYPESCRIPTPORT: I do not use constructor with size 1 as in original Java means capacity and the array length is checked below + let symbolSequence = -1; + let parityData = -1; + try { + let currentCharacterSetECI = null; + let fc1InEffect = false; + let mode; + do { + // While still another segment to read... + if (bits.available() < 4) { + // OK, assume we're done. Really, a TERMINATOR mode should have been recorded here + mode = Mode$1.TERMINATOR; + } + else { + const modeBits = bits.readBits(4); + mode = Mode$1.forBits(modeBits); // mode is encoded by 4 bits + } + switch (mode) { + case Mode$1.TERMINATOR: + break; + case Mode$1.FNC1_FIRST_POSITION: + case Mode$1.FNC1_SECOND_POSITION: + // We do little with FNC1 except alter the parsed result a bit according to the spec + fc1InEffect = true; + break; + case Mode$1.STRUCTURED_APPEND: + if (bits.available() < 16) { + throw new FormatException(); + } + // sequence number and parity is added later to the result metadata + // Read next 8 bits (symbol sequence #) and 8 bits (data: parity), then continue + symbolSequence = bits.readBits(8); + parityData = bits.readBits(8); + break; + case Mode$1.ECI: + // Count doesn't apply to ECI + const value = DecodedBitStreamParser$1.parseECIValue(bits); + currentCharacterSetECI = CharacterSetECI.getCharacterSetECIByValue(value); + if (currentCharacterSetECI === null) { + throw new FormatException(); + } + break; + case Mode$1.HANZI: + // First handle Hanzi mode which does not start with character count + // Chinese mode contains a sub set indicator right after mode indicator + const subset = bits.readBits(4); + const countHanzi = bits.readBits(mode.getCharacterCountBits(version)); + if (subset === DecodedBitStreamParser$1.GB2312_SUBSET) { + DecodedBitStreamParser$1.decodeHanziSegment(bits, result, countHanzi); + } + break; + default: + // "Normal" QR code modes: + // How many characters will follow, encoded in this mode? + const count = bits.readBits(mode.getCharacterCountBits(version)); + switch (mode) { + case Mode$1.NUMERIC: + DecodedBitStreamParser$1.decodeNumericSegment(bits, result, count); + break; + case Mode$1.ALPHANUMERIC: + DecodedBitStreamParser$1.decodeAlphanumericSegment(bits, result, count, fc1InEffect); + break; + case Mode$1.BYTE: + DecodedBitStreamParser$1.decodeByteSegment(bits, result, count, currentCharacterSetECI, byteSegments, hints); + break; + case Mode$1.KANJI: + DecodedBitStreamParser$1.decodeKanjiSegment(bits, result, count); + break; + default: + throw new FormatException(); + } + break; + } + } while (mode !== Mode$1.TERMINATOR); + } + catch (iae /*: IllegalArgumentException*/) { + // from readBits() calls + throw new FormatException(); + } + return new DecoderResult(bytes, result.toString(), byteSegments.length === 0 ? null : byteSegments, ecLevel === null ? null : ecLevel.toString(), symbolSequence, parityData); + } + /** + * See specification GBT 18284-2000 + */ + static decodeHanziSegment(bits, result, count /*int*/) { + // Don't crash trying to read more bits than we have available. + if (count * 13 > bits.available()) { + throw new FormatException(); + } + // Each character will require 2 bytes. Read the characters as 2-byte pairs + // and decode as GB2312 afterwards + const buffer = new Uint8Array(2 * count); + let offset = 0; + while (count > 0) { + // Each 13 bits encodes a 2-byte character + const twoBytes = bits.readBits(13); + let assembledTwoBytes = (((twoBytes / 0x060) << 8) & 0xFFFFFFFF) | (twoBytes % 0x060); + if (assembledTwoBytes < 0x003BF) { + // In the 0xA1A1 to 0xAAFE range + assembledTwoBytes += 0x0A1A1; + } + else { + // In the 0xB0A1 to 0xFAFE range + assembledTwoBytes += 0x0A6A1; + } + buffer[offset] = /*(byte) */ ((assembledTwoBytes >> 8) & 0xFF); + buffer[offset + 1] = /*(byte) */ (assembledTwoBytes & 0xFF); + offset += 2; + count--; + } + try { + result.append(StringEncoding.decode(buffer, StringUtils.GB2312)); + // TYPESCRIPTPORT: TODO: implement GB2312 decode. StringView from MDN could be a starting point + } + catch (ignored /*: UnsupportedEncodingException*/) { + throw new FormatException(ignored); + } + } + static decodeKanjiSegment(bits, result, count /*int*/) { + // Don't crash trying to read more bits than we have available. + if (count * 13 > bits.available()) { + throw new FormatException(); + } + // Each character will require 2 bytes. Read the characters as 2-byte pairs + // and decode as Shift_JIS afterwards + const buffer = new Uint8Array(2 * count); + let offset = 0; + while (count > 0) { + // Each 13 bits encodes a 2-byte character + const twoBytes = bits.readBits(13); + let assembledTwoBytes = (((twoBytes / 0x0C0) << 8) & 0xFFFFFFFF) | (twoBytes % 0x0C0); + if (assembledTwoBytes < 0x01F00) { + // In the 0x8140 to 0x9FFC range + assembledTwoBytes += 0x08140; + } + else { + // In the 0xE040 to 0xEBBF range + assembledTwoBytes += 0x0C140; + } + buffer[offset] = /*(byte) */ (assembledTwoBytes >> 8); + buffer[offset + 1] = /*(byte) */ assembledTwoBytes; + offset += 2; + count--; + } + // Shift_JIS may not be supported in some environments: + try { + result.append(StringEncoding.decode(buffer, StringUtils.SHIFT_JIS)); + // TYPESCRIPTPORT: TODO: implement SHIFT_JIS decode. StringView from MDN could be a starting point + } + catch (ignored /*: UnsupportedEncodingException*/) { + throw new FormatException(ignored); + } + } + static decodeByteSegment(bits, result, count /*int*/, currentCharacterSetECI, byteSegments, hints) { + // Don't crash trying to read more bits than we have available. + if (8 * count > bits.available()) { + throw new FormatException(); + } + const readBytes = new Uint8Array(count); + for (let i = 0; i < count; i++) { + readBytes[i] = /*(byte) */ bits.readBits(8); + } + let encoding; + if (currentCharacterSetECI === null) { + // The spec isn't clear on this mode; see + // section 6.4.5: t does not say which encoding to assuming + // upon decoding. I have seen ISO-8859-1 used as well as + // Shift_JIS -- without anything like an ECI designator to + // give a hint. + encoding = StringUtils.guessEncoding(readBytes, hints); + } + else { + encoding = currentCharacterSetECI.getName(); + } + try { + result.append(StringEncoding.decode(readBytes, encoding)); + } + catch (ignored /*: UnsupportedEncodingException*/) { + throw new FormatException(ignored); + } + byteSegments.push(readBytes); + } + static toAlphaNumericChar(value /*int*/) { + if (value >= DecodedBitStreamParser$1.ALPHANUMERIC_CHARS.length) { + throw new FormatException(); + } + return DecodedBitStreamParser$1.ALPHANUMERIC_CHARS[value]; + } + static decodeAlphanumericSegment(bits, result, count /*int*/, fc1InEffect) { + // Read two characters at a time + const start = result.length(); + while (count > 1) { + if (bits.available() < 11) { + throw new FormatException(); + } + const nextTwoCharsBits = bits.readBits(11); + result.append(DecodedBitStreamParser$1.toAlphaNumericChar(Math.floor(nextTwoCharsBits / 45))); + result.append(DecodedBitStreamParser$1.toAlphaNumericChar(nextTwoCharsBits % 45)); + count -= 2; + } + if (count === 1) { + // special case: one character left + if (bits.available() < 6) { + throw new FormatException(); + } + result.append(DecodedBitStreamParser$1.toAlphaNumericChar(bits.readBits(6))); + } + // See section 6.4.8.1, 6.4.8.2 + if (fc1InEffect) { + // We need to massage the result a bit if in an FNC1 mode: + for (let i = start; i < result.length(); i++) { + if (result.charAt(i) === '%') { + if (i < result.length() - 1 && result.charAt(i + 1) === '%') { + // %% is rendered as % + result.deleteCharAt(i + 1); + } + else { + // In alpha mode, % should be converted to FNC1 separator 0x1D + result.setCharAt(i, String.fromCharCode(0x1D)); + } + } + } + } + } + static decodeNumericSegment(bits, result, count /*int*/) { + // Read three digits at a time + while (count >= 3) { + // Each 10 bits encodes three digits + if (bits.available() < 10) { + throw new FormatException(); + } + const threeDigitsBits = bits.readBits(10); + if (threeDigitsBits >= 1000) { + throw new FormatException(); + } + result.append(DecodedBitStreamParser$1.toAlphaNumericChar(Math.floor(threeDigitsBits / 100))); + result.append(DecodedBitStreamParser$1.toAlphaNumericChar(Math.floor(threeDigitsBits / 10) % 10)); + result.append(DecodedBitStreamParser$1.toAlphaNumericChar(threeDigitsBits % 10)); + count -= 3; + } + if (count === 2) { + // Two digits left over to read, encoded in 7 bits + if (bits.available() < 7) { + throw new FormatException(); + } + const twoDigitsBits = bits.readBits(7); + if (twoDigitsBits >= 100) { + throw new FormatException(); + } + result.append(DecodedBitStreamParser$1.toAlphaNumericChar(Math.floor(twoDigitsBits / 10))); + result.append(DecodedBitStreamParser$1.toAlphaNumericChar(twoDigitsBits % 10)); + } + else if (count === 1) { + // One digit left over to read + if (bits.available() < 4) { + throw new FormatException(); + } + const digitBits = bits.readBits(4); + if (digitBits >= 10) { + throw new FormatException(); + } + result.append(DecodedBitStreamParser$1.toAlphaNumericChar(digitBits)); + } + } + static parseECIValue(bits) { + const firstByte = bits.readBits(8); + if ((firstByte & 0x80) === 0) { + // just one byte + return firstByte & 0x7F; + } + if ((firstByte & 0xC0) === 0x80) { + // two bytes + const secondByte = bits.readBits(8); + return (((firstByte & 0x3F) << 8) & 0xFFFFFFFF) | secondByte; + } + if ((firstByte & 0xE0) === 0xC0) { + // three bytes + const secondThirdBytes = bits.readBits(16); + return (((firstByte & 0x1F) << 16) & 0xFFFFFFFF) | secondThirdBytes; + } + throw new FormatException(); + } + } + /** + * See ISO 18004:2006, 6.4.4 Table 5 + */ + DecodedBitStreamParser$1.ALPHANUMERIC_CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:'; + DecodedBitStreamParser$1.GB2312_SUBSET = 1; + // function Uint8ArrayToString(a: Uint8Array): string { + // const CHUNK_SZ = 0x8000; + // const c = new StringBuilder(); + // for (let i = 0, length = a.length; i < length; i += CHUNK_SZ) { + // c.append(String.fromCharCode.apply(null, a.subarray(i, i + CHUNK_SZ))); + // } + // return c.toString(); + // } + + /* + * Copyright 2013 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. + */ + /** + * Meta-data container for QR Code decoding. Instances of this class may be used to convey information back to the + * decoding caller. Callers are expected to process this. + * + * @see com.google.zxing.common.DecoderResult#getOther() + */ + class QRCodeDecoderMetaData { + constructor(mirrored) { + this.mirrored = mirrored; + } + /** + * @return true if the QR Code was mirrored. + */ + isMirrored() { + return this.mirrored; + } + /** + * Apply the result points' order correction due to mirroring. + * + * @param points Array of points to apply mirror correction to. + */ + applyMirroredCorrection(points) { + if (!this.mirrored || points === null || points.length < 3) { + return; + } + const bottomLeft = points[0]; + points[0] = points[2]; + points[2] = bottomLeft; + // No need to 'fix' top-left and alignment pattern. + } + } + + /* + * 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. + */ + /*import java.util.Map;*/ + /** + *The main class which implements QR Code decoding -- as opposed to locating and extracting + * the QR Code from an image.
+ * + * @author Sean Owen + */ + class Decoder$2 { + constructor() { + this.rsDecoder = new ReedSolomonDecoder(GenericGF.QR_CODE_FIELD_256); + } + // public decode(image: boolean[][]): DecoderResult /*throws ChecksumException, FormatException*/ { + // return decode(image, null) + // } + /** + *Convenience method that can decode a QR Code represented as a 2D array of booleans. + * "true" is taken to mean a black module.
+ * + * @param image booleans representing white/black QR Code modules + * @param hints decoding hints that should be used to influence decoding + * @return text and bytes encoded within the QR Code + * @throws FormatException if the QR Code cannot be decoded + * @throws ChecksumException if error correction fails + */ + decodeBooleanArray(image, hints) { + return this.decodeBitMatrix(BitMatrix.parseFromBooleanArray(image), hints); + } + // public decodeBitMatrix(bits: BitMatrix): DecoderResult /*throws ChecksumException, FormatException*/ { + // return decode(bits, null) + // } + /** + *Decodes a QR Code represented as a {@link BitMatrix}. A 1 or "true" is taken to mean a black module.
+ * + * @param bits booleans representing white/black QR Code modules + * @param hints decoding hints that should be used to influence decoding + * @return text and bytes encoded within the QR Code + * @throws FormatException if the QR Code cannot be decoded + * @throws ChecksumException if error correction fails + */ + decodeBitMatrix(bits, hints) { + // Construct a parser and read version, error-correction level + const parser = new BitMatrixParser$1(bits); + let ex = null; + try { + return this.decodeBitMatrixParser(parser, hints); + } + catch (e /*: FormatException, ChecksumException*/) { + ex = e; + } + try { + // Revert the bit matrix + parser.remask(); + // Will be attempting a mirrored reading of the version and format info. + parser.setMirror(true); + // Preemptively read the version. + parser.readVersion(); + // Preemptively read the format information. + parser.readFormatInformation(); + /* + * Since we're here, this means we have successfully detected some kind + * of version and format information when mirrored. This is a good sign, + * that the QR code may be mirrored, and we should try once more with a + * mirrored content. + */ + // Prepare for a mirrored reading. + parser.mirror(); + const result = this.decodeBitMatrixParser(parser, hints); + // Success! Notify the caller that the code was mirrored. + result.setOther(new QRCodeDecoderMetaData(true)); + return result; + } + catch (e /*FormatException | ChecksumException*/) { + // Throw the exception from the original reading + if (ex !== null) { + throw ex; + } + throw e; + } + } + decodeBitMatrixParser(parser, hints) { + const version = parser.readVersion(); + const ecLevel = parser.readFormatInformation().getErrorCorrectionLevel(); + // Read codewords + const codewords = parser.readCodewords(); + // Separate into data blocks + const dataBlocks = DataBlock$1.getDataBlocks(codewords, version, ecLevel); + // Count total number of data bytes + let totalBytes = 0; + for (const dataBlock of dataBlocks) { + totalBytes += dataBlock.getNumDataCodewords(); + } + const resultBytes = new Uint8Array(totalBytes); + let resultOffset = 0; + // Error-correct and copy data blocks together into a stream of bytes + for (const dataBlock of dataBlocks) { + const codewordBytes = dataBlock.getCodewords(); + const numDataCodewords = dataBlock.getNumDataCodewords(); + this.correctErrors(codewordBytes, numDataCodewords); + for (let i = 0; i < numDataCodewords; i++) { + resultBytes[resultOffset++] = codewordBytes[i]; + } + } + // Decode the contents of that stream of bytes + return DecodedBitStreamParser$1.decode(resultBytes, version, ecLevel, hints); + } + /** + *Given data and error-correction codewords received, possibly corrupted by errors, attempts to + * correct the errors in-place using Reed-Solomon error correction.
+ * + * @param codewordBytes data and error correction codewords + * @param numDataCodewords number of codewords that are data bytes + * @throws ChecksumException if error correction fails + */ + correctErrors(codewordBytes, numDataCodewords /*int*/) { + // const numCodewords = codewordBytes.length; + // First read into an array of ints + const codewordsInts = new Int32Array(codewordBytes); + // TYPESCRIPTPORT: not realy necessary to transform to ints? could redesign everything to work with unsigned bytes? + // const codewordsInts = new Int32Array(numCodewords) + // for (let i = 0; i < numCodewords; i++) { + // codewordsInts[i] = codewordBytes[i] & 0xFF + // } + try { + this.rsDecoder.decode(codewordsInts, codewordBytes.length - numDataCodewords); + } + catch (ignored /*: ReedSolomonException*/) { + throw new ChecksumException(); + } + // Copy back into array of bytes -- only need to worry about the bytes that were data + // We don't care about errors in the error-correction codewords + for (let i = 0; i < numDataCodewords; i++) { + codewordBytes[i] = /*(byte) */ codewordsInts[i]; + } + } + } + + /* + * 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. + */ + /** + *Encapsulates an alignment pattern, which are the smaller square patterns found in + * all but the simplest QR Codes.
+ * + * @author Sean Owen + */ + class AlignmentPattern extends ResultPoint { + constructor(posX /*float*/, posY /*float*/, estimatedModuleSize /*float*/) { + super(posX, posY); + this.estimatedModuleSize = estimatedModuleSize; + } + /** + *Determines if this alignment pattern "about equals" an alignment pattern at the stated + * position and size -- meaning, it is at nearly the same center with nearly the same size.
+ */ + aboutEquals(moduleSize /*float*/, i /*float*/, j /*float*/) { + if (Math.abs(i - this.getY()) <= moduleSize && Math.abs(j - this.getX()) <= moduleSize) { + const moduleSizeDiff = Math.abs(moduleSize - this.estimatedModuleSize); + return moduleSizeDiff <= 1.0 || moduleSizeDiff <= this.estimatedModuleSize; + } + return false; + } + /** + * Combines this object's current estimate of a finder pattern position and module size + * with a new estimate. It returns a new {@code FinderPattern} containing an average of the two. + */ + combineEstimate(i /*float*/, j /*float*/, newModuleSize /*float*/) { + const combinedX = (this.getX() + j) / 2.0; + const combinedY = (this.getY() + i) / 2.0; + const combinedModuleSize = (this.estimatedModuleSize + newModuleSize) / 2.0; + return new AlignmentPattern(combinedX, combinedY, combinedModuleSize); + } + } + + /* + * 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. + */ + /*import java.util.ArrayList;*/ + /*import java.util.List;*/ + /** + *This class attempts to find alignment patterns in a QR Code. Alignment patterns look like finder + * patterns but are smaller and appear at regular intervals throughout the image.
+ * + *At the moment this only looks for the bottom-right alignment pattern.
+ * + *This is mostly a simplified copy of {@link FinderPatternFinder}. It is copied, + * pasted and stripped down here for maximum performance but does unfortunately duplicate + * some code.
+ * + *This class is thread-safe but not reentrant. Each thread must allocate its own object.
+ * + * @author Sean Owen + */ + class AlignmentPatternFinder { + /** + *Creates a finder that will look in a portion of the whole image.
+ * + * @param image image to search + * @param startX left column from which to start searching + * @param startY top row from which to start searching + * @param width width of region to search + * @param height height of region to search + * @param moduleSize estimated module size so far + */ + constructor(image, startX /*int*/, startY /*int*/, width /*int*/, height /*int*/, moduleSize /*float*/, resultPointCallback) { + this.image = image; + this.startX = startX; + this.startY = startY; + this.width = width; + this.height = height; + this.moduleSize = moduleSize; + this.resultPointCallback = resultPointCallback; + this.possibleCenters = []; // new ArrayThis method attempts to find the bottom-right alignment pattern in the image. It is a bit messy since + * it's pretty performance-critical and so is written to be fast foremost.
+ * + * @return {@link AlignmentPattern} if found + * @throws NotFoundException if not found + */ + find() { + const startX = this.startX; + const height = this.height; + const width = this.width; + const maxJ = startX + width; + const middleI = this.startY + (height / 2); + // We are looking for black/white/black modules in 1:1:1 ratio + // this tracks the number of black/white/black modules seen so far + const stateCount = new Int32Array(3); + const image = this.image; + for (let iGen = 0; iGen < height; iGen++) { + // Search from middle outwards + const i = middleI + ((iGen & 0x01) === 0 ? Math.floor((iGen + 1) / 2) : -Math.floor((iGen + 1) / 2)); + stateCount[0] = 0; + stateCount[1] = 0; + stateCount[2] = 0; + let j = startX; + // Burn off leading white pixels before anything else; if we start in the middle of + // a white run, it doesn't make sense to count its length, since we don't know if the + // white run continued to the left of the start point + while (j < maxJ && !image.get(j, i)) { + j++; + } + let currentState = 0; + while (j < maxJ) { + if (image.get(j, i)) { + // Black pixel + if (currentState === 1) { // Counting black pixels + stateCount[1]++; + } + else { // Counting white pixels + if (currentState === 2) { // A winner? + if (this.foundPatternCross(stateCount)) { // Yes + const 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 { // White pixel + if (currentState === 1) { // Counting black pixels + currentState++; + } + stateCount[currentState]++; + } + j++; + } + if (this.foundPatternCross(stateCount)) { + const confirmed = this.handlePossibleCenter(stateCount, i, maxJ); + if (confirmed !== null) { + return confirmed; + } + } + } + // Hmm, nothing we saw was observed and confirmed twice. If we had + // any guess at all, return it. + if (this.possibleCenters.length !== 0) { + return this.possibleCenters[0]; + } + throw new NotFoundException(); + } + /** + * Given a count of black/white/black pixels just seen and an end position, + * figures the location of the center of this black/white/black run. + */ + static centerFromEnd(stateCount, end /*int*/) { + return (end - stateCount[2]) - stateCount[1] / 2.0; + } + /** + * @param stateCount count of black/white/black pixels just read + * @return true iff the proportions of the counts is close enough to the 1/1/1 ratios + * used by alignment patterns to be considered a match + */ + foundPatternCross(stateCount) { + const moduleSize = this.moduleSize; + const maxVariance = moduleSize / 2.0; + for (let i = 0; i < 3; i++) { + if (Math.abs(moduleSize - stateCount[i]) >= maxVariance) { + return false; + } + } + return true; + } + /** + *After a horizontal scan finds a potential alignment pattern, this method + * "cross-checks" by scanning down vertically through the center of the possible + * alignment pattern to see if the same proportion is detected.
+ * + * @param startI row where an alignment pattern was detected + * @param centerJ center of the section that appears to cross an alignment pattern + * @param maxCount maximum reasonable number of modules that should be + * observed in any reading state, based on the results of the horizontal scan + * @return vertical center of alignment pattern, or {@link Float#NaN} if not found + */ + crossCheckVertical(startI /*int*/, centerJ /*int*/, maxCount /*int*/, originalStateCountTotal /*int*/) { + const image = this.image; + const maxI = image.getHeight(); + const stateCount = this.crossCheckStateCount; + stateCount[0] = 0; + stateCount[1] = 0; + stateCount[2] = 0; + // Start counting up from center + let i = startI; + while (i >= 0 && image.get(centerJ, i) && stateCount[1] <= maxCount) { + stateCount[1]++; + i--; + } + // If already too many modules in this state or ran off the edge: + if (i < 0 || stateCount[1] > maxCount) { + return NaN; + } + while (i >= 0 && !image.get(centerJ, i) && stateCount[0] <= maxCount) { + stateCount[0]++; + i--; + } + if (stateCount[0] > maxCount) { + return NaN; + } + // Now also count down from center + i = startI + 1; + while (i < maxI && image.get(centerJ, i) && stateCount[1] <= maxCount) { + stateCount[1]++; + i++; + } + if (i === maxI || stateCount[1] > maxCount) { + return NaN; + } + while (i < maxI && !image.get(centerJ, i) && stateCount[2] <= maxCount) { + stateCount[2]++; + i++; + } + if (stateCount[2] > maxCount) { + return NaN; + } + const stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2]; + if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= 2 * originalStateCountTotal) { + return NaN; + } + return this.foundPatternCross(stateCount) ? AlignmentPatternFinder.centerFromEnd(stateCount, i) : NaN; + } + /** + *This is called when a horizontal scan finds a possible alignment pattern. It will + * cross check with a vertical scan, and if successful, will see if this pattern had been + * found on a previous horizontal scan. If so, we consider it confirmed and conclude we have + * found the alignment pattern.
+ * + * @param stateCount reading state module counts from horizontal scan + * @param i row where alignment pattern may be found + * @param j end of possible alignment pattern in row + * @return {@link AlignmentPattern} if we have found the same pattern twice, or null if not + */ + handlePossibleCenter(stateCount, i /*int*/, j /*int*/) { + const stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2]; + const centerJ = AlignmentPatternFinder.centerFromEnd(stateCount, j); + const centerI = this.crossCheckVertical(i, /*(int) */ centerJ, 2 * stateCount[1], stateCountTotal); + if (!isNaN(centerI)) { + const estimatedModuleSize = (stateCount[0] + stateCount[1] + stateCount[2]) / 3.0; + for (const center of this.possibleCenters) { + // Look for about the same center and module size: + if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) { + return center.combineEstimate(centerI, centerJ, estimatedModuleSize); + } + } + // Hadn't found this before; save it + const point = new AlignmentPattern(centerJ, centerI, estimatedModuleSize); + this.possibleCenters.push(point); + if (this.resultPointCallback !== null && this.resultPointCallback !== undefined) { + this.resultPointCallback.foundPossibleResultPoint(point); + } + } + return null; + } + } + + /* + * 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. + */ + /** + *Encapsulates a finder pattern, which are the three square patterns found in + * the corners of QR Codes. It also encapsulates a count of similar finder patterns, + * as a convenience to the finder's bookkeeping.
+ * + * @author Sean Owen + */ + class FinderPattern$1 extends ResultPoint { + // FinderPattern(posX: number/*float*/, posY: number/*float*/, estimatedModuleSize: number/*float*/) { + // this(posX, posY, estimatedModuleSize, 1) + // } + constructor(posX /*float*/, posY /*float*/, estimatedModuleSize /*float*/, count /*int*/) { + super(posX, posY); + this.estimatedModuleSize = estimatedModuleSize; + this.count = count; + if (undefined === count) { + this.count = 1; + } + } + getEstimatedModuleSize() { + return this.estimatedModuleSize; + } + getCount() { + return this.count; + } + /* + void incrementCount() { + this.count++ + } + */ + /** + *Determines if this finder pattern "about equals" a finder pattern at the stated + * position and size -- meaning, it is at nearly the same center with nearly the same size.
+ */ + aboutEquals(moduleSize /*float*/, i /*float*/, j /*float*/) { + if (Math.abs(i - this.getY()) <= moduleSize && Math.abs(j - this.getX()) <= moduleSize) { + const moduleSizeDiff = Math.abs(moduleSize - this.estimatedModuleSize); + return moduleSizeDiff <= 1.0 || moduleSizeDiff <= this.estimatedModuleSize; + } + return false; + } + /** + * Combines this object's current estimate of a finder pattern position and module size + * with a new estimate. It returns a new {@code FinderPattern} containing a weighted average + * based on count. + */ + combineEstimate(i /*float*/, j /*float*/, newModuleSize /*float*/) { + const combinedCount = this.count + 1; + const combinedX = (this.count * this.getX() + j) / combinedCount; + const combinedY = (this.count * this.getY() + i) / combinedCount; + const combinedModuleSize = (this.count * this.estimatedModuleSize + newModuleSize) / combinedCount; + return new FinderPattern$1(combinedX, combinedY, combinedModuleSize, combinedCount); + } + } + + /* + * 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. + */ + /** + *Encapsulates information about finder patterns in an image, including the location of + * the three finder patterns, and their estimated module size.
+ * + * @author Sean Owen + */ + class FinderPatternInfo { + constructor(patternCenters) { + this.bottomLeft = patternCenters[0]; + this.topLeft = patternCenters[1]; + this.topRight = patternCenters[2]; + } + getBottomLeft() { + return this.bottomLeft; + } + getTopLeft() { + return this.topLeft; + } + getTopRight() { + return this.topRight; + } + } + + /* + * 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. + */ + /*import java.io.Serializable;*/ + /*import java.util.ArrayList;*/ + /*import java.util.Collections;*/ + /*import java.util.Comparator;*/ + /*import java.util.List;*/ + /*import java.util.Map;*/ + /** + *This class attempts to find finder patterns in a QR Code. Finder patterns are the square + * markers at three corners of a QR Code.
+ * + *This class is thread-safe but not reentrant. Each thread must allocate its own object. + * + * @author Sean Owen + */ + class FinderPatternFinder { + /** + *
Creates a finder that will search the image for three finder patterns.
+ * + * @param image image to search + */ + // public constructor(image: BitMatrix) { + // this(image, null) + // } + constructor(image, resultPointCallback) { + this.image = image; + this.resultPointCallback = resultPointCallback; + this.possibleCenters = []; + this.crossCheckStateCount = new Int32Array(5); + this.resultPointCallback = resultPointCallback; + } + getImage() { + return this.image; + } + getPossibleCenters() { + return this.possibleCenters; + } + find(hints) { + const tryHarder = (hints !== null && hints !== undefined) && undefined !== hints.get(DecodeHintType$1.TRY_HARDER); + const pureBarcode = (hints !== null && hints !== undefined) && undefined !== hints.get(DecodeHintType$1.PURE_BARCODE); + const image = this.image; + const maxI = image.getHeight(); + const maxJ = image.getWidth(); + // We are looking for black/white/black/white/black modules in + // 1:1:3:1:1 ratio; this tracks the number of such modules seen so far + // Let's assume that the maximum version QR Code we support takes up 1/4 the height of the + // image, and then account for the center being 3 modules in size. This gives the smallest + // number of pixels the center could be, so skip this often. When trying harder, look for all + // QR versions regardless of how dense they are. + let iSkip = Math.floor((3 * maxI) / (4 * FinderPatternFinder.MAX_MODULES)); + if (iSkip < FinderPatternFinder.MIN_SKIP || tryHarder) { + iSkip = FinderPatternFinder.MIN_SKIP; + } + let done = false; + const stateCount = new Int32Array(5); + for (let i = iSkip - 1; i < maxI && !done; i += iSkip) { + // Get a row of black/white values + 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.get(j, i)) { + // Black pixel + if ((currentState & 1) === 1) { // Counting white pixels + currentState++; + } + stateCount[currentState]++; + } + else { // White pixel + if ((currentState & 1) === 0) { // Counting black pixels + if (currentState === 4) { // A winner? + if (FinderPatternFinder.foundPatternCross(stateCount)) { // Yes + const confirmed = this.handlePossibleCenter(stateCount, i, j, pureBarcode); + if (confirmed === true) { + // Start examining every other line. Checking each line turned out to be too + // expensive and didn't improve performance. + iSkip = 2; + if (this.hasSkipped === true) { + done = this.haveMultiplyConfirmedCenters(); + } + else { + const rowSkip = this.findRowSkip(); + if (rowSkip > stateCount[2]) { + // Skip rows between row of lower confirmed center + // and top of presumed third confirmed center + // but back up a bit to get a full chance of detecting + // it, entire width of center of finder pattern + // Skip by rowSkip, but back off by stateCount[2] (size of last center + // of pattern we saw) to be conservative, and also back off by iSkip which + // is about to be re-added + i += rowSkip - stateCount[2] - iSkip; + j = maxJ - 1; + } + } + } + else { + stateCount[0] = stateCount[2]; + stateCount[1] = stateCount[3]; + stateCount[2] = stateCount[4]; + stateCount[3] = 1; + stateCount[4] = 0; + currentState = 3; + continue; + } + // Clear state to start looking again + currentState = 0; + stateCount[0] = 0; + stateCount[1] = 0; + stateCount[2] = 0; + stateCount[3] = 0; + stateCount[4] = 0; + } + else { // No, shift counts back by two + stateCount[0] = stateCount[2]; + stateCount[1] = stateCount[3]; + stateCount[2] = stateCount[4]; + stateCount[3] = 1; + stateCount[4] = 0; + currentState = 3; + } + } + else { + stateCount[++currentState]++; + } + } + else { // Counting white pixels + stateCount[currentState]++; + } + } + } + if (FinderPatternFinder.foundPatternCross(stateCount)) { + const confirmed = this.handlePossibleCenter(stateCount, i, maxJ, pureBarcode); + if (confirmed === true) { + iSkip = stateCount[0]; + if (this.hasSkipped) { + // Found a third one + done = this.haveMultiplyConfirmedCenters(); + } + } + } + } + const patternInfo = this.selectBestPatterns(); + ResultPoint.orderBestPatterns(patternInfo); + return new FinderPatternInfo(patternInfo); + } + /** + * Given a count of black/white/black/white/black pixels just seen and an end position, + * figures the location of the center of this run. + */ + static centerFromEnd(stateCount, end /*int*/) { + return (end - stateCount[4] - stateCount[3]) - stateCount[2] / 2.0; + } + /** + * @param stateCount count of black/white/black/white/black pixels just read + * @return true iff the proportions of the counts is close enough to the 1/1/3/1/1 ratios + * used by finder patterns to be considered a match + */ + static foundPatternCross(stateCount) { + let totalModuleSize = 0; + for (let i = 0; i < 5; i++) { + const count = stateCount[i]; + if (count === 0) { + return false; + } + totalModuleSize += count; + } + if (totalModuleSize < 7) { + return false; + } + const moduleSize = totalModuleSize / 7.0; + const maxVariance = moduleSize / 2.0; + // Allow less than 50% variance from 1-1-3-1-1 proportions + return Math.abs(moduleSize - stateCount[0]) < maxVariance && + Math.abs(moduleSize - stateCount[1]) < maxVariance && + Math.abs(3.0 * moduleSize - stateCount[2]) < 3 * maxVariance && + Math.abs(moduleSize - stateCount[3]) < maxVariance && + Math.abs(moduleSize - stateCount[4]) < maxVariance; + } + getCrossCheckStateCount() { + const crossCheckStateCount = this.crossCheckStateCount; + crossCheckStateCount[0] = 0; + crossCheckStateCount[1] = 0; + crossCheckStateCount[2] = 0; + crossCheckStateCount[3] = 0; + crossCheckStateCount[4] = 0; + return crossCheckStateCount; + } + /** + * After a vertical and horizontal scan finds a potential finder pattern, this method + * "cross-cross-cross-checks" by scanning down diagonally through the center of the possible + * finder pattern to see if the same proportion is detected. + * + * @param startI row where a finder pattern was detected + * @param centerJ center of the section that appears to cross a finder pattern + * @param maxCount maximum reasonable number of modules that should be + * observed in any reading state, based on the results of the horizontal scan + * @param originalStateCountTotal The original state count total. + * @return true if proportions are withing expected limits + */ + crossCheckDiagonal(startI /*int*/, centerJ /*int*/, maxCount /*int*/, originalStateCountTotal /*int*/) { + const stateCount = this.getCrossCheckStateCount(); + // Start counting up, left from center finding black center mass + let i = 0; + const image = this.image; + while (startI >= i && centerJ >= i && image.get(centerJ - i, startI - i)) { + stateCount[2]++; + i++; + } + if (startI < i || centerJ < i) { + return false; + } + // Continue up, left finding white space + while (startI >= i && centerJ >= i && !image.get(centerJ - i, startI - i) && + stateCount[1] <= maxCount) { + stateCount[1]++; + i++; + } + // If already too many modules in this state or ran off the edge: + if (startI < i || centerJ < i || stateCount[1] > maxCount) { + return false; + } + // Continue up, left finding black border + while (startI >= i && centerJ >= i && image.get(centerJ - i, startI - i) && + stateCount[0] <= maxCount) { + stateCount[0]++; + i++; + } + if (stateCount[0] > maxCount) { + return false; + } + const maxI = image.getHeight(); + const maxJ = image.getWidth(); + // Now also count down, right from center + i = 1; + while (startI + i < maxI && centerJ + i < maxJ && image.get(centerJ + i, startI + i)) { + stateCount[2]++; + i++; + } + // Ran off the edge? + if (startI + i >= maxI || centerJ + i >= maxJ) { + return false; + } + while (startI + i < maxI && centerJ + i < maxJ && !image.get(centerJ + i, startI + i) && + stateCount[3] < maxCount) { + stateCount[3]++; + i++; + } + if (startI + i >= maxI || centerJ + i >= maxJ || stateCount[3] >= maxCount) { + return false; + } + while (startI + i < maxI && centerJ + i < maxJ && image.get(centerJ + i, startI + i) && + stateCount[4] < maxCount) { + stateCount[4]++; + i++; + } + if (stateCount[4] >= maxCount) { + return false; + } + // If we found a finder-pattern-like section, but its size is more than 100% different than + // the original, assume it's a false positive + const stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4]; + return Math.abs(stateCountTotal - originalStateCountTotal) < 2 * originalStateCountTotal && + FinderPatternFinder.foundPatternCross(stateCount); + } + /** + *After a horizontal scan finds a potential finder pattern, this method + * "cross-checks" by scanning down vertically through the center of the possible + * finder pattern to see if the same proportion is detected.
+ * + * @param startI row where a finder pattern was detected + * @param centerJ center of the section that appears to cross a finder pattern + * @param maxCount maximum reasonable number of modules that should be + * observed in any reading state, based on the results of the horizontal scan + * @return vertical center of finder pattern, or {@link Float#NaN} if not found + */ + crossCheckVertical(startI /*int*/, centerJ /*int*/, maxCount /*int*/, originalStateCountTotal /*int*/) { + const image = this.image; + const maxI = image.getHeight(); + const stateCount = this.getCrossCheckStateCount(); + // Start counting up from center + let i = startI; + while (i >= 0 && image.get(centerJ, i)) { + stateCount[2]++; + i--; + } + if (i < 0) { + return NaN; + } + while (i >= 0 && !image.get(centerJ, i) && stateCount[1] <= maxCount) { + stateCount[1]++; + i--; + } + // If already too many modules in this state or ran off the edge: + if (i < 0 || stateCount[1] > maxCount) { + return NaN; + } + while (i >= 0 && image.get(centerJ, i) && stateCount[0] <= maxCount) { + stateCount[0]++; + i--; + } + if (stateCount[0] > maxCount) { + return NaN; + } + // Now also count down from center + i = startI + 1; + while (i < maxI && image.get(centerJ, i)) { + stateCount[2]++; + i++; + } + if (i === maxI) { + return NaN; + } + while (i < maxI && !image.get(centerJ, i) && stateCount[3] < maxCount) { + stateCount[3]++; + i++; + } + if (i === maxI || stateCount[3] >= maxCount) { + return NaN; + } + while (i < maxI && image.get(centerJ, i) && stateCount[4] < maxCount) { + stateCount[4]++; + i++; + } + if (stateCount[4] >= maxCount) { + return NaN; + } + // If we found a finder-pattern-like section, but its size is more than 40% different than + // the original, assume it's a false positive + const stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + + stateCount[4]; + if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= 2 * originalStateCountTotal) { + return NaN; + } + return FinderPatternFinder.foundPatternCross(stateCount) ? FinderPatternFinder.centerFromEnd(stateCount, i) : NaN; + } + /** + *Like {@link #crossCheckVertical(int, int, int, int)}, and in fact is basically identical, + * except it reads horizontally instead of vertically. This is used to cross-cross + * check a vertical cross check and locate the real center of the alignment pattern.
+ */ + crossCheckHorizontal(startJ /*int*/, centerI /*int*/, maxCount /*int*/, originalStateCountTotal /*int*/) { + const image = this.image; + const maxJ = image.getWidth(); + const stateCount = this.getCrossCheckStateCount(); + let j = startJ; + while (j >= 0 && image.get(j, centerI)) { + stateCount[2]++; + j--; + } + if (j < 0) { + return NaN; + } + while (j >= 0 && !image.get(j, centerI) && stateCount[1] <= maxCount) { + stateCount[1]++; + j--; + } + if (j < 0 || stateCount[1] > maxCount) { + return NaN; + } + while (j >= 0 && image.get(j, centerI) && stateCount[0] <= maxCount) { + stateCount[0]++; + j--; + } + if (stateCount[0] > maxCount) { + return NaN; + } + j = startJ + 1; + while (j < maxJ && image.get(j, centerI)) { + stateCount[2]++; + j++; + } + if (j === maxJ) { + return NaN; + } + while (j < maxJ && !image.get(j, centerI) && stateCount[3] < maxCount) { + stateCount[3]++; + j++; + } + if (j === maxJ || stateCount[3] >= maxCount) { + return NaN; + } + while (j < maxJ && image.get(j, centerI) && stateCount[4] < maxCount) { + stateCount[4]++; + j++; + } + if (stateCount[4] >= maxCount) { + return NaN; + } + // If we found a finder-pattern-like section, but its size is significantly different than + // the original, assume it's a false positive + const stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + + stateCount[4]; + if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= originalStateCountTotal) { + return NaN; + } + return FinderPatternFinder.foundPatternCross(stateCount) ? FinderPatternFinder.centerFromEnd(stateCount, j) : NaN; + } + /** + *This is called when a horizontal scan finds a possible alignment pattern. It will + * cross check with a vertical scan, and if successful, will, ah, cross-cross-check + * with another horizontal scan. This is needed primarily to locate the real horizontal + * center of the pattern in cases of extreme skew. + * And then we cross-cross-cross check with another diagonal scan.
+ * + *If that succeeds the finder pattern location is added to a list that tracks + * the number of times each location has been nearly-matched as a finder pattern. + * Each additional find is more evidence that the location is in fact a finder + * pattern center + * + * @param stateCount reading state module counts from horizontal scan + * @param i row where finder pattern may be found + * @param j end of possible finder pattern in row + * @param pureBarcode true if in "pure barcode" mode + * @return true if a finder pattern candidate was found this time + */ + handlePossibleCenter(stateCount, i /*int*/, j /*int*/, pureBarcode) { + const stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + + stateCount[4]; + let centerJ = FinderPatternFinder.centerFromEnd(stateCount, j); + let centerI = this.crossCheckVertical(i, /*(int) */ Math.floor(centerJ), stateCount[2], stateCountTotal); + if (!isNaN(centerI)) { + // Re-cross check + centerJ = this.crossCheckHorizontal(/*(int) */ Math.floor(centerJ), /*(int) */ Math.floor(centerI), stateCount[2], stateCountTotal); + if (!isNaN(centerJ) && + (!pureBarcode || this.crossCheckDiagonal(/*(int) */ Math.floor(centerI), /*(int) */ Math.floor(centerJ), stateCount[2], stateCountTotal))) { + const estimatedModuleSize = stateCountTotal / 7.0; + let found = false; + const possibleCenters = this.possibleCenters; + for (let index = 0, length = possibleCenters.length; index < length; index++) { + const center = possibleCenters[index]; + // Look for about the same center and module size: + if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) { + possibleCenters[index] = center.combineEstimate(centerI, centerJ, estimatedModuleSize); + found = true; + break; + } + } + if (!found) { + const point = new FinderPattern$1(centerJ, centerI, estimatedModuleSize); + possibleCenters.push(point); + if (this.resultPointCallback !== null && this.resultPointCallback !== undefined) { + this.resultPointCallback.foundPossibleResultPoint(point); + } + } + return true; + } + } + return false; + } + /** + * @return number of rows we could safely skip during scanning, based on the first + * two finder patterns that have been located. In some cases their position will + * allow us to infer that the third pattern must lie below a certain point farther + * down in the image. + */ + findRowSkip() { + const max = this.possibleCenters.length; + if (max <= 1) { + return 0; + } + let firstConfirmedCenter = null; + for (const center of this.possibleCenters) { + if (center.getCount() >= FinderPatternFinder.CENTER_QUORUM) { + if (firstConfirmedCenter == null) { + firstConfirmedCenter = center; + } + else { + // We have two confirmed centers + // How far down can we skip before resuming looking for the next + // pattern? In the worst case, only the difference between the + // difference in the x / y coordinates of the two centers. + // This is the case where you find top left last. + this.hasSkipped = true; + return /*(int) */ Math.floor((Math.abs(firstConfirmedCenter.getX() - center.getX()) - + Math.abs(firstConfirmedCenter.getY() - center.getY())) / 2); + } + } + } + return 0; + } + /** + * @return true iff we have found at least 3 finder patterns that have been detected + * at least {@link #CENTER_QUORUM} times each, and, the estimated module size of the + * candidates is "pretty similar" + */ + haveMultiplyConfirmedCenters() { + let confirmedCount = 0; + let totalModuleSize = 0.0; + const max = this.possibleCenters.length; + for (const pattern of this.possibleCenters) { + if (pattern.getCount() >= FinderPatternFinder.CENTER_QUORUM) { + confirmedCount++; + totalModuleSize += pattern.getEstimatedModuleSize(); + } + } + if (confirmedCount < 3) { + return false; + } + // OK, we have at least 3 confirmed centers, but, it's possible that one is a "false positive" + // and that we need to keep looking. We detect this by asking if the estimated module sizes + // vary too much. We arbitrarily say that when the total deviation from average exceeds + // 5% of the total module size estimates, it's too much. + const average = totalModuleSize / max; + let totalDeviation = 0.0; + for (const pattern of this.possibleCenters) { + totalDeviation += Math.abs(pattern.getEstimatedModuleSize() - average); + } + return totalDeviation <= 0.05 * totalModuleSize; + } + /** + * @return the 3 best {@link FinderPattern}s from our list of candidates. The "best" are + * those that have been detected at least {@link #CENTER_QUORUM} times, and whose module + * size differs from the average among those patterns the least + * @throws NotFoundException if 3 such finder patterns do not exist + */ + selectBestPatterns() { + const startSize = this.possibleCenters.length; + if (startSize < 3) { + // Couldn't find enough finder patterns + throw new NotFoundException(); + } + const possibleCenters = this.possibleCenters; + let average; + // Filter outlier possibilities whose module size is too different + if (startSize > 3) { + // But we can only afford to do so if we have at least 4 possibilities to choose from + let totalModuleSize = 0.0; + let square = 0.0; + for (const center of this.possibleCenters) { + const size = center.getEstimatedModuleSize(); + totalModuleSize += size; + square += size * size; + } + average = totalModuleSize / startSize; + let stdDev = Math.sqrt(square / startSize - average * average); + possibleCenters.sort( + /** + *
Orders by furthest from average
+ */ + // FurthestFromAverageComparator implements ComparatorOrders by {@link FinderPattern#getCount()}, descending.
+ */ + // CenterComparator implements ComparatorEncapsulates logic that can detect a QR Code in an image, even if the QR Code + * is rotated or skewed, or partially obscured.
+ * + * @author Sean Owen + */ + class Detector$2 { + constructor(image) { + this.image = image; + } + getImage() { + return this.image; + } + getResultPointCallback() { + return this.resultPointCallback; + } + /** + *Detects a QR Code in an image.
+ * + * @return {@link DetectorResult} encapsulating results of detecting a QR Code + * @throws NotFoundException if QR Code cannot be found + * @throws FormatException if a QR Code cannot be decoded + */ + // public detect(): DetectorResult /*throws NotFoundException, FormatException*/ { + // return detect(null) + // } + /** + *Detects a QR Code in an image.
+ * + * @param hints optional hints to detector + * @return {@link DetectorResult} encapsulating results of detecting a QR Code + * @throws NotFoundException if QR Code cannot be found + * @throws FormatException if a QR Code cannot be decoded + */ + detect(hints) { + this.resultPointCallback = (hints === null || hints === undefined) ? null : + /*(ResultPointCallback) */ hints.get(DecodeHintType$1.NEED_RESULT_POINT_CALLBACK); + const finder = new FinderPatternFinder(this.image, this.resultPointCallback); + const info = finder.find(hints); + return this.processFinderPatternInfo(info); + } + processFinderPatternInfo(info) { + const topLeft = info.getTopLeft(); + const topRight = info.getTopRight(); + const bottomLeft = info.getBottomLeft(); + const moduleSize = this.calculateModuleSize(topLeft, topRight, bottomLeft); + if (moduleSize < 1.0) { + throw new NotFoundException('No pattern found in proccess finder.'); + } + const dimension = Detector$2.computeDimension(topLeft, topRight, bottomLeft, moduleSize); + const provisionalVersion = Version$1.getProvisionalVersionForDimension(dimension); + const modulesBetweenFPCenters = provisionalVersion.getDimensionForVersion() - 7; + let alignmentPattern = null; + // Anything above version 1 has an alignment pattern + if (provisionalVersion.getAlignmentPatternCenters().length > 0) { + // Guess where a "bottom right" finder pattern would have been + const bottomRightX = topRight.getX() - topLeft.getX() + bottomLeft.getX(); + const bottomRightY = topRight.getY() - topLeft.getY() + bottomLeft.getY(); + // Estimate that alignment pattern is closer by 3 modules + // from "bottom right" to known top left location + const correctionToTopLeft = 1.0 - 3.0 / modulesBetweenFPCenters; + const estAlignmentX = /*(int) */ Math.floor(topLeft.getX() + correctionToTopLeft * (bottomRightX - topLeft.getX())); + const estAlignmentY = /*(int) */ Math.floor(topLeft.getY() + correctionToTopLeft * (bottomRightY - topLeft.getY())); + // Kind of arbitrary -- expand search radius before giving up + for (let i = 4; i <= 16; i <<= 1) { + try { + alignmentPattern = this.findAlignmentInRegion(moduleSize, estAlignmentX, estAlignmentY, i); + break; + } + catch (re /*NotFoundException*/) { + if (!(re instanceof NotFoundException)) { + throw re; + } + // try next round + } + } + // If we didn't find alignment pattern... well try anyway without it + } + const transform = Detector$2.createTransform(topLeft, topRight, bottomLeft, alignmentPattern, dimension); + const bits = Detector$2.sampleGrid(this.image, transform, dimension); + let points; + if (alignmentPattern === null) { + points = [bottomLeft, topLeft, topRight]; + } + else { + points = [bottomLeft, topLeft, topRight, alignmentPattern]; + } + return new DetectorResult(bits, points); + } + static createTransform(topLeft, topRight, bottomLeft, alignmentPattern, dimension /*int*/) { + const dimMinusThree = dimension - 3.5; + let bottomRightX; /*float*/ + let bottomRightY; /*float*/ + let sourceBottomRightX; /*float*/ + let sourceBottomRightY; /*float*/ + if (alignmentPattern !== null) { + bottomRightX = alignmentPattern.getX(); + bottomRightY = alignmentPattern.getY(); + sourceBottomRightX = dimMinusThree - 3.0; + sourceBottomRightY = sourceBottomRightX; + } + else { + // Don't have an alignment pattern, just make up the bottom-right point + bottomRightX = (topRight.getX() - topLeft.getX()) + bottomLeft.getX(); + bottomRightY = (topRight.getY() - topLeft.getY()) + bottomLeft.getY(); + sourceBottomRightX = dimMinusThree; + sourceBottomRightY = dimMinusThree; + } + return PerspectiveTransform.quadrilateralToQuadrilateral(3.5, 3.5, dimMinusThree, 3.5, sourceBottomRightX, sourceBottomRightY, 3.5, dimMinusThree, topLeft.getX(), topLeft.getY(), topRight.getX(), topRight.getY(), bottomRightX, bottomRightY, bottomLeft.getX(), bottomLeft.getY()); + } + static sampleGrid(image, transform, dimension /*int*/) { + const sampler = GridSamplerInstance.getInstance(); + return sampler.sampleGridWithTransform(image, dimension, dimension, transform); + } + /** + *Computes the dimension (number of modules on a size) of the QR Code based on the position + * of the finder patterns and estimated module size.
+ */ + static computeDimension(topLeft, topRight, bottomLeft, moduleSize /*float*/) { + const tltrCentersDimension = MathUtils.round(ResultPoint.distance(topLeft, topRight) / moduleSize); + const tlblCentersDimension = MathUtils.round(ResultPoint.distance(topLeft, bottomLeft) / moduleSize); + let dimension = Math.floor((tltrCentersDimension + tlblCentersDimension) / 2) + 7; + switch (dimension & 0x03) { // mod 4 + case 0: + dimension++; + break; + // 1? do nothing + case 2: + dimension--; + break; + case 3: + throw new NotFoundException('Dimensions could be not found.'); + } + return dimension; + } + /** + *Computes an average estimated module size based on estimated derived from the positions + * of the three finder patterns.
+ * + * @param topLeft detected top-left finder pattern center + * @param topRight detected top-right finder pattern center + * @param bottomLeft detected bottom-left finder pattern center + * @return estimated module size + */ + calculateModuleSize(topLeft, topRight, bottomLeft) { + // Take the average + return (this.calculateModuleSizeOneWay(topLeft, topRight) + + this.calculateModuleSizeOneWay(topLeft, bottomLeft)) / 2.0; + } + /** + *Estimates module size based on two finder patterns -- it uses + * {@link #sizeOfBlackWhiteBlackRunBothWays(int, int, int, int)} to figure the + * width of each, measuring along the axis between their centers.
+ */ + calculateModuleSizeOneWay(pattern, otherPattern) { + const moduleSizeEst1 = this.sizeOfBlackWhiteBlackRunBothWays(/*(int) */ Math.floor(pattern.getX()), + /*(int) */ Math.floor(pattern.getY()), + /*(int) */ Math.floor(otherPattern.getX()), + /*(int) */ Math.floor(otherPattern.getY())); + const moduleSizeEst2 = this.sizeOfBlackWhiteBlackRunBothWays(/*(int) */ Math.floor(otherPattern.getX()), + /*(int) */ Math.floor(otherPattern.getY()), + /*(int) */ Math.floor(pattern.getX()), + /*(int) */ Math.floor(pattern.getY())); + if (isNaN(moduleSizeEst1)) { + return moduleSizeEst2 / 7.0; + } + if (isNaN(moduleSizeEst2)) { + return moduleSizeEst1 / 7.0; + } + // Average them, and divide by 7 since we've counted the width of 3 black modules, + // and 1 white and 1 black module on either side. Ergo, divide sum by 14. + return (moduleSizeEst1 + moduleSizeEst2) / 14.0; + } + /** + * See {@link #sizeOfBlackWhiteBlackRun(int, int, int, int)}; computes the total width of + * a finder pattern by looking for a black-white-black run from the center in the direction + * of another point (another finder pattern center), and in the opposite direction too. + */ + sizeOfBlackWhiteBlackRunBothWays(fromX /*int*/, fromY /*int*/, toX /*int*/, toY /*int*/) { + let result = this.sizeOfBlackWhiteBlackRun(fromX, fromY, toX, toY); + // Now count other way -- don't run off image though of course + let scale = 1.0; + let otherToX = fromX - (toX - fromX); + if (otherToX < 0) { + scale = fromX / /*(float) */ (fromX - otherToX); + otherToX = 0; + } + else if (otherToX >= this.image.getWidth()) { + scale = (this.image.getWidth() - 1 - fromX) / /*(float) */ (otherToX - fromX); + otherToX = this.image.getWidth() - 1; + } + let otherToY = /*(int) */ Math.floor(fromY - (toY - fromY) * scale); + scale = 1.0; + if (otherToY < 0) { + scale = fromY / /*(float) */ (fromY - otherToY); + otherToY = 0; + } + else if (otherToY >= this.image.getHeight()) { + scale = (this.image.getHeight() - 1 - fromY) / /*(float) */ (otherToY - fromY); + otherToY = this.image.getHeight() - 1; + } + otherToX = /*(int) */ Math.floor(fromX + (otherToX - fromX) * scale); + result += this.sizeOfBlackWhiteBlackRun(fromX, fromY, otherToX, otherToY); + // Middle pixel is double-counted this way; subtract 1 + return result - 1.0; + } + /** + *This method traces a line from a point in the image, in the direction towards another point. + * It begins in a black region, and keeps going until it finds white, then black, then white again. + * It reports the distance from the start to this point.
+ * + *This is used when figuring out how wide a finder pattern is, when the finder pattern + * may be skewed or rotated.
+ */ + sizeOfBlackWhiteBlackRun(fromX /*int*/, fromY /*int*/, toX /*int*/, toY /*int*/) { + // Mild variant of Bresenham's algorithm + // see http://en.wikipedia.org/wiki/Bresenham's_line_algorithm + const steep = Math.abs(toY - fromY) > Math.abs(toX - fromX); + if (steep) { + let temp = fromX; + fromX = fromY; + fromY = temp; + temp = toX; + toX = toY; + toY = temp; + } + const dx = Math.abs(toX - fromX); + const dy = Math.abs(toY - fromY); + let error = -dx / 2; + const xstep = fromX < toX ? 1 : -1; + const ystep = fromY < toY ? 1 : -1; + // In black pixels, looking for white, first or second time. + let state = 0; + // Loop up until x == toX, but not beyond + const xLimit = toX + xstep; + for (let x = fromX, y = fromY; x !== xLimit; x += xstep) { + const realX = steep ? y : x; + const realY = steep ? x : y; + // Does current pixel mean we have moved white to black or vice versa? + // Scanning black in state 0,2 and white in state 1, so if we find the wrong + // color, advance to next state or end if we are in state 2 already + if ((state === 1) === this.image.get(realX, realY)) { + if (state === 2) { + return MathUtils.distance(x, y, fromX, fromY); + } + state++; + } + error += dy; + if (error > 0) { + if (y === toY) { + break; + } + y += ystep; + error -= dx; + } + } + // Found black-white-black; give the benefit of the doubt that the next pixel outside the image + // is "white" so this last point at (toX+xStep,toY) is the right ending. This is really a + // small approximation; (toX+xStep,toY+yStep) might be really correct. Ignore this. + if (state === 2) { + return MathUtils.distance(toX + xstep, toY, fromX, fromY); + } + // else we didn't find even black-white-black; no estimate is really possible + return NaN; + } + /** + *Attempts to locate an alignment pattern in a limited region of the image, which is + * guessed to contain it. This method uses {@link AlignmentPattern}.
+ * + * @param overallEstModuleSize estimated module size so far + * @param estAlignmentX x coordinate of center of area probably containing alignment pattern + * @param estAlignmentY y coordinate of above + * @param allowanceFactor number of pixels in all directions to search from the center + * @return {@link AlignmentPattern} if found, or null otherwise + * @throws NotFoundException if an unexpected error occurs during detection + */ + findAlignmentInRegion(overallEstModuleSize /*float*/, estAlignmentX /*int*/, estAlignmentY /*int*/, allowanceFactor /*float*/) { + // Look for an alignment pattern (3 modules in size) around where it + // should be + const allowance = /*(int) */ Math.floor(allowanceFactor * overallEstModuleSize); + const alignmentAreaLeftX = Math.max(0, estAlignmentX - allowance); + const alignmentAreaRightX = Math.min(this.image.getWidth() - 1, estAlignmentX + allowance); + if (alignmentAreaRightX - alignmentAreaLeftX < overallEstModuleSize * 3) { + throw new NotFoundException('Alignment top exceeds estimated module size.'); + } + const alignmentAreaTopY = Math.max(0, estAlignmentY - allowance); + const alignmentAreaBottomY = Math.min(this.image.getHeight() - 1, estAlignmentY + allowance); + if (alignmentAreaBottomY - alignmentAreaTopY < overallEstModuleSize * 3) { + throw new NotFoundException('Alignment bottom exceeds estimated module size.'); + } + const alignmentFinder = new AlignmentPatternFinder(this.image, alignmentAreaLeftX, alignmentAreaTopY, alignmentAreaRightX - alignmentAreaLeftX, alignmentAreaBottomY - alignmentAreaTopY, overallEstModuleSize, this.resultPointCallback); + return alignmentFinder.find(); + } + } + + /* + * 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. + */ + /*import java.util.List;*/ + /*import java.util.Map;*/ + /** + * This implementation can detect and decode QR Codes in an image. + * + * @author Sean Owen + */ + class QRCodeReader { + constructor() { + this.decoder = new Decoder$2(); + } + getDecoder() { + return this.decoder; + } + /** + * Locates and decodes a QR code in an image. + * + * @return a representing: string the content encoded by the QR code + * @throws NotFoundException if a QR code cannot be found + * @throws FormatException if a QR code cannot be decoded + * @throws ChecksumException if error correction fails + */ + /*@Override*/ + // public decode(image: BinaryBitmap): Result /*throws NotFoundException, ChecksumException, FormatException */ { + // return this.decode(image, null) + // } + /*@Override*/ + decode(image, hints) { + let decoderResult; + let points; + if (hints !== undefined && hints !== null && undefined !== hints.get(DecodeHintType$1.PURE_BARCODE)) { + const bits = QRCodeReader.extractPureBits(image.getBlackMatrix()); + decoderResult = this.decoder.decodeBitMatrix(bits, hints); + points = QRCodeReader.NO_POINTS; + } + else { + const detectorResult = new Detector$2(image.getBlackMatrix()).detect(hints); + decoderResult = this.decoder.decodeBitMatrix(detectorResult.getBits(), hints); + points = detectorResult.getPoints(); + } + // If the code was mirrored: swap the bottom-left and the top-right points. + if (decoderResult.getOther() instanceof QRCodeDecoderMetaData) { + decoderResult.getOther().applyMirroredCorrection(points); + } + const result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), undefined, points, BarcodeFormat$1.QR_CODE, undefined); + const byteSegments = decoderResult.getByteSegments(); + if (byteSegments !== null) { + result.putMetadata(ResultMetadataType$1.BYTE_SEGMENTS, byteSegments); + } + const ecLevel = decoderResult.getECLevel(); + if (ecLevel !== null) { + result.putMetadata(ResultMetadataType$1.ERROR_CORRECTION_LEVEL, ecLevel); + } + if (decoderResult.hasStructuredAppend()) { + result.putMetadata(ResultMetadataType$1.STRUCTURED_APPEND_SEQUENCE, decoderResult.getStructuredAppendSequenceNumber()); + result.putMetadata(ResultMetadataType$1.STRUCTURED_APPEND_PARITY, decoderResult.getStructuredAppendParity()); + } + return result; + } + /*@Override*/ + reset() { + // do nothing + } + /** + * This method detects a code in a "pure" image -- that is, pure monochrome image + * which contains only an unrotated, unskewed, image of a code, with some white border + * around it. This is a specialized method that works exceptionally fast in this special + * case. + * + * @see com.google.zxing.datamatrix.DataMatrixReader#extractPureBits(BitMatrix) + */ + static extractPureBits(image) { + const leftTopBlack = image.getTopLeftOnBit(); + const rightBottomBlack = image.getBottomRightOnBit(); + if (leftTopBlack === null || rightBottomBlack === null) { + throw new NotFoundException(); + } + const moduleSize = this.moduleSize(leftTopBlack, image); + let top = leftTopBlack[1]; + let bottom = rightBottomBlack[1]; + let left = leftTopBlack[0]; + let right = rightBottomBlack[0]; + // Sanity check! + if (left >= right || top >= bottom) { + throw new NotFoundException(); + } + if (bottom - top !== right - left) { + // Special case, where bottom-right module wasn't black so we found something else in the last row + // Assume it's a square, so use height as the width + right = left + (bottom - top); + if (right >= image.getWidth()) { + // Abort if that would not make sense -- off image + throw new NotFoundException(); + } + } + const matrixWidth = Math.round((right - left + 1) / moduleSize); + const matrixHeight = Math.round((bottom - top + 1) / moduleSize); + if (matrixWidth <= 0 || matrixHeight <= 0) { + throw new NotFoundException(); + } + if (matrixHeight !== matrixWidth) { + // Only possibly decode square regions + throw new NotFoundException(); + } + // Push in the "border" by half the module width so that we start + // sampling in the middle of the module. Just in case the image is a + // little off, this will help recover. + const nudge = /*(int) */ Math.floor(moduleSize / 2.0); + top += nudge; + left += nudge; + // But careful that this does not sample off the edge + // "right" is the farthest-right valid pixel location -- right+1 is not necessarily + // This is positive by how much the inner x loop below would be too large + const nudgedTooFarRight = left + /*(int) */ Math.floor((matrixWidth - 1) * moduleSize) - right; + if (nudgedTooFarRight > 0) { + if (nudgedTooFarRight > nudge) { + // Neither way fits; abort + throw new NotFoundException(); + } + left -= nudgedTooFarRight; + } + // See logic above + const nudgedTooFarDown = top + /*(int) */ Math.floor((matrixHeight - 1) * moduleSize) - bottom; + if (nudgedTooFarDown > 0) { + if (nudgedTooFarDown > nudge) { + // Neither way fits; abort + throw new NotFoundException(); + } + top -= nudgedTooFarDown; + } + // Now just read off the bits + const bits = new BitMatrix(matrixWidth, matrixHeight); + for (let y = 0; y < matrixHeight; y++) { + const iOffset = top + /*(int) */ Math.floor(y * moduleSize); + for (let x = 0; x < matrixWidth; x++) { + if (image.get(left + /*(int) */ Math.floor(x * moduleSize), iOffset)) { + bits.set(x, y); + } + } + } + return bits; + } + static moduleSize(leftTopBlack, image) { + const height = image.getHeight(); + const width = image.getWidth(); + let x = leftTopBlack[0]; + let y = leftTopBlack[1]; + let inBlack = true; + let transitions = 0; + while (x < width && y < height) { + if (inBlack !== image.get(x, y)) { + if (++transitions === 5) { + break; + } + inBlack = !inBlack; + } + x++; + y++; + } + if (x === width || y === height) { + throw new NotFoundException(); + } + return (x - leftTopBlack[0]) / 7.0; + } + } + QRCodeReader.NO_POINTS = new Array(); + + /* + * Copyright 2009 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. + */ + /** + * @author SITA Lab (kevin.osullivan@sita.aero) + * @author Guenther Grau + */ + /*public final*/ class PDF417Common { + PDF417Common() { + } + /** + * @param moduleBitCount values to sum + * @return sum of values + * @deprecated call {@link MathUtils#sum(int[])} + */ + // @Deprecated + static getBitCountSum(moduleBitCount) { + return MathUtils.sum(moduleBitCount); + } + static toIntArray(list) { + if (list == null || !list.length) { + return PDF417Common.EMPTY_INT_ARRAY; + } + const result = new Int32Array(list.length); + let i = 0; + for (const integer of list) { + result[i++] = integer; + } + return result; + } + /** + * @param symbol encoded symbol to translate to a codeword + * @return the codeword corresponding to the symbol. + */ + static getCodeword(symbol /*int*/) { + const i = Arrays.binarySearch(PDF417Common.SYMBOL_TABLE, symbol & 0x3FFFF); + if (i < 0) { + return -1; + } + return (PDF417Common.CODEWORD_TABLE[i] - 1) % PDF417Common.NUMBER_OF_CODEWORDS; + } + } + PDF417Common.NUMBER_OF_CODEWORDS = 929; + // Maximum Codewords (Data + Error). + PDF417Common.MAX_CODEWORDS_IN_BARCODE = PDF417Common.NUMBER_OF_CODEWORDS - 1; + PDF417Common.MIN_ROWS_IN_BARCODE = 3; + PDF417Common.MAX_ROWS_IN_BARCODE = 90; + // One left row indication column + max 30 data columns + one right row indicator column + // public static /*final*/ MAX_CODEWORDS_IN_ROW: /*int*/ number = 32; + PDF417Common.MODULES_IN_CODEWORD = 17; + PDF417Common.MODULES_IN_STOP_PATTERN = 18; + PDF417Common.BARS_IN_MODULE = 8; + PDF417Common.EMPTY_INT_ARRAY = new Int32Array([]); + /** + * The sorted table of all possible symbols. Extracted from the PDF417 + * specification. The index of a symbol in this table corresponds to the + * index into the codeword table. + */ + PDF417Common.SYMBOL_TABLE = Int32Array.from([ + 0x1025e, 0x1027a, 0x1029e, 0x102bc, 0x102f2, 0x102f4, 0x1032e, 0x1034e, 0x1035c, 0x10396, 0x103a6, 0x103ac, + 0x10422, 0x10428, 0x10436, 0x10442, 0x10444, 0x10448, 0x10450, 0x1045e, 0x10466, 0x1046c, 0x1047a, 0x10482, + 0x1049e, 0x104a0, 0x104bc, 0x104c6, 0x104d8, 0x104ee, 0x104f2, 0x104f4, 0x10504, 0x10508, 0x10510, 0x1051e, + 0x10520, 0x1053c, 0x10540, 0x10578, 0x10586, 0x1058c, 0x10598, 0x105b0, 0x105be, 0x105ce, 0x105dc, 0x105e2, + 0x105e4, 0x105e8, 0x105f6, 0x1062e, 0x1064e, 0x1065c, 0x1068e, 0x1069c, 0x106b8, 0x106de, 0x106fa, 0x10716, + 0x10726, 0x1072c, 0x10746, 0x1074c, 0x10758, 0x1076e, 0x10792, 0x10794, 0x107a2, 0x107a4, 0x107a8, 0x107b6, + 0x10822, 0x10828, 0x10842, 0x10848, 0x10850, 0x1085e, 0x10866, 0x1086c, 0x1087a, 0x10882, 0x10884, 0x10890, + 0x1089e, 0x108a0, 0x108bc, 0x108c6, 0x108cc, 0x108d8, 0x108ee, 0x108f2, 0x108f4, 0x10902, 0x10908, 0x1091e, + 0x10920, 0x1093c, 0x10940, 0x10978, 0x10986, 0x10998, 0x109b0, 0x109be, 0x109ce, 0x109dc, 0x109e2, 0x109e4, + 0x109e8, 0x109f6, 0x10a08, 0x10a10, 0x10a1e, 0x10a20, 0x10a3c, 0x10a40, 0x10a78, 0x10af0, 0x10b06, 0x10b0c, + 0x10b18, 0x10b30, 0x10b3e, 0x10b60, 0x10b7c, 0x10b8e, 0x10b9c, 0x10bb8, 0x10bc2, 0x10bc4, 0x10bc8, 0x10bd0, + 0x10bde, 0x10be6, 0x10bec, 0x10c2e, 0x10c4e, 0x10c5c, 0x10c62, 0x10c64, 0x10c68, 0x10c76, 0x10c8e, 0x10c9c, + 0x10cb8, 0x10cc2, 0x10cc4, 0x10cc8, 0x10cd0, 0x10cde, 0x10ce6, 0x10cec, 0x10cfa, 0x10d0e, 0x10d1c, 0x10d38, + 0x10d70, 0x10d7e, 0x10d82, 0x10d84, 0x10d88, 0x10d90, 0x10d9e, 0x10da0, 0x10dbc, 0x10dc6, 0x10dcc, 0x10dd8, + 0x10dee, 0x10df2, 0x10df4, 0x10e16, 0x10e26, 0x10e2c, 0x10e46, 0x10e58, 0x10e6e, 0x10e86, 0x10e8c, 0x10e98, + 0x10eb0, 0x10ebe, 0x10ece, 0x10edc, 0x10f0a, 0x10f12, 0x10f14, 0x10f22, 0x10f28, 0x10f36, 0x10f42, 0x10f44, + 0x10f48, 0x10f50, 0x10f5e, 0x10f66, 0x10f6c, 0x10fb2, 0x10fb4, 0x11022, 0x11028, 0x11042, 0x11048, 0x11050, + 0x1105e, 0x1107a, 0x11082, 0x11084, 0x11090, 0x1109e, 0x110a0, 0x110bc, 0x110c6, 0x110cc, 0x110d8, 0x110ee, + 0x110f2, 0x110f4, 0x11102, 0x1111e, 0x11120, 0x1113c, 0x11140, 0x11178, 0x11186, 0x11198, 0x111b0, 0x111be, + 0x111ce, 0x111dc, 0x111e2, 0x111e4, 0x111e8, 0x111f6, 0x11208, 0x1121e, 0x11220, 0x11278, 0x112f0, 0x1130c, + 0x11330, 0x1133e, 0x11360, 0x1137c, 0x1138e, 0x1139c, 0x113b8, 0x113c2, 0x113c8, 0x113d0, 0x113de, 0x113e6, + 0x113ec, 0x11408, 0x11410, 0x1141e, 0x11420, 0x1143c, 0x11440, 0x11478, 0x114f0, 0x115e0, 0x1160c, 0x11618, + 0x11630, 0x1163e, 0x11660, 0x1167c, 0x116c0, 0x116f8, 0x1171c, 0x11738, 0x11770, 0x1177e, 0x11782, 0x11784, + 0x11788, 0x11790, 0x1179e, 0x117a0, 0x117bc, 0x117c6, 0x117cc, 0x117d8, 0x117ee, 0x1182e, 0x11834, 0x1184e, + 0x1185c, 0x11862, 0x11864, 0x11868, 0x11876, 0x1188e, 0x1189c, 0x118b8, 0x118c2, 0x118c8, 0x118d0, 0x118de, + 0x118e6, 0x118ec, 0x118fa, 0x1190e, 0x1191c, 0x11938, 0x11970, 0x1197e, 0x11982, 0x11984, 0x11990, 0x1199e, + 0x119a0, 0x119bc, 0x119c6, 0x119cc, 0x119d8, 0x119ee, 0x119f2, 0x119f4, 0x11a0e, 0x11a1c, 0x11a38, 0x11a70, + 0x11a7e, 0x11ae0, 0x11afc, 0x11b08, 0x11b10, 0x11b1e, 0x11b20, 0x11b3c, 0x11b40, 0x11b78, 0x11b8c, 0x11b98, + 0x11bb0, 0x11bbe, 0x11bce, 0x11bdc, 0x11be2, 0x11be4, 0x11be8, 0x11bf6, 0x11c16, 0x11c26, 0x11c2c, 0x11c46, + 0x11c4c, 0x11c58, 0x11c6e, 0x11c86, 0x11c98, 0x11cb0, 0x11cbe, 0x11cce, 0x11cdc, 0x11ce2, 0x11ce4, 0x11ce8, + 0x11cf6, 0x11d06, 0x11d0c, 0x11d18, 0x11d30, 0x11d3e, 0x11d60, 0x11d7c, 0x11d8e, 0x11d9c, 0x11db8, 0x11dc4, + 0x11dc8, 0x11dd0, 0x11dde, 0x11de6, 0x11dec, 0x11dfa, 0x11e0a, 0x11e12, 0x11e14, 0x11e22, 0x11e24, 0x11e28, + 0x11e36, 0x11e42, 0x11e44, 0x11e50, 0x11e5e, 0x11e66, 0x11e6c, 0x11e82, 0x11e84, 0x11e88, 0x11e90, 0x11e9e, + 0x11ea0, 0x11ebc, 0x11ec6, 0x11ecc, 0x11ed8, 0x11eee, 0x11f1a, 0x11f2e, 0x11f32, 0x11f34, 0x11f4e, 0x11f5c, + 0x11f62, 0x11f64, 0x11f68, 0x11f76, 0x12048, 0x1205e, 0x12082, 0x12084, 0x12090, 0x1209e, 0x120a0, 0x120bc, + 0x120d8, 0x120f2, 0x120f4, 0x12108, 0x1211e, 0x12120, 0x1213c, 0x12140, 0x12178, 0x12186, 0x12198, 0x121b0, + 0x121be, 0x121e2, 0x121e4, 0x121e8, 0x121f6, 0x12204, 0x12210, 0x1221e, 0x12220, 0x12278, 0x122f0, 0x12306, + 0x1230c, 0x12330, 0x1233e, 0x12360, 0x1237c, 0x1238e, 0x1239c, 0x123b8, 0x123c2, 0x123c8, 0x123d0, 0x123e6, + 0x123ec, 0x1241e, 0x12420, 0x1243c, 0x124f0, 0x125e0, 0x12618, 0x1263e, 0x12660, 0x1267c, 0x126c0, 0x126f8, + 0x12738, 0x12770, 0x1277e, 0x12782, 0x12784, 0x12790, 0x1279e, 0x127a0, 0x127bc, 0x127c6, 0x127cc, 0x127d8, + 0x127ee, 0x12820, 0x1283c, 0x12840, 0x12878, 0x128f0, 0x129e0, 0x12bc0, 0x12c18, 0x12c30, 0x12c3e, 0x12c60, + 0x12c7c, 0x12cc0, 0x12cf8, 0x12df0, 0x12e1c, 0x12e38, 0x12e70, 0x12e7e, 0x12ee0, 0x12efc, 0x12f04, 0x12f08, + 0x12f10, 0x12f20, 0x12f3c, 0x12f40, 0x12f78, 0x12f86, 0x12f8c, 0x12f98, 0x12fb0, 0x12fbe, 0x12fce, 0x12fdc, + 0x1302e, 0x1304e, 0x1305c, 0x13062, 0x13068, 0x1308e, 0x1309c, 0x130b8, 0x130c2, 0x130c8, 0x130d0, 0x130de, + 0x130ec, 0x130fa, 0x1310e, 0x13138, 0x13170, 0x1317e, 0x13182, 0x13184, 0x13190, 0x1319e, 0x131a0, 0x131bc, + 0x131c6, 0x131cc, 0x131d8, 0x131f2, 0x131f4, 0x1320e, 0x1321c, 0x13270, 0x1327e, 0x132e0, 0x132fc, 0x13308, + 0x1331e, 0x13320, 0x1333c, 0x13340, 0x13378, 0x13386, 0x13398, 0x133b0, 0x133be, 0x133ce, 0x133dc, 0x133e2, + 0x133e4, 0x133e8, 0x133f6, 0x1340e, 0x1341c, 0x13438, 0x13470, 0x1347e, 0x134e0, 0x134fc, 0x135c0, 0x135f8, + 0x13608, 0x13610, 0x1361e, 0x13620, 0x1363c, 0x13640, 0x13678, 0x136f0, 0x1370c, 0x13718, 0x13730, 0x1373e, + 0x13760, 0x1377c, 0x1379c, 0x137b8, 0x137c2, 0x137c4, 0x137c8, 0x137d0, 0x137de, 0x137e6, 0x137ec, 0x13816, + 0x13826, 0x1382c, 0x13846, 0x1384c, 0x13858, 0x1386e, 0x13874, 0x13886, 0x13898, 0x138b0, 0x138be, 0x138ce, + 0x138dc, 0x138e2, 0x138e4, 0x138e8, 0x13906, 0x1390c, 0x13930, 0x1393e, 0x13960, 0x1397c, 0x1398e, 0x1399c, + 0x139b8, 0x139c8, 0x139d0, 0x139de, 0x139e6, 0x139ec, 0x139fa, 0x13a06, 0x13a0c, 0x13a18, 0x13a30, 0x13a3e, + 0x13a60, 0x13a7c, 0x13ac0, 0x13af8, 0x13b0e, 0x13b1c, 0x13b38, 0x13b70, 0x13b7e, 0x13b88, 0x13b90, 0x13b9e, + 0x13ba0, 0x13bbc, 0x13bcc, 0x13bd8, 0x13bee, 0x13bf2, 0x13bf4, 0x13c12, 0x13c14, 0x13c22, 0x13c24, 0x13c28, + 0x13c36, 0x13c42, 0x13c48, 0x13c50, 0x13c5e, 0x13c66, 0x13c6c, 0x13c82, 0x13c84, 0x13c90, 0x13c9e, 0x13ca0, + 0x13cbc, 0x13cc6, 0x13ccc, 0x13cd8, 0x13cee, 0x13d02, 0x13d04, 0x13d08, 0x13d10, 0x13d1e, 0x13d20, 0x13d3c, + 0x13d40, 0x13d78, 0x13d86, 0x13d8c, 0x13d98, 0x13db0, 0x13dbe, 0x13dce, 0x13ddc, 0x13de4, 0x13de8, 0x13df6, + 0x13e1a, 0x13e2e, 0x13e32, 0x13e34, 0x13e4e, 0x13e5c, 0x13e62, 0x13e64, 0x13e68, 0x13e76, 0x13e8e, 0x13e9c, + 0x13eb8, 0x13ec2, 0x13ec4, 0x13ec8, 0x13ed0, 0x13ede, 0x13ee6, 0x13eec, 0x13f26, 0x13f2c, 0x13f3a, 0x13f46, + 0x13f4c, 0x13f58, 0x13f6e, 0x13f72, 0x13f74, 0x14082, 0x1409e, 0x140a0, 0x140bc, 0x14104, 0x14108, 0x14110, + 0x1411e, 0x14120, 0x1413c, 0x14140, 0x14178, 0x1418c, 0x14198, 0x141b0, 0x141be, 0x141e2, 0x141e4, 0x141e8, + 0x14208, 0x14210, 0x1421e, 0x14220, 0x1423c, 0x14240, 0x14278, 0x142f0, 0x14306, 0x1430c, 0x14318, 0x14330, + 0x1433e, 0x14360, 0x1437c, 0x1438e, 0x143c2, 0x143c4, 0x143c8, 0x143d0, 0x143e6, 0x143ec, 0x14408, 0x14410, + 0x1441e, 0x14420, 0x1443c, 0x14440, 0x14478, 0x144f0, 0x145e0, 0x1460c, 0x14618, 0x14630, 0x1463e, 0x14660, + 0x1467c, 0x146c0, 0x146f8, 0x1471c, 0x14738, 0x14770, 0x1477e, 0x14782, 0x14784, 0x14788, 0x14790, 0x147a0, + 0x147bc, 0x147c6, 0x147cc, 0x147d8, 0x147ee, 0x14810, 0x14820, 0x1483c, 0x14840, 0x14878, 0x148f0, 0x149e0, + 0x14bc0, 0x14c30, 0x14c3e, 0x14c60, 0x14c7c, 0x14cc0, 0x14cf8, 0x14df0, 0x14e38, 0x14e70, 0x14e7e, 0x14ee0, + 0x14efc, 0x14f04, 0x14f08, 0x14f10, 0x14f1e, 0x14f20, 0x14f3c, 0x14f40, 0x14f78, 0x14f86, 0x14f8c, 0x14f98, + 0x14fb0, 0x14fce, 0x14fdc, 0x15020, 0x15040, 0x15078, 0x150f0, 0x151e0, 0x153c0, 0x15860, 0x1587c, 0x158c0, + 0x158f8, 0x159f0, 0x15be0, 0x15c70, 0x15c7e, 0x15ce0, 0x15cfc, 0x15dc0, 0x15df8, 0x15e08, 0x15e10, 0x15e20, + 0x15e40, 0x15e78, 0x15ef0, 0x15f0c, 0x15f18, 0x15f30, 0x15f60, 0x15f7c, 0x15f8e, 0x15f9c, 0x15fb8, 0x1604e, + 0x1605c, 0x1608e, 0x1609c, 0x160b8, 0x160c2, 0x160c4, 0x160c8, 0x160de, 0x1610e, 0x1611c, 0x16138, 0x16170, + 0x1617e, 0x16184, 0x16188, 0x16190, 0x1619e, 0x161a0, 0x161bc, 0x161c6, 0x161cc, 0x161d8, 0x161f2, 0x161f4, + 0x1620e, 0x1621c, 0x16238, 0x16270, 0x1627e, 0x162e0, 0x162fc, 0x16304, 0x16308, 0x16310, 0x1631e, 0x16320, + 0x1633c, 0x16340, 0x16378, 0x16386, 0x1638c, 0x16398, 0x163b0, 0x163be, 0x163ce, 0x163dc, 0x163e2, 0x163e4, + 0x163e8, 0x163f6, 0x1640e, 0x1641c, 0x16438, 0x16470, 0x1647e, 0x164e0, 0x164fc, 0x165c0, 0x165f8, 0x16610, + 0x1661e, 0x16620, 0x1663c, 0x16640, 0x16678, 0x166f0, 0x16718, 0x16730, 0x1673e, 0x16760, 0x1677c, 0x1678e, + 0x1679c, 0x167b8, 0x167c2, 0x167c4, 0x167c8, 0x167d0, 0x167de, 0x167e6, 0x167ec, 0x1681c, 0x16838, 0x16870, + 0x168e0, 0x168fc, 0x169c0, 0x169f8, 0x16bf0, 0x16c10, 0x16c1e, 0x16c20, 0x16c3c, 0x16c40, 0x16c78, 0x16cf0, + 0x16de0, 0x16e18, 0x16e30, 0x16e3e, 0x16e60, 0x16e7c, 0x16ec0, 0x16ef8, 0x16f1c, 0x16f38, 0x16f70, 0x16f7e, + 0x16f84, 0x16f88, 0x16f90, 0x16f9e, 0x16fa0, 0x16fbc, 0x16fc6, 0x16fcc, 0x16fd8, 0x17026, 0x1702c, 0x17046, + 0x1704c, 0x17058, 0x1706e, 0x17086, 0x1708c, 0x17098, 0x170b0, 0x170be, 0x170ce, 0x170dc, 0x170e8, 0x17106, + 0x1710c, 0x17118, 0x17130, 0x1713e, 0x17160, 0x1717c, 0x1718e, 0x1719c, 0x171b8, 0x171c2, 0x171c4, 0x171c8, + 0x171d0, 0x171de, 0x171e6, 0x171ec, 0x171fa, 0x17206, 0x1720c, 0x17218, 0x17230, 0x1723e, 0x17260, 0x1727c, + 0x172c0, 0x172f8, 0x1730e, 0x1731c, 0x17338, 0x17370, 0x1737e, 0x17388, 0x17390, 0x1739e, 0x173a0, 0x173bc, + 0x173cc, 0x173d8, 0x173ee, 0x173f2, 0x173f4, 0x1740c, 0x17418, 0x17430, 0x1743e, 0x17460, 0x1747c, 0x174c0, + 0x174f8, 0x175f0, 0x1760e, 0x1761c, 0x17638, 0x17670, 0x1767e, 0x176e0, 0x176fc, 0x17708, 0x17710, 0x1771e, + 0x17720, 0x1773c, 0x17740, 0x17778, 0x17798, 0x177b0, 0x177be, 0x177dc, 0x177e2, 0x177e4, 0x177e8, 0x17822, + 0x17824, 0x17828, 0x17836, 0x17842, 0x17844, 0x17848, 0x17850, 0x1785e, 0x17866, 0x1786c, 0x17882, 0x17884, + 0x17888, 0x17890, 0x1789e, 0x178a0, 0x178bc, 0x178c6, 0x178cc, 0x178d8, 0x178ee, 0x178f2, 0x178f4, 0x17902, + 0x17904, 0x17908, 0x17910, 0x1791e, 0x17920, 0x1793c, 0x17940, 0x17978, 0x17986, 0x1798c, 0x17998, 0x179b0, + 0x179be, 0x179ce, 0x179dc, 0x179e2, 0x179e4, 0x179e8, 0x179f6, 0x17a04, 0x17a08, 0x17a10, 0x17a1e, 0x17a20, + 0x17a3c, 0x17a40, 0x17a78, 0x17af0, 0x17b06, 0x17b0c, 0x17b18, 0x17b30, 0x17b3e, 0x17b60, 0x17b7c, 0x17b8e, + 0x17b9c, 0x17bb8, 0x17bc4, 0x17bc8, 0x17bd0, 0x17bde, 0x17be6, 0x17bec, 0x17c2e, 0x17c32, 0x17c34, 0x17c4e, + 0x17c5c, 0x17c62, 0x17c64, 0x17c68, 0x17c76, 0x17c8e, 0x17c9c, 0x17cb8, 0x17cc2, 0x17cc4, 0x17cc8, 0x17cd0, + 0x17cde, 0x17ce6, 0x17cec, 0x17d0e, 0x17d1c, 0x17d38, 0x17d70, 0x17d82, 0x17d84, 0x17d88, 0x17d90, 0x17d9e, + 0x17da0, 0x17dbc, 0x17dc6, 0x17dcc, 0x17dd8, 0x17dee, 0x17e26, 0x17e2c, 0x17e3a, 0x17e46, 0x17e4c, 0x17e58, + 0x17e6e, 0x17e72, 0x17e74, 0x17e86, 0x17e8c, 0x17e98, 0x17eb0, 0x17ece, 0x17edc, 0x17ee2, 0x17ee4, 0x17ee8, + 0x17ef6, 0x1813a, 0x18172, 0x18174, 0x18216, 0x18226, 0x1823a, 0x1824c, 0x18258, 0x1826e, 0x18272, 0x18274, + 0x18298, 0x182be, 0x182e2, 0x182e4, 0x182e8, 0x182f6, 0x1835e, 0x1837a, 0x183ae, 0x183d6, 0x18416, 0x18426, + 0x1842c, 0x1843a, 0x18446, 0x18458, 0x1846e, 0x18472, 0x18474, 0x18486, 0x184b0, 0x184be, 0x184ce, 0x184dc, + 0x184e2, 0x184e4, 0x184e8, 0x184f6, 0x18506, 0x1850c, 0x18518, 0x18530, 0x1853e, 0x18560, 0x1857c, 0x1858e, + 0x1859c, 0x185b8, 0x185c2, 0x185c4, 0x185c8, 0x185d0, 0x185de, 0x185e6, 0x185ec, 0x185fa, 0x18612, 0x18614, + 0x18622, 0x18628, 0x18636, 0x18642, 0x18650, 0x1865e, 0x1867a, 0x18682, 0x18684, 0x18688, 0x18690, 0x1869e, + 0x186a0, 0x186bc, 0x186c6, 0x186cc, 0x186d8, 0x186ee, 0x186f2, 0x186f4, 0x1872e, 0x1874e, 0x1875c, 0x18796, + 0x187a6, 0x187ac, 0x187d2, 0x187d4, 0x18826, 0x1882c, 0x1883a, 0x18846, 0x1884c, 0x18858, 0x1886e, 0x18872, + 0x18874, 0x18886, 0x18898, 0x188b0, 0x188be, 0x188ce, 0x188dc, 0x188e2, 0x188e4, 0x188e8, 0x188f6, 0x1890c, + 0x18930, 0x1893e, 0x18960, 0x1897c, 0x1898e, 0x189b8, 0x189c2, 0x189c8, 0x189d0, 0x189de, 0x189e6, 0x189ec, + 0x189fa, 0x18a18, 0x18a30, 0x18a3e, 0x18a60, 0x18a7c, 0x18ac0, 0x18af8, 0x18b1c, 0x18b38, 0x18b70, 0x18b7e, + 0x18b82, 0x18b84, 0x18b88, 0x18b90, 0x18b9e, 0x18ba0, 0x18bbc, 0x18bc6, 0x18bcc, 0x18bd8, 0x18bee, 0x18bf2, + 0x18bf4, 0x18c22, 0x18c24, 0x18c28, 0x18c36, 0x18c42, 0x18c48, 0x18c50, 0x18c5e, 0x18c66, 0x18c7a, 0x18c82, + 0x18c84, 0x18c90, 0x18c9e, 0x18ca0, 0x18cbc, 0x18ccc, 0x18cf2, 0x18cf4, 0x18d04, 0x18d08, 0x18d10, 0x18d1e, + 0x18d20, 0x18d3c, 0x18d40, 0x18d78, 0x18d86, 0x18d98, 0x18dce, 0x18de2, 0x18de4, 0x18de8, 0x18e2e, 0x18e32, + 0x18e34, 0x18e4e, 0x18e5c, 0x18e62, 0x18e64, 0x18e68, 0x18e8e, 0x18e9c, 0x18eb8, 0x18ec2, 0x18ec4, 0x18ec8, + 0x18ed0, 0x18efa, 0x18f16, 0x18f26, 0x18f2c, 0x18f46, 0x18f4c, 0x18f58, 0x18f6e, 0x18f8a, 0x18f92, 0x18f94, + 0x18fa2, 0x18fa4, 0x18fa8, 0x18fb6, 0x1902c, 0x1903a, 0x19046, 0x1904c, 0x19058, 0x19072, 0x19074, 0x19086, + 0x19098, 0x190b0, 0x190be, 0x190ce, 0x190dc, 0x190e2, 0x190e8, 0x190f6, 0x19106, 0x1910c, 0x19130, 0x1913e, + 0x19160, 0x1917c, 0x1918e, 0x1919c, 0x191b8, 0x191c2, 0x191c8, 0x191d0, 0x191de, 0x191e6, 0x191ec, 0x191fa, + 0x19218, 0x1923e, 0x19260, 0x1927c, 0x192c0, 0x192f8, 0x19338, 0x19370, 0x1937e, 0x19382, 0x19384, 0x19390, + 0x1939e, 0x193a0, 0x193bc, 0x193c6, 0x193cc, 0x193d8, 0x193ee, 0x193f2, 0x193f4, 0x19430, 0x1943e, 0x19460, + 0x1947c, 0x194c0, 0x194f8, 0x195f0, 0x19638, 0x19670, 0x1967e, 0x196e0, 0x196fc, 0x19702, 0x19704, 0x19708, + 0x19710, 0x19720, 0x1973c, 0x19740, 0x19778, 0x19786, 0x1978c, 0x19798, 0x197b0, 0x197be, 0x197ce, 0x197dc, + 0x197e2, 0x197e4, 0x197e8, 0x19822, 0x19824, 0x19842, 0x19848, 0x19850, 0x1985e, 0x19866, 0x1987a, 0x19882, + 0x19884, 0x19890, 0x1989e, 0x198a0, 0x198bc, 0x198cc, 0x198f2, 0x198f4, 0x19902, 0x19908, 0x1991e, 0x19920, + 0x1993c, 0x19940, 0x19978, 0x19986, 0x19998, 0x199ce, 0x199e2, 0x199e4, 0x199e8, 0x19a08, 0x19a10, 0x19a1e, + 0x19a20, 0x19a3c, 0x19a40, 0x19a78, 0x19af0, 0x19b18, 0x19b3e, 0x19b60, 0x19b9c, 0x19bc2, 0x19bc4, 0x19bc8, + 0x19bd0, 0x19be6, 0x19c2e, 0x19c34, 0x19c4e, 0x19c5c, 0x19c62, 0x19c64, 0x19c68, 0x19c8e, 0x19c9c, 0x19cb8, + 0x19cc2, 0x19cc8, 0x19cd0, 0x19ce6, 0x19cfa, 0x19d0e, 0x19d1c, 0x19d38, 0x19d70, 0x19d7e, 0x19d82, 0x19d84, + 0x19d88, 0x19d90, 0x19da0, 0x19dcc, 0x19df2, 0x19df4, 0x19e16, 0x19e26, 0x19e2c, 0x19e46, 0x19e4c, 0x19e58, + 0x19e74, 0x19e86, 0x19e8c, 0x19e98, 0x19eb0, 0x19ebe, 0x19ece, 0x19ee2, 0x19ee4, 0x19ee8, 0x19f0a, 0x19f12, + 0x19f14, 0x19f22, 0x19f24, 0x19f28, 0x19f42, 0x19f44, 0x19f48, 0x19f50, 0x19f5e, 0x19f6c, 0x19f9a, 0x19fae, + 0x19fb2, 0x19fb4, 0x1a046, 0x1a04c, 0x1a072, 0x1a074, 0x1a086, 0x1a08c, 0x1a098, 0x1a0b0, 0x1a0be, 0x1a0e2, + 0x1a0e4, 0x1a0e8, 0x1a0f6, 0x1a106, 0x1a10c, 0x1a118, 0x1a130, 0x1a13e, 0x1a160, 0x1a17c, 0x1a18e, 0x1a19c, + 0x1a1b8, 0x1a1c2, 0x1a1c4, 0x1a1c8, 0x1a1d0, 0x1a1de, 0x1a1e6, 0x1a1ec, 0x1a218, 0x1a230, 0x1a23e, 0x1a260, + 0x1a27c, 0x1a2c0, 0x1a2f8, 0x1a31c, 0x1a338, 0x1a370, 0x1a37e, 0x1a382, 0x1a384, 0x1a388, 0x1a390, 0x1a39e, + 0x1a3a0, 0x1a3bc, 0x1a3c6, 0x1a3cc, 0x1a3d8, 0x1a3ee, 0x1a3f2, 0x1a3f4, 0x1a418, 0x1a430, 0x1a43e, 0x1a460, + 0x1a47c, 0x1a4c0, 0x1a4f8, 0x1a5f0, 0x1a61c, 0x1a638, 0x1a670, 0x1a67e, 0x1a6e0, 0x1a6fc, 0x1a702, 0x1a704, + 0x1a708, 0x1a710, 0x1a71e, 0x1a720, 0x1a73c, 0x1a740, 0x1a778, 0x1a786, 0x1a78c, 0x1a798, 0x1a7b0, 0x1a7be, + 0x1a7ce, 0x1a7dc, 0x1a7e2, 0x1a7e4, 0x1a7e8, 0x1a830, 0x1a860, 0x1a87c, 0x1a8c0, 0x1a8f8, 0x1a9f0, 0x1abe0, + 0x1ac70, 0x1ac7e, 0x1ace0, 0x1acfc, 0x1adc0, 0x1adf8, 0x1ae04, 0x1ae08, 0x1ae10, 0x1ae20, 0x1ae3c, 0x1ae40, + 0x1ae78, 0x1aef0, 0x1af06, 0x1af0c, 0x1af18, 0x1af30, 0x1af3e, 0x1af60, 0x1af7c, 0x1af8e, 0x1af9c, 0x1afb8, + 0x1afc4, 0x1afc8, 0x1afd0, 0x1afde, 0x1b042, 0x1b05e, 0x1b07a, 0x1b082, 0x1b084, 0x1b088, 0x1b090, 0x1b09e, + 0x1b0a0, 0x1b0bc, 0x1b0cc, 0x1b0f2, 0x1b0f4, 0x1b102, 0x1b104, 0x1b108, 0x1b110, 0x1b11e, 0x1b120, 0x1b13c, + 0x1b140, 0x1b178, 0x1b186, 0x1b198, 0x1b1ce, 0x1b1e2, 0x1b1e4, 0x1b1e8, 0x1b204, 0x1b208, 0x1b210, 0x1b21e, + 0x1b220, 0x1b23c, 0x1b240, 0x1b278, 0x1b2f0, 0x1b30c, 0x1b33e, 0x1b360, 0x1b39c, 0x1b3c2, 0x1b3c4, 0x1b3c8, + 0x1b3d0, 0x1b3e6, 0x1b410, 0x1b41e, 0x1b420, 0x1b43c, 0x1b440, 0x1b478, 0x1b4f0, 0x1b5e0, 0x1b618, 0x1b660, + 0x1b67c, 0x1b6c0, 0x1b738, 0x1b782, 0x1b784, 0x1b788, 0x1b790, 0x1b79e, 0x1b7a0, 0x1b7cc, 0x1b82e, 0x1b84e, + 0x1b85c, 0x1b88e, 0x1b89c, 0x1b8b8, 0x1b8c2, 0x1b8c4, 0x1b8c8, 0x1b8d0, 0x1b8e6, 0x1b8fa, 0x1b90e, 0x1b91c, + 0x1b938, 0x1b970, 0x1b97e, 0x1b982, 0x1b984, 0x1b988, 0x1b990, 0x1b99e, 0x1b9a0, 0x1b9cc, 0x1b9f2, 0x1b9f4, + 0x1ba0e, 0x1ba1c, 0x1ba38, 0x1ba70, 0x1ba7e, 0x1bae0, 0x1bafc, 0x1bb08, 0x1bb10, 0x1bb20, 0x1bb3c, 0x1bb40, + 0x1bb98, 0x1bbce, 0x1bbe2, 0x1bbe4, 0x1bbe8, 0x1bc16, 0x1bc26, 0x1bc2c, 0x1bc46, 0x1bc4c, 0x1bc58, 0x1bc72, + 0x1bc74, 0x1bc86, 0x1bc8c, 0x1bc98, 0x1bcb0, 0x1bcbe, 0x1bcce, 0x1bce2, 0x1bce4, 0x1bce8, 0x1bd06, 0x1bd0c, + 0x1bd18, 0x1bd30, 0x1bd3e, 0x1bd60, 0x1bd7c, 0x1bd9c, 0x1bdc2, 0x1bdc4, 0x1bdc8, 0x1bdd0, 0x1bde6, 0x1bdfa, + 0x1be12, 0x1be14, 0x1be22, 0x1be24, 0x1be28, 0x1be42, 0x1be44, 0x1be48, 0x1be50, 0x1be5e, 0x1be66, 0x1be82, + 0x1be84, 0x1be88, 0x1be90, 0x1be9e, 0x1bea0, 0x1bebc, 0x1becc, 0x1bef4, 0x1bf1a, 0x1bf2e, 0x1bf32, 0x1bf34, + 0x1bf4e, 0x1bf5c, 0x1bf62, 0x1bf64, 0x1bf68, 0x1c09a, 0x1c0b2, 0x1c0b4, 0x1c11a, 0x1c132, 0x1c134, 0x1c162, + 0x1c164, 0x1c168, 0x1c176, 0x1c1ba, 0x1c21a, 0x1c232, 0x1c234, 0x1c24e, 0x1c25c, 0x1c262, 0x1c264, 0x1c268, + 0x1c276, 0x1c28e, 0x1c2c2, 0x1c2c4, 0x1c2c8, 0x1c2d0, 0x1c2de, 0x1c2e6, 0x1c2ec, 0x1c2fa, 0x1c316, 0x1c326, + 0x1c33a, 0x1c346, 0x1c34c, 0x1c372, 0x1c374, 0x1c41a, 0x1c42e, 0x1c432, 0x1c434, 0x1c44e, 0x1c45c, 0x1c462, + 0x1c464, 0x1c468, 0x1c476, 0x1c48e, 0x1c49c, 0x1c4b8, 0x1c4c2, 0x1c4c8, 0x1c4d0, 0x1c4de, 0x1c4e6, 0x1c4ec, + 0x1c4fa, 0x1c51c, 0x1c538, 0x1c570, 0x1c57e, 0x1c582, 0x1c584, 0x1c588, 0x1c590, 0x1c59e, 0x1c5a0, 0x1c5bc, + 0x1c5c6, 0x1c5cc, 0x1c5d8, 0x1c5ee, 0x1c5f2, 0x1c5f4, 0x1c616, 0x1c626, 0x1c62c, 0x1c63a, 0x1c646, 0x1c64c, + 0x1c658, 0x1c66e, 0x1c672, 0x1c674, 0x1c686, 0x1c68c, 0x1c698, 0x1c6b0, 0x1c6be, 0x1c6ce, 0x1c6dc, 0x1c6e2, + 0x1c6e4, 0x1c6e8, 0x1c712, 0x1c714, 0x1c722, 0x1c728, 0x1c736, 0x1c742, 0x1c744, 0x1c748, 0x1c750, 0x1c75e, + 0x1c766, 0x1c76c, 0x1c77a, 0x1c7ae, 0x1c7d6, 0x1c7ea, 0x1c81a, 0x1c82e, 0x1c832, 0x1c834, 0x1c84e, 0x1c85c, + 0x1c862, 0x1c864, 0x1c868, 0x1c876, 0x1c88e, 0x1c89c, 0x1c8b8, 0x1c8c2, 0x1c8c8, 0x1c8d0, 0x1c8de, 0x1c8e6, + 0x1c8ec, 0x1c8fa, 0x1c90e, 0x1c938, 0x1c970, 0x1c97e, 0x1c982, 0x1c984, 0x1c990, 0x1c99e, 0x1c9a0, 0x1c9bc, + 0x1c9c6, 0x1c9cc, 0x1c9d8, 0x1c9ee, 0x1c9f2, 0x1c9f4, 0x1ca38, 0x1ca70, 0x1ca7e, 0x1cae0, 0x1cafc, 0x1cb02, + 0x1cb04, 0x1cb08, 0x1cb10, 0x1cb20, 0x1cb3c, 0x1cb40, 0x1cb78, 0x1cb86, 0x1cb8c, 0x1cb98, 0x1cbb0, 0x1cbbe, + 0x1cbce, 0x1cbdc, 0x1cbe2, 0x1cbe4, 0x1cbe8, 0x1cbf6, 0x1cc16, 0x1cc26, 0x1cc2c, 0x1cc3a, 0x1cc46, 0x1cc58, + 0x1cc72, 0x1cc74, 0x1cc86, 0x1ccb0, 0x1ccbe, 0x1ccce, 0x1cce2, 0x1cce4, 0x1cce8, 0x1cd06, 0x1cd0c, 0x1cd18, + 0x1cd30, 0x1cd3e, 0x1cd60, 0x1cd7c, 0x1cd9c, 0x1cdc2, 0x1cdc4, 0x1cdc8, 0x1cdd0, 0x1cdde, 0x1cde6, 0x1cdfa, + 0x1ce22, 0x1ce28, 0x1ce42, 0x1ce50, 0x1ce5e, 0x1ce66, 0x1ce7a, 0x1ce82, 0x1ce84, 0x1ce88, 0x1ce90, 0x1ce9e, + 0x1cea0, 0x1cebc, 0x1cecc, 0x1cef2, 0x1cef4, 0x1cf2e, 0x1cf32, 0x1cf34, 0x1cf4e, 0x1cf5c, 0x1cf62, 0x1cf64, + 0x1cf68, 0x1cf96, 0x1cfa6, 0x1cfac, 0x1cfca, 0x1cfd2, 0x1cfd4, 0x1d02e, 0x1d032, 0x1d034, 0x1d04e, 0x1d05c, + 0x1d062, 0x1d064, 0x1d068, 0x1d076, 0x1d08e, 0x1d09c, 0x1d0b8, 0x1d0c2, 0x1d0c4, 0x1d0c8, 0x1d0d0, 0x1d0de, + 0x1d0e6, 0x1d0ec, 0x1d0fa, 0x1d11c, 0x1d138, 0x1d170, 0x1d17e, 0x1d182, 0x1d184, 0x1d188, 0x1d190, 0x1d19e, + 0x1d1a0, 0x1d1bc, 0x1d1c6, 0x1d1cc, 0x1d1d8, 0x1d1ee, 0x1d1f2, 0x1d1f4, 0x1d21c, 0x1d238, 0x1d270, 0x1d27e, + 0x1d2e0, 0x1d2fc, 0x1d302, 0x1d304, 0x1d308, 0x1d310, 0x1d31e, 0x1d320, 0x1d33c, 0x1d340, 0x1d378, 0x1d386, + 0x1d38c, 0x1d398, 0x1d3b0, 0x1d3be, 0x1d3ce, 0x1d3dc, 0x1d3e2, 0x1d3e4, 0x1d3e8, 0x1d3f6, 0x1d470, 0x1d47e, + 0x1d4e0, 0x1d4fc, 0x1d5c0, 0x1d5f8, 0x1d604, 0x1d608, 0x1d610, 0x1d620, 0x1d640, 0x1d678, 0x1d6f0, 0x1d706, + 0x1d70c, 0x1d718, 0x1d730, 0x1d73e, 0x1d760, 0x1d77c, 0x1d78e, 0x1d79c, 0x1d7b8, 0x1d7c2, 0x1d7c4, 0x1d7c8, + 0x1d7d0, 0x1d7de, 0x1d7e6, 0x1d7ec, 0x1d826, 0x1d82c, 0x1d83a, 0x1d846, 0x1d84c, 0x1d858, 0x1d872, 0x1d874, + 0x1d886, 0x1d88c, 0x1d898, 0x1d8b0, 0x1d8be, 0x1d8ce, 0x1d8e2, 0x1d8e4, 0x1d8e8, 0x1d8f6, 0x1d90c, 0x1d918, + 0x1d930, 0x1d93e, 0x1d960, 0x1d97c, 0x1d99c, 0x1d9c2, 0x1d9c4, 0x1d9c8, 0x1d9d0, 0x1d9e6, 0x1d9fa, 0x1da0c, + 0x1da18, 0x1da30, 0x1da3e, 0x1da60, 0x1da7c, 0x1dac0, 0x1daf8, 0x1db38, 0x1db82, 0x1db84, 0x1db88, 0x1db90, + 0x1db9e, 0x1dba0, 0x1dbcc, 0x1dbf2, 0x1dbf4, 0x1dc22, 0x1dc42, 0x1dc44, 0x1dc48, 0x1dc50, 0x1dc5e, 0x1dc66, + 0x1dc7a, 0x1dc82, 0x1dc84, 0x1dc88, 0x1dc90, 0x1dc9e, 0x1dca0, 0x1dcbc, 0x1dccc, 0x1dcf2, 0x1dcf4, 0x1dd04, + 0x1dd08, 0x1dd10, 0x1dd1e, 0x1dd20, 0x1dd3c, 0x1dd40, 0x1dd78, 0x1dd86, 0x1dd98, 0x1ddce, 0x1dde2, 0x1dde4, + 0x1dde8, 0x1de2e, 0x1de32, 0x1de34, 0x1de4e, 0x1de5c, 0x1de62, 0x1de64, 0x1de68, 0x1de8e, 0x1de9c, 0x1deb8, + 0x1dec2, 0x1dec4, 0x1dec8, 0x1ded0, 0x1dee6, 0x1defa, 0x1df16, 0x1df26, 0x1df2c, 0x1df46, 0x1df4c, 0x1df58, + 0x1df72, 0x1df74, 0x1df8a, 0x1df92, 0x1df94, 0x1dfa2, 0x1dfa4, 0x1dfa8, 0x1e08a, 0x1e092, 0x1e094, 0x1e0a2, + 0x1e0a4, 0x1e0a8, 0x1e0b6, 0x1e0da, 0x1e10a, 0x1e112, 0x1e114, 0x1e122, 0x1e124, 0x1e128, 0x1e136, 0x1e142, + 0x1e144, 0x1e148, 0x1e150, 0x1e166, 0x1e16c, 0x1e17a, 0x1e19a, 0x1e1b2, 0x1e1b4, 0x1e20a, 0x1e212, 0x1e214, + 0x1e222, 0x1e224, 0x1e228, 0x1e236, 0x1e242, 0x1e248, 0x1e250, 0x1e25e, 0x1e266, 0x1e26c, 0x1e27a, 0x1e282, + 0x1e284, 0x1e288, 0x1e290, 0x1e2a0, 0x1e2bc, 0x1e2c6, 0x1e2cc, 0x1e2d8, 0x1e2ee, 0x1e2f2, 0x1e2f4, 0x1e31a, + 0x1e332, 0x1e334, 0x1e35c, 0x1e362, 0x1e364, 0x1e368, 0x1e3ba, 0x1e40a, 0x1e412, 0x1e414, 0x1e422, 0x1e428, + 0x1e436, 0x1e442, 0x1e448, 0x1e450, 0x1e45e, 0x1e466, 0x1e46c, 0x1e47a, 0x1e482, 0x1e484, 0x1e490, 0x1e49e, + 0x1e4a0, 0x1e4bc, 0x1e4c6, 0x1e4cc, 0x1e4d8, 0x1e4ee, 0x1e4f2, 0x1e4f4, 0x1e502, 0x1e504, 0x1e508, 0x1e510, + 0x1e51e, 0x1e520, 0x1e53c, 0x1e540, 0x1e578, 0x1e586, 0x1e58c, 0x1e598, 0x1e5b0, 0x1e5be, 0x1e5ce, 0x1e5dc, + 0x1e5e2, 0x1e5e4, 0x1e5e8, 0x1e5f6, 0x1e61a, 0x1e62e, 0x1e632, 0x1e634, 0x1e64e, 0x1e65c, 0x1e662, 0x1e668, + 0x1e68e, 0x1e69c, 0x1e6b8, 0x1e6c2, 0x1e6c4, 0x1e6c8, 0x1e6d0, 0x1e6e6, 0x1e6fa, 0x1e716, 0x1e726, 0x1e72c, + 0x1e73a, 0x1e746, 0x1e74c, 0x1e758, 0x1e772, 0x1e774, 0x1e792, 0x1e794, 0x1e7a2, 0x1e7a4, 0x1e7a8, 0x1e7b6, + 0x1e812, 0x1e814, 0x1e822, 0x1e824, 0x1e828, 0x1e836, 0x1e842, 0x1e844, 0x1e848, 0x1e850, 0x1e85e, 0x1e866, + 0x1e86c, 0x1e87a, 0x1e882, 0x1e884, 0x1e888, 0x1e890, 0x1e89e, 0x1e8a0, 0x1e8bc, 0x1e8c6, 0x1e8cc, 0x1e8d8, + 0x1e8ee, 0x1e8f2, 0x1e8f4, 0x1e902, 0x1e904, 0x1e908, 0x1e910, 0x1e920, 0x1e93c, 0x1e940, 0x1e978, 0x1e986, + 0x1e98c, 0x1e998, 0x1e9b0, 0x1e9be, 0x1e9ce, 0x1e9dc, 0x1e9e2, 0x1e9e4, 0x1e9e8, 0x1e9f6, 0x1ea04, 0x1ea08, + 0x1ea10, 0x1ea20, 0x1ea40, 0x1ea78, 0x1eaf0, 0x1eb06, 0x1eb0c, 0x1eb18, 0x1eb30, 0x1eb3e, 0x1eb60, 0x1eb7c, + 0x1eb8e, 0x1eb9c, 0x1ebb8, 0x1ebc2, 0x1ebc4, 0x1ebc8, 0x1ebd0, 0x1ebde, 0x1ebe6, 0x1ebec, 0x1ec1a, 0x1ec2e, + 0x1ec32, 0x1ec34, 0x1ec4e, 0x1ec5c, 0x1ec62, 0x1ec64, 0x1ec68, 0x1ec8e, 0x1ec9c, 0x1ecb8, 0x1ecc2, 0x1ecc4, + 0x1ecc8, 0x1ecd0, 0x1ece6, 0x1ecfa, 0x1ed0e, 0x1ed1c, 0x1ed38, 0x1ed70, 0x1ed7e, 0x1ed82, 0x1ed84, 0x1ed88, + 0x1ed90, 0x1ed9e, 0x1eda0, 0x1edcc, 0x1edf2, 0x1edf4, 0x1ee16, 0x1ee26, 0x1ee2c, 0x1ee3a, 0x1ee46, 0x1ee4c, + 0x1ee58, 0x1ee6e, 0x1ee72, 0x1ee74, 0x1ee86, 0x1ee8c, 0x1ee98, 0x1eeb0, 0x1eebe, 0x1eece, 0x1eedc, 0x1eee2, + 0x1eee4, 0x1eee8, 0x1ef12, 0x1ef22, 0x1ef24, 0x1ef28, 0x1ef36, 0x1ef42, 0x1ef44, 0x1ef48, 0x1ef50, 0x1ef5e, + 0x1ef66, 0x1ef6c, 0x1ef7a, 0x1efae, 0x1efb2, 0x1efb4, 0x1efd6, 0x1f096, 0x1f0a6, 0x1f0ac, 0x1f0ba, 0x1f0ca, + 0x1f0d2, 0x1f0d4, 0x1f116, 0x1f126, 0x1f12c, 0x1f13a, 0x1f146, 0x1f14c, 0x1f158, 0x1f16e, 0x1f172, 0x1f174, + 0x1f18a, 0x1f192, 0x1f194, 0x1f1a2, 0x1f1a4, 0x1f1a8, 0x1f1da, 0x1f216, 0x1f226, 0x1f22c, 0x1f23a, 0x1f246, + 0x1f258, 0x1f26e, 0x1f272, 0x1f274, 0x1f286, 0x1f28c, 0x1f298, 0x1f2b0, 0x1f2be, 0x1f2ce, 0x1f2dc, 0x1f2e2, + 0x1f2e4, 0x1f2e8, 0x1f2f6, 0x1f30a, 0x1f312, 0x1f314, 0x1f322, 0x1f328, 0x1f342, 0x1f344, 0x1f348, 0x1f350, + 0x1f35e, 0x1f366, 0x1f37a, 0x1f39a, 0x1f3ae, 0x1f3b2, 0x1f3b4, 0x1f416, 0x1f426, 0x1f42c, 0x1f43a, 0x1f446, + 0x1f44c, 0x1f458, 0x1f46e, 0x1f472, 0x1f474, 0x1f486, 0x1f48c, 0x1f498, 0x1f4b0, 0x1f4be, 0x1f4ce, 0x1f4dc, + 0x1f4e2, 0x1f4e4, 0x1f4e8, 0x1f4f6, 0x1f506, 0x1f50c, 0x1f518, 0x1f530, 0x1f53e, 0x1f560, 0x1f57c, 0x1f58e, + 0x1f59c, 0x1f5b8, 0x1f5c2, 0x1f5c4, 0x1f5c8, 0x1f5d0, 0x1f5de, 0x1f5e6, 0x1f5ec, 0x1f5fa, 0x1f60a, 0x1f612, + 0x1f614, 0x1f622, 0x1f624, 0x1f628, 0x1f636, 0x1f642, 0x1f644, 0x1f648, 0x1f650, 0x1f65e, 0x1f666, 0x1f67a, + 0x1f682, 0x1f684, 0x1f688, 0x1f690, 0x1f69e, 0x1f6a0, 0x1f6bc, 0x1f6cc, 0x1f6f2, 0x1f6f4, 0x1f71a, 0x1f72e, + 0x1f732, 0x1f734, 0x1f74e, 0x1f75c, 0x1f762, 0x1f764, 0x1f768, 0x1f776, 0x1f796, 0x1f7a6, 0x1f7ac, 0x1f7ba, + 0x1f7d2, 0x1f7d4, 0x1f89a, 0x1f8ae, 0x1f8b2, 0x1f8b4, 0x1f8d6, 0x1f8ea, 0x1f91a, 0x1f92e, 0x1f932, 0x1f934, + 0x1f94e, 0x1f95c, 0x1f962, 0x1f964, 0x1f968, 0x1f976, 0x1f996, 0x1f9a6, 0x1f9ac, 0x1f9ba, 0x1f9ca, 0x1f9d2, + 0x1f9d4, 0x1fa1a, 0x1fa2e, 0x1fa32, 0x1fa34, 0x1fa4e, 0x1fa5c, 0x1fa62, 0x1fa64, 0x1fa68, 0x1fa76, 0x1fa8e, + 0x1fa9c, 0x1fab8, 0x1fac2, 0x1fac4, 0x1fac8, 0x1fad0, 0x1fade, 0x1fae6, 0x1faec, 0x1fb16, 0x1fb26, 0x1fb2c, + 0x1fb3a, 0x1fb46, 0x1fb4c, 0x1fb58, 0x1fb6e, 0x1fb72, 0x1fb74, 0x1fb8a, 0x1fb92, 0x1fb94, 0x1fba2, 0x1fba4, + 0x1fba8, 0x1fbb6, 0x1fbda + ]); + /** + * This table contains to codewords for all symbols. + */ + PDF417Common.CODEWORD_TABLE = Int32Array.from([ + 2627, 1819, 2622, 2621, 1813, 1812, 2729, 2724, 2723, 2779, 2774, 2773, 902, 896, 908, 868, 865, 861, 859, 2511, + 873, 871, 1780, 835, 2493, 825, 2491, 842, 837, 844, 1764, 1762, 811, 810, 809, 2483, 807, 2482, 806, 2480, 815, + 814, 813, 812, 2484, 817, 816, 1745, 1744, 1742, 1746, 2655, 2637, 2635, 2626, 2625, 2623, 2628, 1820, 2752, + 2739, 2737, 2728, 2727, 2725, 2730, 2785, 2783, 2778, 2777, 2775, 2780, 787, 781, 747, 739, 736, 2413, 754, 752, + 1719, 692, 689, 681, 2371, 678, 2369, 700, 697, 694, 703, 1688, 1686, 642, 638, 2343, 631, 2341, 627, 2338, 651, + 646, 643, 2345, 654, 652, 1652, 1650, 1647, 1654, 601, 599, 2322, 596, 2321, 594, 2319, 2317, 611, 610, 608, 606, + 2324, 603, 2323, 615, 614, 612, 1617, 1616, 1614, 1612, 616, 1619, 1618, 2575, 2538, 2536, 905, 901, 898, 909, + 2509, 2507, 2504, 870, 867, 864, 860, 2512, 875, 872, 1781, 2490, 2489, 2487, 2485, 1748, 836, 834, 832, 830, + 2494, 827, 2492, 843, 841, 839, 845, 1765, 1763, 2701, 2676, 2674, 2653, 2648, 2656, 2634, 2633, 2631, 2629, + 1821, 2638, 2636, 2770, 2763, 2761, 2750, 2745, 2753, 2736, 2735, 2733, 2731, 1848, 2740, 2738, 2786, 2784, 591, + 588, 576, 569, 566, 2296, 1590, 537, 534, 526, 2276, 522, 2274, 545, 542, 539, 548, 1572, 1570, 481, 2245, 466, + 2242, 462, 2239, 492, 485, 482, 2249, 496, 494, 1534, 1531, 1528, 1538, 413, 2196, 406, 2191, 2188, 425, 419, + 2202, 415, 2199, 432, 430, 427, 1472, 1467, 1464, 433, 1476, 1474, 368, 367, 2160, 365, 2159, 362, 2157, 2155, + 2152, 378, 377, 375, 2166, 372, 2165, 369, 2162, 383, 381, 379, 2168, 1419, 1418, 1416, 1414, 385, 1411, 384, + 1423, 1422, 1420, 1424, 2461, 802, 2441, 2439, 790, 786, 783, 794, 2409, 2406, 2403, 750, 742, 738, 2414, 756, + 753, 1720, 2367, 2365, 2362, 2359, 1663, 693, 691, 684, 2373, 680, 2370, 702, 699, 696, 704, 1690, 1687, 2337, + 2336, 2334, 2332, 1624, 2329, 1622, 640, 637, 2344, 634, 2342, 630, 2340, 650, 648, 645, 2346, 655, 653, 1653, + 1651, 1649, 1655, 2612, 2597, 2595, 2571, 2568, 2565, 2576, 2534, 2529, 2526, 1787, 2540, 2537, 907, 904, 900, + 910, 2503, 2502, 2500, 2498, 1768, 2495, 1767, 2510, 2508, 2506, 869, 866, 863, 2513, 876, 874, 1782, 2720, 2713, + 2711, 2697, 2694, 2691, 2702, 2672, 2670, 2664, 1828, 2678, 2675, 2647, 2646, 2644, 2642, 1823, 2639, 1822, 2654, + 2652, 2650, 2657, 2771, 1855, 2765, 2762, 1850, 1849, 2751, 2749, 2747, 2754, 353, 2148, 344, 342, 336, 2142, + 332, 2140, 345, 1375, 1373, 306, 2130, 299, 2128, 295, 2125, 319, 314, 311, 2132, 1354, 1352, 1349, 1356, 262, + 257, 2101, 253, 2096, 2093, 274, 273, 267, 2107, 263, 2104, 280, 278, 275, 1316, 1311, 1308, 1320, 1318, 2052, + 202, 2050, 2044, 2040, 219, 2063, 212, 2060, 208, 2055, 224, 221, 2066, 1260, 1258, 1252, 231, 1248, 229, 1266, + 1264, 1261, 1268, 155, 1998, 153, 1996, 1994, 1991, 1988, 165, 164, 2007, 162, 2006, 159, 2003, 2000, 172, 171, + 169, 2012, 166, 2010, 1186, 1184, 1182, 1179, 175, 1176, 173, 1192, 1191, 1189, 1187, 176, 1194, 1193, 2313, + 2307, 2305, 592, 589, 2294, 2292, 2289, 578, 572, 568, 2297, 580, 1591, 2272, 2267, 2264, 1547, 538, 536, 529, + 2278, 525, 2275, 547, 544, 541, 1574, 1571, 2237, 2235, 2229, 1493, 2225, 1489, 478, 2247, 470, 2244, 465, 2241, + 493, 488, 484, 2250, 498, 495, 1536, 1533, 1530, 1539, 2187, 2186, 2184, 2182, 1432, 2179, 1430, 2176, 1427, 414, + 412, 2197, 409, 2195, 405, 2193, 2190, 426, 424, 421, 2203, 418, 2201, 431, 429, 1473, 1471, 1469, 1466, 434, + 1477, 1475, 2478, 2472, 2470, 2459, 2457, 2454, 2462, 803, 2437, 2432, 2429, 1726, 2443, 2440, 792, 789, 785, + 2401, 2399, 2393, 1702, 2389, 1699, 2411, 2408, 2405, 745, 741, 2415, 758, 755, 1721, 2358, 2357, 2355, 2353, + 1661, 2350, 1660, 2347, 1657, 2368, 2366, 2364, 2361, 1666, 690, 687, 2374, 683, 2372, 701, 698, 705, 1691, 1689, + 2619, 2617, 2610, 2608, 2605, 2613, 2593, 2588, 2585, 1803, 2599, 2596, 2563, 2561, 2555, 1797, 2551, 1795, 2573, + 2570, 2567, 2577, 2525, 2524, 2522, 2520, 1786, 2517, 1785, 2514, 1783, 2535, 2533, 2531, 2528, 1788, 2541, 2539, + 906, 903, 911, 2721, 1844, 2715, 2712, 1838, 1836, 2699, 2696, 2693, 2703, 1827, 1826, 1824, 2673, 2671, 2669, + 2666, 1829, 2679, 2677, 1858, 1857, 2772, 1854, 1853, 1851, 1856, 2766, 2764, 143, 1987, 139, 1986, 135, 133, + 131, 1984, 128, 1983, 125, 1981, 138, 137, 136, 1985, 1133, 1132, 1130, 112, 110, 1974, 107, 1973, 104, 1971, + 1969, 122, 121, 119, 117, 1977, 114, 1976, 124, 1115, 1114, 1112, 1110, 1117, 1116, 84, 83, 1953, 81, 1952, 78, + 1950, 1948, 1945, 94, 93, 91, 1959, 88, 1958, 85, 1955, 99, 97, 95, 1961, 1086, 1085, 1083, 1081, 1078, 100, + 1090, 1089, 1087, 1091, 49, 47, 1917, 44, 1915, 1913, 1910, 1907, 59, 1926, 56, 1925, 53, 1922, 1919, 66, 64, + 1931, 61, 1929, 1042, 1040, 1038, 71, 1035, 70, 1032, 68, 1048, 1047, 1045, 1043, 1050, 1049, 12, 10, 1869, 1867, + 1864, 1861, 21, 1880, 19, 1877, 1874, 1871, 28, 1888, 25, 1886, 22, 1883, 982, 980, 977, 974, 32, 30, 991, 989, + 987, 984, 34, 995, 994, 992, 2151, 2150, 2147, 2146, 2144, 356, 355, 354, 2149, 2139, 2138, 2136, 2134, 1359, + 343, 341, 338, 2143, 335, 2141, 348, 347, 346, 1376, 1374, 2124, 2123, 2121, 2119, 1326, 2116, 1324, 310, 308, + 305, 2131, 302, 2129, 298, 2127, 320, 318, 316, 313, 2133, 322, 321, 1355, 1353, 1351, 1357, 2092, 2091, 2089, + 2087, 1276, 2084, 1274, 2081, 1271, 259, 2102, 256, 2100, 252, 2098, 2095, 272, 269, 2108, 266, 2106, 281, 279, + 277, 1317, 1315, 1313, 1310, 282, 1321, 1319, 2039, 2037, 2035, 2032, 1203, 2029, 1200, 1197, 207, 2053, 205, + 2051, 201, 2049, 2046, 2043, 220, 218, 2064, 215, 2062, 211, 2059, 228, 226, 223, 2069, 1259, 1257, 1254, 232, + 1251, 230, 1267, 1265, 1263, 2316, 2315, 2312, 2311, 2309, 2314, 2304, 2303, 2301, 2299, 1593, 2308, 2306, 590, + 2288, 2287, 2285, 2283, 1578, 2280, 1577, 2295, 2293, 2291, 579, 577, 574, 571, 2298, 582, 581, 1592, 2263, 2262, + 2260, 2258, 1545, 2255, 1544, 2252, 1541, 2273, 2271, 2269, 2266, 1550, 535, 532, 2279, 528, 2277, 546, 543, 549, + 1575, 1573, 2224, 2222, 2220, 1486, 2217, 1485, 2214, 1482, 1479, 2238, 2236, 2234, 2231, 1496, 2228, 1492, 480, + 477, 2248, 473, 2246, 469, 2243, 490, 487, 2251, 497, 1537, 1535, 1532, 2477, 2476, 2474, 2479, 2469, 2468, 2466, + 2464, 1730, 2473, 2471, 2453, 2452, 2450, 2448, 1729, 2445, 1728, 2460, 2458, 2456, 2463, 805, 804, 2428, 2427, + 2425, 2423, 1725, 2420, 1724, 2417, 1722, 2438, 2436, 2434, 2431, 1727, 2444, 2442, 793, 791, 788, 795, 2388, + 2386, 2384, 1697, 2381, 1696, 2378, 1694, 1692, 2402, 2400, 2398, 2395, 1703, 2392, 1701, 2412, 2410, 2407, 751, + 748, 744, 2416, 759, 757, 1807, 2620, 2618, 1806, 1805, 2611, 2609, 2607, 2614, 1802, 1801, 1799, 2594, 2592, + 2590, 2587, 1804, 2600, 2598, 1794, 1793, 1791, 1789, 2564, 2562, 2560, 2557, 1798, 2554, 1796, 2574, 2572, 2569, + 2578, 1847, 1846, 2722, 1843, 1842, 1840, 1845, 2716, 2714, 1835, 1834, 1832, 1830, 1839, 1837, 2700, 2698, 2695, + 2704, 1817, 1811, 1810, 897, 862, 1777, 829, 826, 838, 1760, 1758, 808, 2481, 1741, 1740, 1738, 1743, 2624, 1818, + 2726, 2776, 782, 740, 737, 1715, 686, 679, 695, 1682, 1680, 639, 628, 2339, 647, 644, 1645, 1643, 1640, 1648, + 602, 600, 597, 595, 2320, 593, 2318, 609, 607, 604, 1611, 1610, 1608, 1606, 613, 1615, 1613, 2328, 926, 924, 892, + 886, 899, 857, 850, 2505, 1778, 824, 823, 821, 819, 2488, 818, 2486, 833, 831, 828, 840, 1761, 1759, 2649, 2632, + 2630, 2746, 2734, 2732, 2782, 2781, 570, 567, 1587, 531, 527, 523, 540, 1566, 1564, 476, 467, 463, 2240, 486, + 483, 1524, 1521, 1518, 1529, 411, 403, 2192, 399, 2189, 423, 416, 1462, 1457, 1454, 428, 1468, 1465, 2210, 366, + 363, 2158, 360, 2156, 357, 2153, 376, 373, 370, 2163, 1410, 1409, 1407, 1405, 382, 1402, 380, 1417, 1415, 1412, + 1421, 2175, 2174, 777, 774, 771, 784, 732, 725, 722, 2404, 743, 1716, 676, 674, 668, 2363, 665, 2360, 685, 1684, + 1681, 626, 624, 622, 2335, 620, 2333, 617, 2330, 641, 635, 649, 1646, 1644, 1642, 2566, 928, 925, 2530, 2527, + 894, 891, 888, 2501, 2499, 2496, 858, 856, 854, 851, 1779, 2692, 2668, 2665, 2645, 2643, 2640, 2651, 2768, 2759, + 2757, 2744, 2743, 2741, 2748, 352, 1382, 340, 337, 333, 1371, 1369, 307, 300, 296, 2126, 315, 312, 1347, 1342, + 1350, 261, 258, 250, 2097, 246, 2094, 271, 268, 264, 1306, 1301, 1298, 276, 1312, 1309, 2115, 203, 2048, 195, + 2045, 191, 2041, 213, 209, 2056, 1246, 1244, 1238, 225, 1234, 222, 1256, 1253, 1249, 1262, 2080, 2079, 154, 1997, + 150, 1995, 147, 1992, 1989, 163, 160, 2004, 156, 2001, 1175, 1174, 1172, 1170, 1167, 170, 1164, 167, 1185, 1183, + 1180, 1177, 174, 1190, 1188, 2025, 2024, 2022, 587, 586, 564, 559, 556, 2290, 573, 1588, 520, 518, 512, 2268, + 508, 2265, 530, 1568, 1565, 461, 457, 2233, 450, 2230, 446, 2226, 479, 471, 489, 1526, 1523, 1520, 397, 395, + 2185, 392, 2183, 389, 2180, 2177, 410, 2194, 402, 422, 1463, 1461, 1459, 1456, 1470, 2455, 799, 2433, 2430, 779, + 776, 773, 2397, 2394, 2390, 734, 728, 724, 746, 1717, 2356, 2354, 2351, 2348, 1658, 677, 675, 673, 670, 667, 688, + 1685, 1683, 2606, 2589, 2586, 2559, 2556, 2552, 927, 2523, 2521, 2518, 2515, 1784, 2532, 895, 893, 890, 2718, + 2709, 2707, 2689, 2687, 2684, 2663, 2662, 2660, 2658, 1825, 2667, 2769, 1852, 2760, 2758, 142, 141, 1139, 1138, + 134, 132, 129, 126, 1982, 1129, 1128, 1126, 1131, 113, 111, 108, 105, 1972, 101, 1970, 120, 118, 115, 1109, 1108, + 1106, 1104, 123, 1113, 1111, 82, 79, 1951, 75, 1949, 72, 1946, 92, 89, 86, 1956, 1077, 1076, 1074, 1072, 98, + 1069, 96, 1084, 1082, 1079, 1088, 1968, 1967, 48, 45, 1916, 42, 1914, 39, 1911, 1908, 60, 57, 54, 1923, 50, 1920, + 1031, 1030, 1028, 1026, 67, 1023, 65, 1020, 62, 1041, 1039, 1036, 1033, 69, 1046, 1044, 1944, 1943, 1941, 11, 9, + 1868, 7, 1865, 1862, 1859, 20, 1878, 16, 1875, 13, 1872, 970, 968, 966, 963, 29, 960, 26, 23, 983, 981, 978, 975, + 33, 971, 31, 990, 988, 985, 1906, 1904, 1902, 993, 351, 2145, 1383, 331, 330, 328, 326, 2137, 323, 2135, 339, + 1372, 1370, 294, 293, 291, 289, 2122, 286, 2120, 283, 2117, 309, 303, 317, 1348, 1346, 1344, 245, 244, 242, 2090, + 239, 2088, 236, 2085, 2082, 260, 2099, 249, 270, 1307, 1305, 1303, 1300, 1314, 189, 2038, 186, 2036, 183, 2033, + 2030, 2026, 206, 198, 2047, 194, 216, 1247, 1245, 1243, 1240, 227, 1237, 1255, 2310, 2302, 2300, 2286, 2284, + 2281, 565, 563, 561, 558, 575, 1589, 2261, 2259, 2256, 2253, 1542, 521, 519, 517, 514, 2270, 511, 533, 1569, + 1567, 2223, 2221, 2218, 2215, 1483, 2211, 1480, 459, 456, 453, 2232, 449, 474, 491, 1527, 1525, 1522, 2475, 2467, + 2465, 2451, 2449, 2446, 801, 800, 2426, 2424, 2421, 2418, 1723, 2435, 780, 778, 775, 2387, 2385, 2382, 2379, + 1695, 2375, 1693, 2396, 735, 733, 730, 727, 749, 1718, 2616, 2615, 2604, 2603, 2601, 2584, 2583, 2581, 2579, + 1800, 2591, 2550, 2549, 2547, 2545, 1792, 2542, 1790, 2558, 929, 2719, 1841, 2710, 2708, 1833, 1831, 2690, 2688, + 2686, 1815, 1809, 1808, 1774, 1756, 1754, 1737, 1736, 1734, 1739, 1816, 1711, 1676, 1674, 633, 629, 1638, 1636, + 1633, 1641, 598, 1605, 1604, 1602, 1600, 605, 1609, 1607, 2327, 887, 853, 1775, 822, 820, 1757, 1755, 1584, 524, + 1560, 1558, 468, 464, 1514, 1511, 1508, 1519, 408, 404, 400, 1452, 1447, 1444, 417, 1458, 1455, 2208, 364, 361, + 358, 2154, 1401, 1400, 1398, 1396, 374, 1393, 371, 1408, 1406, 1403, 1413, 2173, 2172, 772, 726, 723, 1712, 672, + 669, 666, 682, 1678, 1675, 625, 623, 621, 618, 2331, 636, 632, 1639, 1637, 1635, 920, 918, 884, 880, 889, 849, + 848, 847, 846, 2497, 855, 852, 1776, 2641, 2742, 2787, 1380, 334, 1367, 1365, 301, 297, 1340, 1338, 1335, 1343, + 255, 251, 247, 1296, 1291, 1288, 265, 1302, 1299, 2113, 204, 196, 192, 2042, 1232, 1230, 1224, 214, 1220, 210, + 1242, 1239, 1235, 1250, 2077, 2075, 151, 148, 1993, 144, 1990, 1163, 1162, 1160, 1158, 1155, 161, 1152, 157, + 1173, 1171, 1168, 1165, 168, 1181, 1178, 2021, 2020, 2018, 2023, 585, 560, 557, 1585, 516, 509, 1562, 1559, 458, + 447, 2227, 472, 1516, 1513, 1510, 398, 396, 393, 390, 2181, 386, 2178, 407, 1453, 1451, 1449, 1446, 420, 1460, + 2209, 769, 764, 720, 712, 2391, 729, 1713, 664, 663, 661, 659, 2352, 656, 2349, 671, 1679, 1677, 2553, 922, 919, + 2519, 2516, 885, 883, 881, 2685, 2661, 2659, 2767, 2756, 2755, 140, 1137, 1136, 130, 127, 1125, 1124, 1122, 1127, + 109, 106, 102, 1103, 1102, 1100, 1098, 116, 1107, 1105, 1980, 80, 76, 73, 1947, 1068, 1067, 1065, 1063, 90, 1060, + 87, 1075, 1073, 1070, 1080, 1966, 1965, 46, 43, 40, 1912, 36, 1909, 1019, 1018, 1016, 1014, 58, 1011, 55, 1008, + 51, 1029, 1027, 1024, 1021, 63, 1037, 1034, 1940, 1939, 1937, 1942, 8, 1866, 4, 1863, 1, 1860, 956, 954, 952, + 949, 946, 17, 14, 969, 967, 964, 961, 27, 957, 24, 979, 976, 972, 1901, 1900, 1898, 1896, 986, 1905, 1903, 350, + 349, 1381, 329, 327, 324, 1368, 1366, 292, 290, 287, 284, 2118, 304, 1341, 1339, 1337, 1345, 243, 240, 237, 2086, + 233, 2083, 254, 1297, 1295, 1293, 1290, 1304, 2114, 190, 187, 184, 2034, 180, 2031, 177, 2027, 199, 1233, 1231, + 1229, 1226, 217, 1223, 1241, 2078, 2076, 584, 555, 554, 552, 550, 2282, 562, 1586, 507, 506, 504, 502, 2257, 499, + 2254, 515, 1563, 1561, 445, 443, 441, 2219, 438, 2216, 435, 2212, 460, 454, 475, 1517, 1515, 1512, 2447, 798, + 797, 2422, 2419, 770, 768, 766, 2383, 2380, 2376, 721, 719, 717, 714, 731, 1714, 2602, 2582, 2580, 2548, 2546, + 2543, 923, 921, 2717, 2706, 2705, 2683, 2682, 2680, 1771, 1752, 1750, 1733, 1732, 1731, 1735, 1814, 1707, 1670, + 1668, 1631, 1629, 1626, 1634, 1599, 1598, 1596, 1594, 1603, 1601, 2326, 1772, 1753, 1751, 1581, 1554, 1552, 1504, + 1501, 1498, 1509, 1442, 1437, 1434, 401, 1448, 1445, 2206, 1392, 1391, 1389, 1387, 1384, 359, 1399, 1397, 1394, + 1404, 2171, 2170, 1708, 1672, 1669, 619, 1632, 1630, 1628, 1773, 1378, 1363, 1361, 1333, 1328, 1336, 1286, 1281, + 1278, 248, 1292, 1289, 2111, 1218, 1216, 1210, 197, 1206, 193, 1228, 1225, 1221, 1236, 2073, 2071, 1151, 1150, + 1148, 1146, 152, 1143, 149, 1140, 145, 1161, 1159, 1156, 1153, 158, 1169, 1166, 2017, 2016, 2014, 2019, 1582, + 510, 1556, 1553, 452, 448, 1506, 1500, 394, 391, 387, 1443, 1441, 1439, 1436, 1450, 2207, 765, 716, 713, 1709, + 662, 660, 657, 1673, 1671, 916, 914, 879, 878, 877, 882, 1135, 1134, 1121, 1120, 1118, 1123, 1097, 1096, 1094, + 1092, 103, 1101, 1099, 1979, 1059, 1058, 1056, 1054, 77, 1051, 74, 1066, 1064, 1061, 1071, 1964, 1963, 1007, + 1006, 1004, 1002, 999, 41, 996, 37, 1017, 1015, 1012, 1009, 52, 1025, 1022, 1936, 1935, 1933, 1938, 942, 940, + 938, 935, 932, 5, 2, 955, 953, 950, 947, 18, 943, 15, 965, 962, 958, 1895, 1894, 1892, 1890, 973, 1899, 1897, + 1379, 325, 1364, 1362, 288, 285, 1334, 1332, 1330, 241, 238, 234, 1287, 1285, 1283, 1280, 1294, 2112, 188, 185, + 181, 178, 2028, 1219, 1217, 1215, 1212, 200, 1209, 1227, 2074, 2072, 583, 553, 551, 1583, 505, 503, 500, 513, + 1557, 1555, 444, 442, 439, 436, 2213, 455, 451, 1507, 1505, 1502, 796, 763, 762, 760, 767, 711, 710, 708, 706, + 2377, 718, 715, 1710, 2544, 917, 915, 2681, 1627, 1597, 1595, 2325, 1769, 1749, 1747, 1499, 1438, 1435, 2204, + 1390, 1388, 1385, 1395, 2169, 2167, 1704, 1665, 1662, 1625, 1623, 1620, 1770, 1329, 1282, 1279, 2109, 1214, 1207, + 1222, 2068, 2065, 1149, 1147, 1144, 1141, 146, 1157, 1154, 2013, 2011, 2008, 2015, 1579, 1549, 1546, 1495, 1487, + 1433, 1431, 1428, 1425, 388, 1440, 2205, 1705, 658, 1667, 1664, 1119, 1095, 1093, 1978, 1057, 1055, 1052, 1062, + 1962, 1960, 1005, 1003, 1000, 997, 38, 1013, 1010, 1932, 1930, 1927, 1934, 941, 939, 936, 933, 6, 930, 3, 951, + 948, 944, 1889, 1887, 1884, 1881, 959, 1893, 1891, 35, 1377, 1360, 1358, 1327, 1325, 1322, 1331, 1277, 1275, + 1272, 1269, 235, 1284, 2110, 1205, 1204, 1201, 1198, 182, 1195, 179, 1213, 2070, 2067, 1580, 501, 1551, 1548, + 440, 437, 1497, 1494, 1490, 1503, 761, 709, 707, 1706, 913, 912, 2198, 1386, 2164, 2161, 1621, 1766, 2103, 1208, + 2058, 2054, 1145, 1142, 2005, 2002, 1999, 2009, 1488, 1429, 1426, 2200, 1698, 1659, 1656, 1975, 1053, 1957, 1954, + 1001, 998, 1924, 1921, 1918, 1928, 937, 934, 931, 1879, 1876, 1873, 1870, 945, 1885, 1882, 1323, 1273, 1270, + 2105, 1202, 1199, 1196, 1211, 2061, 2057, 1576, 1543, 1540, 1484, 1481, 1478, 1491, 1700 + ]); + + /* + * 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. + */ + // import java.util.List; + /** + * @author Guenther Grau + */ + /*public final*/ class PDF417DetectorResult { + constructor(bits, points) { + this.bits = bits; + this.points = points; + } + getBits() { + return this.bits; + } + getPoints() { + return this.points; + } + } + + /* + * Copyright 2009 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. + */ + // import java.util.ArrayList; + // import java.util.Arrays; + // import java.util.List; + // import java.util.Map; + /** + *Encapsulates logic that can detect a PDF417 Code in an image, even if the + * PDF417 Code is rotated or skewed, or partially obscured.
+ * + * @author SITA Lab (kevin.osullivan@sita.aero) + * @author dswitkin@google.com (Daniel Switkin) + * @author Guenther Grau + */ + /*public*/ /*final*/ class Detector$3 { + /** + *Detects a PDF417 Code in an image. Only checks 0 and 180 degree rotations.
+ * + * @param image barcode image to decode + * @param hints optional hints to detector + * @param multiple if true, then the image is searched for multiple codes. If false, then at most one code will + * be found and returned + * @return {@link PDF417DetectorResult} encapsulating results of detecting a PDF417 code + * @throws NotFoundException if no PDF417 Code can be found + */ + static detectMultiple(image, hints, multiple) { + // TODO detection improvement, tryHarder could try several different luminance thresholds/blackpoints or even + // different binarizers + // boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER); + let bitMatrix = image.getBlackMatrix(); + let barcodeCoordinates = Detector$3.detect(multiple, bitMatrix); + if (!barcodeCoordinates.length) { + bitMatrix = bitMatrix.clone(); + bitMatrix.rotate180(); + barcodeCoordinates = Detector$3.detect(multiple, bitMatrix); + } + return new PDF417DetectorResult(bitMatrix, barcodeCoordinates); + } + /** + * Detects PDF417 codes in an image. Only checks 0 degree rotation + * @param multiple if true, then the image is searched for multiple codes. If false, then at most one code will + * be found and returned + * @param bitMatrix bit matrix to detect barcodes in + * @return List of ResultPoint arrays containing the coordinates of found barcodes + */ + static detect(multiple, bitMatrix) { + const barcodeCoordinates = new Array(); + let row = 0; + let column = 0; + let foundBarcodeInRow = false; + while (row < bitMatrix.getHeight()) { + const vertices = Detector$3.findVertices(bitMatrix, row, column); + if (vertices[0] == null && vertices[3] == null) { + if (!foundBarcodeInRow) { + // we didn't find any barcode so that's the end of searching + break; + } + // we didn't find a barcode starting at the given column and row. Try again from the first column and slightly + // below the lowest barcode we found so far. + foundBarcodeInRow = false; + column = 0; + for (const barcodeCoordinate of barcodeCoordinates) { + if (barcodeCoordinate[1] != null) { + row = Math.trunc(Math.max(row, barcodeCoordinate[1].getY())); + } + if (barcodeCoordinate[3] != null) { + row = Math.max(row, Math.trunc(barcodeCoordinate[3].getY())); + } + } + row += Detector$3.ROW_STEP; + continue; + } + foundBarcodeInRow = true; + barcodeCoordinates.push(vertices); + if (!multiple) { + break; + } + // if we didn't find a right row indicator column, then continue the search for the next barcode after the + // start pattern of the barcode just found. + if (vertices[2] != null) { + column = Math.trunc(vertices[2].getX()); + row = Math.trunc(vertices[2].getY()); + } + else { + column = Math.trunc(vertices[4].getX()); + row = Math.trunc(vertices[4].getY()); + } + } + return barcodeCoordinates; + } + /** + * Locate the vertices and the codewords area of a black blob using the Start + * and Stop patterns as locators. + * + * @param matrix the scanned barcode image. + * @return an array containing the vertices: + * vertices[0] x, y top left barcode + * vertices[1] x, y bottom left barcode + * vertices[2] x, y top right barcode + * vertices[3] x, y bottom right barcode + * vertices[4] x, y top left codeword area + * vertices[5] x, y bottom left codeword area + * vertices[6] x, y top right codeword area + * vertices[7] x, y bottom right codeword area + */ + static findVertices(matrix, startRow, startColumn) { + const height = matrix.getHeight(); + const width = matrix.getWidth(); + // const result = new ResultPoint[8]; + const result = new Array(8); + Detector$3.copyToResult(result, Detector$3.findRowsWithPattern(matrix, height, width, startRow, startColumn, Detector$3.START_PATTERN), Detector$3.INDEXES_START_PATTERN); + if (result[4] != null) { + startColumn = Math.trunc(result[4].getX()); + startRow = Math.trunc(result[4].getY()); + } + Detector$3.copyToResult(result, Detector$3.findRowsWithPattern(matrix, height, width, startRow, startColumn, Detector$3.STOP_PATTERN), Detector$3.INDEXES_STOP_PATTERN); + return result; + } + static copyToResult(result, tmpResult, destinationIndexes) { + for (let i = 0; i < destinationIndexes.length; i++) { + result[destinationIndexes[i]] = tmpResult[i]; + } + } + static findRowsWithPattern(matrix, height, width, startRow, startColumn, pattern) { + // const result = new ResultPoint[4]; + const result = new Array(4); + let found = false; + const counters = new Int32Array(pattern.length); + for (; startRow < height; startRow += Detector$3.ROW_STEP) { + let loc = Detector$3.findGuardPattern(matrix, startColumn, startRow, width, false, pattern, counters); + if (loc != null) { + while (startRow > 0) { + const previousRowLoc = Detector$3.findGuardPattern(matrix, startColumn, --startRow, width, false, pattern, counters); + if (previousRowLoc != null) { + loc = previousRowLoc; + } + else { + startRow++; + break; + } + } + result[0] = new ResultPoint(loc[0], startRow); + result[1] = new ResultPoint(loc[1], startRow); + found = true; + break; + } + } + let stopRow = startRow + 1; + // Last row of the current symbol that contains pattern + if (found) { + let skippedRowCount = 0; + let previousRowLoc = Int32Array.from([Math.trunc(result[0].getX()), Math.trunc(result[1].getX())]); + for (; stopRow < height; stopRow++) { + const loc = Detector$3.findGuardPattern(matrix, previousRowLoc[0], stopRow, width, false, pattern, counters); + // a found pattern is only considered to belong to the same barcode if the start and end positions + // don't differ too much. Pattern drift should be not bigger than two for consecutive rows. With + // a higher number of skipped rows drift could be larger. To keep it simple for now, we allow a slightly + // larger drift and don't check for skipped rows. + if (loc != null && + Math.abs(previousRowLoc[0] - loc[0]) < Detector$3.MAX_PATTERN_DRIFT && + Math.abs(previousRowLoc[1] - loc[1]) < Detector$3.MAX_PATTERN_DRIFT) { + previousRowLoc = loc; + skippedRowCount = 0; + } + else { + if (skippedRowCount > Detector$3.SKIPPED_ROW_COUNT_MAX) { + break; + } + else { + skippedRowCount++; + } + } + } + stopRow -= skippedRowCount + 1; + result[2] = new ResultPoint(previousRowLoc[0], stopRow); + result[3] = new ResultPoint(previousRowLoc[1], stopRow); + } + if (stopRow - startRow < Detector$3.BARCODE_MIN_HEIGHT) { + Arrays.fill(result, null); + } + return result; + } + /** + * @param matrix row of black/white values to search + * @param column x position to start search + * @param row y position to start search + * @param width the number of pixels to search on this row + * @param pattern pattern of counts of number of black and white pixels that are + * being searched for as a pattern + * @param counters array of counters, as long as pattern, to re-use + * @return start/end horizontal offset of guard pattern, as an array of two ints. + */ + static findGuardPattern(matrix, column, row, width, whiteFirst, pattern, counters) { + Arrays.fillWithin(counters, 0, counters.length, 0); + let patternStart = column; + let pixelDrift = 0; + // if there are black pixels left of the current pixel shift to the left, but only for MAX_PIXEL_DRIFT pixels + while (matrix.get(patternStart, row) && patternStart > 0 && pixelDrift++ < Detector$3.MAX_PIXEL_DRIFT) { + patternStart--; + } + let x = patternStart; + let counterPosition = 0; + let patternLength = pattern.length; + for (let isWhite = whiteFirst; x < width; x++) { + let pixel = matrix.get(x, row); + if (pixel !== isWhite) { + counters[counterPosition]++; + } + else { + if (counterPosition === patternLength - 1) { + if (Detector$3.patternMatchVariance(counters, pattern, Detector$3.MAX_INDIVIDUAL_VARIANCE) < Detector$3.MAX_AVG_VARIANCE) { + return new Int32Array([patternStart, x]); + } + patternStart += counters[0] + counters[1]; + System.arraycopy(counters, 2, counters, 0, counterPosition - 1); + counters[counterPosition - 1] = 0; + counters[counterPosition] = 0; + counterPosition--; + } + else { + counterPosition++; + } + counters[counterPosition] = 1; + isWhite = !isWhite; + } + } + if (counterPosition === patternLength - 1 && + Detector$3.patternMatchVariance(counters, pattern, Detector$3.MAX_INDIVIDUAL_VARIANCE) < Detector$3.MAX_AVG_VARIANCE) { + return new Int32Array([patternStart, x - 1]); + } + return null; + } + /** + * Determines how closely a set of observed counts of runs of black/white + * values matches a given target pattern. This is reported as the ratio of + * the total variance from the expected pattern proportions across all + * pattern elements, to the length of the pattern. + * + * @param counters observed counters + * @param pattern expected pattern + * @param maxIndividualVariance The most any counter can differ before we give up + * @return ratio of total variance between counters and pattern compared to total pattern size + */ + static patternMatchVariance(counters, pattern, maxIndividualVariance) { + let numCounters = counters.length; + let total = 0; + let patternLength = 0; + for (let i = 0; i < numCounters; i++) { + total += counters[i]; + patternLength += pattern[i]; + } + if (total < patternLength) { + // If we don't even have one pixel per unit of bar width, assume this + // is too small to reliably match, so fail: + return /*Float.POSITIVE_INFINITY*/ Infinity; + } + // We're going to fake floating-point math in integers. We just need to use more bits. + // Scale up patternLength so that intermediate values below like scaledCounter will have + // more "significant digits". + let unitBarWidth = total / patternLength; + maxIndividualVariance *= unitBarWidth; + let totalVariance = 0.0; + for (let x = 0; x < numCounters; x++) { + let counter = counters[x]; + let scaledPattern = pattern[x] * unitBarWidth; + let variance = counter > scaledPattern ? counter - scaledPattern : scaledPattern - counter; + if (variance > maxIndividualVariance) { + return /*Float.POSITIVE_INFINITY*/ Infinity; + } + totalVariance += variance; + } + return totalVariance / total; + } + } + Detector$3.INDEXES_START_PATTERN = Int32Array.from([0, 4, 1, 5]); + Detector$3.INDEXES_STOP_PATTERN = Int32Array.from([6, 2, 7, 3]); + Detector$3.MAX_AVG_VARIANCE = 0.42; + Detector$3.MAX_INDIVIDUAL_VARIANCE = 0.8; + // B S B S B S B S Bar/Space pattern + // 11111111 0 1 0 1 0 1 000 + Detector$3.START_PATTERN = Int32Array.from([8, 1, 1, 1, 1, 1, 1, 3]); + // 1111111 0 1 000 1 0 1 00 1 + Detector$3.STOP_PATTERN = Int32Array.from([7, 1, 1, 3, 1, 1, 1, 2, 1]); + Detector$3.MAX_PIXEL_DRIFT = 3; + Detector$3.MAX_PATTERN_DRIFT = 5; + // if we set the value too low, then we don't detect the correct height of the bar if the start patterns are damaged. + // if we set the value too high, then we might detect the start pattern from a neighbor barcode. + Detector$3.SKIPPED_ROW_COUNT_MAX = 25; + // A PDF471 barcode should have at least 3 rows, with each row being >= 3 times the module width. Therefore it should be at least + // 9 pixels tall. To be conservative, we use about half the size to ensure we don't miss it. + Detector$3.ROW_STEP = 5; + Detector$3.BARCODE_MIN_HEIGHT = 10; + + /* + * Copyright 2012 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. + */ + /** + * @author Sean Owen + * @see com.google.zxing.common.reedsolomon.GenericGFPoly + */ + /*final*/ class ModulusPoly { + constructor(field, coefficients) { + if (coefficients.length === 0) { + throw new IllegalArgumentException(); + } + this.field = field; + let coefficientsLength = /*int*/ coefficients.length; + if (coefficientsLength > 1 && coefficients[0] === 0) { + // Leading term must be non-zero for anything except the constant polynomial "0" + let firstNonZero = /*int*/ 1; + while (firstNonZero < coefficientsLength && coefficients[firstNonZero] === 0) { + firstNonZero++; + } + if (firstNonZero === coefficientsLength) { + this.coefficients = new Int32Array([0]); + } + else { + this.coefficients = new Int32Array(coefficientsLength - firstNonZero); + System.arraycopy(coefficients, firstNonZero, this.coefficients, 0, this.coefficients.length); + } + } + else { + this.coefficients = coefficients; + } + } + getCoefficients() { + return this.coefficients; + } + /** + * @return degree of this polynomial + */ + getDegree() { + return this.coefficients.length - 1; + } + /** + * @return true iff this polynomial is the monomial "0" + */ + isZero() { + return this.coefficients[0] === 0; + } + /** + * @return coefficient of x^degree term in this polynomial + */ + getCoefficient(degree) { + return this.coefficients[this.coefficients.length - 1 - degree]; + } + /** + * @return evaluation of this polynomial at a given point + */ + evaluateAt(a) { + if (a === 0) { + // Just return the x^0 coefficient + return this.getCoefficient(0); + } + if (a === 1) { + // Just the sum of the coefficients + let sum = /*int*/ 0; + for (let coefficient /*int*/ of this.coefficients) { + sum = this.field.add(sum, coefficient); + } + return sum; + } + let result = /*int*/ this.coefficients[0]; + let size = /*int*/ this.coefficients.length; + for (let i /*int*/ = 1; i < size; i++) { + result = this.field.add(this.field.multiply(a, result), this.coefficients[i]); + } + return result; + } + add(other) { + if (!this.field.equals(other.field)) { + throw new IllegalArgumentException('ModulusPolys do not have same ModulusGF field'); + } + if (this.isZero()) { + return other; + } + if (other.isZero()) { + 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 Int32Array(largerCoefficients.length); + let lengthDiff = /*int*/ largerCoefficients.length - smallerCoefficients.length; + // Copy high-order terms only found in higher-degree polynomial's coefficients + System.arraycopy(largerCoefficients, 0, sumDiff, 0, lengthDiff); + for (let i /*int*/ = lengthDiff; i < largerCoefficients.length; i++) { + sumDiff[i] = this.field.add(smallerCoefficients[i - lengthDiff], largerCoefficients[i]); + } + return new ModulusPoly(this.field, sumDiff); + } + subtract(other) { + if (!this.field.equals(other.field)) { + throw new IllegalArgumentException('ModulusPolys do not have same ModulusGF field'); + } + if (other.isZero()) { + return this; + } + return this.add(other.negative()); + } + multiply(other) { + if (other instanceof ModulusPoly) { + return this.multiplyOther(other); + } + return this.multiplyScalar(other); + } + multiplyOther(other) { + if (!this.field.equals(other.field)) { + throw new IllegalArgumentException('ModulusPolys do not have same ModulusGF field'); + } + if (this.isZero() || other.isZero()) { + // return this.field.getZero(); + return new ModulusPoly(this.field, new Int32Array([0])); + } + let aCoefficients = this.coefficients; + let aLength = /*int*/ aCoefficients.length; + let bCoefficients = other.coefficients; + let bLength = /*int*/ bCoefficients.length; + let product = new Int32Array(aLength + bLength - 1); + for (let i /*int*/ = 0; i < aLength; i++) { + let aCoeff = /*int*/ aCoefficients[i]; + for (let j /*int*/ = 0; j < bLength; j++) { + product[i + j] = this.field.add(product[i + j], this.field.multiply(aCoeff, bCoefficients[j])); + } + } + return new ModulusPoly(this.field, product); + } + negative() { + let size = /*int*/ this.coefficients.length; + let negativeCoefficients = new Int32Array(size); + for (let i /*int*/ = 0; i < size; i++) { + negativeCoefficients[i] = this.field.subtract(0, this.coefficients[i]); + } + return new ModulusPoly(this.field, negativeCoefficients); + } + multiplyScalar(scalar) { + if (scalar === 0) { + return new ModulusPoly(this.field, new Int32Array([0])); + } + if (scalar === 1) { + return this; + } + let size = /*int*/ this.coefficients.length; + let product = new Int32Array(size); + for (let i /*int*/ = 0; i < size; i++) { + product[i] = this.field.multiply(this.coefficients[i], scalar); + } + return new ModulusPoly(this.field, product); + } + multiplyByMonomial(degree, coefficient) { + if (degree < 0) { + throw new IllegalArgumentException(); + } + if (coefficient === 0) { + return new ModulusPoly(this.field, new Int32Array([0])); + } + let size = /*int*/ this.coefficients.length; + let product = new Int32Array(size + degree); + for (let i /*int*/ = 0; i < size; i++) { + product[i] = this.field.multiply(this.coefficients[i], coefficient); + } + return new ModulusPoly(this.field, product); + } + /* + ModulusPoly[] divide(other: ModulusPoly) { + if (!field.equals(other.field)) { + throw new IllegalArgumentException("ModulusPolys do not have same ModulusGF field"); + } + if (other.isZero()) { + throw new IllegalArgumentException("Divide by 0"); + } + + let quotient: ModulusPoly = field.getZero(); + let remainder: ModulusPoly = this; + + let denominatorLeadingTerm: /*int/ number = other.getCoefficient(other.getDegree()); + let inverseDenominatorLeadingTerm: /*int/ number = field.inverse(denominatorLeadingTerm); + + while (remainder.getDegree() >= other.getDegree() && !remainder.isZero()) { + let degreeDifference: /*int/ number = remainder.getDegree() - other.getDegree(); + let scale: /*int/ number = field.multiply(remainder.getCoefficient(remainder.getDegree()), inverseDenominatorLeadingTerm); + let term: ModulusPoly = other.multiplyByMonomial(degreeDifference, scale); + let iterationQuotient: ModulusPoly = field.buildMonomial(degreeDifference, scale); + quotient = quotient.add(iterationQuotient); + remainder = remainder.subtract(term); + } + + return new ModulusPoly[] { quotient, remainder }; + } + */ + // @Override + toString() { + let result = new StringBuilder( /*8 * this.getDegree()*/); // dynamic string size in JS + for (let degree /*int*/ = this.getDegree(); degree >= 0; degree--) { + let coefficient = /*int*/ this.getCoefficient(degree); + if (coefficient !== 0) { + if (coefficient < 0) { + result.append(' - '); + coefficient = -coefficient; + } + else { + if (result.length() > 0) { + result.append(' + '); + } + } + if (degree === 0 || coefficient !== 1) { + result.append(coefficient); + } + if (degree !== 0) { + if (degree === 1) { + result.append('x'); + } + else { + result.append('x^'); + result.append(degree); + } + } + } + } + return result.toString(); + } + } + + class ModulusBase { + add(a, b) { + return (a + b) % this.modulus; + } + subtract(a, b) { + return (this.modulus + a - b) % this.modulus; + } + exp(a) { + return this.expTable[a]; + } + log(a) { + if (a === 0) { + throw new IllegalArgumentException(); + } + return this.logTable[a]; + } + inverse(a) { + if (a === 0) { + throw new ArithmeticException(); + } + return this.expTable[this.modulus - this.logTable[a] - 1]; + } + multiply(a, b) { + if (a === 0 || b === 0) { + return 0; + } + return this.expTable[(this.logTable[a] + this.logTable[b]) % (this.modulus - 1)]; + } + getSize() { + return this.modulus; + } + equals(o) { + return o === this; + } + } + + /* + * Copyright 2012 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. + */ + /** + *A field based on powers of a generator integer, modulo some modulus.
+ * + * @author Sean Owen + * @see com.google.zxing.common.reedsolomon.GenericGF + */ + /*public final*/ class ModulusGF extends ModulusBase { + // private /*final*/ modulus: /*int*/ number; + constructor(modulus, generator) { + super(); + this.modulus = modulus; + this.expTable = new Int32Array(modulus); + this.logTable = new Int32Array(modulus); + let x = /*int*/ 1; + for (let i /*int*/ = 0; i < modulus; i++) { + this.expTable[i] = x; + x = (x * generator) % modulus; + } + for (let i /*int*/ = 0; i < modulus - 1; i++) { + this.logTable[this.expTable[i]] = i; + } + // logTable[0] == 0 but this should never be used + this.zero = new ModulusPoly(this, new Int32Array([0])); + this.one = new ModulusPoly(this, new Int32Array([1])); + } + getZero() { + return this.zero; + } + getOne() { + return this.one; + } + buildMonomial(degree, coefficient) { + if (degree < 0) { + throw new IllegalArgumentException(); + } + if (coefficient === 0) { + return this.zero; + } + let coefficients = new Int32Array(degree + 1); + coefficients[0] = coefficient; + return new ModulusPoly(this, coefficients); + } + } + ModulusGF.PDF417_GF = new ModulusGF(PDF417Common.NUMBER_OF_CODEWORDS, 3); + + /* + * Copyright 2012 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. + */ + /** + *PDF417 error correction implementation.
+ * + *This example + * is quite useful in understanding the algorithm.
+ * + * @author Sean Owen + * @see com.google.zxing.common.reedsolomon.ReedSolomonDecoder + */ + /*public final*/ class ErrorCorrection { + constructor() { + this.field = ModulusGF.PDF417_GF; + } + /** + * @param received received codewords + * @param numECCodewords number of those codewords used for EC + * @param erasures location of erasures + * @return number of errors + * @throws ChecksumException if errors cannot be corrected, maybe because of too many errors + */ + decode(received, numECCodewords, erasures) { + let poly = new ModulusPoly(this.field, received); + let S = new Int32Array(numECCodewords); + let error = false; + for (let i /*int*/ = numECCodewords; i > 0; i--) { + let evaluation = poly.evaluateAt(this.field.exp(i)); + S[numECCodewords - i] = evaluation; + if (evaluation !== 0) { + error = true; + } + } + if (!error) { + return 0; + } + let knownErrors = this.field.getOne(); + if (erasures != null) { + for (const erasure of erasures) { + let b = this.field.exp(received.length - 1 - erasure); + // Add (1 - bx) term: + let term = new ModulusPoly(this.field, new Int32Array([this.field.subtract(0, b), 1])); + knownErrors = knownErrors.multiply(term); + } + } + let syndrome = new ModulusPoly(this.field, S); + // syndrome = syndrome.multiply(knownErrors); + let sigmaOmega = this.runEuclideanAlgorithm(this.field.buildMonomial(numECCodewords, 1), syndrome, numECCodewords); + let sigma = sigmaOmega[0]; + let omega = sigmaOmega[1]; + // sigma = sigma.multiply(knownErrors); + let errorLocations = this.findErrorLocations(sigma); + let errorMagnitudes = this.findErrorMagnitudes(omega, sigma, errorLocations); + for (let i /*int*/ = 0; i < errorLocations.length; i++) { + let position = received.length - 1 - this.field.log(errorLocations[i]); + if (position < 0) { + throw ChecksumException.getChecksumInstance(); + } + received[position] = this.field.subtract(received[position], errorMagnitudes[i]); + } + return errorLocations.length; + } + /** + * + * @param ModulusPoly + * @param a + * @param ModulusPoly + * @param b + * @param int + * @param R + * @throws ChecksumException + */ + runEuclideanAlgorithm(a, b, R) { + // Assume a's degree is >= b's + if (a.getDegree() < b.getDegree()) { + let temp = a; + a = b; + b = temp; + } + let rLast = a; + let r = b; + let tLast = this.field.getZero(); + let t = this.field.getOne(); + // Run Euclidean algorithm until r's degree is less than R/2 + while (r.getDegree() >= Math.round(R / 2)) { + let rLastLast = rLast; + let tLastLast = tLast; + rLast = r; + tLast = t; + // Divide rLastLast by rLast, with quotient in q and remainder in r + if (rLast.isZero()) { + // Oops, Euclidean algorithm already terminated? + throw ChecksumException.getChecksumInstance(); + } + r = rLastLast; + let q = this.field.getZero(); + let denominatorLeadingTerm = rLast.getCoefficient(rLast.getDegree()); + let dltInverse = this.field.inverse(denominatorLeadingTerm); + while (r.getDegree() >= rLast.getDegree() && !r.isZero()) { + let degreeDiff = r.getDegree() - rLast.getDegree(); + let scale = this.field.multiply(r.getCoefficient(r.getDegree()), dltInverse); + q = q.add(this.field.buildMonomial(degreeDiff, scale)); + r = r.subtract(rLast.multiplyByMonomial(degreeDiff, scale)); + } + t = q.multiply(tLast).subtract(tLastLast).negative(); + } + let sigmaTildeAtZero = t.getCoefficient(0); + if (sigmaTildeAtZero === 0) { + throw ChecksumException.getChecksumInstance(); + } + let inverse = this.field.inverse(sigmaTildeAtZero); + let sigma = t.multiply(inverse); + let omega = r.multiply(inverse); + return [sigma, omega]; + } + /** + * + * @param errorLocator + * @throws ChecksumException + */ + findErrorLocations(errorLocator) { + // This is a direct application of Chien's search + let numErrors = errorLocator.getDegree(); + let result = new Int32Array(numErrors); + let e = 0; + for (let i /*int*/ = 1; i < this.field.getSize() && e < numErrors; i++) { + if (errorLocator.evaluateAt(i) === 0) { + result[e] = this.field.inverse(i); + e++; + } + } + if (e !== numErrors) { + throw ChecksumException.getChecksumInstance(); + } + return result; + } + findErrorMagnitudes(errorEvaluator, errorLocator, errorLocations) { + let errorLocatorDegree = errorLocator.getDegree(); + let formalDerivativeCoefficients = new Int32Array(errorLocatorDegree); + for (let i /*int*/ = 1; i <= errorLocatorDegree; i++) { + formalDerivativeCoefficients[errorLocatorDegree - i] = + this.field.multiply(i, errorLocator.getCoefficient(i)); + } + let formalDerivative = new ModulusPoly(this.field, formalDerivativeCoefficients); + // This is directly applying Forney's Formula + let s = errorLocations.length; + let result = new Int32Array(s); + for (let i /*int*/ = 0; i < s; i++) { + let xiInverse = this.field.inverse(errorLocations[i]); + let numerator = this.field.subtract(0, errorEvaluator.evaluateAt(xiInverse)); + let denominator = this.field.inverse(formalDerivative.evaluateAt(xiInverse)); + result[i] = this.field.multiply(numerator, denominator); + } + return result; + } + } + + /* + * Copyright 2013 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. + */ + /** + * @author Guenther Grau + */ + /*final*/ class BoundingBox { + constructor(image, topLeft, bottomLeft, topRight, bottomRight) { + if (image instanceof BoundingBox) { + this.constructor_2(image); + } + else { + this.constructor_1(image, topLeft, bottomLeft, topRight, bottomRight); + } + } + /** + * + * @param image + * @param topLeft + * @param bottomLeft + * @param topRight + * @param bottomRight + * + * @throws NotFoundException + */ + constructor_1(image, topLeft, bottomLeft, topRight, bottomRight) { + const leftUnspecified = topLeft == null || bottomLeft == null; + const rightUnspecified = topRight == null || bottomRight == null; + if (leftUnspecified && rightUnspecified) { + throw new NotFoundException(); + } + if (leftUnspecified) { + topLeft = new ResultPoint(0, topRight.getY()); + bottomLeft = new ResultPoint(0, bottomRight.getY()); + } + else if (rightUnspecified) { + topRight = new ResultPoint(image.getWidth() - 1, topLeft.getY()); + bottomRight = new ResultPoint(image.getWidth() - 1, bottomLeft.getY()); + } + this.image = image; + this.topLeft = topLeft; + this.bottomLeft = bottomLeft; + this.topRight = topRight; + this.bottomRight = bottomRight; + this.minX = Math.trunc(Math.min(topLeft.getX(), bottomLeft.getX())); + this.maxX = Math.trunc(Math.max(topRight.getX(), bottomRight.getX())); + this.minY = Math.trunc(Math.min(topLeft.getY(), topRight.getY())); + this.maxY = Math.trunc(Math.max(bottomLeft.getY(), bottomRight.getY())); + } + constructor_2(boundingBox) { + this.image = boundingBox.image; + this.topLeft = boundingBox.getTopLeft(); + this.bottomLeft = boundingBox.getBottomLeft(); + this.topRight = boundingBox.getTopRight(); + this.bottomRight = boundingBox.getBottomRight(); + this.minX = boundingBox.getMinX(); + this.maxX = boundingBox.getMaxX(); + this.minY = boundingBox.getMinY(); + this.maxY = boundingBox.getMaxY(); + } + /** + * @throws NotFoundException + */ + static merge(leftBox, rightBox) { + if (leftBox == null) { + return rightBox; + } + if (rightBox == null) { + return leftBox; + } + return new BoundingBox(leftBox.image, leftBox.topLeft, leftBox.bottomLeft, rightBox.topRight, rightBox.bottomRight); + } + /** + * @throws NotFoundException + */ + addMissingRows(missingStartRows, missingEndRows, isLeft) { + let newTopLeft = this.topLeft; + let newBottomLeft = this.bottomLeft; + let newTopRight = this.topRight; + let newBottomRight = this.bottomRight; + if (missingStartRows > 0) { + let top = isLeft ? this.topLeft : this.topRight; + let newMinY = Math.trunc(top.getY() - missingStartRows); + if (newMinY < 0) { + newMinY = 0; + } + let newTop = new ResultPoint(top.getX(), newMinY); + if (isLeft) { + newTopLeft = newTop; + } + else { + newTopRight = newTop; + } + } + if (missingEndRows > 0) { + let bottom = isLeft ? this.bottomLeft : this.bottomRight; + let newMaxY = Math.trunc(bottom.getY() + missingEndRows); + if (newMaxY >= this.image.getHeight()) { + newMaxY = this.image.getHeight() - 1; + } + let newBottom = new ResultPoint(bottom.getX(), newMaxY); + if (isLeft) { + newBottomLeft = newBottom; + } + else { + newBottomRight = newBottom; + } + } + return new BoundingBox(this.image, newTopLeft, newBottomLeft, newTopRight, newBottomRight); + } + getMinX() { + return this.minX; + } + getMaxX() { + return this.maxX; + } + getMinY() { + return this.minY; + } + getMaxY() { + return this.maxY; + } + getTopLeft() { + return this.topLeft; + } + getTopRight() { + return this.topRight; + } + getBottomLeft() { + return this.bottomLeft; + } + getBottomRight() { + return this.bottomRight; + } + } + + /* + * Copyright 2013 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. + */ + // package com.google.zxing.pdf417.decoder; + /** + * @author Guenther Grau + */ + /*final*/ class BarcodeMetadata { + constructor(columnCount, rowCountUpperPart, rowCountLowerPart, errorCorrectionLevel) { + this.columnCount = columnCount; + this.errorCorrectionLevel = errorCorrectionLevel; + this.rowCountUpperPart = rowCountUpperPart; + this.rowCountLowerPart = rowCountLowerPart; + this.rowCount = rowCountUpperPart + rowCountLowerPart; + } + getColumnCount() { + return this.columnCount; + } + getErrorCorrectionLevel() { + return this.errorCorrectionLevel; + } + getRowCount() { + return this.rowCount; + } + getRowCountUpperPart() { + return this.rowCountUpperPart; + } + getRowCountLowerPart() { + return this.rowCountLowerPart; + } + } + + /** + * Java Formatter class polyfill that works in the JS way. + */ + class Formatter { + constructor() { + this.buffer = ''; + } + /** + * + * @see https://stackoverflow.com/a/13439711/4367683 + * + * @param str + * @param arr + */ + static form(str, arr) { + let i = -1; + function callback(exp, p0, p1, p2, p3, p4) { + if (exp === '%%') + return '%'; + if (arr[++i] === undefined) + return undefined; + exp = p2 ? parseInt(p2.substr(1)) : undefined; + let base = p3 ? parseInt(p3.substr(1)) : undefined; + let val; + switch (p4) { + case 's': + val = arr[i]; + break; + case 'c': + val = arr[i][0]; + break; + case 'f': + val = parseFloat(arr[i]).toFixed(exp); + break; + case 'p': + val = parseFloat(arr[i]).toPrecision(exp); + break; + case 'e': + val = parseFloat(arr[i]).toExponential(exp); + break; + case 'x': + val = parseInt(arr[i]).toString(base ? base : 16); + break; + case 'd': + val = parseFloat(parseInt(arr[i], base ? base : 10).toPrecision(exp)).toFixed(0); + break; + } + val = typeof val === 'object' ? JSON.stringify(val) : (+val).toString(base); + let size = parseInt(p1); /* padding size */ + let ch = p1 && (p1[0] + '') === '0' ? '0' : ' '; /* isnull? */ + while (val.length < size) + val = p0 !== undefined ? val + ch : ch + val; /* isminus? */ + return val; + } + let regex = /%(-)?(0?[0-9]+)?([.][0-9]+)?([#][0-9]+)?([scfpexd%])/g; + return str.replace(regex, callback); + } + /** + * + * @param append The new string to append. + * @param args Argumets values to be formated. + */ + format(append, ...args) { + this.buffer += Formatter.form(append, args); + } + /** + * Returns the Formatter string value. + */ + toString() { + return this.buffer; + } + } + + /* + * Copyright 2013 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. + */ + /** + * @author Guenther Grau + */ + class DetectionResultColumn { + constructor(boundingBox) { + this.boundingBox = new BoundingBox(boundingBox); + // this.codewords = new Codeword[boundingBox.getMaxY() - boundingBox.getMinY() + 1]; + this.codewords = new Array(boundingBox.getMaxY() - boundingBox.getMinY() + 1); + } + /*final*/ getCodewordNearby(imageRow) { + let codeword = this.getCodeword(imageRow); + if (codeword != null) { + return codeword; + } + for (let i = 1; i < DetectionResultColumn.MAX_NEARBY_DISTANCE; i++) { + let nearImageRow = this.imageRowToCodewordIndex(imageRow) - i; + if (nearImageRow >= 0) { + codeword = this.codewords[nearImageRow]; + if (codeword != null) { + return codeword; + } + } + nearImageRow = this.imageRowToCodewordIndex(imageRow) + i; + if (nearImageRow < this.codewords.length) { + codeword = this.codewords[nearImageRow]; + if (codeword != null) { + return codeword; + } + } + } + return null; + } + /*final int*/ imageRowToCodewordIndex(imageRow) { + return imageRow - this.boundingBox.getMinY(); + } + /*final void*/ setCodeword(imageRow, codeword) { + this.codewords[this.imageRowToCodewordIndex(imageRow)] = codeword; + } + /*final*/ getCodeword(imageRow) { + return this.codewords[this.imageRowToCodewordIndex(imageRow)]; + } + /*final*/ getBoundingBox() { + return this.boundingBox; + } + /*final*/ getCodewords() { + return this.codewords; + } + // @Override + toString() { + const formatter = new Formatter(); + let row = 0; + for (const codeword of this.codewords) { + if (codeword == null) { + formatter.format('%3d: | %n', row++); + continue; + } + formatter.format('%3d: %3d|%3d%n', row++, codeword.getRowNumber(), codeword.getValue()); + } + return formatter.toString(); + } + } + DetectionResultColumn.MAX_NEARBY_DISTANCE = 5; + + /* + * Copyright 2013 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. + */ + // import java.util.ArrayList; + // import java.util.Collection; + // import java.util.HashMap; + // import java.util.Map; + // import java.util.Map.Entry; + /** + * @author Guenther Grau + */ + /*final*/ class BarcodeValue { + constructor() { + this.values = new Map(); + } + /** + * Add an occurrence of a value + */ + setValue(value) { + value = Math.trunc(value); + let confidence = this.values.get(value); + if (confidence == null) { + confidence = 0; + } + confidence++; + this.values.set(value, confidence); + } + /** + * Determines the maximum occurrence of a set value and returns all values which were set with this occurrence. + * @return an array of int, containing the values with the highest occurrence, or null, if no value was set + */ + getValue() { + let maxConfidence = -1; + let result = new Array(); + for (const [key, value] of this.values.entries()) { + const entry = { + getKey: () => key, + getValue: () => value, + }; + if (entry.getValue() > maxConfidence) { + maxConfidence = entry.getValue(); + result = []; + result.push(entry.getKey()); + } + else if (entry.getValue() === maxConfidence) { + result.push(entry.getKey()); + } + } + return PDF417Common.toIntArray(result); + } + getConfidence(value) { + return this.values.get(value); + } + } + + /* + * Copyright 2013 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. + */ + /** + * @author Guenther Grau + */ + /*final*/ class DetectionResultRowIndicatorColumn extends DetectionResultColumn { + constructor(boundingBox, isLeft) { + super(boundingBox); + this._isLeft = isLeft; + } + setRowNumbers() { + for (let codeword /*Codeword*/ of this.getCodewords()) { + if (codeword != null) { + codeword.setRowNumberAsRowIndicatorColumn(); + } + } + } + // TODO implement properly + // TODO maybe we should add missing codewords to store the correct row number to make + // finding row numbers for other columns easier + // use row height count to make detection of invalid row numbers more reliable + adjustCompleteIndicatorColumnRowNumbers(barcodeMetadata) { + let codewords = this.getCodewords(); + this.setRowNumbers(); + this.removeIncorrectCodewords(codewords, barcodeMetadata); + let boundingBox = this.getBoundingBox(); + let top = this._isLeft ? boundingBox.getTopLeft() : boundingBox.getTopRight(); + let bottom = this._isLeft ? boundingBox.getBottomLeft() : boundingBox.getBottomRight(); + let firstRow = this.imageRowToCodewordIndex(Math.trunc(top.getY())); + let lastRow = this.imageRowToCodewordIndex(Math.trunc(bottom.getY())); + // We need to be careful using the average row height. Barcode could be skewed so that we have smaller and + // taller rows + // float averageRowHeight = (lastRow - firstRow) / /*(float)*/ barcodeMetadata.getRowCount(); + let barcodeRow = -1; + let maxRowHeight = 1; + let currentRowHeight = 0; + for (let codewordsRow /*int*/ = firstRow; codewordsRow < lastRow; codewordsRow++) { + if (codewords[codewordsRow] == null) { + continue; + } + let codeword = codewords[codewordsRow]; + // float expectedRowNumber = (codewordsRow - firstRow) / averageRowHeight; + // if (Math.abs(codeword.getRowNumber() - expectedRowNumber) > 2) { + // SimpleLog.log(LEVEL.WARNING, + // "Removing codeword, rowNumberSkew too high, codeword[" + codewordsRow + "]: Expected Row: " + + // expectedRowNumber + ", RealRow: " + codeword.getRowNumber() + ", value: " + codeword.getValue()); + // codewords[codewordsRow] = null; + // } + let rowDifference = codeword.getRowNumber() - barcodeRow; + // TODO improve handling with case where first row indicator doesn't start with 0 + if (rowDifference === 0) { + currentRowHeight++; + } + else if (rowDifference === 1) { + maxRowHeight = Math.max(maxRowHeight, currentRowHeight); + currentRowHeight = 1; + barcodeRow = codeword.getRowNumber(); + } + else if (rowDifference < 0 || + codeword.getRowNumber() >= barcodeMetadata.getRowCount() || + rowDifference > codewordsRow) { + codewords[codewordsRow] = null; + } + else { + let checkedRows; + if (maxRowHeight > 2) { + checkedRows = (maxRowHeight - 2) * rowDifference; + } + else { + checkedRows = rowDifference; + } + let closePreviousCodewordFound = checkedRows >= codewordsRow; + for (let i /*int*/ = 1; i <= checkedRows && !closePreviousCodewordFound; i++) { + // there must be (height * rowDifference) number of codewords missing. For now we assume height = 1. + // This should hopefully get rid of most problems already. + closePreviousCodewordFound = codewords[codewordsRow - i] != null; + } + if (closePreviousCodewordFound) { + codewords[codewordsRow] = null; + } + else { + barcodeRow = codeword.getRowNumber(); + currentRowHeight = 1; + } + } + } + // return (int) (averageRowHeight + 0.5); + } + getRowHeights() { + let barcodeMetadata = this.getBarcodeMetadata(); + if (barcodeMetadata == null) { + return null; + } + this.adjustIncompleteIndicatorColumnRowNumbers(barcodeMetadata); + let result = new Int32Array(barcodeMetadata.getRowCount()); + for (let codeword /*Codeword*/ of this.getCodewords()) { + if (codeword != null) { + let rowNumber = codeword.getRowNumber(); + if (rowNumber >= result.length) { + // We have more rows than the barcode metadata allows for, ignore them. + continue; + } + result[rowNumber]++; + } // else throw exception? + } + return result; + } + // TODO maybe we should add missing codewords to store the correct row number to make + // finding row numbers for other columns easier + // use row height count to make detection of invalid row numbers more reliable + adjustIncompleteIndicatorColumnRowNumbers(barcodeMetadata) { + let boundingBox = this.getBoundingBox(); + let top = this._isLeft ? boundingBox.getTopLeft() : boundingBox.getTopRight(); + let bottom = this._isLeft ? boundingBox.getBottomLeft() : boundingBox.getBottomRight(); + let firstRow = this.imageRowToCodewordIndex(Math.trunc(top.getY())); + let lastRow = this.imageRowToCodewordIndex(Math.trunc(bottom.getY())); + // float averageRowHeight = (lastRow - firstRow) / /*(float)*/ barcodeMetadata.getRowCount(); + let codewords = this.getCodewords(); + let barcodeRow = -1; + for (let codewordsRow /*int*/ = firstRow; codewordsRow < lastRow; codewordsRow++) { + if (codewords[codewordsRow] == null) { + continue; + } + let codeword = codewords[codewordsRow]; + codeword.setRowNumberAsRowIndicatorColumn(); + let rowDifference = codeword.getRowNumber() - barcodeRow; + // TODO improve handling with case where first row indicator doesn't start with 0 + if (rowDifference === 0) ; + else if (rowDifference === 1) { + barcodeRow = codeword.getRowNumber(); + } + else if (codeword.getRowNumber() >= barcodeMetadata.getRowCount()) { + codewords[codewordsRow] = null; + } + else { + barcodeRow = codeword.getRowNumber(); + } + } + // return (int) (averageRowHeight + 0.5); + } + getBarcodeMetadata() { + let codewords = this.getCodewords(); + let barcodeColumnCount = new BarcodeValue(); + let barcodeRowCountUpperPart = new BarcodeValue(); + let barcodeRowCountLowerPart = new BarcodeValue(); + let barcodeECLevel = new BarcodeValue(); + for (let codeword /*Codeword*/ of codewords) { + if (codeword == null) { + continue; + } + codeword.setRowNumberAsRowIndicatorColumn(); + let rowIndicatorValue = codeword.getValue() % 30; + let codewordRowNumber = codeword.getRowNumber(); + if (!this._isLeft) { + codewordRowNumber += 2; + } + switch (codewordRowNumber % 3) { + case 0: + barcodeRowCountUpperPart.setValue(rowIndicatorValue * 3 + 1); + break; + case 1: + barcodeECLevel.setValue(rowIndicatorValue / 3); + barcodeRowCountLowerPart.setValue(rowIndicatorValue % 3); + break; + case 2: + barcodeColumnCount.setValue(rowIndicatorValue + 1); + break; + } + } + // Maybe we should check if we have ambiguous values? + if ((barcodeColumnCount.getValue().length === 0) || + (barcodeRowCountUpperPart.getValue().length === 0) || + (barcodeRowCountLowerPart.getValue().length === 0) || + (barcodeECLevel.getValue().length === 0) || + barcodeColumnCount.getValue()[0] < 1 || + barcodeRowCountUpperPart.getValue()[0] + barcodeRowCountLowerPart.getValue()[0] < PDF417Common.MIN_ROWS_IN_BARCODE || + barcodeRowCountUpperPart.getValue()[0] + barcodeRowCountLowerPart.getValue()[0] > PDF417Common.MAX_ROWS_IN_BARCODE) { + return null; + } + let barcodeMetadata = new BarcodeMetadata(barcodeColumnCount.getValue()[0], barcodeRowCountUpperPart.getValue()[0], barcodeRowCountLowerPart.getValue()[0], barcodeECLevel.getValue()[0]); + this.removeIncorrectCodewords(codewords, barcodeMetadata); + return barcodeMetadata; + } + removeIncorrectCodewords(codewords, barcodeMetadata) { + // Remove codewords which do not match the metadata + // TODO Maybe we should keep the incorrect codewords for the start and end positions? + for (let codewordRow /*int*/ = 0; codewordRow < codewords.length; codewordRow++) { + let codeword = codewords[codewordRow]; + if (codewords[codewordRow] == null) { + continue; + } + let rowIndicatorValue = codeword.getValue() % 30; + let codewordRowNumber = codeword.getRowNumber(); + if (codewordRowNumber > barcodeMetadata.getRowCount()) { + codewords[codewordRow] = null; + continue; + } + if (!this._isLeft) { + codewordRowNumber += 2; + } + switch (codewordRowNumber % 3) { + case 0: + if (rowIndicatorValue * 3 + 1 !== barcodeMetadata.getRowCountUpperPart()) { + codewords[codewordRow] = null; + } + break; + case 1: + if (Math.trunc(rowIndicatorValue / 3) !== barcodeMetadata.getErrorCorrectionLevel() || + rowIndicatorValue % 3 !== barcodeMetadata.getRowCountLowerPart()) { + codewords[codewordRow] = null; + } + break; + case 2: + if (rowIndicatorValue + 1 !== barcodeMetadata.getColumnCount()) { + codewords[codewordRow] = null; + } + break; + } + } + } + isLeft() { + return this._isLeft; + } + // @Override + toString() { + return 'IsLeft: ' + this._isLeft + '\n' + super.toString(); + } + } + + /* + * Copyright 2013 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. + */ + /** + * @author Guenther Grau + */ + /*final*/ class DetectionResult { + constructor(barcodeMetadata, boundingBox) { + /*final*/ this.ADJUST_ROW_NUMBER_SKIP = 2; + this.barcodeMetadata = barcodeMetadata; + this.barcodeColumnCount = barcodeMetadata.getColumnCount(); + this.boundingBox = boundingBox; + // this.detectionResultColumns = new DetectionResultColumn[this.barcodeColumnCount + 2]; + this.detectionResultColumns = new Array(this.barcodeColumnCount + 2); + } + getDetectionResultColumns() { + this.adjustIndicatorColumnRowNumbers(this.detectionResultColumns[0]); + this.adjustIndicatorColumnRowNumbers(this.detectionResultColumns[this.barcodeColumnCount + 1]); + let unadjustedCodewordCount = PDF417Common.MAX_CODEWORDS_IN_BARCODE; + let previousUnadjustedCount; + do { + previousUnadjustedCount = unadjustedCodewordCount; + unadjustedCodewordCount = this.adjustRowNumbersAndGetCount(); + } while (unadjustedCodewordCount > 0 && unadjustedCodewordCount < previousUnadjustedCount); + return this.detectionResultColumns; + } + adjustIndicatorColumnRowNumbers(detectionResultColumn) { + if (detectionResultColumn != null) { + detectionResultColumn + .adjustCompleteIndicatorColumnRowNumbers(this.barcodeMetadata); + } + } + // TODO ensure that no detected codewords with unknown row number are left + // we should be able to estimate the row height and use it as a hint for the row number + // we should also fill the rows top to bottom and bottom to top + /** + * @return number of codewords which don't have a valid row number. Note that the count is not accurate as codewords + * will be counted several times. It just serves as an indicator to see when we can stop adjusting row numbers + */ + adjustRowNumbersAndGetCount() { + let unadjustedCount = this.adjustRowNumbersByRow(); + if (unadjustedCount === 0) { + return 0; + } + for (let barcodeColumn /*int*/ = 1; barcodeColumn < this.barcodeColumnCount + 1; barcodeColumn++) { + let codewords = this.detectionResultColumns[barcodeColumn].getCodewords(); + for (let codewordsRow /*int*/ = 0; codewordsRow < codewords.length; codewordsRow++) { + if (codewords[codewordsRow] == null) { + continue; + } + if (!codewords[codewordsRow].hasValidRowNumber()) { + this.adjustRowNumbers(barcodeColumn, codewordsRow, codewords); + } + } + } + return unadjustedCount; + } + adjustRowNumbersByRow() { + this.adjustRowNumbersFromBothRI(); + // TODO we should only do full row adjustments if row numbers of left and right row indicator column match. + // Maybe it's even better to calculated the height (rows: d) and divide it by the number of barcode + // rows. This, together with the LRI and RRI row numbers should allow us to get a good estimate where a row + // number starts and ends. + let unadjustedCount = this.adjustRowNumbersFromLRI(); + return unadjustedCount + this.adjustRowNumbersFromRRI(); + } + adjustRowNumbersFromBothRI() { + if (this.detectionResultColumns[0] == null || this.detectionResultColumns[this.barcodeColumnCount + 1] == null) { + return; + } + let LRIcodewords = this.detectionResultColumns[0].getCodewords(); + let RRIcodewords = this.detectionResultColumns[this.barcodeColumnCount + 1].getCodewords(); + for (let codewordsRow /*int*/ = 0; codewordsRow < LRIcodewords.length; codewordsRow++) { + if (LRIcodewords[codewordsRow] != null && + RRIcodewords[codewordsRow] != null && + LRIcodewords[codewordsRow].getRowNumber() === RRIcodewords[codewordsRow].getRowNumber()) { + for (let barcodeColumn /*int*/ = 1; barcodeColumn <= this.barcodeColumnCount; barcodeColumn++) { + let codeword = this.detectionResultColumns[barcodeColumn].getCodewords()[codewordsRow]; + if (codeword == null) { + continue; + } + codeword.setRowNumber(LRIcodewords[codewordsRow].getRowNumber()); + if (!codeword.hasValidRowNumber()) { + this.detectionResultColumns[barcodeColumn].getCodewords()[codewordsRow] = null; + } + } + } + } + } + adjustRowNumbersFromRRI() { + if (this.detectionResultColumns[this.barcodeColumnCount + 1] == null) { + return 0; + } + let unadjustedCount = 0; + let codewords = this.detectionResultColumns[this.barcodeColumnCount + 1].getCodewords(); + for (let codewordsRow /*int*/ = 0; codewordsRow < codewords.length; codewordsRow++) { + if (codewords[codewordsRow] == null) { + continue; + } + let rowIndicatorRowNumber = codewords[codewordsRow].getRowNumber(); + let invalidRowCounts = 0; + for (let barcodeColumn /*int*/ = this.barcodeColumnCount + 1; barcodeColumn > 0 && invalidRowCounts < this.ADJUST_ROW_NUMBER_SKIP; barcodeColumn--) { + let codeword = this.detectionResultColumns[barcodeColumn].getCodewords()[codewordsRow]; + if (codeword != null) { + invalidRowCounts = DetectionResult.adjustRowNumberIfValid(rowIndicatorRowNumber, invalidRowCounts, codeword); + if (!codeword.hasValidRowNumber()) { + unadjustedCount++; + } + } + } + } + return unadjustedCount; + } + adjustRowNumbersFromLRI() { + if (this.detectionResultColumns[0] == null) { + return 0; + } + let unadjustedCount = 0; + let codewords = this.detectionResultColumns[0].getCodewords(); + for (let codewordsRow /*int*/ = 0; codewordsRow < codewords.length; codewordsRow++) { + if (codewords[codewordsRow] == null) { + continue; + } + let rowIndicatorRowNumber = codewords[codewordsRow].getRowNumber(); + let invalidRowCounts = 0; + for (let barcodeColumn /*int*/ = 1; barcodeColumn < this.barcodeColumnCount + 1 && invalidRowCounts < this.ADJUST_ROW_NUMBER_SKIP; barcodeColumn++) { + let codeword = this.detectionResultColumns[barcodeColumn].getCodewords()[codewordsRow]; + if (codeword != null) { + invalidRowCounts = DetectionResult.adjustRowNumberIfValid(rowIndicatorRowNumber, invalidRowCounts, codeword); + if (!codeword.hasValidRowNumber()) { + unadjustedCount++; + } + } + } + } + return unadjustedCount; + } + static adjustRowNumberIfValid(rowIndicatorRowNumber, invalidRowCounts, codeword) { + if (codeword == null) { + return invalidRowCounts; + } + if (!codeword.hasValidRowNumber()) { + if (codeword.isValidRowNumber(rowIndicatorRowNumber)) { + codeword.setRowNumber(rowIndicatorRowNumber); + invalidRowCounts = 0; + } + else { + ++invalidRowCounts; + } + } + return invalidRowCounts; + } + adjustRowNumbers(barcodeColumn, codewordsRow, codewords) { + let codeword = codewords[codewordsRow]; + let previousColumnCodewords = this.detectionResultColumns[barcodeColumn - 1].getCodewords(); + let nextColumnCodewords = previousColumnCodewords; + if (this.detectionResultColumns[barcodeColumn + 1] != null) { + nextColumnCodewords = this.detectionResultColumns[barcodeColumn + 1].getCodewords(); + } + // let otherCodewords: Codeword[] = new Codeword[14]; + let otherCodewords = new Array(14); + otherCodewords[2] = previousColumnCodewords[codewordsRow]; + otherCodewords[3] = nextColumnCodewords[codewordsRow]; + if (codewordsRow > 0) { + otherCodewords[0] = codewords[codewordsRow - 1]; + otherCodewords[4] = previousColumnCodewords[codewordsRow - 1]; + otherCodewords[5] = nextColumnCodewords[codewordsRow - 1]; + } + if (codewordsRow > 1) { + otherCodewords[8] = codewords[codewordsRow - 2]; + otherCodewords[10] = previousColumnCodewords[codewordsRow - 2]; + otherCodewords[11] = nextColumnCodewords[codewordsRow - 2]; + } + if (codewordsRow < codewords.length - 1) { + otherCodewords[1] = codewords[codewordsRow + 1]; + otherCodewords[6] = previousColumnCodewords[codewordsRow + 1]; + otherCodewords[7] = nextColumnCodewords[codewordsRow + 1]; + } + if (codewordsRow < codewords.length - 2) { + otherCodewords[9] = codewords[codewordsRow + 2]; + otherCodewords[12] = previousColumnCodewords[codewordsRow + 2]; + otherCodewords[13] = nextColumnCodewords[codewordsRow + 2]; + } + for (let otherCodeword of otherCodewords) { + if (DetectionResult.adjustRowNumber(codeword, otherCodeword)) { + return; + } + } + } + /** + * @return true, if row number was adjusted, false otherwise + */ + static adjustRowNumber(codeword, otherCodeword) { + if (otherCodeword == null) { + return false; + } + if (otherCodeword.hasValidRowNumber() && otherCodeword.getBucket() === codeword.getBucket()) { + codeword.setRowNumber(otherCodeword.getRowNumber()); + return true; + } + return false; + } + getBarcodeColumnCount() { + return this.barcodeColumnCount; + } + getBarcodeRowCount() { + return this.barcodeMetadata.getRowCount(); + } + getBarcodeECLevel() { + return this.barcodeMetadata.getErrorCorrectionLevel(); + } + setBoundingBox(boundingBox) { + this.boundingBox = boundingBox; + } + getBoundingBox() { + return this.boundingBox; + } + setDetectionResultColumn(barcodeColumn, detectionResultColumn) { + this.detectionResultColumns[barcodeColumn] = detectionResultColumn; + } + getDetectionResultColumn(barcodeColumn) { + return this.detectionResultColumns[barcodeColumn]; + } + // @Override + toString() { + let rowIndicatorColumn = this.detectionResultColumns[0]; + if (rowIndicatorColumn == null) { + rowIndicatorColumn = this.detectionResultColumns[this.barcodeColumnCount + 1]; + } + // try ( + let formatter = new Formatter(); + // ) { + for (let codewordsRow /*int*/ = 0; codewordsRow < rowIndicatorColumn.getCodewords().length; codewordsRow++) { + formatter.format('CW %3d:', codewordsRow); + for (let barcodeColumn /*int*/ = 0; barcodeColumn < this.barcodeColumnCount + 2; barcodeColumn++) { + if (this.detectionResultColumns[barcodeColumn] == null) { + formatter.format(' | '); + continue; + } + let codeword = this.detectionResultColumns[barcodeColumn].getCodewords()[codewordsRow]; + if (codeword == null) { + formatter.format(' | '); + continue; + } + formatter.format(' %3d|%3d', codeword.getRowNumber(), codeword.getValue()); + } + formatter.format('%n'); + } + return formatter.toString(); + // } + } + } + + /* + * Copyright 2013 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. + */ + // package com.google.zxing.pdf417.decoder; + /** + * @author Guenther Grau + */ + /*final*/ class Codeword { + constructor(startX, endX, bucket, value) { + this.rowNumber = Codeword.BARCODE_ROW_UNKNOWN; + this.startX = Math.trunc(startX); + this.endX = Math.trunc(endX); + this.bucket = Math.trunc(bucket); + this.value = Math.trunc(value); + } + hasValidRowNumber() { + return this.isValidRowNumber(this.rowNumber); + } + isValidRowNumber(rowNumber) { + return rowNumber !== Codeword.BARCODE_ROW_UNKNOWN && this.bucket === (rowNumber % 3) * 3; + } + setRowNumberAsRowIndicatorColumn() { + this.rowNumber = Math.trunc((Math.trunc(this.value / 30)) * 3 + Math.trunc(this.bucket / 3)); + } + getWidth() { + return this.endX - this.startX; + } + getStartX() { + return this.startX; + } + getEndX() { + return this.endX; + } + getBucket() { + return this.bucket; + } + getValue() { + return this.value; + } + getRowNumber() { + return this.rowNumber; + } + setRowNumber(rowNumber) { + this.rowNumber = rowNumber; + } + // @Override + toString() { + return this.rowNumber + '|' + this.value; + } + } + Codeword.BARCODE_ROW_UNKNOWN = -1; + + /* + * Copyright 2013 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. + */ + /** + * @author Guenther Grau + * @author creatale GmbH (christoph.schulz@creatale.de) + */ + /*final*/ class PDF417CodewordDecoder { + /* @note + * this action have to be performed before first use of class + * - static constructor + * working with 32bit float (based from Java logic) + */ + static initialize() { + // Pre-computes the symbol ratio table. + for ( /*int*/let i = 0; i < PDF417Common.SYMBOL_TABLE.length; i++) { + let currentSymbol = PDF417Common.SYMBOL_TABLE[i]; + let currentBit = currentSymbol & 0x1; + for ( /*int*/let j = 0; j < PDF417Common.BARS_IN_MODULE; j++) { + let size = 0.0; + while ((currentSymbol & 0x1) === currentBit) { + size += 1.0; + currentSymbol >>= 1; + } + currentBit = currentSymbol & 0x1; + if (!PDF417CodewordDecoder.RATIOS_TABLE[i]) { + PDF417CodewordDecoder.RATIOS_TABLE[i] = new Array(PDF417Common.BARS_IN_MODULE); + } + PDF417CodewordDecoder.RATIOS_TABLE[i][PDF417Common.BARS_IN_MODULE - j - 1] = Math.fround(size / PDF417Common.MODULES_IN_CODEWORD); + } + } + this.bSymbolTableReady = true; + } + static getDecodedValue(moduleBitCount) { + let decodedValue = PDF417CodewordDecoder.getDecodedCodewordValue(PDF417CodewordDecoder.sampleBitCounts(moduleBitCount)); + if (decodedValue !== -1) { + return decodedValue; + } + return PDF417CodewordDecoder.getClosestDecodedValue(moduleBitCount); + } + static sampleBitCounts(moduleBitCount) { + let bitCountSum = MathUtils.sum(moduleBitCount); + let result = new Int32Array(PDF417Common.BARS_IN_MODULE); + let bitCountIndex = 0; + let sumPreviousBits = 0; + for ( /*int*/let i = 0; i < PDF417Common.MODULES_IN_CODEWORD; i++) { + let sampleIndex = bitCountSum / (2 * PDF417Common.MODULES_IN_CODEWORD) + + (i * bitCountSum) / PDF417Common.MODULES_IN_CODEWORD; + if (sumPreviousBits + moduleBitCount[bitCountIndex] <= sampleIndex) { + sumPreviousBits += moduleBitCount[bitCountIndex]; + bitCountIndex++; + } + result[bitCountIndex]++; + } + return result; + } + static getDecodedCodewordValue(moduleBitCount) { + let decodedValue = PDF417CodewordDecoder.getBitValue(moduleBitCount); + return PDF417Common.getCodeword(decodedValue) === -1 ? -1 : decodedValue; + } + static getBitValue(moduleBitCount) { + let result = /*long*/ 0; + for (let /*int*/ i = 0; i < moduleBitCount.length; i++) { + for ( /*int*/let bit = 0; bit < moduleBitCount[i]; bit++) { + result = (result << 1) | (i % 2 === 0 ? 1 : 0); + } + } + return Math.trunc(result); + } + // working with 32bit float (as in Java) + static getClosestDecodedValue(moduleBitCount) { + let bitCountSum = MathUtils.sum(moduleBitCount); + let bitCountRatios = new Array(PDF417Common.BARS_IN_MODULE); + if (bitCountSum > 1) { + for (let /*int*/ i = 0; i < bitCountRatios.length; i++) { + bitCountRatios[i] = Math.fround(moduleBitCount[i] / bitCountSum); + } + } + let bestMatchError = Float.MAX_VALUE; + let bestMatch = -1; + if (!this.bSymbolTableReady) { + PDF417CodewordDecoder.initialize(); + } + for ( /*int*/let j = 0; j < PDF417CodewordDecoder.RATIOS_TABLE.length; j++) { + let error = 0.0; + let ratioTableRow = PDF417CodewordDecoder.RATIOS_TABLE[j]; + for ( /*int*/let k = 0; k < PDF417Common.BARS_IN_MODULE; k++) { + let diff = Math.fround(ratioTableRow[k] - bitCountRatios[k]); + error += Math.fround(diff * diff); + if (error >= bestMatchError) { + break; + } + } + if (error < bestMatchError) { + bestMatchError = error; + bestMatch = PDF417Common.SYMBOL_TABLE[j]; + } + } + return bestMatch; + } + } + // flag that the table is ready for use + PDF417CodewordDecoder.bSymbolTableReady = false; + PDF417CodewordDecoder.RATIOS_TABLE = new Array(PDF417Common.SYMBOL_TABLE.length).map(x => x = new Array(PDF417Common.BARS_IN_MODULE)); + + /* + * Copyright 2013 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. + */ + // package com.google.zxing.pdf417; + /** + * @author Guenther Grau + */ + /*public final*/ class PDF417ResultMetadata { + constructor() { + this.segmentCount = -1; + this.fileSize = -1; + this.timestamp = -1; + this.checksum = -1; + } + /** + * The Segment ID represents the segment of the whole file distributed over different symbols. + * + * @return File segment index + */ + getSegmentIndex() { + return this.segmentIndex; + } + setSegmentIndex(segmentIndex) { + this.segmentIndex = segmentIndex; + } + /** + * Is the same for each related PDF417 symbol + * + * @return File ID + */ + getFileId() { + return this.fileId; + } + setFileId(fileId) { + this.fileId = fileId; + } + /** + * @return always null + * @deprecated use dedicated already parsed fields + */ + // @Deprecated + getOptionalData() { + return this.optionalData; + } + /** + * @param optionalData old optional data format as int array + * @deprecated parse and use new fields + */ + // @Deprecated + setOptionalData(optionalData) { + this.optionalData = optionalData; + } + /** + * @return true if it is the last segment + */ + isLastSegment() { + return this.lastSegment; + } + setLastSegment(lastSegment) { + this.lastSegment = lastSegment; + } + /** + * @return count of segments, -1 if not set + */ + getSegmentCount() { + return this.segmentCount; + } + setSegmentCount(segmentCount /*int*/) { + this.segmentCount = segmentCount; + } + getSender() { + return this.sender || null; + } + setSender(sender) { + this.sender = sender; + } + getAddressee() { + return this.addressee || null; + } + setAddressee(addressee) { + this.addressee = addressee; + } + /** + * Filename of the encoded file + * + * @return filename + */ + getFileName() { + return this.fileName; + } + setFileName(fileName) { + this.fileName = fileName; + } + /** + * filesize in bytes of the encoded file + * + * @return filesize in bytes, -1 if not set + */ + getFileSize() { + return this.fileSize; + } + setFileSize(fileSize /*long*/) { + this.fileSize = fileSize; + } + /** + * 16-bit CRC checksum using CCITT-16 + * + * @return crc checksum, -1 if not set + */ + getChecksum() { + return this.checksum; + } + setChecksum(checksum /*int*/) { + this.checksum = checksum; + } + /** + * unix epock timestamp, elapsed seconds since 1970-01-01 + * + * @return elapsed seconds, -1 if not set + */ + getTimestamp() { + return this.timestamp; + } + setTimestamp(timestamp /*long*/) { + this.timestamp = timestamp; + } + } + + /** + * Ponyfill for Java's Long class. + */ + class Long { + /** + * Parses a string to a number, since JS has no really Int64. + * + * @param num Numeric string. + * @param radix Destination radix. + */ + static parseLong(num, radix = undefined) { + return parseInt(num, radix); + } + } + + /** + * Custom Error class of type Exception. + */ + class NullPointerException extends Exception { + } + NullPointerException.kind = 'NullPointerException'; + + /* + * Copyright (c) 1994, 2004, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + // package java.io; + /** + * This abstract class is the superclass of all classes representing + * an output stream of bytes. An output stream accepts output bytes + * and sends them to some sink. + *
+ * Applications that need to define a subclass of
+ * OutputStream
must always provide at least a method
+ * that writes one byte of output.
+ *
+ * @author Arthur van Hoff
+ * @see java.io.BufferedOutputStream
+ * @see java.io.ByteArrayOutputStream
+ * @see java.io.DataOutputStream
+ * @see java.io.FilterOutputStream
+ * @see java.io.InputStream
+ * @see java.io.OutputStream#write(int)
+ * @since JDK1.0
+ */
+ /*public*/ class OutputStream /*implements Closeable, Flushable*/ {
+ /**
+ * Writes b.length
bytes from the specified byte array
+ * to this output stream. The general contract for write(b)
+ * is that it should have exactly the same effect as the call
+ * write(b, 0, b.length)
.
+ *
+ * @param b the data.
+ * @exception IOException if an I/O error occurs.
+ * @see java.io.OutputStream#write(byte[], int, int)
+ */
+ writeBytes(b) {
+ this.writeBytesOffset(b, 0, b.length);
+ }
+ /**
+ * Writes len
bytes from the specified byte array
+ * starting at offset off
to this output stream.
+ * The general contract for write(b, off, len)
is that
+ * some of the bytes in the array b
are written to the
+ * output stream in order; element b[off]
is the first
+ * byte written and b[off+len-1]
is the last byte written
+ * by this operation.
+ *
+ * The write
method of OutputStream
calls
+ * the write method of one argument on each of the bytes to be
+ * written out. Subclasses are encouraged to override this method and
+ * provide a more efficient implementation.
+ *
+ * If b
is null
, a
+ * NullPointerException
is thrown.
+ *
+ * If off
is negative, or len
is negative, or
+ * off+len
is greater than the length of the array
+ * b
, then an IndexOutOfBoundsException is thrown.
+ *
+ * @param b the data.
+ * @param off the start offset in the data.
+ * @param len the number of bytes to write.
+ * @exception IOException if an I/O error occurs. In particular,
+ * an IOException
is thrown if the output
+ * stream is closed.
+ */
+ writeBytesOffset(b, off, len) {
+ if (b == null) {
+ throw new NullPointerException();
+ }
+ else if ((off < 0) || (off > b.length) || (len < 0) ||
+ ((off + len) > b.length) || ((off + len) < 0)) {
+ throw new IndexOutOfBoundsException();
+ }
+ else if (len === 0) {
+ return;
+ }
+ for (let i = 0; i < len; i++) {
+ this.write(b[off + i]);
+ }
+ }
+ /**
+ * Flushes this output stream and forces any buffered output bytes
+ * to be written out. The general contract of flush
is
+ * that calling it is an indication that, if any bytes previously
+ * written have been buffered by the implementation of the output
+ * stream, such bytes should immediately be written to their
+ * intended destination.
+ *
+ * If the intended destination of this stream is an abstraction provided by + * the underlying operating system, for example a file, then flushing the + * stream guarantees only that bytes previously written to the stream are + * passed to the operating system for writing; it does not guarantee that + * they are actually written to a physical device such as a disk drive. + *
+ * The flush
method of OutputStream
does nothing.
+ *
+ * @exception IOException if an I/O error occurs.
+ */
+ flush() {
+ }
+ /**
+ * Closes this output stream and releases any system resources
+ * associated with this stream. The general contract of close
+ * is that it closes the output stream. A closed stream cannot perform
+ * output operations and cannot be reopened.
+ *
+ * The close
method of OutputStream
does nothing.
+ *
+ * @exception IOException if an I/O error occurs.
+ */
+ close() {
+ }
+ }
+
+ /**
+ * Custom Error class of type Exception.
+ */
+ class OutOfMemoryError extends Exception {
+ }
+
+ /*
+ * Copyright (c) 1994, 2010, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+ /**
+ * This class implements an output stream in which the data is
+ * written into a byte array. The buffer automatically grows as data
+ * is written to it.
+ * The data can be retrieved using toByteArray()
and
+ * toString()
.
+ *
+ * Closing a ByteArrayOutputStream has no effect. The methods in
+ * this class can be called after the stream has been closed without
+ * generating an IOException.
+ *
+ * @author Arthur van Hoff
+ * @since JDK1.0
+ */
+ /*public*/ class ByteArrayOutputStream extends OutputStream {
+ /**
+ * Creates a new byte array output stream. The buffer capacity is
+ * initially 32 bytes, though its size increases if necessary.
+ */
+ // public constructor() {
+ // this(32);
+ // }
+ /**
+ * Creates a new byte array output stream, with a buffer capacity of
+ * the specified size, in bytes.
+ *
+ * @param size the initial size.
+ * @exception IllegalArgumentException if size is negative.
+ */
+ constructor(size = 32) {
+ super();
+ /**
+ * The number of valid bytes in the buffer.
+ */
+ this.count = 0;
+ if (size < 0) {
+ throw new IllegalArgumentException('Negative initial size: '
+ + size);
+ }
+ this.buf = new Uint8Array(size);
+ }
+ /**
+ * Increases the capacity if necessary to ensure that it can hold
+ * at least the number of elements specified by the minimum
+ * capacity argument.
+ *
+ * @param minCapacity the desired minimum capacity
+ * @throws OutOfMemoryError if {@code minCapacity < 0}. This is
+ * interpreted as a request for the unsatisfiably large capacity
+ * {@code (long) Integer.MAX_VALUE + (minCapacity - Integer.MAX_VALUE)}.
+ */
+ ensureCapacity(minCapacity) {
+ // overflow-conscious code
+ if (minCapacity - this.buf.length > 0)
+ this.grow(minCapacity);
+ }
+ /**
+ * Increases the capacity to ensure that it can hold at least the
+ * number of elements specified by the minimum capacity argument.
+ *
+ * @param minCapacity the desired minimum capacity
+ */
+ grow(minCapacity) {
+ // overflow-conscious code
+ let oldCapacity = this.buf.length;
+ let newCapacity = oldCapacity << 1;
+ if (newCapacity - minCapacity < 0)
+ newCapacity = minCapacity;
+ if (newCapacity < 0) {
+ if (minCapacity < 0) // overflow
+ throw new OutOfMemoryError();
+ newCapacity = Integer.MAX_VALUE;
+ }
+ this.buf = Arrays.copyOfUint8Array(this.buf, newCapacity);
+ }
+ /**
+ * Writes the specified byte to this byte array output stream.
+ *
+ * @param b the byte to be written.
+ */
+ write(b) {
+ this.ensureCapacity(this.count + 1);
+ this.buf[this.count] = /*(byte)*/ b;
+ this.count += 1;
+ }
+ /**
+ * Writes len
bytes from the specified byte array
+ * starting at offset off
to this byte array output stream.
+ *
+ * @param b the data.
+ * @param off the start offset in the data.
+ * @param len the number of bytes to write.
+ */
+ writeBytesOffset(b, off, len) {
+ if ((off < 0) || (off > b.length) || (len < 0) ||
+ ((off + len) - b.length > 0)) {
+ throw new IndexOutOfBoundsException();
+ }
+ this.ensureCapacity(this.count + len);
+ System.arraycopy(b, off, this.buf, this.count, len);
+ this.count += len;
+ }
+ /**
+ * Writes the complete contents of this byte array output stream to
+ * the specified output stream argument, as if by calling the output
+ * stream's write method using out.write(buf, 0, count)
.
+ *
+ * @param out the output stream to which to write the data.
+ * @exception IOException if an I/O error occurs.
+ */
+ writeTo(out) {
+ out.writeBytesOffset(this.buf, 0, this.count);
+ }
+ /**
+ * Resets the count
field of this byte array output
+ * stream to zero, so that all currently accumulated output in the
+ * output stream is discarded. The output stream can be used again,
+ * reusing the already allocated buffer space.
+ *
+ * @see java.io.ByteArrayInputStream#count
+ */
+ reset() {
+ this.count = 0;
+ }
+ /**
+ * Creates a newly allocated byte array. Its size is the current
+ * size of this output stream and the valid contents of the buffer
+ * have been copied into it.
+ *
+ * @return the current contents of this output stream, as a byte array.
+ * @see java.io.ByteArrayOutputStream#size()
+ */
+ toByteArray() {
+ return Arrays.copyOfUint8Array(this.buf, this.count);
+ }
+ /**
+ * Returns the current size of the buffer.
+ *
+ * @return the value of the count
field, which is the number
+ * of valid bytes in this output stream.
+ * @see java.io.ByteArrayOutputStream#count
+ */
+ size() {
+ return this.count;
+ }
+ toString(param) {
+ if (!param) {
+ return this.toString_void();
+ }
+ if (typeof param === 'string') {
+ return this.toString_string(param);
+ }
+ return this.toString_number(param);
+ }
+ /**
+ * Converts the buffer's contents into a string decoding bytes using the
+ * platform's default character set. The length of the new String
+ * is a function of the character set, and hence may not be equal to the
+ * size of the buffer.
+ *
+ *
This method always replaces malformed-input and unmappable-character + * sequences with the default replacement string for the platform's + * default character set. The {@linkplain java.nio.charset.CharsetDecoder} + * class should be used when more control over the decoding process is + * required. + * + * @return String decoded from the buffer's contents. + * @since JDK1.1 + */ + toString_void() { + return new String(this.buf /*, 0, this.count*/).toString(); + } + /** + * Converts the buffer's contents into a string by decoding the bytes using + * the specified {@link java.nio.charset.Charset charsetName}. The length of + * the new String is a function of the charset, and hence may not be + * equal to the length of the byte array. + * + *
This method always replaces malformed-input and unmappable-character
+ * sequences with this charset's default replacement string. The {@link
+ * java.nio.charset.CharsetDecoder} class should be used when more control
+ * over the decoding process is required.
+ *
+ * @param charsetName the name of a supported
+ * {@linkplain java.nio.charset.Charset charset
+ *
+ * @throws IOException
+ */
+ close() {
+ }
+ }
+
+ /*
+ * Copyright 2009 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.
+ */
+ /*private*/ var Mode$2;
+ (function (Mode) {
+ Mode[Mode["ALPHA"] = 0] = "ALPHA";
+ Mode[Mode["LOWER"] = 1] = "LOWER";
+ Mode[Mode["MIXED"] = 2] = "MIXED";
+ Mode[Mode["PUNCT"] = 3] = "PUNCT";
+ Mode[Mode["ALPHA_SHIFT"] = 4] = "ALPHA_SHIFT";
+ Mode[Mode["PUNCT_SHIFT"] = 5] = "PUNCT_SHIFT";
+ })(Mode$2 || (Mode$2 = {}));
+ /**
+ * Indirectly access the global BigInt constructor, it
+ * allows browsers that doesn't support BigInt to run
+ * the library without breaking due to "undefined BigInt"
+ * errors.
+ */
+ function getBigIntConstructor() {
+ if (typeof window !== 'undefined') {
+ return window['BigInt'] || null;
+ }
+ if (typeof global !== 'undefined') {
+ return global['BigInt'] || null;
+ }
+ if (typeof self !== 'undefined') {
+ return self['BigInt'] || null;
+ }
+ throw new Error('Can\'t search globals for BigInt!');
+ }
+ /**
+ * Used to store the BigInt constructor.
+ */
+ let BigInteger;
+ /**
+ * This function creates a bigint value. It allows browsers
+ * that doesn't support BigInt to run the rest of the library
+ * by not directly accessing the BigInt constructor.
+ */
+ function createBigInt(num) {
+ if (typeof BigInteger === 'undefined') {
+ BigInteger = getBigIntConstructor();
+ }
+ if (BigInteger === null) {
+ throw new Error('BigInt is not supported!');
+ }
+ return BigInteger(num);
+ }
+ function getEXP900() {
+ // in Java - array with length = 16
+ let EXP900 = [];
+ EXP900[0] = createBigInt(1);
+ let nineHundred = createBigInt(900);
+ EXP900[1] = nineHundred;
+ // in Java - array with length = 16
+ for (let i /*int*/ = 2; i < 16; i++) {
+ EXP900[i] = EXP900[i - 1] * nineHundred;
+ }
+ return EXP900;
+ }
+ /**
+ * This class contains the methods for decoding the PDF417 codewords. Given data and error-correction codewords received, possibly corrupted by errors, attempts to
+ * correct the errors in-place. Implements Reed-Solomon encoding, as the name implies. Encode a sequence of code words (symbols) using Reed-Solomon to allow decoders
+ * to detect and correct errors that may have been introduced when the resulting
+ * data is stored or transmitted.}
+ * @return String decoded from the buffer's contents.
+ * @exception UnsupportedEncodingException
+ * If the named charset is not supported
+ * @since JDK1.1
+ */
+ toString_string(charsetName) {
+ return new String(this.buf /*, 0, this.count, charsetName*/).toString();
+ }
+ /**
+ * Creates a newly allocated string. Its size is the current size of
+ * the output stream and the valid contents of the buffer have been
+ * copied into it. Each character c in the resulting string is
+ * constructed from the corresponding element b in the byte
+ * array such that:
+ *
+ *
+ * @deprecated This method does not properly convert bytes into characters.
+ * As of JDK 1.1, the preferred way to do this is via the
+ *
+ * c == (char)(((hibyte & 0xff) << 8) | (b & 0xff))
+ *
toString(String enc)
method, which takes an encoding-name
+ * argument, or the toString()
method, which uses the
+ * platform's default character encoding.
+ *
+ * @param hibyte the high byte of each resulting Unicode character.
+ * @return the current contents of the output stream, as a string.
+ * @see java.io.ByteArrayOutputStream#size()
+ * @see java.io.ByteArrayOutputStream#toString(String)
+ * @see java.io.ByteArrayOutputStream#toString()
+ */
+ // @Deprecated
+ toString_number(hibyte) {
+ return new String(this.buf /*, hibyte, 0, this.count*/).toString();
+ }
+ /**
+ * Closing a ByteArrayOutputStream has no effect. The methods in
+ * this class can be called after the stream has been closed without
+ * generating an IOException.
+ *