diff --git a/Cargo.toml b/Cargo.toml index 5892a3c..b6bfaca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ chrono-tz = "0.4" image = {version = "0.24.3", optional = true} imageproc = {version = "0.23.0", optional = true} unicode-segmentation = "1.10.0" +codepage-437 = "0.1.0" [dev-dependencies] java-properties = "1.4.1" diff --git a/src/common/mod.rs b/src/common/mod.rs index a9d7f5d..25a94c3 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -1545,7 +1545,7 @@ impl BitSource { return Err(Exceptions::IllegalArgumentException(numBits.to_string())); } - let mut result = 0; + let mut result:u32 = 0; let mut num_bits = numBits; @@ -1555,7 +1555,8 @@ impl BitSource { let toRead = cmp::min(num_bits, bitsLeft); let bitsToNotRead = bitsLeft - toRead; let mask = (0xFF >> (8 - toRead)) << bitsToNotRead; - result = (self.bytes[self.byte_offset] & mask) >> bitsToNotRead; + + result = (self.bytes[self.byte_offset] & mask) as u32 >> bitsToNotRead; num_bits -= toRead; self.bit_offset += toRead; if self.bit_offset == 8 { @@ -1567,7 +1568,8 @@ impl BitSource { // Next read whole bytes if num_bits > 0 { while num_bits >= 8 { - result = ((result as u16) << 8) as u8 | (self.bytes[self.byte_offset] & 0xFF); + result = (result << 8) | (self.bytes[self.byte_offset] & 0xFF) as u32; + // result = ((result as u16) << 8) as u8 | (self.bytes[self.byte_offset]); self.byte_offset += 1; num_bits -= 8; } @@ -1577,12 +1579,12 @@ impl BitSource { let bits_to_not_read = 8 - num_bits; let mask = (0xFF >> bits_to_not_read) << bits_to_not_read; result = (result << num_bits) - | ((self.bytes[self.byte_offset] & mask) >> bits_to_not_read); + | ((self.bytes[self.byte_offset] & mask) as u32 >> bits_to_not_read); self.bit_offset += num_bits; } } - return Ok(result.into()); + return Ok(result); } /** @@ -2514,7 +2516,8 @@ impl CharacterSetECI { pub fn getCharset(cs_eci: &CharacterSetECI) -> EncodingRef { let name = match cs_eci { - CharacterSetECI::Cp437 => "CP437", + // CharacterSetECI::Cp437 => "CP437", + CharacterSetECI::Cp437 => "UTF-8", CharacterSetECI::ISO8859_1 => "ISO-8859-1", CharacterSetECI::ISO8859_2 => "ISO-8859-2", CharacterSetECI::ISO8859_3 => "ISO-8859-3", diff --git a/src/exceptions.rs b/src/exceptions.rs index cd2dcb1..938666a 100644 --- a/src/exceptions.rs +++ b/src/exceptions.rs @@ -1,6 +1,6 @@ use std::{error::Error, fmt}; -#[derive(Debug)] +#[derive(Debug,PartialEq, Eq)] pub enum Exceptions { IllegalArgumentException(String), UnsupportedOperationException(String), diff --git a/src/qrcode/decoder/DecodedBitStreamParserTestCase.java b/src/qrcode/decoder/DecodedBitStreamParserTestCase.java deleted file mode 100644 index cba20b1..0000000 --- a/src/qrcode/decoder/DecodedBitStreamParserTestCase.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2008 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.zxing.qrcode.decoder; - -import com.google.zxing.common.BitSourceBuilder; -import org.junit.Assert; -import org.junit.Test; - -/** - * Tests {@link DecodedBitStreamParser}. - * - * @author Sean Owen - */ -public final class DecodedBitStreamParserTestCase extends Assert { - - @Test - public void testSimpleByteMode() throws Exception { - BitSourceBuilder builder = new BitSourceBuilder(); - builder.write(0x04, 4); // Byte mode - builder.write(0x03, 8); // 3 bytes - builder.write(0xF1, 8); - builder.write(0xF2, 8); - builder.write(0xF3, 8); - String result = DecodedBitStreamParser.decode(builder.toByteArray(), - Version.getVersionForNumber(1), null, null).getText(); - assertEquals("\u00f1\u00f2\u00f3", result); - } - - @Test - public void testSimpleSJIS() throws Exception { - BitSourceBuilder builder = new BitSourceBuilder(); - builder.write(0x04, 4); // Byte mode - builder.write(0x04, 8); // 4 bytes - builder.write(0xA1, 8); - builder.write(0xA2, 8); - builder.write(0xA3, 8); - builder.write(0xD0, 8); - String result = DecodedBitStreamParser.decode(builder.toByteArray(), - Version.getVersionForNumber(1), null, null).getText(); - assertEquals("\uff61\uff62\uff63\uff90", result); - } - - @Test - public void testECI() throws Exception { - BitSourceBuilder builder = new BitSourceBuilder(); - builder.write(0x07, 4); // ECI mode - builder.write(0x02, 8); // ECI 2 = CP437 encoding - builder.write(0x04, 4); // Byte mode - builder.write(0x03, 8); // 3 bytes - builder.write(0xA1, 8); - builder.write(0xA2, 8); - builder.write(0xA3, 8); - String result = DecodedBitStreamParser.decode(builder.toByteArray(), - Version.getVersionForNumber(1), null, null).getText(); - assertEquals("\u00ed\u00f3\u00fa", result); - } - - @Test - public void testHanzi() throws Exception { - BitSourceBuilder builder = new BitSourceBuilder(); - builder.write(0x0D, 4); // Hanzi mode - builder.write(0x01, 4); // Subset 1 = GB2312 encoding - builder.write(0x01, 8); // 1 characters - builder.write(0x03C1, 13); - String result = DecodedBitStreamParser.decode(builder.toByteArray(), - Version.getVersionForNumber(1), null, null).getText(); - assertEquals("\u963f", result); - } - - @Test - public void testHanziLevel1() throws Exception { - BitSourceBuilder builder = new BitSourceBuilder(); - builder.write(0x0D, 4); // Hanzi mode - builder.write(0x01, 4); // Subset 1 = GB2312 encoding - builder.write(0x01, 8); // 1 characters - // A5A2 (U+30A2) => A5A2 - A1A1 = 401, 4*60 + 01 = 0181 - builder.write(0x0181, 13); - String result = DecodedBitStreamParser.decode(builder.toByteArray(), - Version.getVersionForNumber(1), null, null).getText(); - assertEquals("\u30a2", result); - } - - // TODO definitely need more tests here - -} diff --git a/src/qrcode/decoder/DecodedBitStreamParserTestCase.rs b/src/qrcode/decoder/DecodedBitStreamParserTestCase.rs new file mode 100644 index 0000000..3f552b6 --- /dev/null +++ b/src/qrcode/decoder/DecodedBitStreamParserTestCase.rs @@ -0,0 +1,135 @@ +/* + * Copyright 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use std::collections::HashMap; + +use crate::{ + common::BitSourceBuilder, + qrcode::decoder::{decoded_bit_stream_parser, ErrorCorrectionLevel, Version}, +}; + +/** + * Tests {@link DecodedBitStreamParser}. + * + * @author Sean Owen + */ + +#[test] +fn testSimpleByteMode() { + let mut builder = BitSourceBuilder::new(); + builder.write(0x04, 4); // Byte mode + builder.write(0x03, 8); // 3 bytes + builder.write(0xF1, 8); + builder.write(0xF2, 8); + builder.write(0xF3, 8); + let result = decoded_bit_stream_parser::decode( + builder.toByteArray(), + Version::getVersionForNumber(1).expect("unwrap"), + ErrorCorrectionLevel::H, + &HashMap::new(), + ) + .expect("unwrap") + .getText() + .to_string(); + assert_eq!("\u{00f1}\u{00f2}\u{00f3}", result); +} + +#[test] +fn testSimpleSJIS() { + let mut builder = BitSourceBuilder::new(); + builder.write(0x04, 4); // Byte mode + builder.write(0x04, 8); // 4 bytes + builder.write(0xA1, 8); + builder.write(0xA2, 8); + builder.write(0xA3, 8); + builder.write(0xD0, 8); + let result = decoded_bit_stream_parser::decode( + builder.toByteArray(), + Version::getVersionForNumber(1).expect("unwrap"), + ErrorCorrectionLevel::H, + &HashMap::new(), + ) + .expect("unwrap") + .getText() + .to_owned(); + assert_eq!("\u{ff61}\u{ff62}\u{ff63}\u{ff90}", result); +} + +#[test] +fn testECI() { + let mut builder = BitSourceBuilder::new(); + + builder.write(0x07, 4); // ECI mode + builder.write(0x02, 8); // ECI 2 = CP437 encoding + builder.write(0x04, 4); // Byte mode + builder.write(0x03, 8); // 3 bytes + builder.write(0xA1, 8); + builder.write(0xA2, 8); + builder.write(0xA3, 8); + let result = decoded_bit_stream_parser::decode( + builder.toByteArray(), + Version::getVersionForNumber(1).expect("unwrap"), + ErrorCorrectionLevel::H, + &HashMap::new(), + ) + .expect("unwrap") + .getText() + .to_owned(); + assert_eq!("\u{00ed}\u{00f3}\u{00fa}", result); +} + +#[test] +fn testHanzi() { + let mut builder = BitSourceBuilder::new(); + + builder.write(0x0D, 4); // Hanzi mode + builder.write(0x01, 4); // Subset 1 = GB2312 encoding + builder.write(0x01, 8); // 1 characters + builder.write(0x03C1, 13); + let result = decoded_bit_stream_parser::decode( + builder.toByteArray(), + Version::getVersionForNumber(1).expect("unwrap"), + ErrorCorrectionLevel::H, + &HashMap::new(), + ) + .expect("unwrap") + .getText() + .to_owned(); + assert_eq!("\u{963f}", result); +} + +#[test] +fn testHanziLevel1() { + let mut builder = BitSourceBuilder::new(); + + builder.write(0x0D, 4); // Hanzi mode + builder.write(0x01, 4); // Subset 1 = GB2312 encoding + builder.write(0x01, 8); // 1 characters + // A5A2 (U+30A2) => A5A2 - A1A1 = 401, 4*60 + 01 = 0181 + builder.write(0x0181, 13); + let result = decoded_bit_stream_parser::decode( + builder.toByteArray(), + Version::getVersionForNumber(1).expect("unwrap"), + ErrorCorrectionLevel::H, + &HashMap::new(), + ) + .expect("unwrap") + .getText() + .to_owned(); + assert_eq!("\u{30a2}", result); +} + +// TODO definitely need more tests here diff --git a/src/qrcode/decoder/Decoder.java b/src/qrcode/decoder/Decoder.java deleted file mode 100644 index 65396b2..0000000 --- a/src/qrcode/decoder/Decoder.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright 2007 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.zxing.qrcode.decoder; - -import com.google.zxing.ChecksumException; -import com.google.zxing.DecodeHintType; -import com.google.zxing.FormatException; -import com.google.zxing.common.BitMatrix; -import com.google.zxing.common.DecoderRXingResult; -import com.google.zxing.common.reedsolomon.GenericGF; -import com.google.zxing.common.reedsolomon.ReedSolomonDecoder; -import com.google.zxing.common.reedsolomon.ReedSolomonException; - -import java.util.Map; - -/** - *

The main class which implements QR Code decoding -- as opposed to locating and extracting - * the QR Code from an image.

- * - * @author Sean Owen - */ -public final class Decoder { - - private final ReedSolomonDecoder rsDecoder; - - public Decoder() { - rsDecoder = new ReedSolomonDecoder(GenericGF.QR_CODE_FIELD_256); - } - - public DecoderRXingResult decode(boolean[][] image) throws ChecksumException, FormatException { - return decode(image, null); - } - - /** - *

Convenience method that can decode a QR Code represented as a 2D array of booleans. - * "true" is taken to mean a black module.

- * - * @param image booleans representing white/black QR Code modules - * @param hints decoding hints that should be used to influence decoding - * @return text and bytes encoded within the QR Code - * @throws FormatException if the QR Code cannot be decoded - * @throws ChecksumException if error correction fails - */ - public DecoderRXingResult decode(boolean[][] image, Map hints) - throws ChecksumException, FormatException { - return decode(BitMatrix.parse(image), hints); - } - - public DecoderRXingResult decode(BitMatrix bits) throws ChecksumException, FormatException { - return decode(bits, null); - } - - /** - *

Decodes a QR Code represented as a {@link BitMatrix}. A 1 or "true" is taken to mean a black module.

- * - * @param bits booleans representing white/black QR Code modules - * @param hints decoding hints that should be used to influence decoding - * @return text and bytes encoded within the QR Code - * @throws FormatException if the QR Code cannot be decoded - * @throws ChecksumException if error correction fails - */ - public DecoderRXingResult decode(BitMatrix bits, Map hints) - throws FormatException, ChecksumException { - - // Construct a parser and read version, error-correction level - BitMatrixParser parser = new BitMatrixParser(bits); - FormatException fe = null; - ChecksumException ce = null; - try { - return decode(parser, hints); - } catch (FormatException e) { - fe = e; - } catch (ChecksumException e) { - ce = e; - } - - try { - - // Revert the bit matrix - parser.remask(); - - // Will be attempting a mirrored reading of the version and format info. - parser.setMirror(true); - - // Preemptively read the version. - parser.readVersion(); - - // Preemptively read the format information. - parser.readFormatInformation(); - - /* - * Since we're here, this means we have successfully detected some kind - * of version and format information when mirrored. This is a good sign, - * that the QR code may be mirrored, and we should try once more with a - * mirrored content. - */ - // Prepare for a mirrored reading. - parser.mirror(); - - DecoderRXingResult result = decode(parser, hints); - - // Success! Notify the caller that the code was mirrored. - result.setOther(new QRCodeDecoderMetaData(true)); - - return result; - - } catch (FormatException | ChecksumException e) { - // Throw the exception from the original reading - if (fe != null) { - throw fe; - } - throw ce; // If fe is null, this can't be - } - } - - private DecoderRXingResult decode(BitMatrixParser parser, Map hints) - throws FormatException, ChecksumException { - Version version = parser.readVersion(); - ErrorCorrectionLevel ecLevel = parser.readFormatInformation().getErrorCorrectionLevel(); - - // Read codewords - byte[] codewords = parser.readCodewords(); - // Separate into data blocks - DataBlock[] dataBlocks = DataBlock.getDataBlocks(codewords, version, ecLevel); - - // Count total number of data bytes - int totalBytes = 0; - for (DataBlock dataBlock : dataBlocks) { - totalBytes += dataBlock.getNumDataCodewords(); - } - byte[] resultBytes = new byte[totalBytes]; - int resultOffset = 0; - - // Error-correct and copy data blocks together into a stream of bytes - for (DataBlock dataBlock : dataBlocks) { - byte[] codewordBytes = dataBlock.getCodewords(); - int numDataCodewords = dataBlock.getNumDataCodewords(); - correctErrors(codewordBytes, numDataCodewords); - for (int i = 0; i < numDataCodewords; i++) { - resultBytes[resultOffset++] = codewordBytes[i]; - } - } - - // Decode the contents of that stream of bytes - return DecodedBitStreamParser.decode(resultBytes, version, ecLevel, hints); - } - - /** - *

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

- * - * @param codewordBytes data and error correction codewords - * @param numDataCodewords number of codewords that are data bytes - * @throws ChecksumException if error correction fails - */ - private void correctErrors(byte[] codewordBytes, int numDataCodewords) throws ChecksumException { - int numCodewords = codewordBytes.length; - // First read into an array of ints - int[] codewordsInts = new int[numCodewords]; - for (int i = 0; i < numCodewords; i++) { - codewordsInts[i] = codewordBytes[i] & 0xFF; - } - try { - rsDecoder.decode(codewordsInts, codewordBytes.length - numDataCodewords); - } catch (ReedSolomonException ignored) { - throw ChecksumException.getChecksumInstance(); - } - // Copy back into array of bytes -- only need to worry about the bytes that were data - // We don't care about errors in the error-correction codewords - for (int i = 0; i < numDataCodewords; i++) { - codewordBytes[i] = (byte) codewordsInts[i]; - } - } - -} diff --git a/src/qrcode/decoder/decoded_bit_stream_parser.rs b/src/qrcode/decoder/decoded_bit_stream_parser.rs index 77784ac..28d21c7 100644 --- a/src/qrcode/decoder/decoded_bit_stream_parser.rs +++ b/src/qrcode/decoder/decoded_bit_stream_parser.rs @@ -76,7 +76,7 @@ pub fn decode( fc1InEffect = true; } Mode::STRUCTURED_APPEND => { - if (bits.available() < 16) { + if bits.available() < 16 { return Err(Exceptions::FormatException(format!( "Mode::Structured append expected bits.available() < 16, found bits of {}", bits.available() @@ -131,7 +131,7 @@ pub fn decode( } } - if mode != Mode::TERMINATOR { + if mode == Mode::TERMINATOR { break; } } @@ -207,7 +207,7 @@ fn decodeHanziSegment( count -= 1; } - let gb_encoder = encoding::label::encoding_from_whatwg_label("GB2312").unwrap(); + let gb_encoder = encoding::label::encoding_from_whatwg_label("GBK").unwrap(); let encode_string = gb_encoder .decode(&buffer, encoding::DecoderTrap::Strict) .unwrap(); @@ -221,7 +221,7 @@ fn decodeKanjiSegment( count: usize, ) -> Result<(), Exceptions> { // Don't crash trying to read more bits than we have available. - if (count * 13 > bits.available()) { + if count * 13 > bits.available() { return Err(Exceptions::FormatException("".to_owned())); } @@ -285,9 +285,23 @@ fn decodeByteSegment( } else { encoding = CharacterSetECI::getCharset(currentCharacterSetECI.as_ref().unwrap()); } - let encode_string = encoding - .decode(&readBytes, encoding::DecoderTrap::Strict) - .unwrap(); + + let encode_string = if currentCharacterSetECI.is_some() && currentCharacterSetECI.as_ref().unwrap() == &CharacterSetECI::Cp437 { + { + use codepage_437::BorrowFromCp437; + use codepage_437::CP437_CONTROL; + + String::borrow_from_cp437(&readBytes, &CP437_CONTROL) + } + }else { + encoding + .decode(&readBytes, encoding::DecoderTrap::Strict) + .unwrap() + }; + + // let encode_string = encoding + // .decode(&readBytes, encoding::DecoderTrap::Strict) + // .unwrap(); result.push_str(&encode_string); byteSegments.push(readBytes); diff --git a/src/qrcode/decoder/decoder.rs b/src/qrcode/decoder/decoder.rs new file mode 100644 index 0000000..f962cc9 --- /dev/null +++ b/src/qrcode/decoder/decoder.rs @@ -0,0 +1,216 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use std::collections::HashMap; + +/** + *

The main class which implements QR Code decoding -- as opposed to locating and extracting + * the QR Code from an image.

+ * + * @author Sean Owen + */ +use lazy_static::lazy_static; + +use crate::{ + common::{ + reedsolomon::get_predefined_genericgf, reedsolomon::PredefinedGenericGF, + reedsolomon::ReedSolomonDecoder, BitMatrix, DecoderRXingResult, + }, + DecodingHintDictionary, Exceptions, +}; + +use super::{decoded_bit_stream_parser, BitMatrixParser, DataBlock, QRCodeDecoderMetaData}; + +lazy_static! { + //rsDecoder = new ReedSolomonDecoder(GenericGF.QR_CODE_FIELD_256); + static ref rsDecoder : ReedSolomonDecoder = ReedSolomonDecoder::new(get_predefined_genericgf(PredefinedGenericGF::QrCodeField256)); +} + +pub fn decode_bool_array(image: &Vec>) -> Result { + decode_bool_array_with_hints(image, &HashMap::new()) +} + +/** + *

Convenience method that can decode a QR Code represented as a 2D array of booleans. + * "true" is taken to mean a black module.

+ * + * @param image booleans representing white/black QR Code modules + * @param hints decoding hints that should be used to influence decoding + * @return text and bytes encoded within the QR Code + * @throws FormatException if the QR Code cannot be decoded + * @throws ChecksumException if error correction fails + */ +pub fn decode_bool_array_with_hints( + image: &Vec>, + hints: &DecodingHintDictionary, +) -> Result { + decode_bitmatrix_with_hints(&BitMatrix::parse_bools(image), hints) +} + +pub fn decode_bitmatrix(bits: &BitMatrix) -> Result { + decode_bitmatrix_with_hints(bits, &HashMap::new()) +} + +/** + *

Decodes a QR Code represented as a {@link BitMatrix}. A 1 or "true" is taken to mean a black module.

+ * + * @param bits booleans representing white/black QR Code modules + * @param hints decoding hints that should be used to influence decoding + * @return text and bytes encoded within the QR Code + * @throws FormatException if the QR Code cannot be decoded + * @throws ChecksumException if error correction fails + */ +pub fn decode_bitmatrix_with_hints( + bits: &BitMatrix, + hints: &DecodingHintDictionary, +) -> Result { + // Construct a parser and read version, error-correction level + let mut parser = BitMatrixParser::new(bits.clone())?; + let mut fe = None; + let mut ce = None; + match decode_bitmatrix_parser_with_hints(&mut parser, hints) { + Ok(ok) => return Ok(ok), + Err(er) => match er { + Exceptions::FormatException(_) => fe = Some(er), + Exceptions::ChecksumException(_) => ce = Some(er), + _ => return Err(er), + }, + } + + let mut trying = || -> Result { + // Revert the bit matrix + parser.remask(); + + // Will be attempting a mirrored reading of the version and format info. + parser.setMirror(true); + + // Preemptively read the version. + parser.readVersion()?; + + // Preemptively read the format information. + parser.readFormatInformation()?; + + /* + * Since we're here, this means we have successfully detected some kind + * of version and format information when mirrored. This is a good sign, + * that the QR code may be mirrored, and we should try once more with a + * mirrored content. + */ + // Prepare for a mirrored reading. + parser.mirror(); + + let mut result = decode_bitmatrix_parser_with_hints(&mut parser, hints)?; + + // Success! Notify the caller that the code was mirrored. + result.setOther(Box::new(QRCodeDecoderMetaData::new(true))); + + Ok(result) + }; + + match trying() { + Ok(res) => Ok(res), + Err(er) => match er { + Exceptions::FormatException(_) | Exceptions::ChecksumException(_) => { + if fe.is_some() { + Err(fe.unwrap()) + } else { + Err(ce.unwrap()) + } + } + _ => Err(er), + }, + } + + // catch (FormatException | ChecksumException e) { + // // Throw the exception from the original reading + // if (fe != null) { + // throw fe; + // } + // throw ce; // If fe is null, this can't be + // } +} + +fn decode_bitmatrix_parser_with_hints( + parser: &mut BitMatrixParser, + hints: &DecodingHintDictionary, +) -> Result { + let version = parser.readVersion()?; + let ecLevel = parser.readFormatInformation()?.getErrorCorrectionLevel(); + + // Read codewords + let codewords = parser.readCodewords()?; + // Separate into data blocks + let dataBlocks = DataBlock::getDataBlocks(&codewords, version, ecLevel)?; + + // Count total number of data bytes + let mut totalBytes = 0usize; + for dataBlock in &dataBlocks { + // for (DataBlock dataBlock : dataBlocks) { + totalBytes += dataBlock.getNumDataCodewords() as usize; + } + let mut resultBytes = vec![0u8; totalBytes]; + let mut resultOffset = 0; + + // Error-correct and copy data blocks together into a stream of bytes + for dataBlock in &dataBlocks { + // for (DataBlock dataBlock : dataBlocks) { + let mut codewordBytes = dataBlock.getCodewords().to_vec(); + let numDataCodewords = dataBlock.getNumDataCodewords() as usize; + correctErrors(&mut codewordBytes, numDataCodewords); + for i in 0..numDataCodewords { + // for (int i = 0; i < numDataCodewords; i++) { + resultBytes[resultOffset] = codewordBytes[i]; + resultOffset += 1; + } + } + + // Decode the contents of that stream of bytes + decoded_bit_stream_parser::decode(&resultBytes, version, ecLevel, hints) +} + +/** + *

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

+ * + * @param codewordBytes data and error correction codewords + * @param numDataCodewords number of codewords that are data bytes + * @throws ChecksumException if error correction fails + */ +fn correctErrors(codewordBytes: &mut [u8], numDataCodewords: usize) -> Result<(), Exceptions> { + let numCodewords = codewordBytes.len(); + // First read into an array of ints + let mut codewordsInts = vec![0u8; numCodewords]; + for i in 0..numCodewords { + // for (int i = 0; i < numCodewords; i++) { + codewordsInts[i] = codewordBytes[i]; // & 0xFF; + } + if let Err(e) = rsDecoder.decode( + &mut codewordsInts.iter().map(|x| *x as i32).collect(), + (codewordBytes.len() - numDataCodewords) as i32, + ) { + if let Exceptions::ReedSolomonException(error_str) = e { + return Err(Exceptions::ChecksumException(error_str)); + } + } + + // Copy back into array of bytes -- only need to worry about the bytes that were data + // We don't care about errors in the error-correction codewords + for i in 0..numDataCodewords { + // for (int i = 0; i < numDataCodewords; i++) { + codewordBytes[i] = codewordsInts[i]; + } + Ok(()) +} diff --git a/src/qrcode/decoder/mod.rs b/src/qrcode/decoder/mod.rs index 74eb7b6..437277b 100644 --- a/src/qrcode/decoder/mod.rs +++ b/src/qrcode/decoder/mod.rs @@ -7,6 +7,7 @@ mod qr_code_decoder_meta_data; mod data_mask; mod bit_matrix_parser; pub mod decoded_bit_stream_parser; +pub mod decoder; #[cfg(test)] mod ErrorCorrectionLevelTestCase; @@ -18,6 +19,8 @@ mod VersionTestCase; mod FormatInformationTestCase; #[cfg(test)] mod DataMaskTestCase; +#[cfg(test)] +mod DecodedBitStreamParserTestCase; pub use version::*; pub use mode::*;