/* * 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 DecoderResult from '../../common/DecoderResult'; import GenericGF from '../../common/reedsolomon/GenericGF'; import ReedSolomonDecoder from '../../common/reedsolomon/ReedSolomonDecoder'; import IllegalStateException from '../../IllegalStateException'; import FormatException from '../../FormatException'; import StringUtils from '../../common/StringUtils'; import Integer from '../../util/Integer'; // 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 */ export default 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 is