diff --git a/Cargo.toml b/Cargo.toml index d4c2831..27b8157 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ imageproc = {version = "0.23.0", optional = true} unicode-segmentation = "1.10.0" codepage-437 = "0.1.0" one-d-reader-derive = { path = "one-d-reader-derive" } +num = "0.4.0" [dev-dependencies] java-properties = "1.4.1" diff --git a/src/common/decoder_rxing_result.rs b/src/common/decoder_rxing_result.rs index e688a76..e5f197d 100644 --- a/src/common/decoder_rxing_result.rs +++ b/src/common/decoder_rxing_result.rs @@ -33,8 +33,8 @@ pub struct DecoderRXingResult { text: String, byteSegments: Vec>, ecLevel: String, - errorsCorrected: u64, - erasures: u64, + errorsCorrected: usize, + erasures: usize, other: Rc, structuredAppendParity: i32, structuredAppendSequenceNumber: i32, @@ -160,22 +160,22 @@ impl DecoderRXingResult { /** * @return number of errors corrected, or {@code null} if not applicable */ - pub fn getErrorsCorrected(&self) -> u64 { + pub fn getErrorsCorrected(&self) -> usize { self.errorsCorrected } - pub fn setErrorsCorrected(&mut self, errorsCorrected: u64) { + pub fn setErrorsCorrected(&mut self, errorsCorrected: usize) { self.errorsCorrected = errorsCorrected; } /** * @return number of erasures corrected, or {@code null} if not applicable */ - pub fn getErasures(&self) -> u64 { + pub fn getErasures(&self) -> usize { self.erasures } - pub fn setErasures(&mut self, erasures: u64) { + pub fn setErasures(&mut self, erasures: usize) { self.erasures = erasures } diff --git a/src/pdf417/PDF417ResultMetadata.java b/src/pdf417/PDF417ResultMetadata.java deleted file mode 100644 index 5265d52..0000000 --- a/src/pdf417/PDF417ResultMetadata.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * 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 PDF417RXingResultMetadata { - - private int segmentIndex; - private String fileId; - private boolean lastSegment; - private int segmentCount = -1; - private String sender; - private String addressee; - private String fileName; - private long fileSize = -1; - private long timestamp = -1; - private int checksum = -1; - private int[] optionalData; - - /** - * The Segment ID represents the segment of the whole file distributed over different symbols. - * - * @return File segment index - */ - public int getSegmentIndex() { - return segmentIndex; - } - - public void setSegmentIndex(int segmentIndex) { - this.segmentIndex = segmentIndex; - } - - /** - * Is the same for each related PDF417 symbol - * - * @return File ID - */ - public String getFileId() { - return fileId; - } - - public void setFileId(String fileId) { - this.fileId = fileId; - } - - /** - * @return always null - * @deprecated use dedicated already parsed fields - */ - @Deprecated - public int[] getOptionalData() { - return optionalData; - } - - /** - * @param optionalData old optional data format as int array - * @deprecated parse and use new fields - */ - @Deprecated - public void setOptionalData(int[] optionalData) { - this.optionalData = optionalData; - } - - - /** - * @return true if it is the last segment - */ - public boolean isLastSegment() { - return lastSegment; - } - - public void setLastSegment(boolean lastSegment) { - this.lastSegment = lastSegment; - } - - /** - * @return count of segments, -1 if not set - */ - public int getSegmentCount() { - return segmentCount; - } - - public void setSegmentCount(int segmentCount) { - this.segmentCount = segmentCount; - } - - public String getSender() { - return sender; - } - - public void setSender(String sender) { - this.sender = sender; - } - - public String getAddressee() { - return addressee; - } - - public void setAddressee(String addressee) { - this.addressee = addressee; - } - - /** - * Filename of the encoded file - * - * @return filename - */ - public String getFileName() { - return fileName; - } - - public void setFileName(String fileName) { - this.fileName = fileName; - } - - /** - * filesize in bytes of the encoded file - * - * @return filesize in bytes, -1 if not set - */ - public long getFileSize() { - return fileSize; - } - - public void setFileSize(long fileSize) { - this.fileSize = fileSize; - } - - /** - * 16-bit CRC checksum using CCITT-16 - * - * @return crc checksum, -1 if not set - */ - public int getChecksum() { - return checksum; - } - - public void setChecksum(int checksum) { - this.checksum = checksum; - } - - /** - * unix epock timestamp, elapsed seconds since 1970-01-01 - * - * @return elapsed seconds, -1 if not set - */ - public long getTimestamp() { - return timestamp; - } - - public void setTimestamp(long timestamp) { - this.timestamp = timestamp; - } - -} diff --git a/src/pdf417/decoder/DecodedBitStreamParser.java b/src/pdf417/decoder/DecodedBitStreamParser.java deleted file mode 100644 index fcf790a..0000000 --- a/src/pdf417/decoder/DecodedBitStreamParser.java +++ /dev/null @@ -1,698 +0,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. - */ - -package com.google.zxing.pdf417.decoder; - -import com.google.zxing.FormatException; -import com.google.zxing.common.ECIStringBuilder; -import com.google.zxing.common.DecoderRXingResult; -import com.google.zxing.pdf417.PDF417RXingResultMetadata; - -import java.math.BigInteger; -import java.util.Arrays; - -/** - *

This class contains the methods for decoding the PDF417 codewords.

- * - * @author SITA Lab (kevin.osullivan@sita.aero) - * @author Guenther Grau - */ -final class DecodedBitStreamParser { - - private enum Mode { - ALPHA, - LOWER, - MIXED, - PUNCT, - ALPHA_SHIFT, - PUNCT_SHIFT - } - - private static final int TEXT_COMPACTION_MODE_LATCH = 900; - private static final int BYTE_COMPACTION_MODE_LATCH = 901; - private static final int NUMERIC_COMPACTION_MODE_LATCH = 902; - private static final int BYTE_COMPACTION_MODE_LATCH_6 = 924; - private static final int ECI_USER_DEFINED = 925; - private static final int ECI_GENERAL_PURPOSE = 926; - private static final int ECI_CHARSET = 927; - private static final int BEGIN_MACRO_PDF417_CONTROL_BLOCK = 928; - private static final int BEGIN_MACRO_PDF417_OPTIONAL_FIELD = 923; - private static final int MACRO_PDF417_TERMINATOR = 922; - private static final int MODE_SHIFT_TO_BYTE_COMPACTION_MODE = 913; - private static final int MAX_NUMERIC_CODEWORDS = 15; - - private static final int MACRO_PDF417_OPTIONAL_FIELD_FILE_NAME = 0; - private static final int MACRO_PDF417_OPTIONAL_FIELD_SEGMENT_COUNT = 1; - private static final int MACRO_PDF417_OPTIONAL_FIELD_TIME_STAMP = 2; - private static final int MACRO_PDF417_OPTIONAL_FIELD_SENDER = 3; - private static final int MACRO_PDF417_OPTIONAL_FIELD_ADDRESSEE = 4; - private static final int MACRO_PDF417_OPTIONAL_FIELD_FILE_SIZE = 5; - private static final int MACRO_PDF417_OPTIONAL_FIELD_CHECKSUM = 6; - - private static final int PL = 25; - private static final int LL = 27; - private static final int AS = 27; - private static final int ML = 28; - private static final int AL = 28; - private static final int PS = 29; - private static final int PAL = 29; - - private static final char[] PUNCT_CHARS = - ";<>@[\\]_`~!\r\t,:\n-.$/\"|*()?{}'".toCharArray(); - - private static final char[] MIXED_CHARS = - "0123456789&\r\t,:#-.$/+%*=^".toCharArray(); - - /** - * Table containing values for the exponent of 900. - * This is used in the numeric compaction decode algorithm. - */ - private static final BigInteger[] EXP900; - - static { - EXP900 = new BigInteger[16]; - EXP900[0] = BigInteger.ONE; - BigInteger nineHundred = BigInteger.valueOf(900); - EXP900[1] = nineHundred; - for (int i = 2; i < EXP900.length; i++) { - EXP900[i] = EXP900[i - 1].multiply(nineHundred); - } - } - - private static final int NUMBER_OF_SEQUENCE_CODEWORDS = 2; - - private DecodedBitStreamParser() { - } - - static DecoderRXingResult decode(int[] codewords, String ecLevel) throws FormatException { - ECIStringBuilder result = new ECIStringBuilder(codewords.length * 2); - int codeIndex = textCompaction(codewords, 1, result); - PDF417RXingResultMetadata resultMetadata = new PDF417RXingResultMetadata(); - while (codeIndex < codewords[0]) { - int code = codewords[codeIndex++]; - switch (code) { - case TEXT_COMPACTION_MODE_LATCH: - codeIndex = textCompaction(codewords, codeIndex, result); - break; - case BYTE_COMPACTION_MODE_LATCH: - case BYTE_COMPACTION_MODE_LATCH_6: - codeIndex = byteCompaction(code, codewords, codeIndex, result); - break; - case MODE_SHIFT_TO_BYTE_COMPACTION_MODE: - result.append((char) codewords[codeIndex++]); - break; - case NUMERIC_COMPACTION_MODE_LATCH: - codeIndex = numericCompaction(codewords, codeIndex, result); - break; - case ECI_CHARSET: - result.appendECI(codewords[codeIndex++]); - break; - case ECI_GENERAL_PURPOSE: - // Can't do anything with generic ECI; skip its 2 characters - codeIndex += 2; - break; - case ECI_USER_DEFINED: - // Can't do anything with user ECI; skip its 1 character - codeIndex++; - break; - case BEGIN_MACRO_PDF417_CONTROL_BLOCK: - codeIndex = decodeMacroBlock(codewords, codeIndex, resultMetadata); - break; - case BEGIN_MACRO_PDF417_OPTIONAL_FIELD: - case MACRO_PDF417_TERMINATOR: - // Should not see these outside a macro block - throw FormatException.getFormatInstance(); - default: - // Default to text compaction. During testing numerous barcodes - // appeared to be missing the starting mode. In these cases defaulting - // to text compaction seems to work. - codeIndex--; - codeIndex = textCompaction(codewords, codeIndex, result); - break; - } - } - if (result.isEmpty() && resultMetadata.getFileId() == null) { - throw FormatException.getFormatInstance(); - } - DecoderRXingResult decoderRXingResult = new DecoderRXingResult(null, result.toString(), null, ecLevel); - decoderRXingResult.setOther(resultMetadata); - return decoderRXingResult; - } - - @SuppressWarnings("deprecation") - static int decodeMacroBlock(int[] codewords, int codeIndex, PDF417RXingResultMetadata resultMetadata) - throws FormatException { - if (codeIndex + NUMBER_OF_SEQUENCE_CODEWORDS > codewords[0]) { - // we must have at least two bytes left for the segment index - throw FormatException.getFormatInstance(); - } - int[] segmentIndexArray = new int[NUMBER_OF_SEQUENCE_CODEWORDS]; - for (int i = 0; i < NUMBER_OF_SEQUENCE_CODEWORDS; i++, codeIndex++) { - segmentIndexArray[i] = codewords[codeIndex]; - } - String segmentIndexString = decodeBase900toBase10(segmentIndexArray, NUMBER_OF_SEQUENCE_CODEWORDS); - if (segmentIndexString.isEmpty()) { - resultMetadata.setSegmentIndex(0); - } else { - try { - resultMetadata.setSegmentIndex(Integer.parseInt(segmentIndexString)); - } catch (NumberFormatException nfe) { - // too large; bad input? - throw FormatException.getFormatInstance(); - } - } - - // Decoding the fileId codewords as 0-899 numbers, each 0-filled to width 3. This follows the spec - // (See ISO/IEC 15438:2015 Annex H.6) and preserves all info, but some generators (e.g. TEC-IT) write - // the fileId using text compaction, so in those cases the fileId will appear mangled. - StringBuilder fileId = new StringBuilder(); - while (codeIndex < codewords[0] && - codeIndex < codewords.length && - codewords[codeIndex] != MACRO_PDF417_TERMINATOR && - codewords[codeIndex] != BEGIN_MACRO_PDF417_OPTIONAL_FIELD) { - fileId.append(String.format("%03d", codewords[codeIndex])); - codeIndex++; - } - if (fileId.length() == 0) { - // at least one fileId codeword is required (Annex H.2) - throw FormatException.getFormatInstance(); - } - resultMetadata.setFileId(fileId.toString()); - - int optionalFieldsStart = -1; - if (codewords[codeIndex] == BEGIN_MACRO_PDF417_OPTIONAL_FIELD) { - optionalFieldsStart = codeIndex + 1; - } - - while (codeIndex < codewords[0]) { - switch (codewords[codeIndex]) { - case BEGIN_MACRO_PDF417_OPTIONAL_FIELD: - codeIndex++; - switch (codewords[codeIndex]) { - case MACRO_PDF417_OPTIONAL_FIELD_FILE_NAME: - ECIStringBuilder fileName = new ECIStringBuilder(); - codeIndex = textCompaction(codewords, codeIndex + 1, fileName); - resultMetadata.setFileName(fileName.toString()); - break; - case MACRO_PDF417_OPTIONAL_FIELD_SENDER: - ECIStringBuilder sender = new ECIStringBuilder(); - codeIndex = textCompaction(codewords, codeIndex + 1, sender); - resultMetadata.setSender(sender.toString()); - break; - case MACRO_PDF417_OPTIONAL_FIELD_ADDRESSEE: - ECIStringBuilder addressee = new ECIStringBuilder(); - codeIndex = textCompaction(codewords, codeIndex + 1, addressee); - resultMetadata.setAddressee(addressee.toString()); - break; - case MACRO_PDF417_OPTIONAL_FIELD_SEGMENT_COUNT: - ECIStringBuilder segmentCount = new ECIStringBuilder(); - codeIndex = numericCompaction(codewords, codeIndex + 1, segmentCount); - resultMetadata.setSegmentCount(Integer.parseInt(segmentCount.toString())); - break; - case MACRO_PDF417_OPTIONAL_FIELD_TIME_STAMP: - ECIStringBuilder timestamp = new ECIStringBuilder(); - codeIndex = numericCompaction(codewords, codeIndex + 1, timestamp); - resultMetadata.setTimestamp(Long.parseLong(timestamp.toString())); - break; - case MACRO_PDF417_OPTIONAL_FIELD_CHECKSUM: - ECIStringBuilder checksum = new ECIStringBuilder(); - codeIndex = numericCompaction(codewords, codeIndex + 1, checksum); - resultMetadata.setChecksum(Integer.parseInt(checksum.toString())); - break; - case MACRO_PDF417_OPTIONAL_FIELD_FILE_SIZE: - ECIStringBuilder fileSize = new ECIStringBuilder(); - codeIndex = numericCompaction(codewords, codeIndex + 1, fileSize); - resultMetadata.setFileSize(Long.parseLong(fileSize.toString())); - break; - default: - throw FormatException.getFormatInstance(); - } - break; - case MACRO_PDF417_TERMINATOR: - codeIndex++; - resultMetadata.setLastSegment(true); - break; - default: - throw FormatException.getFormatInstance(); - } - } - - // copy optional fields to additional options - if (optionalFieldsStart != -1) { - int optionalFieldsLength = codeIndex - optionalFieldsStart; - if (resultMetadata.isLastSegment()) { - // do not include terminator - optionalFieldsLength--; - } - resultMetadata.setOptionalData( - Arrays.copyOfRange(codewords, optionalFieldsStart, optionalFieldsStart + optionalFieldsLength)); - } - - return codeIndex; - } - - /** - * Text Compaction mode (see 5.4.1.5) permits all printable ASCII characters to be - * encoded, i.e. values 32 - 126 inclusive in accordance with ISO/IEC 646 (IRV), as - * well as selected control characters. - * - * @param codewords The array of codewords (data + error) - * @param codeIndex The current index into the codeword array. - * @param result The decoded data is appended to the result. - * @return The next index into the codeword array. - */ - private static int textCompaction(int[] codewords, int codeIndex, ECIStringBuilder result) throws FormatException { - // 2 character per codeword - int[] textCompactionData = new int[(codewords[0] - codeIndex) * 2]; - // Used to hold the byte compaction value if there is a mode shift - int[] byteCompactionData = new int[(codewords[0] - codeIndex) * 2]; - - int index = 0; - boolean end = false; - Mode subMode = Mode.ALPHA; - while ((codeIndex < codewords[0]) && !end) { - int code = codewords[codeIndex++]; - if (code < TEXT_COMPACTION_MODE_LATCH) { - textCompactionData[index] = code / 30; - textCompactionData[index + 1] = code % 30; - index += 2; - } else { - switch (code) { - case TEXT_COMPACTION_MODE_LATCH: - // reinitialize text compaction mode to alpha sub mode - textCompactionData[index++] = TEXT_COMPACTION_MODE_LATCH; - break; - case BYTE_COMPACTION_MODE_LATCH: - case BYTE_COMPACTION_MODE_LATCH_6: - case NUMERIC_COMPACTION_MODE_LATCH: - case BEGIN_MACRO_PDF417_CONTROL_BLOCK: - case BEGIN_MACRO_PDF417_OPTIONAL_FIELD: - case MACRO_PDF417_TERMINATOR: - codeIndex--; - end = true; - break; - case MODE_SHIFT_TO_BYTE_COMPACTION_MODE: - // The Mode Shift codeword 913 shall cause a temporary - // switch from Text Compaction mode to Byte Compaction mode. - // This switch shall be in effect for only the next codeword, - // after which the mode shall revert to the prevailing sub-mode - // of the Text Compaction mode. Codeword 913 is only available - // in Text Compaction mode; its use is described in 5.4.2.4. - textCompactionData[index] = MODE_SHIFT_TO_BYTE_COMPACTION_MODE; - code = codewords[codeIndex++]; - byteCompactionData[index] = code; - index++; - break; - case ECI_CHARSET: - subMode = decodeTextCompaction(textCompactionData, byteCompactionData, index, result, subMode); - result.appendECI(codewords[codeIndex++]); - textCompactionData = new int[(codewords[0] - codeIndex) * 2]; - byteCompactionData = new int[(codewords[0] - codeIndex) * 2]; - index = 0; - break; - } - } - } - decodeTextCompaction(textCompactionData, byteCompactionData, index, result, subMode); - return codeIndex; - } - - /** - * The Text Compaction mode includes all the printable ASCII characters - * (i.e. values from 32 to 126) and three ASCII control characters: HT or tab - * (ASCII value 9), LF or line feed (ASCII value 10), and CR or carriage - * return (ASCII value 13). The Text Compaction mode also includes various latch - * and shift characters which are used exclusively within the mode. The Text - * Compaction mode encodes up to 2 characters per codeword. The compaction rules - * for converting data into PDF417 codewords are defined in 5.4.2.2. The sub-mode - * switches are defined in 5.4.2.3. - * - * @param textCompactionData The text compaction data. - * @param byteCompactionData The byte compaction data if there - * was a mode shift. - * @param length The size of the text compaction and byte compaction data. - * @param result The decoded data is appended to the result. - * @param startMode The mode in which decoding starts - * @return The mode in which decoding ended - */ - private static Mode decodeTextCompaction(int[] textCompactionData, - int[] byteCompactionData, - int length, - ECIStringBuilder result, - Mode startMode) { - // Beginning from an initial state - // The default compaction mode for PDF417 in effect at the start of each symbol shall always be Text - // Compaction mode Alpha sub-mode (uppercase alphabetic). A latch codeword from another mode to the Text - // Compaction mode shall always switch to the Text Compaction Alpha sub-mode. - Mode subMode = startMode; - Mode priorToShiftMode = startMode; - Mode latchedMode = startMode; - int i = 0; - while (i < length) { - int subModeCh = textCompactionData[i]; - char ch = 0; - switch (subMode) { - case ALPHA: - // Alpha (uppercase alphabetic) - if (subModeCh < 26) { - // Upper case Alpha Character - ch = (char) ('A' + subModeCh); - } else { - switch (subModeCh) { - case 26: - ch = ' '; - break; - case LL: - subMode = Mode.LOWER; - latchedMode = subMode; - break; - case ML: - subMode = Mode.MIXED; - latchedMode = subMode; - break; - case PS: - // Shift to punctuation - priorToShiftMode = subMode; - subMode = Mode.PUNCT_SHIFT; - break; - case MODE_SHIFT_TO_BYTE_COMPACTION_MODE: - result.append((char) byteCompactionData[i]); - break; - case TEXT_COMPACTION_MODE_LATCH: - subMode = Mode.ALPHA; - latchedMode = subMode; - break; - } - } - break; - - case LOWER: - // Lower (lowercase alphabetic) - if (subModeCh < 26) { - ch = (char) ('a' + subModeCh); - } else { - switch (subModeCh) { - case 26: - ch = ' '; - break; - case AS: - // Shift to alpha - priorToShiftMode = subMode; - subMode = Mode.ALPHA_SHIFT; - break; - case ML: - subMode = Mode.MIXED; - latchedMode = subMode; - break; - case PS: - // Shift to punctuation - priorToShiftMode = subMode; - subMode = Mode.PUNCT_SHIFT; - break; - case MODE_SHIFT_TO_BYTE_COMPACTION_MODE: - result.append((char) byteCompactionData[i]); - break; - case TEXT_COMPACTION_MODE_LATCH: - subMode = Mode.ALPHA; - latchedMode = subMode; - break; - } - } - break; - - case MIXED: - // Mixed (numeric and some punctuation) - if (subModeCh < PL) { - ch = MIXED_CHARS[subModeCh]; - } else { - switch (subModeCh) { - case PL: - subMode = Mode.PUNCT; - latchedMode = subMode; - break; - case 26: - ch = ' '; - break; - case LL: - subMode = Mode.LOWER; - latchedMode = subMode; - break; - case AL: - case TEXT_COMPACTION_MODE_LATCH: - subMode = Mode.ALPHA; - latchedMode = subMode; - break; - case PS: - // Shift to punctuation - priorToShiftMode = subMode; - subMode = Mode.PUNCT_SHIFT; - break; - case MODE_SHIFT_TO_BYTE_COMPACTION_MODE: - result.append((char) byteCompactionData[i]); - break; - } - } - break; - - case PUNCT: - // Punctuation - if (subModeCh < PAL) { - ch = PUNCT_CHARS[subModeCh]; - } else { - switch (subModeCh) { - case PAL: - case TEXT_COMPACTION_MODE_LATCH: - subMode = Mode.ALPHA; - latchedMode = subMode; - break; - case MODE_SHIFT_TO_BYTE_COMPACTION_MODE: - result.append((char) byteCompactionData[i]); - break; - } - } - break; - - case ALPHA_SHIFT: - // Restore sub-mode - subMode = priorToShiftMode; - if (subModeCh < 26) { - ch = (char) ('A' + subModeCh); - } else { - switch (subModeCh) { - case 26: - ch = ' '; - break; - case TEXT_COMPACTION_MODE_LATCH: - subMode = Mode.ALPHA; - break; - } - } - break; - - case PUNCT_SHIFT: - // Restore sub-mode - subMode = priorToShiftMode; - if (subModeCh < PAL) { - ch = PUNCT_CHARS[subModeCh]; - } else { - switch (subModeCh) { - case PAL: - case TEXT_COMPACTION_MODE_LATCH: - subMode = Mode.ALPHA; - break; - case MODE_SHIFT_TO_BYTE_COMPACTION_MODE: - // PS before Shift-to-Byte is used as a padding character, - // see 5.4.2.4 of the specification - result.append((char) byteCompactionData[i]); - break; - } - } - break; - } - if (ch != 0) { - // Append decoded character to result - result.append(ch); - } - i++; - } - return latchedMode; - } - - /** - * Byte Compaction mode (see 5.4.3) permits all 256 possible 8-bit byte values to be encoded. - * This includes all ASCII characters value 0 to 127 inclusive and provides for international - * character set support. - * - * @param mode The byte compaction mode i.e. 901 or 924 - * @param codewords The array of codewords (data + error) - * @param codeIndex The current index into the codeword array. - * @param result The decoded data is appended to the result. - * @return The next index into the codeword array. - */ - private static int byteCompaction(int mode, - int[] codewords, - int codeIndex, - ECIStringBuilder result) throws FormatException { - boolean end = false; - - while (codeIndex < codewords[0] && !end) { - //handle leading ECIs - while (codeIndex < codewords[0] && codewords[codeIndex] == ECI_CHARSET) { - result.appendECI(codewords[++codeIndex]); - codeIndex++; - } - - if (codeIndex >= codewords[0] || codewords[codeIndex] >= TEXT_COMPACTION_MODE_LATCH) { - end = true; - } else { - //decode one block of 5 codewords to 6 bytes - long value = 0; - int count = 0; - do { - value = 900 * value + codewords[codeIndex++]; - count++; - } while (count < 5 && - codeIndex < codewords[0] && - codewords[codeIndex] < TEXT_COMPACTION_MODE_LATCH); - if (count == 5 && (mode == BYTE_COMPACTION_MODE_LATCH_6 || - codeIndex < codewords[0] && - codewords[codeIndex] < TEXT_COMPACTION_MODE_LATCH)) { - for (int i = 0; i < 6; i++) { - result.append((byte) (value >> (8 * (5 - i)))); - } - } else { - codeIndex -= count; - while ((codeIndex < codewords[0]) && !end) { - int code = codewords[codeIndex++]; - if (code < TEXT_COMPACTION_MODE_LATCH) { - result.append((byte) code); - } else if (code == ECI_CHARSET) { - result.appendECI(codewords[codeIndex++]); - } else { - codeIndex--; - end = true; - } - } - } - } - } - return codeIndex; - } - - /** - * Numeric Compaction mode (see 5.4.4) permits efficient encoding of numeric data strings. - * - * @param codewords The array of codewords (data + error) - * @param codeIndex The current index into the codeword array. - * @param result The decoded data is appended to the result. - * @return The next index into the codeword array. - */ - private static int numericCompaction(int[] codewords, int codeIndex, ECIStringBuilder result) throws FormatException { - int count = 0; - boolean end = false; - - int[] numericCodewords = new int[MAX_NUMERIC_CODEWORDS]; - - while (codeIndex < codewords[0] && !end) { - int code = codewords[codeIndex++]; - if (codeIndex == codewords[0]) { - end = true; - } - if (code < TEXT_COMPACTION_MODE_LATCH) { - numericCodewords[count] = code; - count++; - } else { - switch (code) { - case TEXT_COMPACTION_MODE_LATCH: - case BYTE_COMPACTION_MODE_LATCH: - case BYTE_COMPACTION_MODE_LATCH_6: - case BEGIN_MACRO_PDF417_CONTROL_BLOCK: - case BEGIN_MACRO_PDF417_OPTIONAL_FIELD: - case MACRO_PDF417_TERMINATOR: - case ECI_CHARSET: - codeIndex--; - end = true; - break; - } - } - if ((count % MAX_NUMERIC_CODEWORDS == 0 || code == NUMERIC_COMPACTION_MODE_LATCH || end) && count > 0) { - // Re-invoking Numeric Compaction mode (by using codeword 902 - // while in Numeric Compaction mode) serves to terminate the - // current Numeric Compaction mode grouping as described in 5.4.4.2, - // and then to start a new one grouping. - result.append(decodeBase900toBase10(numericCodewords, count)); - count = 0; - } - } - return codeIndex; - } - - /** - * Convert a list of Numeric Compacted codewords from Base 900 to Base 10. - * - * @param codewords The array of codewords - * @param count The number of codewords - * @return The decoded string representing the Numeric data. - */ - /* - EXAMPLE - Encode the fifteen digit numeric string 000213298174000 - Prefix the numeric string with a 1 and set the initial value of - t = 1 000 213 298 174 000 - Calculate codeword 0 - d0 = 1 000 213 298 174 000 mod 900 = 200 - - t = 1 000 213 298 174 000 div 900 = 1 111 348 109 082 - Calculate codeword 1 - d1 = 1 111 348 109 082 mod 900 = 282 - - t = 1 111 348 109 082 div 900 = 1 234 831 232 - Calculate codeword 2 - d2 = 1 234 831 232 mod 900 = 632 - - t = 1 234 831 232 div 900 = 1 372 034 - Calculate codeword 3 - d3 = 1 372 034 mod 900 = 434 - - t = 1 372 034 div 900 = 1 524 - Calculate codeword 4 - d4 = 1 524 mod 900 = 624 - - t = 1 524 div 900 = 1 - Calculate codeword 5 - d5 = 1 mod 900 = 1 - t = 1 div 900 = 0 - Codeword sequence is: 1, 624, 434, 632, 282, 200 - - Decode the above codewords involves - 1 x 900 power of 5 + 624 x 900 power of 4 + 434 x 900 power of 3 + - 632 x 900 power of 2 + 282 x 900 power of 1 + 200 x 900 power of 0 = 1000213298174000 - - Remove leading 1 => RXingResult is 000213298174000 - */ - private static String decodeBase900toBase10(int[] codewords, int count) throws FormatException { - BigInteger result = BigInteger.ZERO; - for (int i = 0; i < count; i++) { - result = result.add(EXP900[count - i - 1].multiply(BigInteger.valueOf(codewords[i]))); - } - String resultString = result.toString(); - if (resultString.charAt(0) != '1') { - throw FormatException.getFormatInstance(); - } - return resultString.substring(1); - } - -} diff --git a/src/pdf417/decoder/barcode_value.rs b/src/pdf417/decoder/barcode_value.rs index b9c8e30..b1724d3 100644 --- a/src/pdf417/decoder/barcode_value.rs +++ b/src/pdf417/decoder/barcode_value.rs @@ -19,6 +19,7 @@ use std::collections::HashMap; /** * @author Guenther Grau */ +#[derive(Clone)] pub struct BarcodeValue(HashMap); // private final Map values = new HashMap<>(); diff --git a/src/pdf417/decoder/bounding_box.rs b/src/pdf417/decoder/bounding_box.rs index e7592a4..9ce71ba 100644 --- a/src/pdf417/decoder/bounding_box.rs +++ b/src/pdf417/decoder/bounding_box.rs @@ -14,14 +14,16 @@ * limitations under the License. */ +use std::rc::Rc; + use crate::{common::BitMatrix, Exceptions, RXingResultPoint, ResultPoint}; /** * @author Guenther Grau */ #[derive(Clone)] -pub struct BoundingBox<'a> { - image: &'a BitMatrix, +pub struct BoundingBox { + image: Rc, topLeft: RXingResultPoint, bottomLeft: RXingResultPoint, topRight: RXingResultPoint, @@ -31,14 +33,14 @@ pub struct BoundingBox<'a> { minY: u32, maxY: u32, } -impl<'a> BoundingBox<'_> { +impl BoundingBox { pub fn new( - image: &'a BitMatrix, + image: Rc, topLeft: Option, bottomLeft: Option, topRight: Option, bottomRight: Option, - ) -> Result, Exceptions> { + ) -> Result { let leftUnspecified = topLeft.is_none() || bottomLeft.is_none(); let rightUnspecified = topRight.is_none() || bottomRight.is_none(); if leftUnspecified && rightUnspecified { @@ -81,9 +83,9 @@ impl<'a> BoundingBox<'_> { }) } - pub fn from_other(boundingBox: &'a BoundingBox) -> BoundingBox<'a> { + pub fn from_other(boundingBox: Rc) -> BoundingBox { BoundingBox { - image: boundingBox.image, + image: boundingBox.image.clone(), topLeft: boundingBox.topLeft, bottomLeft: boundingBox.bottomLeft, topRight: boundingBox.topRight, @@ -96,14 +98,14 @@ impl<'a> BoundingBox<'_> { } pub fn merge( - leftBox: Option<&'a BoundingBox>, - rightBox: Option<&'a BoundingBox>, - ) -> Result, Exceptions> { + leftBox: Option, + rightBox: Option, + ) -> Result { if leftBox.is_none() { - return Ok(rightBox.unwrap().clone()); + return Ok(rightBox.as_ref().unwrap().clone()); } if rightBox.is_none() { - return Ok(leftBox.unwrap().clone()); + return Ok(leftBox.as_ref().unwrap().clone()); } let leftBox = leftBox.unwrap(); let rightBox = rightBox.unwrap(); @@ -161,7 +163,7 @@ impl<'a> BoundingBox<'_> { } BoundingBox::new( - self.image, + self.image.clone(), Some(newTopLeft), Some(newBottomLeft), Some(newTopRight), diff --git a/src/pdf417/decoder/decoded_bit_stream_parser.rs b/src/pdf417/decoder/decoded_bit_stream_parser.rs new file mode 100644 index 0000000..2b43c1a --- /dev/null +++ b/src/pdf417/decoder/decoded_bit_stream_parser.rs @@ -0,0 +1,797 @@ +/* + * 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. + */ + +use num::{self, bigint::ToBigUint, BigUint}; +use std::rc::Rc; + +use crate::{ + common::{DecoderRXingResult, ECIStringBuilder}, + pdf417::PDF417RXingResultMetadata, + Exceptions, +}; + +/** + *

This class contains the methods for decoding the PDF417 codewords.

+ * + * @author SITA Lab (kevin.osullivan@sita.aero) + * @author Guenther Grau + */ + +#[derive(Clone, Copy, PartialEq, Eq)] +enum Mode { + ALPHA, + LOWER, + MIXED, + PUNCT, + ALPHA_SHIFT, + PUNCT_SHIFT, +} + +const TEXT_COMPACTION_MODE_LATCH: u32 = 900; +const BYTE_COMPACTION_MODE_LATCH: u32 = 901; +const NUMERIC_COMPACTION_MODE_LATCH: u32 = 902; +const BYTE_COMPACTION_MODE_LATCH_6: u32 = 924; +const ECI_USER_DEFINED: u32 = 925; +const ECI_GENERAL_PURPOSE: u32 = 926; +const ECI_CHARSET: u32 = 927; +const BEGIN_MACRO_PDF417_CONTROL_BLOCK: u32 = 928; +const BEGIN_MACRO_PDF417_OPTIONAL_FIELD: u32 = 923; +const MACRO_PDF417_TERMINATOR: u32 = 922; +const MODE_SHIFT_TO_BYTE_COMPACTION_MODE: u32 = 913; +const MAX_NUMERIC_CODEWORDS: usize = 15; + +const MACRO_PDF417_OPTIONAL_FIELD_FILE_NAME: u32 = 0; +const MACRO_PDF417_OPTIONAL_FIELD_SEGMENT_COUNT: u32 = 1; +const MACRO_PDF417_OPTIONAL_FIELD_TIME_STAMP: u32 = 2; +const MACRO_PDF417_OPTIONAL_FIELD_SENDER: u32 = 3; +const MACRO_PDF417_OPTIONAL_FIELD_ADDRESSEE: u32 = 4; +const MACRO_PDF417_OPTIONAL_FIELD_FILE_SIZE: u32 = 5; +const MACRO_PDF417_OPTIONAL_FIELD_CHECKSUM: u32 = 6; + +const PL: u32 = 25; +const LL: u32 = 27; +const AS: u32 = 27; +const ML: u32 = 28; +const AL: u32 = 28; +const PS: u32 = 29; +const PAL: u32 = 29; + +const PUNCT_CHARS: [char; 29] = [ + ';', '<', '>', '@', '[', '\\', ']', '_', '`', '~', '!', '\r', '\t', ',', ':', '\n', '-', '.', + '$', '/', '"', '|', '*', '(', ')', '?', '{', '}', '\'', +]; + +const MIXED_CHARS: [char; 25] = [ + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '&', '\r', '\t', ',', ':', '#', '-', '.', + '$', '/', '+', '%', '*', '=', '^', +]; + +use lazy_static::lazy_static; + +lazy_static! { + /** + * Table containing values for the exponent of 900. + * This is used in the numeric compaction decode algorithm. + */ + static ref EXP900 : Vec = { + const EXP_LEN :usize= 16; + let mut exp900 = Vec::with_capacity(EXP_LEN); //[0;16]; + exp900.push(ToBigUint::to_biguint(&1).unwrap()); + let nineHundred = ToBigUint::to_biguint(&900).unwrap(); + exp900.push(nineHundred); + let mut i = 2; + while i < EXP_LEN { + // for (int i = 2; i < EXP900.length; i++) { + exp900.push( &exp900[i - 1] * 900_u32); + + i+=1; + } + + exp900 + }; +} + +// /** +// * Table containing values for the exponent of 900. +// * This is used in the numeric compaction decode algorithm. +// */ +// const EXP900 : [u128;16] = + +// { +// let mut exp900 = [0;16]; +// exp900[0] = 1; +// let nineHundred = 900; +// exp900[1] = nineHundred; +// let mut i = 2; +// while i < exp900.len() { +// // for (int i = 2; i < EXP900.length; i++) { +// exp900[i] = exp900[i - 1] * (nineHundred); + +// i+=1; +// } + +// exp900 +// }; + +const NUMBER_OF_SEQUENCE_CODEWORDS: usize = 2; + +pub fn decode(codewords: &[u32], ecLevel: &str) -> Result { + let mut result = ECIStringBuilder::with_capacity(codewords.len() * 2); + let mut codeIndex = textCompaction(codewords, 1, &mut result)? as usize; + let mut resultMetadata = PDF417RXingResultMetadata::default(); + while codeIndex < codewords[0] as usize { + let code = codewords[codeIndex]; + codeIndex += 1; + match code { + TEXT_COMPACTION_MODE_LATCH => { + codeIndex = textCompaction(codewords, codeIndex, &mut result)? + } + BYTE_COMPACTION_MODE_LATCH | BYTE_COMPACTION_MODE_LATCH_6 => { + codeIndex = byteCompaction(code, codewords, codeIndex, &mut result)? + } + MODE_SHIFT_TO_BYTE_COMPACTION_MODE => { + result.append_char(char::from_u32(codewords[codeIndex]).unwrap()); + codeIndex += 1; + } + NUMERIC_COMPACTION_MODE_LATCH => { + codeIndex = numericCompaction(codewords, codeIndex, &mut result)? + } + ECI_CHARSET => { + result.appendECI(codewords[codeIndex])?; + codeIndex += 1; + } + ECI_GENERAL_PURPOSE => + // Can't do anything with generic ECI; skip its 2 characters + { + codeIndex += 2 + } + ECI_USER_DEFINED => + // Can't do anything with user ECI; skip its 1 character + { + codeIndex += 1 + } + BEGIN_MACRO_PDF417_CONTROL_BLOCK => { + codeIndex = decodeMacroBlock(codewords, codeIndex, &mut resultMetadata)? + } + BEGIN_MACRO_PDF417_OPTIONAL_FIELD | MACRO_PDF417_TERMINATOR => + // Should not see these outside a macro block + { + return Err(Exceptions::FormatException("".to_owned())) + } + _ => { + // Default to text compaction. During testing numerous barcodes + // appeared to be missing the starting Mode:: In these cases defaulting + // to text compaction seems to work. + codeIndex -= 1; + codeIndex = textCompaction(codewords, codeIndex, &mut result)?; + } + } + } + if result.is_empty() && resultMetadata.getFileId().is_empty() { + return Err(Exceptions::FormatException("".to_owned())); + } + let mut decoderRXingResult = DecoderRXingResult::new( + Vec::new(), + result.to_string(), + Vec::new(), + ecLevel.to_owned(), + ); + decoderRXingResult.setOther(Rc::new(resultMetadata)); + + Ok(decoderRXingResult) +} + +pub fn decodeMacroBlock( + codewords: &[u32], + codeIndex: usize, + resultMetadata: &mut PDF417RXingResultMetadata, +) -> Result { + let mut codeIndex = codeIndex; + if codeIndex + NUMBER_OF_SEQUENCE_CODEWORDS > codewords[0] as usize { + // we must have at least two bytes left for the segment index + return Err(Exceptions::FormatException("".to_owned())); + } + let mut segmentIndexArray = [0; NUMBER_OF_SEQUENCE_CODEWORDS]; + for i in 0..NUMBER_OF_SEQUENCE_CODEWORDS { + // for (int i = 0; i < NUMBER_OF_SEQUENCE_CODEWORDS; i++, codeIndex++) { + segmentIndexArray[i] = codewords[codeIndex]; + codeIndex += 1; + } + let segmentIndexString = + decodeBase900toBase10(&segmentIndexArray, NUMBER_OF_SEQUENCE_CODEWORDS)?; + if segmentIndexString.is_empty() { + resultMetadata.setSegmentIndex(0); + } else { + if let Ok(parsed_int) = segmentIndexString.parse::() { + resultMetadata.setSegmentIndex(parsed_int); + } else { + // too large; bad input? + return Err(Exceptions::FormatException("".to_owned())); + } + } + + // Decoding the fileId codewords as 0-899 numbers, each 0-filled to width 3. This follows the spec + // (See ISO/IEC 15438:2015 Annex H.6) and preserves all info, but some generators (e.g. TEC-IT) write + // the fileId using text compaction, so in those cases the fileId will appear mangled. + let mut fileId = String::new(); + while codeIndex < codewords[0] as usize + && codeIndex < codewords.len() + && codewords[codeIndex] != MACRO_PDF417_TERMINATOR + && codewords[codeIndex] != BEGIN_MACRO_PDF417_OPTIONAL_FIELD + { + fileId.push_str(&format!("{:0>3}", codewords[codeIndex])/*String.format("%03d", codewords[codeIndex])*/); + codeIndex += 1; + } + if fileId.chars().count() == 0 { + // at least one fileId codeword is required (Annex H.2) + return Err(Exceptions::FormatException("".to_owned())); + } + resultMetadata.setFileId(fileId); + + let mut optionalFieldsStart = -1_isize; + if codewords[codeIndex] == BEGIN_MACRO_PDF417_OPTIONAL_FIELD { + optionalFieldsStart = codeIndex as isize + 1; + } + + while codeIndex < codewords[0] as usize { + match codewords[codeIndex] { + BEGIN_MACRO_PDF417_OPTIONAL_FIELD => { + codeIndex += 1; + match codewords[codeIndex] { + MACRO_PDF417_OPTIONAL_FIELD_FILE_NAME => { + let mut fileName = ECIStringBuilder::new(); + codeIndex = textCompaction(codewords, codeIndex + 1, &mut fileName)?; + resultMetadata.setFileName(fileName.to_string()); + } + MACRO_PDF417_OPTIONAL_FIELD_SENDER => { + let mut sender = ECIStringBuilder::new(); + codeIndex = textCompaction(codewords, codeIndex + 1, &mut sender)?; + resultMetadata.setSender(sender.to_string()); + } + MACRO_PDF417_OPTIONAL_FIELD_ADDRESSEE => { + let mut addressee = ECIStringBuilder::new(); + codeIndex = textCompaction(codewords, codeIndex + 1, &mut addressee)?; + resultMetadata.setAddressee(addressee.to_string()); + } + MACRO_PDF417_OPTIONAL_FIELD_SEGMENT_COUNT => { + let mut segmentCount = ECIStringBuilder::new(); + codeIndex = numericCompaction(codewords, codeIndex + 1, &mut segmentCount)?; + resultMetadata.setSegmentCount(segmentCount.to_string().parse().unwrap()); + } + MACRO_PDF417_OPTIONAL_FIELD_TIME_STAMP => { + let mut timestamp = ECIStringBuilder::new(); + codeIndex = numericCompaction(codewords, codeIndex + 1, &mut timestamp)?; + resultMetadata.setTimestamp(timestamp.to_string().parse().unwrap()); + } + MACRO_PDF417_OPTIONAL_FIELD_CHECKSUM => { + let mut checksum = ECIStringBuilder::new(); + codeIndex = numericCompaction(codewords, codeIndex + 1, &mut checksum)?; + resultMetadata.setChecksum(checksum.to_string().parse().unwrap()); + } + MACRO_PDF417_OPTIONAL_FIELD_FILE_SIZE => { + let mut fileSize = ECIStringBuilder::new(); + codeIndex = numericCompaction(codewords, codeIndex + 1, &mut fileSize)?; + resultMetadata.setFileSize(fileSize.to_string().parse().unwrap()); + } + _ => return Err(Exceptions::FormatException("".to_owned())), + } + } + MACRO_PDF417_TERMINATOR => { + codeIndex += 1; + resultMetadata.setLastSegment(true); + } + _ => return Err(Exceptions::FormatException("".to_owned())), + } + } + + // copy optional fields to additional options + if optionalFieldsStart != -1 { + let mut optionalFieldsLength = codeIndex - optionalFieldsStart as usize; + if resultMetadata.isLastSegment() { + // do not include terminator + optionalFieldsLength -= 1; + } + // resultMetadata.setOptionalData( + // Arrays.copyOfRange(codewords, optionalFieldsStart, optionalFieldsStart + optionalFieldsLength)); + resultMetadata.setOptionalData( + codewords[optionalFieldsStart as usize + ..(optionalFieldsStart + optionalFieldsLength as isize) as usize] + .to_vec(), + ); + } + + Ok(codeIndex) +} + +/** + * Text Compaction mode (see 5.4.1.5) permits all printable ASCII characters to be + * encoded, i.e. values 32 - 126 inclusive in accordance with ISO/IEC 646 (IRV), as + * well as selected control characters. + * + * @param codewords The array of codewords (data + error) + * @param codeIndex The current index into the codeword array. + * @param result The decoded data is appended to the result. + * @return The next index into the codeword array. + */ +fn textCompaction( + codewords: &[u32], + codeIndex: usize, + result: &mut ECIStringBuilder, +) -> Result { + let mut codeIndex = codeIndex; + // 2 character per codeword + let mut textCompactionData = vec![0; (codewords[0] as usize - codeIndex) as usize * 2]; + // Used to hold the byte compaction value if there is a mode shift + let mut byteCompactionData = vec![0; (codewords[0] as usize - codeIndex) as usize * 2]; + + let mut index = 0; + let mut end = false; + let mut subMode = Mode::ALPHA; + while (codeIndex < codewords[0] as usize) && !end { + let mut code = codewords[codeIndex]; + codeIndex += 1; + if code < TEXT_COMPACTION_MODE_LATCH { + textCompactionData[index] = code / 30; + textCompactionData[index + 1] = code % 30; + index += 2; + } else { + match code { + TEXT_COMPACTION_MODE_LATCH => { + // reinitialize text compaction mode to alpha sub mode + textCompactionData[index] = TEXT_COMPACTION_MODE_LATCH; + index += 1; + } + BYTE_COMPACTION_MODE_LATCH + | BYTE_COMPACTION_MODE_LATCH_6 + | NUMERIC_COMPACTION_MODE_LATCH + | BEGIN_MACRO_PDF417_CONTROL_BLOCK + | BEGIN_MACRO_PDF417_OPTIONAL_FIELD + | MACRO_PDF417_TERMINATOR => { + codeIndex -= 1; + end = true; + } + MODE_SHIFT_TO_BYTE_COMPACTION_MODE => { + // The Mode Shift codeword 913 shall cause a temporary + // switch from Text Compaction mode to Byte Compaction Mode:: + // This switch shall be in effect for only the next codeword, + // after which the mode shall revert to the prevailing sub-mode + // of the Text Compaction Mode:: Codeword 913 is only available + // in Text Compaction mode; its use is described in 5.4.2.4. + textCompactionData[index] = MODE_SHIFT_TO_BYTE_COMPACTION_MODE; + code = codewords[codeIndex]; + codeIndex += 1; + byteCompactionData[index] = code; + index += 1; + } + ECI_CHARSET => { + subMode = decodeTextCompaction( + &textCompactionData, + &byteCompactionData, + index, + result, + subMode, + ); + result.appendECI(codewords[codeIndex])?; + codeIndex += 1; + textCompactionData = vec![0; (codewords[0] as usize - codeIndex) * 2]; + byteCompactionData = vec![0; (codewords[0] as usize - codeIndex) * 2]; + index = 0; + } + _ => {} + } + } + } + decodeTextCompaction( + &textCompactionData, + &byteCompactionData, + index, + result, + subMode, + ); + + Ok(codeIndex) +} + +/** + * The Text Compaction mode includes all the printable ASCII characters + * (i.e. values from 32 to 126) and three ASCII control characters: HT or tab + * (ASCII value 9), LF or line feed (ASCII value 10), and CR or carriage + * return (ASCII value 13). The Text Compaction mode also includes various latch + * and shift characters which are used exclusively within the Mode:: The Text + * Compaction mode encodes up to 2 characters per codeword. The compaction rules + * for converting data into PDF417 codewords are defined in 5.4.2.2. The sub-mode + * switches are defined in 5.4.2.3. + * + * @param textCompactionData The text compaction data. + * @param byteCompactionData The byte compaction data if there + * was a mode shift. + * @param length The size of the text compaction and byte compaction data. + * @param result The decoded data is appended to the result. + * @param startMode The mode in which decoding starts + * @return The mode in which decoding ended + */ +fn decodeTextCompaction( + textCompactionData: &[u32], + byteCompactionData: &[u32], + length: usize, + result: &mut ECIStringBuilder, + startMode: Mode, +) -> Mode { + // Beginning from an initial state + // The default compaction mode for PDF417 in effect at the start of each symbol shall always be Text + // Compaction mode Alpha sub-mode (uppercase alphabetic). A latch codeword from another mode to the Text + // Compaction mode shall always switch to the Text Compaction Alpha sub-Mode:: + let mut subMode = startMode; + let mut priorToShiftMode = startMode; + let mut latchedMode = startMode; + let mut i = 0; + while i < length { + let subModeCh = textCompactionData[i]; + let mut ch = 0 as char; + match subMode { + Mode::ALPHA => + // Alpha (uppercase alphabetic) + { + if subModeCh < 26 { + // Upper case Alpha Character + ch = char::from_u32('A' as u32 + subModeCh).unwrap(); + } else { + match subModeCh { + 26 => ch = ' ', + LL => { + subMode = Mode::LOWER; + latchedMode = subMode; + } + ML => { + subMode = Mode::MIXED; + latchedMode = subMode; + } + PS => { + // Shift to punctuation + priorToShiftMode = subMode; + subMode = Mode::PUNCT_SHIFT; + } + MODE_SHIFT_TO_BYTE_COMPACTION_MODE => { + result.append_char(char::from_u32(byteCompactionData[i]).unwrap()) + } + TEXT_COMPACTION_MODE_LATCH => { + subMode = Mode::ALPHA; + latchedMode = subMode; + } + _ => {} + } + } + } + + Mode::LOWER => + // Lower (lowercase alphabetic) + { + if subModeCh < 26 { + ch = char::from_u32('a' as u32 + subModeCh).unwrap(); + } else { + match subModeCh { + 26 => ch = ' ', + AS => { + // Shift to alpha + priorToShiftMode = subMode; + subMode = Mode::ALPHA_SHIFT; + } + ML => { + subMode = Mode::MIXED; + latchedMode = subMode; + } + PS => { + // Shift to punctuation + priorToShiftMode = subMode; + subMode = Mode::PUNCT_SHIFT; + } + MODE_SHIFT_TO_BYTE_COMPACTION_MODE => { + result.append_char(char::from_u32(byteCompactionData[i]).unwrap()) + } + TEXT_COMPACTION_MODE_LATCH => { + subMode = Mode::ALPHA; + latchedMode = subMode; + } + _ => {} + } + } + } + + Mode::MIXED => + // Mixed (numeric and some punctuation) + { + if subModeCh < PL { + ch = MIXED_CHARS[subModeCh as usize]; + } else { + match subModeCh { + PL => { + subMode = Mode::PUNCT; + latchedMode = subMode; + } + 26 => ch = ' ', + LL => { + subMode = Mode::LOWER; + latchedMode = subMode; + } + AL | TEXT_COMPACTION_MODE_LATCH => { + subMode = Mode::ALPHA; + latchedMode = subMode; + } + PS => { + // Shift to punctuation + priorToShiftMode = subMode; + subMode = Mode::PUNCT_SHIFT; + } + MODE_SHIFT_TO_BYTE_COMPACTION_MODE => { + result.append_char(char::from_u32(byteCompactionData[i]).unwrap()) + } + _ => {} + } + } + } + + Mode::PUNCT => + // Punctuation + { + if subModeCh < PAL { + ch = PUNCT_CHARS[subModeCh as usize]; + } else { + match subModeCh { + PAL | TEXT_COMPACTION_MODE_LATCH => { + subMode = Mode::ALPHA; + latchedMode = subMode; + } + MODE_SHIFT_TO_BYTE_COMPACTION_MODE => { + result.append_char(char::from_u32(byteCompactionData[i]).unwrap()) + } + _ => {} + } + } + } + + Mode::ALPHA_SHIFT => { + // Restore sub-mode + subMode = priorToShiftMode; + if subModeCh < 26 { + ch = char::from_u32('A' as u32 + subModeCh).unwrap(); + } else { + match subModeCh { + 26 => ch = ' ', + TEXT_COMPACTION_MODE_LATCH => subMode = Mode::ALPHA, + _ => {} + } + } + } + + Mode::PUNCT_SHIFT => { + // Restore sub-mode + subMode = priorToShiftMode; + if subModeCh < PAL { + ch = PUNCT_CHARS[subModeCh as usize]; + } else { + match subModeCh { + PAL | TEXT_COMPACTION_MODE_LATCH => subMode = Mode::ALPHA, + MODE_SHIFT_TO_BYTE_COMPACTION_MODE => + // PS before Shift-to-Byte is used as a padding character, + // see 5.4.2.4 of the specification + { + result.append_char(char::from_u32(byteCompactionData[i]).unwrap()) + } + _ => {} + } + } + } + } + if ch as u32 != 0 { + // Append decoded character to result + result.append_char(ch); + } + i += 1; + } + return latchedMode; +} + +/** + * Byte Compaction mode (see 5.4.3) permits all 256 possible 8-bit byte values to be encoded. + * This includes all ASCII characters value 0 to 127 inclusive and provides for international + * character set support. + * + * @param mode The byte compaction mode i.e. 901 or 924 + * @param codewords The array of codewords (data + error) + * @param codeIndex The current index into the codeword array. + * @param result The decoded data is appended to the result. + * @return The next index into the codeword array. + */ +fn byteCompaction( + mode: u32, + codewords: &[u32], + codeIndex: usize, + result: &mut ECIStringBuilder, +) -> Result { + let mut end = false; + let mut codeIndex = codeIndex; + + while codeIndex < codewords[0] as usize && !end { + //handle leading ECIs + while codeIndex < codewords[0] as usize && codewords[codeIndex] == ECI_CHARSET { + codeIndex += 1; + result.appendECI(codewords[codeIndex])?; + codeIndex += 1; + } + + if codeIndex >= codewords[0] as usize || codewords[codeIndex] >= TEXT_COMPACTION_MODE_LATCH + { + end = true; + } else { + //decode one block of 5 codewords to 6 bytes + let mut value: u64 = 0; + let mut count = 0; + loop { + value = 900 * value + codewords[codeIndex] as u64; + codeIndex += 1; + count += 1; + if !(count < 5 + && codeIndex < codewords[0] as usize + && codewords[codeIndex] < TEXT_COMPACTION_MODE_LATCH) + { + break; + } + } /*while (count < 5 && + codeIndex < codewords[0] && + codewords[codeIndex] < TEXT_COMPACTION_MODE_LATCH);*/ + if count == 5 + && (mode == BYTE_COMPACTION_MODE_LATCH_6 + || codeIndex < codewords[0] as usize + && codewords[codeIndex] < TEXT_COMPACTION_MODE_LATCH) + { + for i in 0..6 { + // for (int i = 0; i < 6; i++) { + result.append_byte((value >> (8 * (5 - i))) as u8); + } + } else { + codeIndex -= count; + while (codeIndex < codewords[0] as usize) && !end { + let code = codewords[codeIndex]; + codeIndex += 1; + if code < TEXT_COMPACTION_MODE_LATCH { + result.append_byte(code as u8); + } else if code == ECI_CHARSET { + result.appendECI(codewords[codeIndex])?; + codeIndex += 1; + } else { + codeIndex -= 1; + end = true; + } + } + } + } + } + Ok(codeIndex) +} + +/** + * Numeric Compaction mode (see 5.4.4) permits efficient encoding of numeric data strings. + * + * @param codewords The array of codewords (data + error) + * @param codeIndex The current index into the codeword array. + * @param result The decoded data is appended to the result. + * @return The next index into the codeword array. + */ +fn numericCompaction( + codewords: &[u32], + codeIndex: usize, + result: &mut ECIStringBuilder, +) -> Result { + let mut count = 0; + let mut end = false; + let mut codeIndex = codeIndex; + + let mut numericCodewords = [0; MAX_NUMERIC_CODEWORDS]; + + while codeIndex < codewords[0] as usize && !end { + let code = codewords[codeIndex]; + codeIndex += 1; + if codeIndex == codewords[0] as usize { + end = true; + } + if code < TEXT_COMPACTION_MODE_LATCH { + numericCodewords[count] = code; + count += 1; + } else { + match code { + TEXT_COMPACTION_MODE_LATCH + | BYTE_COMPACTION_MODE_LATCH + | BYTE_COMPACTION_MODE_LATCH_6 + | BEGIN_MACRO_PDF417_CONTROL_BLOCK + | BEGIN_MACRO_PDF417_OPTIONAL_FIELD + | MACRO_PDF417_TERMINATOR + | ECI_CHARSET => { + codeIndex -= 1; + end = true; + } + _ => {} + } + } + if (count % MAX_NUMERIC_CODEWORDS == 0 || code == NUMERIC_COMPACTION_MODE_LATCH || end) + && count > 0 + { + // Re-invoking Numeric Compaction mode (by using codeword 902 + // while in Numeric Compaction mode) serves to terminate the + // current Numeric Compaction mode grouping as described in 5.4.4.2, + // and then to start a new one grouping. + result.append_string(&decodeBase900toBase10(&numericCodewords, count)?); + count = 0; + } + } + Ok(codeIndex) +} + +/** + * Convert a list of Numeric Compacted codewords from Base 900 to Base 10. + * + * @param codewords The array of codewords + * @param count The number of codewords + * @return The decoded string representing the Numeric data. + */ +/* + EXAMPLE + Encode the fifteen digit numeric string 000213298174000 + Prefix the numeric string with a 1 and set the initial value of + t = 1 000 213 298 174 000 + Calculate codeword 0 + d0 = 1 000 213 298 174 000 mod 900 = 200 + + t = 1 000 213 298 174 000 div 900 = 1 111 348 109 082 + Calculate codeword 1 + d1 = 1 111 348 109 082 mod 900 = 282 + + t = 1 111 348 109 082 div 900 = 1 234 831 232 + Calculate codeword 2 + d2 = 1 234 831 232 mod 900 = 632 + + t = 1 234 831 232 div 900 = 1 372 034 + Calculate codeword 3 + d3 = 1 372 034 mod 900 = 434 + + t = 1 372 034 div 900 = 1 524 + Calculate codeword 4 + d4 = 1 524 mod 900 = 624 + + t = 1 524 div 900 = 1 + Calculate codeword 5 + d5 = 1 mod 900 = 1 + t = 1 div 900 = 0 + Codeword sequence is: 1, 624, 434, 632, 282, 200 + + Decode the above codewords involves + 1 x 900 power of 5 + 624 x 900 power of 4 + 434 x 900 power of 3 + + 632 x 900 power of 2 + 282 x 900 power of 1 + 200 x 900 power of 0 = 1000213298174000 + + Remove leading 1 => RXingResult is 000213298174000 +*/ +fn decodeBase900toBase10(codewords: &[u32], count: usize) -> Result { + let mut result = 0.to_biguint().unwrap(); + for i in 0..count { + // for (int i = 0; i < count; i++) { + result += &EXP900[count - i - 1] * (codewords[i].to_biguint().unwrap()); + // result = result.add(EXP900[count - i - 1].multiply(BigInteger.valueOf(codewords[i]))); + } + let resultString = result.to_string(); + if resultString.chars().nth(0).unwrap() != '1' { + return Err(Exceptions::FormatException("".to_owned())); + } + Ok(resultString[1..].to_owned()) +} diff --git a/src/pdf417/decoder/detection_result.rs b/src/pdf417/decoder/detection_result.rs index 800724c..c7d58a4 100644 --- a/src/pdf417/decoder/detection_result.rs +++ b/src/pdf417/decoder/detection_result.rs @@ -14,13 +14,13 @@ * limitations under the License. */ -use std::fmt::Display; +use std::{fmt::Display, rc::Rc}; use crate::pdf417::pdf_417_common; use super::{ BarcodeMetadata, BoundingBox, Codeword, DetectionRXingResultColumn, - DetectionRXingResultRowIndicatorColumn, + DetectionRXingResultColumnTrait, DetectionRXingResultRowIndicatorColumn, }; const ADJUST_ROW_NUMBER_SKIP: u32 = 2; @@ -28,21 +28,25 @@ const ADJUST_ROW_NUMBER_SKIP: u32 = 2; /** * @author Guenther Grau */ -pub struct DetectionRXingResult<'a> { +pub struct DetectionRXingResult { barcodeMetadata: BarcodeMetadata, - detectionRXingResultColumns: Vec>>, - boundingBox: BoundingBox<'a>, + detectionRXingResultColumns: Vec>>, + boundingBox: Rc, barcodeColumnCount: usize, } -impl<'a> DetectionRXingResult<'_> { +impl DetectionRXingResult { pub fn new( barcodeMetadata: BarcodeMetadata, - boundingBox: BoundingBox<'a>, - ) -> DetectionRXingResult<'a> { + boundingBox: Rc, + ) -> DetectionRXingResult { + let mut columns = Vec::new(); + for i in 0..(barcodeMetadata.getColumnCount() as usize + 2) { + columns.push(None); + } DetectionRXingResult { barcodeColumnCount: barcodeMetadata.getColumnCount() as usize, - detectionRXingResultColumns: vec![None; barcodeMetadata.getColumnCount() as usize + 2], + detectionRXingResultColumns: columns, //vec![None; barcodeMetadata.getColumnCount() as usize + 2], barcodeMetadata, boundingBox, } @@ -52,7 +56,9 @@ impl<'a> DetectionRXingResult<'_> { // detectionRXingResultColumns = new DetectionRXingResultColumn[barcodeColumnCount + 2]; } - pub fn getDetectionRXingResultColumns(&mut self) -> &Vec> { + pub fn getDetectionRXingResultColumns( + &mut self, + ) -> &Vec>> { self.adjustIndicatorColumnRowNumbers(0); let pos = self.barcodeColumnCount + 1; self.adjustIndicatorColumnRowNumbers(pos); @@ -81,6 +87,7 @@ impl<'a> DetectionRXingResult<'_> { self.detectionRXingResultColumns[pos] .as_mut() .unwrap() + .as_indicator_row() .adjustCompleteIndicatorColumnRowNumbers(&self.barcodeMetadata); } } @@ -515,27 +522,35 @@ impl<'a> DetectionRXingResult<'_> { self.barcodeMetadata.getErrorCorrectionLevel() } - // pub fn setBoundingBox(&'a mut self, boundingBox:BoundingBox<'a>) { - // self.boundingBox = boundingBox; - // } - - pub fn getBoundingBox(&self) -> &BoundingBox { - &self.boundingBox + pub fn setBoundingBox(&mut self, boundingBox: Rc) { + self.boundingBox = boundingBox; } - // pub fn setDetectionRXingResultColumn(&mut self, barcodeColumn:usize, detectionRXingResultColumn:Option>) { - // self.detectionRXingResultColumns[barcodeColumn] = detectionRXingResultColumn; - // } + pub fn getBoundingBox(&self) -> Rc { + self.boundingBox.clone() + } + + pub fn setDetectionRXingResultColumn( + &mut self, + barcodeColumn: usize, + detectionRXingResultColumn: Option, + ) { + self.detectionRXingResultColumns[barcodeColumn] = if detectionRXingResultColumn.is_none() { + None + } else { + Some(Box::new(detectionRXingResultColumn.unwrap())) + }; + } pub fn getDetectionRXingResultColumn( &self, barcodeColumn: usize, - ) -> &Option { + ) -> &Option> { &self.detectionRXingResultColumns[barcodeColumn] } } -impl Display for DetectionRXingResult<'_> { +impl Display for DetectionRXingResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { todo!() } diff --git a/src/pdf417/decoder/detection_result_column.rs b/src/pdf417/decoder/detection_result_column.rs index 56057bb..dd4a8fb 100644 --- a/src/pdf417/decoder/detection_result_column.rs +++ b/src/pdf417/decoder/detection_result_column.rs @@ -14,26 +14,43 @@ * limitations under the License. */ -use std::fmt::Display; +use std::{fmt::Display, rc::Rc}; use super::{BoundingBox, Codeword, DetectionRXingResultRowIndicatorColumn}; const MAX_NEARBY_DISTANCE: u32 = 5; +pub trait DetectionRXingResultColumnTrait { + fn new(boundingBox: Rc) -> DetectionRXingResultColumn + where + Self: Sized; + fn new_with_is_left(boundingBox: Rc, isLeft: bool) -> DetectionRXingResultColumn + where + Self: Sized; + fn getCodewordNearby(&self, imageRow: u32) -> &Option; + fn imageRowToCodewordIndex(&self, imageRow: u32) -> usize; + fn setCodeword(&mut self, imageRow: u32, codeword: Codeword); + fn getCodeword(&self, imageRow: u32) -> &Option; + fn getBoundingBox(&self) -> &BoundingBox; + fn getCodewords(&self) -> &[Option]; + fn getCodewordsMut(&mut self) -> &mut [Option]; + fn as_indicator_row(&mut self) -> &mut dyn DetectionRXingResultRowIndicatorColumn; +} + /** * @author Guenther Grau */ #[derive(Clone)] -pub struct DetectionRXingResultColumn<'a> { - boundingBox: BoundingBox<'a>, +pub struct DetectionRXingResultColumn { + boundingBox: BoundingBox, codewords: Vec>, pub(super) isLeft: Option, } -impl<'a> DetectionRXingResultColumn<'_> { - pub fn new(boundingBox: &'a BoundingBox) -> DetectionRXingResultColumn<'a> { +impl DetectionRXingResultColumnTrait for DetectionRXingResultColumn { + fn new(boundingBox: Rc) -> DetectionRXingResultColumn { DetectionRXingResultColumn { - boundingBox: BoundingBox::from_other(boundingBox), + boundingBox: BoundingBox::from_other(boundingBox.clone()), codewords: vec![None; (boundingBox.getMaxY() - boundingBox.getMinY() + 1) as usize], isLeft: None, } @@ -41,18 +58,15 @@ impl<'a> DetectionRXingResultColumn<'_> { // codewords = new Codeword[boundingBox.getMaxY() - boundingBox.getMinY() + 1]; } - pub fn new_with_is_left( - boundingBox: &'a BoundingBox, - isLeft: bool, - ) -> DetectionRXingResultColumn<'a> { + fn new_with_is_left(boundingBox: Rc, isLeft: bool) -> DetectionRXingResultColumn { DetectionRXingResultColumn { - boundingBox: BoundingBox::from_other(boundingBox), + boundingBox: BoundingBox::from_other(boundingBox.clone()), codewords: vec![None; (boundingBox.getMaxY() - boundingBox.getMinY() + 1) as usize], isLeft: Some(isLeft), } } - pub fn getCodewordNearby(&self, imageRow: u32) -> &Option { + fn getCodewordNearby(&self, imageRow: u32) -> &Option { let mut codeword = self.getCodeword(imageRow); if codeword.is_some() { return codeword; @@ -77,35 +91,40 @@ impl<'a> DetectionRXingResultColumn<'_> { &None } - pub fn imageRowToCodewordIndex(&self, imageRow: u32) -> usize { + fn imageRowToCodewordIndex(&self, imageRow: u32) -> usize { (imageRow - self.boundingBox.getMinY()) as usize } - pub fn setCodeword(&mut self, imageRow: u32, codeword: Codeword) { + fn setCodeword(&mut self, imageRow: u32, codeword: Codeword) { let pos = self.imageRowToCodewordIndex(imageRow); self.codewords[pos] = Some(codeword); } - pub fn getCodeword(&self, imageRow: u32) -> &Option { + fn getCodeword(&self, imageRow: u32) -> &Option { &self.codewords[self.imageRowToCodewordIndex(imageRow)] } - pub fn getBoundingBox(&self) -> &BoundingBox { + fn getBoundingBox(&self) -> &BoundingBox { &self.boundingBox } - pub fn getCodewords(&self) -> &[Option] { + fn getCodewords(&self) -> &[Option] { &self.codewords } - pub(super) fn getCodewordsMut(&mut self) -> &mut [Option] { + + fn getCodewordsMut(&mut self) -> &mut [Option] { &mut self.codewords } + + fn as_indicator_row(&mut self) -> &mut dyn DetectionRXingResultRowIndicatorColumn { + self as &mut dyn DetectionRXingResultRowIndicatorColumn + } // pub fn as_row_indicator(&self) -> DetectionRXingResultRowIndicatorColumn { // DetectionRXingResultRowIndicatorColumn::new(&self.boundingBox, false) // } } -impl Display for DetectionRXingResultColumn<'_> { +impl Display for DetectionRXingResultColumn { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if self.isLeft.is_some() { write!(f, "IsLeft: {} \n", self.isLeft.as_ref().unwrap()); diff --git a/src/pdf417/decoder/detection_result_row_indicator_column.rs b/src/pdf417/decoder/detection_result_row_indicator_column.rs index dfe09f9..ffb9e4b 100644 --- a/src/pdf417/decoder/detection_result_row_indicator_column.rs +++ b/src/pdf417/decoder/detection_result_row_indicator_column.rs @@ -18,12 +18,15 @@ use std::fmt::Display; use crate::{pdf417::pdf_417_common, ResultPoint}; -use super::{BarcodeMetadata, BarcodeValue, BoundingBox, Codeword, DetectionRXingResultColumn}; +use super::{ + BarcodeMetadata, BarcodeValue, BoundingBox, Codeword, DetectionRXingResultColumn, + DetectionRXingResultColumnTrait, +}; /** * @author Guenther Grau */ -pub trait DetectionRXingResultRowIndicatorColumn { +pub trait DetectionRXingResultRowIndicatorColumn: DetectionRXingResultColumnTrait { // 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 @@ -34,7 +37,7 @@ pub trait DetectionRXingResultRowIndicatorColumn { fn isLeft(&self) -> bool; } -impl<'a> DetectionRXingResultRowIndicatorColumn for DetectionRXingResultColumn<'_> { +impl DetectionRXingResultRowIndicatorColumn for DetectionRXingResultColumn { // private final boolean isLeft; // TODO implement properly diff --git a/src/pdf417/decoder/mod.rs b/src/pdf417/decoder/mod.rs index a7b1595..9b64288 100644 --- a/src/pdf417/decoder/mod.rs +++ b/src/pdf417/decoder/mod.rs @@ -23,4 +23,5 @@ pub use detection_result_row_indicator_column::*; mod detection_result; pub use detection_result::*; -// pub mod pdf_417_scanning_decoder; +pub mod decoded_bit_stream_parser; +pub mod pdf_417_scanning_decoder; diff --git a/src/pdf417/decoder/pdf_417_scanning_decoder.rs b/src/pdf417/decoder/pdf_417_scanning_decoder.rs index 4cf4700..282ec38 100644 --- a/src/pdf417/decoder/pdf_417_scanning_decoder.rs +++ b/src/pdf417/decoder/pdf_417_scanning_decoder.rs @@ -14,407 +14,689 @@ * limitations under the License. */ -use crate::Exceptions; +use std::rc::Rc; +use crate::{ + common::{BitMatrix, DecoderRXingResult}, + pdf417::pdf_417_common, + Exceptions, RXingResultPoint, ResultPoint, +}; + +use super::{ + decoded_bit_stream_parser, ec, pdf_417_codeword_decoder, BarcodeMetadata, BarcodeValue, + BoundingBox, Codeword, DetectionRXingResult, DetectionRXingResultColumn, + DetectionRXingResultColumnTrait, DetectionRXingResultRowIndicatorColumn, +}; /** * @author Guenther Grau */ - const CODEWORD_SKEW_SIZE :u32= 2; +const CODEWORD_SKEW_SIZE: u32 = 2; - const MAX_ERRORS :u32= 3; - const MAX_EC_CODEWORDS:u32 = 512; - // const errorCorrection:ErrorCorrection = ErrorCorrection::new(); +const MAX_ERRORS: u32 = 3; +const MAX_EC_CODEWORDS: u32 = 512; +// const errorCorrection:ErrorCorrection = ErrorCorrection::new(); +// TODO don't pass in minCodewordWidth and maxCodewordWidth, pass in barcode columns for start and stop pattern +// columns. That way width can be deducted from the pattern column. +// This approach also allows to detect more details about the barcode, e.g. if a bar type (white or black) is wider +// than it should be. This can happen if the scanner used a bad blackpoint. +pub fn decode( + image: &BitMatrix, + imageTopLeft: Option, + imageBottomLeft: Option, + imageTopRight: Option, + imageBottomRight: Option, + minCodewordWidth: u32, + maxCodewordWidth: u32, +) -> Result { + let mut minCodewordWidth = minCodewordWidth; + let mut maxCodewordWidth = maxCodewordWidth; + let mut boundingBox = Rc::new(BoundingBox::new( + Rc::new(image.clone()), + imageTopLeft, + imageBottomLeft, + imageTopRight, + imageBottomRight, + )?); + let mut leftRowIndicatorColumn = None; + let mut rightRowIndicatorColumn = None; + let mut detectionRXingResult = None; + for firstPass in [true, false] { + // for (boolean firstPass = true; ; firstPass = false) { + if imageTopLeft.is_some() { + leftRowIndicatorColumn = Some(getRowIndicatorColumn( + image, + boundingBox.clone(), + imageTopLeft.as_ref().unwrap(), + true, + minCodewordWidth, + maxCodewordWidth, + )); + } + if imageTopRight.is_some() { + rightRowIndicatorColumn = Some(getRowIndicatorColumn( + image, + boundingBox.clone(), + imageTopRight.as_ref().unwrap(), + false, + minCodewordWidth, + maxCodewordWidth, + )); + } + detectionRXingResult = merge(&mut leftRowIndicatorColumn, &mut rightRowIndicatorColumn)?; + if detectionRXingResult.is_none() { + return Err(Exceptions::NotFoundException("".to_owned())); + } + // detectionRXingResult = detectionRXingResult; - // TODO don't pass in minCodewordWidth and maxCodewordWidth, pass in barcode columns for start and stop pattern - // columns. That way width can be deducted from the pattern column. - // This approach also allows to detect more details about the barcode, e.g. if a bar type (white or black) is wider - // than it should be. This can happen if the scanner used a bad blackpoint. - pub fn decode( image:&BitMatrix, - imageTopLeft:RXingResultPoint, - imageBottomLeft:RXingResultPoint, - imageTopRight:RXingResultPoint, - imageBottomRight:RXingResultPoint, - minCodewordWidth:u32, - maxCodewordWidth:u32) - -> Result { - BoundingBox boundingBox = new BoundingBox(image, imageTopLeft, imageBottomLeft, imageTopRight, imageBottomRight); - DetectionRXingResultRowIndicatorColumn leftRowIndicatorColumn = null; - DetectionRXingResultRowIndicatorColumn rightRowIndicatorColumn = null; - DetectionRXingResult detectionRXingResult; - for (boolean firstPass = true; ; firstPass = false) { - if (imageTopLeft != null) { - leftRowIndicatorColumn = getRowIndicatorColumn(image, boundingBox, imageTopLeft, true, minCodewordWidth, - maxCodewordWidth); - } - if (imageTopRight != null) { - rightRowIndicatorColumn = getRowIndicatorColumn(image, boundingBox, imageTopRight, false, minCodewordWidth, - maxCodewordWidth); - } - detectionRXingResult = merge(leftRowIndicatorColumn, rightRowIndicatorColumn); - if (detectionRXingResult == null) { - throw NotFoundException.getNotFoundInstance(); - } - BoundingBox resultBox = detectionRXingResult.getBoundingBox(); - if (firstPass && resultBox != null && - (resultBox.getMinY() < boundingBox.getMinY() || resultBox.getMaxY() > boundingBox.getMaxY())) { - boundingBox = resultBox; - } else { - break; - } + let resultBox = detectionRXingResult.as_ref().unwrap().getBoundingBox(); + if firstPass + && (resultBox.getMinY() < boundingBox.getMinY() + || resultBox.getMaxY() > boundingBox.getMaxY()) + // if firstPass && resultBox.is_some() && + { + boundingBox = resultBox.clone(); + } else { + break; + } } - detectionRXingResult.setBoundingBox(boundingBox); - int maxBarcodeColumn = detectionRXingResult.getBarcodeColumnCount() + 1; + let mut detectionRXingResult = detectionRXingResult.unwrap(); + + let leftToRight = leftRowIndicatorColumn.is_some(); + + detectionRXingResult.setBoundingBox(boundingBox.clone()); + let maxBarcodeColumn = detectionRXingResult.getBarcodeColumnCount() + 1; detectionRXingResult.setDetectionRXingResultColumn(0, leftRowIndicatorColumn); detectionRXingResult.setDetectionRXingResultColumn(maxBarcodeColumn, rightRowIndicatorColumn); - boolean leftToRight = leftRowIndicatorColumn != null; - for (int barcodeColumnCount = 1; barcodeColumnCount <= maxBarcodeColumn; barcodeColumnCount++) { - int barcodeColumn = leftToRight ? barcodeColumnCount : maxBarcodeColumn - barcodeColumnCount; - if (detectionRXingResult.getDetectionRXingResultColumn(barcodeColumn) != null) { - // This will be the case for the opposite row indicator column, which doesn't need to be decoded again. - continue; - } - DetectionRXingResultColumn detectionRXingResultColumn; - if (barcodeColumn == 0 || barcodeColumn == maxBarcodeColumn) { - detectionRXingResultColumn = new DetectionRXingResultRowIndicatorColumn(boundingBox, barcodeColumn == 0); - } else { - detectionRXingResultColumn = new DetectionRXingResultColumn(boundingBox); - } - detectionRXingResult.setDetectionRXingResultColumn(barcodeColumn, detectionRXingResultColumn); - int startColumn = -1; - int previousStartColumn = startColumn; - // TODO start at a row for which we know the start position, then detect upwards and downwards from there. - for (int imageRow = boundingBox.getMinY(); imageRow <= boundingBox.getMaxY(); imageRow++) { - startColumn = getStartColumn(detectionRXingResult, barcodeColumn, imageRow, leftToRight); - if (startColumn < 0 || startColumn > boundingBox.getMaxX()) { - if (previousStartColumn == -1) { + // let leftToRight = leftRowIndicatorColumn.is_some(); + for barcodeColumnCount in 1..=maxBarcodeColumn { + // for (int barcodeColumnCount = 1; barcodeColumnCount <= maxBarcodeColumn; barcodeColumnCount++) { + let barcodeColumn = if leftToRight { + barcodeColumnCount + } else { + maxBarcodeColumn - barcodeColumnCount + }; + if detectionRXingResult + .getDetectionRXingResultColumn(barcodeColumn) + .is_some() + { + // This will be the case for the opposite row indicator column, which doesn't need to be decoded again. continue; - } - startColumn = previousStartColumn; } - Codeword codeword = detectCodeword(image, boundingBox.getMinX(), boundingBox.getMaxX(), leftToRight, - startColumn, imageRow, minCodewordWidth, maxCodewordWidth); - if (codeword != null) { - detectionRXingResultColumn.setCodeword(imageRow, codeword); - previousStartColumn = startColumn; - minCodewordWidth = Math.min(minCodewordWidth, codeword.getWidth()); - maxCodewordWidth = Math.max(maxCodewordWidth, codeword.getWidth()); - } - } - } - return createDecoderRXingResult(detectionRXingResult); - } - - fn merge( leftRowIndicatorColumn:&DetectionRXingResultRowIndicatorColumn, - rightRowIndicatorColumn:&DetectionRXingResultRowIndicatorColumn) - -> Result { - if (leftRowIndicatorColumn == null && rightRowIndicatorColumn == null) { - return null; - } - BarcodeMetadata barcodeMetadata = getBarcodeMetadata(leftRowIndicatorColumn, rightRowIndicatorColumn); - if (barcodeMetadata == null) { - return null; - } - BoundingBox boundingBox = BoundingBox.merge(adjustBoundingBox(leftRowIndicatorColumn), - adjustBoundingBox(rightRowIndicatorColumn)); - return new DetectionRXingResult(barcodeMetadata, boundingBox); - } - - fn adjustBoundingBox( rowIndicatorColumn:&DetectionRXingResultRowIndicatorColumn) - -> Result { - if (rowIndicatorColumn == null) { - return null; - } - int[] rowHeights = rowIndicatorColumn.getRowHeights(); - if (rowHeights == null) { - return null; - } - int maxRowHeight = getMax(rowHeights); - int missingStartRows = 0; - for (int rowHeight : rowHeights) { - missingStartRows += maxRowHeight - rowHeight; - if (rowHeight > 0) { - break; - } - } - Codeword[] codewords = rowIndicatorColumn.getCodewords(); - for (int row = 0; missingStartRows > 0 && codewords[row] == null; row++) { - missingStartRows--; - } - int missingEndRows = 0; - for (int row = rowHeights.length - 1; row >= 0; row--) { - missingEndRows += maxRowHeight - rowHeights[row]; - if (rowHeights[row] > 0) { - break; - } - } - for (int row = codewords.length - 1; missingEndRows > 0 && codewords[row] == null; row--) { - missingEndRows--; - } - return rowIndicatorColumn.getBoundingBox().addMissingRows(missingStartRows, missingEndRows, - rowIndicatorColumn.isLeft()); - } - - fn getMax( values:&[u32]) -> u32{ - int maxValue = -1; - for (int value : values) { - maxValue = Math.max(maxValue, value); - } - return maxValue; - } - - fn getBarcodeMetadata( leftRowIndicatorColumn:&DetectionRXingResultRowIndicatorColumn, - rightRowIndicatorColumn:&DetectionRXingResultRowIndicatorColumn) -> BarcodeMetadata{ - BarcodeMetadata leftBarcodeMetadata; - if (leftRowIndicatorColumn == null || - (leftBarcodeMetadata = leftRowIndicatorColumn.getBarcodeMetadata()) == null) { - return rightRowIndicatorColumn == null ? null : rightRowIndicatorColumn.getBarcodeMetadata(); - } - BarcodeMetadata rightBarcodeMetadata; - if (rightRowIndicatorColumn == null || - (rightBarcodeMetadata = rightRowIndicatorColumn.getBarcodeMetadata()) == null) { - return leftBarcodeMetadata; - } - - if (leftBarcodeMetadata.getColumnCount() != rightBarcodeMetadata.getColumnCount() && - leftBarcodeMetadata.getErrorCorrectionLevel() != rightBarcodeMetadata.getErrorCorrectionLevel() && - leftBarcodeMetadata.getRowCount() != rightBarcodeMetadata.getRowCount()) { - return null; - } - return leftBarcodeMetadata; - } - - fn getRowIndicatorColumn( image:&BitMatrix, - boundingBox:&BoundingBox, - startPoint:&RXingResultPoint, - leftToRight:u32, - minCodewordWidth:u32, - maxCodewordWidth:u32)-> DetectionRXingResultRowIndicatorColumn { - DetectionRXingResultRowIndicatorColumn rowIndicatorColumn = new DetectionRXingResultRowIndicatorColumn(boundingBox, - leftToRight); - for (int i = 0; i < 2; i++) { - int increment = i == 0 ? 1 : -1; - int startColumn = (int) startPoint.getX(); - for (int imageRow = (int) startPoint.getY(); imageRow <= boundingBox.getMaxY() && - imageRow >= boundingBox.getMinY(); imageRow += increment) { - Codeword codeword = detectCodeword(image, 0, image.getWidth(), leftToRight, startColumn, imageRow, - minCodewordWidth, maxCodewordWidth); - if (codeword != null) { - rowIndicatorColumn.setCodeword(imageRow, codeword); - if (leftToRight) { - startColumn = codeword.getStartX(); - } else { - startColumn = codeword.getEndX(); - } - } - } - } - return rowIndicatorColumn; - } - - fn adjustCodewordCount( detectionRXingResult:&DetectionRXingResult, barcodeMatrix:&Vec>) - throws NotFoundException { - BarcodeValue barcodeMatrix01 = barcodeMatrix[0][1]; - int[] numberOfCodewords = barcodeMatrix01.getValue(); - int calculatedNumberOfCodewords = detectionRXingResult.getBarcodeColumnCount() * - detectionRXingResult.getBarcodeRowCount() - - getNumberOfECCodeWords(detectionRXingResult.getBarcodeECLevel()); - if (numberOfCodewords.length == 0) { - if (calculatedNumberOfCodewords < 1 || calculatedNumberOfCodewords > PDF417Common.MAX_CODEWORDS_IN_BARCODE) { - throw NotFoundException.getNotFoundInstance(); - } - barcodeMatrix01.setValue(calculatedNumberOfCodewords); - } else if (numberOfCodewords[0] != calculatedNumberOfCodewords) { - if (calculatedNumberOfCodewords >= 1 && calculatedNumberOfCodewords <= PDF417Common.MAX_CODEWORDS_IN_BARCODE) { - // The calculated one is more reliable as it is derived from the row indicator columns - barcodeMatrix01.setValue(calculatedNumberOfCodewords); - } - } - } - - fn createDecoderRXingResult( detectionRXingResult:&DetectionRXingResult) -> Result { - BarcodeValue[][] barcodeMatrix = createBarcodeMatrix(detectionRXingResult); - adjustCodewordCount(detectionRXingResult, barcodeMatrix); - Collection erasures = new ArrayList<>(); - int[] codewords = new int[detectionRXingResult.getBarcodeRowCount() * detectionRXingResult.getBarcodeColumnCount()]; - List ambiguousIndexValuesList = new ArrayList<>(); - Collection ambiguousIndexesList = new ArrayList<>(); - for (int row = 0; row < detectionRXingResult.getBarcodeRowCount(); row++) { - for (int column = 0; column < detectionRXingResult.getBarcodeColumnCount(); column++) { - int[] values = barcodeMatrix[row][column + 1].getValue(); - int codewordIndex = row * detectionRXingResult.getBarcodeColumnCount() + column; - if (values.length == 0) { - erasures.add(codewordIndex); - } else if (values.length == 1) { - codewords[codewordIndex] = values[0]; + let mut detectionRXingResultColumn = if barcodeColumn == 0 + || barcodeColumn == maxBarcodeColumn + { + DetectionRXingResultColumn::new_with_is_left(boundingBox.clone(), barcodeColumn == 0) } else { - ambiguousIndexesList.add(codewordIndex); - ambiguousIndexValuesList.add(values); - } - } - } - int[][] ambiguousIndexValues = new int[ambiguousIndexValuesList.size()][]; - for (int i = 0; i < ambiguousIndexValues.length; i++) { - ambiguousIndexValues[i] = ambiguousIndexValuesList.get(i); - } - return createDecoderRXingResultFromAmbiguousValues(detectionRXingResult.getBarcodeECLevel(), codewords, - PDF417Common.toIntArray(erasures), PDF417Common.toIntArray(ambiguousIndexesList), ambiguousIndexValues); - } - - /** - * This method deals with the fact, that the decoding process doesn't always yield a single most likely value. The - * current error correction implementation doesn't deal with erasures very well, so it's better to provide a value - * for these ambiguous codewords instead of treating it as an erasure. The problem is that we don't know which of - * the ambiguous values to choose. We try decode using the first value, and if that fails, we use another of the - * ambiguous values and try to decode again. This usually only happens on very hard to read and decode barcodes, - * so decoding the normal barcodes is not affected by this. - * - * @param erasureArray contains the indexes of erasures - * @param ambiguousIndexes array with the indexes that have more than one most likely value - * @param ambiguousIndexValues two dimensional array that contains the ambiguous values. The first dimension must - * be the same length as the ambiguousIndexes array - */ - fn createDecoderRXingResultFromAmbiguousValues( ecLevel:u21, - codewords:&[u32], - erasureArray&[u32], - ambiguousIndexes&[u32], - ambiguousIndexValues:&Vec>) - -> Result { - int[] ambiguousIndexCount = new int[ambiguousIndexes.length]; - - int tries = 100; - while (tries-- > 0) { - for (int i = 0; i < ambiguousIndexCount.length; i++) { - codewords[ambiguousIndexes[i]] = ambiguousIndexValues[i][ambiguousIndexCount[i]]; - } - try { - return decodeCodewords(codewords, ecLevel, erasureArray); - } catch (ChecksumException ignored) { - // - } - if (ambiguousIndexCount.length == 0) { - throw ChecksumException.getChecksumInstance(); - } - for (int i = 0; i < ambiguousIndexCount.length; i++) { - if (ambiguousIndexCount[i] < ambiguousIndexValues[i].length - 1) { - ambiguousIndexCount[i]++; - break; - } else { - ambiguousIndexCount[i] = 0; - if (i == ambiguousIndexCount.length - 1) { - throw ChecksumException.getChecksumInstance(); - } - } - } - } - throw ChecksumException.getChecksumInstance(); - } - - fn createBarcodeMatrix( detectionRXingResult:&DetectionRXingResult) -> Vec>{ - BarcodeValue[][] barcodeMatrix = - new BarcodeValue[detectionRXingResult.getBarcodeRowCount()][detectionRXingResult.getBarcodeColumnCount() + 2]; - for (int row = 0; row < barcodeMatrix.length; row++) { - for (int column = 0; column < barcodeMatrix[row].length; column++) { - barcodeMatrix[row][column] = new BarcodeValue(); - } - } - - int column = 0; - for (DetectionRXingResultColumn detectionRXingResultColumn : detectionRXingResult.getDetectionRXingResultColumns()) { - if (detectionRXingResultColumn != null) { - for (Codeword codeword : detectionRXingResultColumn.getCodewords()) { - if (codeword != null) { - int rowNumber = codeword.getRowNumber(); - if (rowNumber >= 0) { - if (rowNumber >= barcodeMatrix.length) { - // We have more rows than the barcode metadata allows for, ignore them. - continue; - } - barcodeMatrix[rowNumber][column].setValue(codeword.getValue()); + DetectionRXingResultColumn::new(boundingBox.clone()) + }; + detectionRXingResult + .setDetectionRXingResultColumn(barcodeColumn, Some(detectionRXingResultColumn.clone())); + let mut startColumn: i32 = -1; + let mut previousStartColumn = startColumn; + // TODO start at a row for which we know the start position, then detect upwards and downwards from there. + for imageRow in boundingBox.getMinY()..=boundingBox.getMaxY() { + // for (int imageRow = boundingBox.getMinY(); imageRow <= boundingBox.getMaxY(); imageRow++) { + startColumn = + getStartColumn(&detectionRXingResult, barcodeColumn, imageRow, leftToRight) as i32; + if startColumn < 0 || startColumn > boundingBox.getMaxX() as i32 { + if previousStartColumn == -1 { + continue; + } + startColumn = previousStartColumn; + } + let codeword = detectCodeword( + image, + boundingBox.getMinX(), + boundingBox.getMaxX(), + leftToRight, + startColumn as u32, + imageRow, + minCodewordWidth, + maxCodewordWidth, + ); + if codeword.is_some() { + let codeword = codeword.unwrap(); + detectionRXingResultColumn.setCodeword(imageRow, codeword); + previousStartColumn = startColumn; + minCodewordWidth = minCodewordWidth.min(codeword.getWidth()); + maxCodewordWidth = maxCodewordWidth.min(codeword.getWidth()); } - } } - } - column++; } - return barcodeMatrix; - } - fn isValidBarcodeColumn( detectionRXingResult:&DetectionRXingResult, barcodeColumn:u32) -> bool{ - return barcodeColumn >= 0 && barcodeColumn <= detectionRXingResult.getBarcodeColumnCount() + 1; - } + createDecoderRXingResult(&mut detectionRXingResult) +} - fn getStartColumn( detectionRXingResult:&DetectionRXingResult, - barcodeColumn:u32, - imageRow:u32, - leftToRight:bool) -> u32{ - int offset = leftToRight ? 1 : -1; - Codeword codeword = null; - if (isValidBarcodeColumn(detectionRXingResult, barcodeColumn - offset)) { - codeword = detectionRXingResult.getDetectionRXingResultColumn(barcodeColumn - offset).getCodeword(imageRow); +fn merge<'a, T: DetectionRXingResultRowIndicatorColumn>( + leftRowIndicatorColumn: &'a mut Option, + rightRowIndicatorColumn: &'a mut Option, +) -> Result, Exceptions> { + if leftRowIndicatorColumn.is_none() && rightRowIndicatorColumn.is_none() { + return Ok(None); } - if (codeword != null) { - return leftToRight ? codeword.getEndX() : codeword.getStartX(); + let barcodeMetadata = getBarcodeMetadata(leftRowIndicatorColumn, rightRowIndicatorColumn); + if barcodeMetadata.is_none() { + return Ok(None); } - codeword = detectionRXingResult.getDetectionRXingResultColumn(barcodeColumn).getCodewordNearby(imageRow); - if (codeword != null) { - return leftToRight ? codeword.getStartX() : codeword.getEndX(); - } - if (isValidBarcodeColumn(detectionRXingResult, barcodeColumn - offset)) { - codeword = detectionRXingResult.getDetectionRXingResultColumn(barcodeColumn - offset).getCodewordNearby(imageRow); - } - if (codeword != null) { - return leftToRight ? codeword.getEndX() : codeword.getStartX(); - } - int skippedColumns = 0; + let boundingBox = Rc::new(BoundingBox::merge( + adjustBoundingBox(leftRowIndicatorColumn)?, + adjustBoundingBox(rightRowIndicatorColumn)?, + )?); - while (isValidBarcodeColumn(detectionRXingResult, barcodeColumn - offset)) { - barcodeColumn -= offset; - for (Codeword previousRowCodeword : detectionRXingResult.getDetectionRXingResultColumn(barcodeColumn).getCodewords()) { - if (previousRowCodeword != null) { - return (leftToRight ? previousRowCodeword.getEndX() : previousRowCodeword.getStartX()) + - offset * - skippedColumns * - (previousRowCodeword.getEndX() - previousRowCodeword.getStartX()); + Ok(Some(DetectionRXingResult::new( + barcodeMetadata.unwrap(), + boundingBox, + ))) +} + +fn adjustBoundingBox( + rowIndicatorColumn: &mut Option, +) -> Result, Exceptions> { + if rowIndicatorColumn.is_none() { + return Ok(None); + } + let rowIndicatorColumn = rowIndicatorColumn.as_mut().unwrap(); + + let rowHeights = rowIndicatorColumn.getRowHeights(); + if rowHeights.is_none() { + return Ok(None); + } + let rowHeights = rowHeights.unwrap(); + let maxRowHeight = getMax(&rowHeights); + let mut missingStartRows = 0; + for rowHeight in &rowHeights { + // for (int rowHeight : rowHeights) { + missingStartRows += maxRowHeight - rowHeight; + if *rowHeight > 0 { + break; } - } - skippedColumns++; } - return leftToRight ? detectionRXingResult.getBoundingBox().getMinX() : detectionRXingResult.getBoundingBox().getMaxX(); - } + let codewords = rowIndicatorColumn.getCodewords(); - fn detectCodeword( image:&BitMatrix, - minColumn:u32, - maxColumn:u32, - leftToRight:bool, - startColumn:u32, - imageRow:u32, - minCodewordWidth:u32, - maxCodewordWidth:u32) -> Codeword{ - startColumn = adjustCodewordStartColumn(image, minColumn, maxColumn, leftToRight, startColumn, imageRow); + let mut row = 0; + while missingStartRows > 0 && codewords[row].is_none() { + // for (int row = 0; missingStartRows > 0 && codewords[row] == null; row++) { + missingStartRows -= 1; + row += 1; + } + let mut missingEndRows = 0; + for row in (0..rowHeights.len()).rev() { + // for (int row = rowHeights.length - 1; row >= 0; row--) { + missingEndRows += maxRowHeight - rowHeights[row]; + if rowHeights[row] > 0 { + break; + } + } + let mut row = codewords.len() - 1; + while missingEndRows > 0 && codewords[row].is_none() { + // for (int row = codewords.length - 1; missingEndRows > 0 && codewords[row] == null; row--) { + missingEndRows -= 1; + + row -= 1; + } + Ok(Some(rowIndicatorColumn.getBoundingBox().addMissingRows( + missingStartRows, + missingEndRows, + rowIndicatorColumn.isLeft(), + )?)) +} + +fn getMax(values: &[u32]) -> u32 { + // let maxValue = -1; + // for (int value : values) { + // maxValue = Math.max(maxValue, value); + // } + // return maxValue; + *values.iter().max().unwrap() +} + +fn getBarcodeMetadata( + leftRowIndicatorColumn: &mut Option, + rightRowIndicatorColumn: &mut Option, +) -> Option { + let leftBarcodeMetadata; + + if leftRowIndicatorColumn.is_none() { + return if rightRowIndicatorColumn.is_none() { + None + } else { + rightRowIndicatorColumn + .as_mut() + .unwrap() + .getBarcodeMetadata() + }; + } else { + if leftRowIndicatorColumn + .as_mut() + .unwrap() + .getBarcodeMetadata() + .is_none() + { + return if rightRowIndicatorColumn.is_none() { + None + } else { + rightRowIndicatorColumn + .as_mut() + .unwrap() + .getBarcodeMetadata() + }; + } else { + leftBarcodeMetadata = leftRowIndicatorColumn + .as_mut() + .unwrap() + .getBarcodeMetadata() + } + } + // if leftRowIndicatorColumn.is_none() || + // (leftBarcodeMetadata = leftRowIndicatorColumn.getBarcodeMetadata()).is_none() { + // return if rightRowIndicatorColumn.is_none() {None} else {rightRowIndicatorColumn.getBarcodeMetadata()}; + // } + + let rightBarcodeMetadata; + + if rightRowIndicatorColumn.is_none() { + return leftBarcodeMetadata; + } else { + if rightRowIndicatorColumn + .as_mut() + .unwrap() + .getBarcodeMetadata() + .is_none() + { + return leftBarcodeMetadata; + } else { + rightBarcodeMetadata = rightRowIndicatorColumn + .as_mut() + .unwrap() + .getBarcodeMetadata(); + } + } + // if rightRowIndicatorColumn.is_none() || + // (rightBarcodeMetadata = rightRowIndicatorColumn.getBarcodeMetadata()).is_none() { + // return leftBarcodeMetadata; + // } + + if leftBarcodeMetadata.as_ref().unwrap().getColumnCount() + != rightBarcodeMetadata.as_ref().unwrap().getColumnCount() + && leftBarcodeMetadata + .as_ref() + .unwrap() + .getErrorCorrectionLevel() + != rightBarcodeMetadata + .as_ref() + .unwrap() + .getErrorCorrectionLevel() + && leftBarcodeMetadata.as_ref().unwrap().getRowCount() + != rightBarcodeMetadata.as_ref().unwrap().getRowCount() + { + return None; + } + + leftBarcodeMetadata +} + +fn getRowIndicatorColumn<'a>( + image: &BitMatrix, + boundingBox: Rc, + startPoint: &RXingResultPoint, + leftToRight: bool, + minCodewordWidth: u32, + maxCodewordWidth: u32, +) -> impl DetectionRXingResultRowIndicatorColumn + 'a { + let mut rowIndicatorColumn = + DetectionRXingResultColumn::new_with_is_left(boundingBox.clone(), leftToRight); + for i in 0..2 { + // for (int i = 0; i < 2; i++) { + let increment: i32 = if i == 0 { 1 } else { -1 }; + let mut startColumn: u32 = startPoint.getX() as u32; + let mut imageRow: i32 = startPoint.getY() as i32; + while imageRow <= boundingBox.getMaxY() as i32 && imageRow >= boundingBox.getMinY() as i32 { + // for (int imageRow = (int) startPoint.getY(); imageRow <= boundingBox.getMaxY() && + // imageRow >= boundingBox.getMinY(); imageRow += increment) { + let codeword = detectCodeword( + image, + 0, + image.getWidth(), + leftToRight, + startColumn, + imageRow as u32, + minCodewordWidth, + maxCodewordWidth, + ); + if let Some(codeword) = codeword { + // if codeword.is_some() { + rowIndicatorColumn.setCodeword(imageRow as u32, codeword); + if leftToRight { + startColumn = codeword.getStartX(); + } else { + startColumn = codeword.getEndX(); + } + } + + imageRow += increment; + } + } + + rowIndicatorColumn +} + +fn adjustCodewordCount( + detectionRXingResult: &DetectionRXingResult, + barcodeMatrix: &mut Vec>, +) -> Result<(), Exceptions> { + let barcodeMatrix01 = &mut barcodeMatrix[0][1]; + let numberOfCodewords = barcodeMatrix01.getValue(); + let calculatedNumberOfCodewords = (detectionRXingResult.getBarcodeColumnCount() as isize + * detectionRXingResult.getBarcodeRowCount() as isize + - getNumberOfECCodeWords(detectionRXingResult.getBarcodeECLevel()) as isize) + as u32; + if numberOfCodewords.len() == 0 { + if calculatedNumberOfCodewords < 1 + || calculatedNumberOfCodewords > pdf_417_common::MAX_CODEWORDS_IN_BARCODE + { + return Err(Exceptions::NotFoundException("".to_owned())); + } + barcodeMatrix01.setValue(calculatedNumberOfCodewords); + } else if numberOfCodewords[0] != calculatedNumberOfCodewords { + if calculatedNumberOfCodewords >= 1 + && calculatedNumberOfCodewords <= pdf_417_common::MAX_CODEWORDS_IN_BARCODE + { + // The calculated one is more reliable as it is derived from the row indicator columns + barcodeMatrix01.setValue(calculatedNumberOfCodewords); + } + } + Ok(()) +} + +fn createDecoderRXingResult( + detectionRXingResult: &mut DetectionRXingResult, +) -> Result { + let mut barcodeMatrix = createBarcodeMatrix(detectionRXingResult); + adjustCodewordCount(detectionRXingResult, &mut barcodeMatrix); + let mut erasures = Vec::new(); //new ArrayList<>(); + let mut codewords = vec![ + 0; + detectionRXingResult.getBarcodeRowCount() as usize + * detectionRXingResult.getBarcodeColumnCount() + ]; + let mut ambiguousIndexValuesList: Vec> = Vec::new(); + let mut ambiguousIndexesList = Vec::new(); //new ArrayList<>(); + for row in 0..detectionRXingResult.getBarcodeRowCount() { + // for (int row = 0; row < detectionRXingResult.getBarcodeRowCount(); row++) { + for column in 0..detectionRXingResult.getBarcodeColumnCount() { + // for (int column = 0; column < detectionRXingResult.getBarcodeColumnCount(); column++) { + let values = barcodeMatrix[row as usize][column + 1].getValue(); + let codewordIndex = + row as usize * detectionRXingResult.getBarcodeColumnCount() + column; + if values.len() == 0 { + erasures.push(codewordIndex as u32); + } else if values.len() == 1 { + codewords[codewordIndex] = values[0]; + } else { + ambiguousIndexesList.push(codewordIndex as u32); + ambiguousIndexValuesList.push(values); + } + } + } + let mut ambiguousIndexValues = Vec::new(); //new int[ambiguousIndexValuesList.size()][]; + for value in ambiguousIndexValuesList { + ambiguousIndexValues.push(value); + } + // for i in 0..ambiguousIndexValuesList.len() { + // // for (int i = 0; i < ambiguousIndexValues.length; i++) { + // ambiguousIndexValues[i] = ambiguousIndexValuesList.get(i) as u32; + // } + createDecoderRXingResultFromAmbiguousValues( + detectionRXingResult.getBarcodeECLevel(), + &mut codewords, + &mut erasures, + &mut ambiguousIndexesList, + &ambiguousIndexValues, + ) +} + +/** + * This method deals with the fact, that the decoding process doesn't always yield a single most likely value. The + * current error correction implementation doesn't deal with erasures very well, so it's better to provide a value + * for these ambiguous codewords instead of treating it as an erasure. The problem is that we don't know which of + * the ambiguous values to choose. We try decode using the first value, and if that fails, we use another of the + * ambiguous values and try to decode again. This usually only happens on very hard to read and decode barcodes, + * so decoding the normal barcodes is not affected by this. + * + * @param erasureArray contains the indexes of erasures + * @param ambiguousIndexes array with the indexes that have more than one most likely value + * @param ambiguousIndexValues two dimensional array that contains the ambiguous values. The first dimension must + * be the same length as the ambiguousIndexes array + */ +fn createDecoderRXingResultFromAmbiguousValues( + ecLevel: u32, + codewords: &mut [u32], + erasureArray: &mut [u32], + ambiguousIndexes: &mut [u32], + ambiguousIndexValues: &Vec>, +) -> Result { + let mut ambiguousIndexCount = vec![0; ambiguousIndexes.len()]; + + let mut tries = 100; + while tries > 0 { + for i in 0..ambiguousIndexCount.len() { + // for (int i = 0; i < ambiguousIndexCount.length; i++) { + codewords[ambiguousIndexes[i] as usize] = + ambiguousIndexValues[i][ambiguousIndexCount[i]]; + } + let attempted_decode = decodeCodewords(codewords, ecLevel, erasureArray); + if attempted_decode.is_ok() { + return attempted_decode; + } + // try { + // return decodeCodewords(codewords, ecLevel, erasureArray); + // } catch (ChecksumException ignored) { + // // + // } + if ambiguousIndexCount.len() == 0 { + return Err(Exceptions::ChecksumException("".to_owned())); + } + for i in 0..ambiguousIndexCount.len() { + // for (int i = 0; i < ambiguousIndexCount.length; i++) { + if ambiguousIndexCount[i] < ambiguousIndexValues[i].len() - 1 { + ambiguousIndexCount[i] += 1; + break; + } else { + ambiguousIndexCount[i] = 0; + if i == ambiguousIndexCount.len() - 1 { + return Err(Exceptions::ChecksumException("".to_owned())); + } + } + } + + tries -= 1; + } + Err(Exceptions::ChecksumException("".to_owned())) +} + +fn createBarcodeMatrix(detectionRXingResult: &mut DetectionRXingResult) -> Vec> { + let mut barcodeMatrix = + vec![ + vec![BarcodeValue::new(); detectionRXingResult.getBarcodeRowCount() as usize]; + detectionRXingResult.getBarcodeColumnCount() + 2 + ]; + // BarcodeValue[][] barcodeMatrix = + // new BarcodeValue[detectionRXingResult.getBarcodeRowCount()][detectionRXingResult.getBarcodeColumnCount() + 2]; + // for row in 0..barcodeMatrix.len() { + // // for (int row = 0; row < barcodeMatrix.length; row++) { + // for column in 0..barcodeMatrix[row].len() { + // // for (int column = 0; column < barcodeMatrix[row].length; column++) { + // barcodeMatrix[row][column] = BarcodeValue::new(); + // } + // } + + let mut column = 0; + for detectionRXingResultColumn in detectionRXingResult.getDetectionRXingResultColumns() { + // for (DetectionRXingResultColumn detectionRXingResultColumn : detectionRXingResult.getDetectionRXingResultColumns()) { + if detectionRXingResultColumn.is_some() { + for codeword in detectionRXingResultColumn.as_ref().unwrap().getCodewords() { + // for (Codeword codeword : detectionRXingResultColumn.getCodewords()) { + if let Some(codeword) = codeword { + // if codeword.is_some() { + let rowNumber = codeword.getRowNumber(); + if rowNumber >= 0 { + if rowNumber as usize >= barcodeMatrix.len() { + // We have more rows than the barcode metadata allows for, ignore them. + continue; + } + barcodeMatrix[rowNumber as usize][column].setValue(codeword.getValue()); + } + } + } + } + column += 1; + } + barcodeMatrix +} + +fn isValidBarcodeColumn(detectionRXingResult: &DetectionRXingResult, barcodeColumn: usize) -> bool { + barcodeColumn >= 0 && barcodeColumn <= detectionRXingResult.getBarcodeColumnCount() + 1 +} + +fn getStartColumn( + detectionRXingResult: &DetectionRXingResult, + barcodeColumn: usize, + imageRow: u32, + leftToRight: bool, +) -> u32 { + let offset: isize = if leftToRight { 1 } else { -1 }; + let mut barcodeColumn = barcodeColumn as isize; + let mut codeword = &None; + if isValidBarcodeColumn(detectionRXingResult, (barcodeColumn - offset) as usize) { + codeword = detectionRXingResult + .getDetectionRXingResultColumn((barcodeColumn as isize - offset) as usize) + .as_ref() + .unwrap() + .getCodeword(imageRow); + } + if let Some(codeword) = codeword { + return if leftToRight { + codeword.getEndX() + } else { + codeword.getStartX() + }; + } + codeword = detectionRXingResult + .getDetectionRXingResultColumn(barcodeColumn as usize) + .as_ref() + .unwrap() + .getCodewordNearby(imageRow); + + if let Some(codeword) = codeword { + return if leftToRight { + codeword.getStartX() + } else { + codeword.getEndX() + }; + } + if isValidBarcodeColumn(detectionRXingResult, (barcodeColumn - offset) as usize) { + codeword = detectionRXingResult + .getDetectionRXingResultColumn((barcodeColumn - offset) as usize) + .as_ref() + .unwrap() + .getCodewordNearby(imageRow); + } + if let Some(codeword) = codeword { + return if leftToRight { + codeword.getEndX() + } else { + codeword.getStartX() + }; + } + let mut skippedColumns = 0; + + while isValidBarcodeColumn(detectionRXingResult, (barcodeColumn - offset) as usize) { + barcodeColumn -= offset; + for previousRowCodeword in detectionRXingResult + .getDetectionRXingResultColumn(barcodeColumn as usize) + .as_ref() + .unwrap() + .getCodewords() + { + // for (Codeword previousRowCodeword : detectionRXingResult.getDetectionRXingResultColumn(barcodeColumn).getCodewords()) { + if let Some(previousRowCodeword) = previousRowCodeword { + // if previousRowCodeword.is_some() { + return ((if leftToRight { + previousRowCodeword.getEndX() + } else { + previousRowCodeword.getStartX() + }) as isize + + offset + * skippedColumns as isize + * (previousRowCodeword.getEndX() - previousRowCodeword.getStartX()) + as isize) as u32; + } + } + skippedColumns += 1; + } + if leftToRight { + detectionRXingResult.getBoundingBox().getMinX() + } else { + detectionRXingResult.getBoundingBox().getMaxX() + } +} + +fn detectCodeword( + image: &BitMatrix, + minColumn: u32, + maxColumn: u32, + leftToRight: bool, + startColumn: u32, + imageRow: u32, + minCodewordWidth: u32, + maxCodewordWidth: u32, +) -> Option { + let mut startColumn = adjustCodewordStartColumn( + image, + minColumn, + maxColumn, + leftToRight, + startColumn, + imageRow, + ); // we usually know fairly exact now how long a codeword is. We should provide minimum and maximum expected length // and try to adjust the read pixels, e.g. remove single pixel errors or try to cut off exceeding pixels. // min and maxCodewordWidth should not be used as they are calculated for the whole barcode an can be inaccurate // for the current position - int[] moduleBitCount = getModuleBitCount(image, minColumn, maxColumn, leftToRight, startColumn, imageRow); - if (moduleBitCount == null) { - return null; + let moduleBitCount = getModuleBitCount( + image, + minColumn, + maxColumn, + leftToRight, + startColumn, + imageRow, + ); + if moduleBitCount.is_none() { + return None; } - int endColumn; - int codewordBitCount = MathUtils.sum(moduleBitCount); - if (leftToRight) { - endColumn = startColumn + codewordBitCount; + let mut moduleBitCount = moduleBitCount.unwrap(); + + let endColumn; + let codewordBitCount = moduleBitCount.iter().sum::(); + if leftToRight { + endColumn = startColumn + codewordBitCount; } else { - for (int i = 0; i < moduleBitCount.length / 2; i++) { - int tmpCount = moduleBitCount[i]; - moduleBitCount[i] = moduleBitCount[moduleBitCount.length - 1 - i]; - moduleBitCount[moduleBitCount.length - 1 - i] = tmpCount; - } - endColumn = startColumn; - startColumn = endColumn - codewordBitCount; + for i in 0..(moduleBitCount.len() / 2) { + // for (int i = 0; i < moduleBitCount.length / 2; i++) { + + let len = moduleBitCount.len(); + moduleBitCount.swap(i, len - 1 - i); + + // let tmpCount = moduleBitCount[i]; + // moduleBitCount[i] = moduleBitCount[moduleBitCount.len() - 1 - i]; + // moduleBitCount[moduleBitCount.len() - 1 - i] = tmpCount; + } + endColumn = startColumn; + startColumn = endColumn - codewordBitCount; } // TODO implement check for width and correction of black and white bars // use start (and maybe stop pattern) to determine if black bars are wider than white bars. If so, adjust. @@ -430,185 +712,226 @@ use crate::Exceptions; // We could also use the width of surrounding codewords for more accurate results, but this seems // sufficient for now - if (!checkCodewordSkew(codewordBitCount, minCodewordWidth, maxCodewordWidth)) { - // We could try to use the startX and endX position of the codeword in the same column in the previous row, - // create the bit count from it and normalize it to 8. This would help with single pixel errors. - return null; + if !checkCodewordSkew(codewordBitCount, minCodewordWidth, maxCodewordWidth) { + // We could try to use the startX and endX position of the codeword in the same column in the previous row, + // create the bit count from it and normalize it to 8. This would help with single pixel errors. + return None; } - int decodedValue = PDF417CodewordDecoder.getDecodedValue(moduleBitCount); - int codeword = PDF417Common.getCodeword(decodedValue); - if (codeword == -1) { - return null; + let decodedValue = pdf_417_codeword_decoder::getDecodedValue(&moduleBitCount); + let codeword = pdf_417_common::getCodeword(decodedValue); + if codeword == -1 { + return None; } - return new Codeword(startColumn, endColumn, getCodewordBucketNumber(decodedValue), codeword); - } - fn getModuleBitCount( image:&BitMatrix, - minColumn:u32, - maxColumn:u32, - leftToRight:bool, - startColumn:u32, - imageRow:u32) -> Option<[u32;8]>{ - int imageColumn = startColumn; - int[] moduleBitCount = new int[8]; - int moduleNumber = 0; - int increment = leftToRight ? 1 : -1; - boolean previousPixelValue = leftToRight; - while ((leftToRight ? imageColumn < maxColumn : imageColumn >= minColumn) && - moduleNumber < moduleBitCount.length) { - if (image.get(imageColumn, imageRow) == previousPixelValue) { - moduleBitCount[moduleNumber]++; - imageColumn += increment; - } else { - moduleNumber++; - previousPixelValue = !previousPixelValue; - } - } - if (moduleNumber == moduleBitCount.length || - ((imageColumn == (leftToRight ? maxColumn : minColumn)) && - moduleNumber == moduleBitCount.length - 1)) { - return moduleBitCount; - } - return null; - } + Some(Codeword::new( + startColumn, + endColumn, + getCodewordBucketNumber(decodedValue), + codeword as u32, + )) +} - fn getNumberOfECCodeWords(int barcodeECLevel) -> u32{ - return 2 << barcodeECLevel; - } - - fn adjustCodewordStartColumn( image:&BitMatrix, - minColumn:u32, - maxColumn:u32, - leftToRight:bool, - codewordStartColumn:u32, - imageRow:u32) -> u32{ - int correctedStartColumn = codewordStartColumn; - int increment = leftToRight ? -1 : 1; - // there should be no black pixels before the start column. If there are, then we need to start earlier. - for (int i = 0; i < 2; i++) { - while ((leftToRight ? correctedStartColumn >= minColumn : correctedStartColumn < maxColumn) && - leftToRight == image.get(correctedStartColumn, imageRow)) { - if (Math.abs(codewordStartColumn - correctedStartColumn) > CODEWORD_SKEW_SIZE) { - return codewordStartColumn; +fn getModuleBitCount( + image: &BitMatrix, + minColumn: u32, + maxColumn: u32, + leftToRight: bool, + startColumn: u32, + imageRow: u32, +) -> Option<[u32; 8]> { + let mut imageColumn = startColumn as i32; + let mut moduleBitCount = [0_u32; 8]; + let mut moduleNumber = 0; + let increment: i32 = if leftToRight { 1 } else { -1 }; + let mut previousPixelValue = leftToRight; + while (if leftToRight { + imageColumn < maxColumn as i32 + } else { + imageColumn >= minColumn as i32 + }) && moduleNumber < moduleBitCount.len() + { + if image.get(imageColumn as u32, imageRow) == previousPixelValue { + moduleBitCount[moduleNumber] += 1; + imageColumn += increment; + } else { + moduleNumber += 1; + previousPixelValue = !previousPixelValue; } - correctedStartColumn += increment; - } - increment = -increment; - leftToRight = !leftToRight; + } + if moduleNumber == moduleBitCount.len() + || ((imageColumn + == (if leftToRight { + maxColumn as i32 + } else { + minColumn as i32 + })) + && moduleNumber == moduleBitCount.len() - 1) + { + return Some(moduleBitCount); + } + + None +} + +fn getNumberOfECCodeWords(barcodeECLevel: u32) -> u32 { + 2 << barcodeECLevel +} + +fn adjustCodewordStartColumn( + image: &BitMatrix, + minColumn: u32, + maxColumn: u32, + leftToRight: bool, + codewordStartColumn: u32, + imageRow: u32, +) -> u32 { + let mut correctedStartColumn = codewordStartColumn; + let mut increment: i32 = if leftToRight { -1 } else { 1 }; + let mut leftToRight = leftToRight; + // there should be no black pixels before the start column. If there are, then we need to start earlier. + for _i in 0..2 { + // for (int i = 0; i < 2; i++) { + while (if leftToRight { + correctedStartColumn >= minColumn + } else { + correctedStartColumn < maxColumn + }) && leftToRight == image.get(correctedStartColumn, imageRow) + { + if (codewordStartColumn as i64 - correctedStartColumn as i64).abs() as u32 + > CODEWORD_SKEW_SIZE + { + return codewordStartColumn; + } + correctedStartColumn += increment as u32; + } + increment = -increment; + leftToRight = !leftToRight; } return correctedStartColumn; - } +} - fn checkCodewordSkew( codewordSize:u32, minCodewordWidth:u32, maxCodewordWidth:u32) -> bool{ - return minCodewordWidth - CODEWORD_SKEW_SIZE <= codewordSize && - codewordSize <= maxCodewordWidth + CODEWORD_SKEW_SIZE; - } +fn checkCodewordSkew(codewordSize: u32, minCodewordWidth: u32, maxCodewordWidth: u32) -> bool { + return minCodewordWidth - CODEWORD_SKEW_SIZE <= codewordSize + && codewordSize <= maxCodewordWidth + CODEWORD_SKEW_SIZE; +} - fn decodeCodewords( codewords:&[u32], ecLevel:u32, erasures:&[u32]) -> Result{ - if (codewords.length == 0) { - throw FormatException.getFormatInstance(); +fn decodeCodewords( + codewords: &mut [u32], + ecLevel: u32, + erasures: &mut [u32], +) -> Result { + if codewords.is_empty() { + return Err(Exceptions::FormatException("".to_owned())); } - int numECCodewords = 1 << (ecLevel + 1); - int correctedErrorsCount = correctErrors(codewords, erasures, numECCodewords); + let numECCodewords = 1 << (ecLevel + 1); + let correctedErrorsCount = correctErrors(codewords, erasures, numECCodewords)?; verifyCodewordCount(codewords, numECCodewords); // Decode the codewords - DecoderRXingResult decoderRXingResult = DecodedBitStreamParser.decode(codewords, String.valueOf(ecLevel)); + let mut decoderRXingResult = + decoded_bit_stream_parser::decode(codewords, &ecLevel.to_string())?; decoderRXingResult.setErrorsCorrected(correctedErrorsCount); - decoderRXingResult.setErasures(erasures.length); - return decoderRXingResult; - } + decoderRXingResult.setErasures(erasures.len()); - /** - *

Given data and error-correction codewords received, possibly corrupted by errors, attempts to - * correct the errors in-place.

- * - * @param codewords data and error correction codewords - * @param erasures positions of any known erasures - * @param numECCodewords number of error correction codewords that are available in codewords - * @throws ChecksumException if error correction fails - */ - fn correctErrors( codewords:&[u32], erasures:&[u32], numECCodewords:u32) -> Result { - if (erasures != null && - erasures.length > numECCodewords / 2 + MAX_ERRORS || - numECCodewords < 0 || - numECCodewords > MAX_EC_CODEWORDS) { - // Too many errors or EC Codewords is corrupted - throw ChecksumException.getChecksumInstance(); + Ok(decoderRXingResult) +} + +/** + *

Given data and error-correction codewords received, possibly corrupted by errors, attempts to + * correct the errors in-place.

+ * + * @param codewords data and error correction codewords + * @param erasures positions of any known erasures + * @param numECCodewords number of error correction codewords that are available in codewords + * @throws ChecksumException if error correction fails + */ +fn correctErrors( + codewords: &mut [u32], + erasures: &mut [u32], + numECCodewords: u32, +) -> Result { + if !erasures.is_empty() && erasures.len() as u32 > numECCodewords / 2 + MAX_ERRORS + || numECCodewords < 0 + || numECCodewords > MAX_EC_CODEWORDS + { + // Too many errors or EC Codewords is corrupted + return Err(Exceptions::ChecksumException("".to_owned())); } - return errorCorrection.decode(codewords, numECCodewords, erasures); - } + ec::error_correction::decode(codewords, numECCodewords, erasures) +} - /** - * Verify that all is OK with the codeword array. - */ - fn verifyCodewordCount( codewords:&[u32], numECCodewords:u32) -> Result<(),Exceptions> { - if (codewords.length < 4) { - // Codeword array size should be at least 4 allowing for - // Count CW, At least one Data CW, Error Correction CW, Error Correction CW - throw FormatException.getFormatInstance(); +/** + * Verify that all is OK with the codeword array. + */ +fn verifyCodewordCount(codewords: &mut [u32], numECCodewords: u32) -> Result<(), Exceptions> { + if codewords.len() < 4 { + // Codeword array size should be at least 4 allowing for + // Count CW, At least one Data CW, Error Correction CW, Error Correction CW + return Err(Exceptions::FormatException("".to_owned())); } // The first codeword, the Symbol Length Descriptor, shall always encode the total number of data // codewords in the symbol, including the Symbol Length Descriptor itself, data codewords and pad // codewords, but excluding the number of error correction codewords. - int numberOfCodewords = codewords[0]; - if (numberOfCodewords > codewords.length) { - throw FormatException.getFormatInstance(); + let numberOfCodewords = codewords[0]; + if numberOfCodewords > codewords.len() as u32 { + return Err(Exceptions::FormatException("".to_owned())); } - if (numberOfCodewords == 0) { - // Reset to the length of the array - 8 (Allow for at least level 3 Error Correction (8 Error Codewords) - if (numECCodewords < codewords.length) { - codewords[0] = codewords.length - numECCodewords; - } else { - throw FormatException.getFormatInstance(); - } - } - } - - fn getBitCountForCodeword( codeword:u32) -> [u32;8]{ - int[] result = new int[8]; - int previousValue = 0; - int i = result.length - 1; - while (true) { - if ((codeword & 0x1) != previousValue) { - previousValue = codeword & 0x1; - i--; - if (i < 0) { - break; + if numberOfCodewords == 0 { + // Reset to the length of the array - 8 (Allow for at least level 3 Error Correction (8 Error Codewords) + if numECCodewords < codewords.len() as u32 { + codewords[0] = codewords.len() as u32 - numECCodewords; + } else { + return Err(Exceptions::FormatException("".to_owned())); } - } - result[i]++; - codeword >>= 1; } - return result; - } + Ok(()) +} - fn getCodewordBucketNumber( codeword:u32) -> u32{ - return getCodewordBucketNumber(getBitCountForCodeword(codeword)); - } - - fn getCodewordBucketNumber( moduleBitCount:&[u32]) -> u32{ - return (moduleBitCount[0] - moduleBitCount[2] + moduleBitCount[4] - moduleBitCount[6] + 9) % 9; - } - - fn toString( barcodeMatrix:Vec>) -> String{ - try (Formatter formatter = new Formatter()) { - for (int row = 0; row < barcodeMatrix.length; row++) { - formatter.format("Row %2d: ", row); - for (int column = 0; column < barcodeMatrix[row].length; column++) { - BarcodeValue barcodeValue = barcodeMatrix[row][column]; - if (barcodeValue.getValue().length == 0) { - formatter.format(" ", (Object[]) null); - } else { - formatter.format("%4d(%2d)", barcodeValue.getValue()[0], - barcodeValue.getConfidence(barcodeValue.getValue()[0])); - } +fn getBitCountForCodeword(codeword: u32) -> [u32; 8] { + let mut codeword = codeword; + let mut result = [0; 8]; //new int[8]; + let mut previousValue = 0; + let mut i = result.len() - 1; + loop { + if (codeword & 0x1) != previousValue { + previousValue = codeword & 0x1; + i -= 1; + if i < 0 { + break; + } } - formatter.format("%n"); - } - return formatter.toString(); + result[i] += 1; + codeword >>= 1; } - } + result +} + +fn getCodewordBucketNumber(codeword: u32) -> u32 { + getCodewordBucketNumberArray(&getBitCountForCodeword(codeword)) +} + +fn getCodewordBucketNumberArray(moduleBitCount: &[u32]) -> u32 { + (moduleBitCount[0] - moduleBitCount[2] + moduleBitCount[4] - moduleBitCount[6] + 9) % 9 +} + +// fn toString( barcodeMatrix:Vec>) -> String{ +// try (Formatter formatter = new Formatter()) { +// for (int row = 0; row < barcodeMatrix.length; row++) { +// formatter.format("Row %2d: ", row); +// for (int column = 0; column < barcodeMatrix[row].length; column++) { +// BarcodeValue barcodeValue = barcodeMatrix[row][column]; +// if (barcodeValue.getValue().length == 0) { +// formatter.format(" ", (Object[]) null); +// } else { +// formatter.format("%4d(%2d)", barcodeValue.getValue()[0], +// barcodeValue.getConfidence(barcodeValue.getValue()[0])); +// } +// } +// formatter.format("%n"); +// } +// return formatter.toString(); +// } +// } diff --git a/src/pdf417/mod.rs b/src/pdf417/mod.rs index e0df49f..2b9f2da 100644 --- a/src/pdf417/mod.rs +++ b/src/pdf417/mod.rs @@ -3,3 +3,6 @@ pub mod detector; pub mod encoder; pub mod pdf_417_common; + +mod pdf_417_result_metadata; +pub use pdf_417_result_metadata::*; diff --git a/src/pdf417/pdf_417_common.rs b/src/pdf417/pdf_417_common.rs index 414373d..50e35d1 100644 --- a/src/pdf417/pdf_417_common.rs +++ b/src/pdf417/pdf_417_common.rs @@ -43,18 +43,18 @@ pub fn getBitCountSum(moduleBitCount: &[u32]) -> u32 { moduleBitCount.iter().sum::() } -pub fn toIntArray(list: Vec) -> Vec { - // if (list == null || list.isEmpty()) { - // return EMPTY_INT_ARRAY; - // } - // int[] result = new int[list.size()]; - // int i = 0; - // for (Integer integer : list) { - // result[i++] = integer; - // } - // return result; - list -} +// pub fn toIntArray(list: Vec) -> Vec { +// // if (list == null || list.isEmpty()) { +// // return EMPTY_INT_ARRAY; +// // } +// // int[] result = new int[list.size()]; +// // int i = 0; +// // for (Integer integer : list) { +// // result[i++] = integer; +// // } +// // return result; +// list +// } /** * @param symbol encoded symbol to translate to a codeword diff --git a/src/pdf417/pdf_417_result_metadata.rs b/src/pdf417/pdf_417_result_metadata.rs new file mode 100644 index 0000000..183d516 --- /dev/null +++ b/src/pdf417/pdf_417_result_metadata.rs @@ -0,0 +1,186 @@ +/* + * 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 + */ +pub struct PDF417RXingResultMetadata { + segmentIndex: usize, + fileId: String, + lastSegment: bool, + segmentCount: isize, + sender: String, + addressee: String, + fileName: String, + fileSize: i64, + timestamp: i64, + checksum: i32, + optionalData: Vec, +} + +impl Default for PDF417RXingResultMetadata { + fn default() -> Self { + Self { + segmentIndex: Default::default(), + fileId: Default::default(), + lastSegment: Default::default(), + segmentCount: -1, + sender: Default::default(), + addressee: Default::default(), + fileName: Default::default(), + fileSize: -1, + timestamp: -1, + checksum: -1, + optionalData: Default::default(), + } + } +} + +impl PDF417RXingResultMetadata { + /** + * The Segment ID represents the segment of the whole file distributed over different symbols. + * + * @return File segment index + */ + pub fn getSegmentIndex(&self) -> usize { + self.segmentIndex + } + + pub fn setSegmentIndex(&mut self, segmentIndex: usize) { + self.segmentIndex = segmentIndex; + } + + /** + * Is the same for each related PDF417 symbol + * + * @return File ID + */ + pub fn getFileId(&self) -> &str { + &self.fileId + } + + pub fn setFileId(&mut self, fileId: String) { + self.fileId = fileId; + } + + /** + * @return always null + * @deprecated use dedicated already parsed fields + */ + #[deprecated] + pub fn getOptionalData(&self) -> &[u32] { + &self.optionalData + } + + /** + * @param optionalData old optional data format as int array + * @deprecated parse and use new fields + */ + #[deprecated] + pub fn setOptionalData(&mut self, optionalData: Vec) { + self.optionalData = optionalData; + } + + /** + * @return true if it is the last segment + */ + pub fn isLastSegment(&self) -> bool { + self.lastSegment + } + + pub fn setLastSegment(&mut self, lastSegment: bool) { + self.lastSegment = lastSegment; + } + + /** + * @return count of segments, -1 if not set + */ + pub fn getSegmentCount(&self) -> isize { + self.segmentCount + } + + pub fn setSegmentCount(&mut self, segmentCount: isize) { + self.segmentCount = segmentCount; + } + + pub fn getSender(&self) -> &str { + &self.sender + } + + pub fn setSender(&mut self, sender: String) { + self.sender = sender; + } + + pub fn getAddressee(&self) -> &str { + &self.addressee + } + + pub fn setAddressee(&mut self, addressee: String) { + self.addressee = addressee; + } + + /** + * Filename of the encoded file + * + * @return filename + */ + pub fn getFileName(&self) -> &str { + &self.fileName + } + + pub fn setFileName(&mut self, fileName: String) { + self.fileName = fileName; + } + + /** + * filesize in bytes of the encoded file + * + * @return filesize in bytes, -1 if not set + */ + pub fn getFileSize(&self) -> i64 { + self.fileSize + } + + pub fn setFileSize(&mut self, fileSize: i64) { + self.fileSize = fileSize; + } + + /** + * 16-bit CRC checksum using CCITT-16 + * + * @return crc checksum, -1 if not set + */ + pub fn getChecksum(&self) -> i32 { + self.checksum + } + + pub fn setChecksum(&mut self, checksum: i32) { + self.checksum = checksum; + } + + /** + * unix epock timestamp, elapsed seconds since 1970-01-01 + * + * @return elapsed seconds, -1 if not set + */ + pub fn getTimestamp(&self) -> i64 { + self.timestamp + } + + pub fn setTimestamp(&mut self, timestamp: i64) { + self.timestamp = timestamp; + } +}