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, MapDecodes 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, MapGiven 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: &VecConvenience 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: &VecDecodes 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, +) -> ResultGiven 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::*;