From deb2de709b8b8cb28b5a3066553c140ec48d3295 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Thu, 11 Jan 2024 17:28:46 -0600 Subject: [PATCH] port: migrate to new cpp Version / FormatInformation --- .../base_extentions/qr_formatinformation.rs | 168 ++++++++++-------- .../base_extentions/qrcode_version.rs | 115 ++++++------ src/qrcode/cpp_port/bitmatrix_parser.rs | 47 +++-- src/qrcode/cpp_port/decoder.rs | 22 +-- src/qrcode/cpp_port/mod.rs | 3 + src/qrcode/cpp_port/qr_type.rs | 14 ++ .../cpp_port/test/QRBitMatrixParserTest.rs | 12 +- .../test/QRDecodedBitStreamParserTest.rs | 52 ++---- .../cpp_port/test/QRFormatInformationTest.rs | 14 +- src/qrcode/cpp_port/test/QRModeTest.rs | 39 ++-- src/qrcode/cpp_port/test/QRVersionTest.rs | 32 ++-- src/qrcode/decoder/format_information.rs | 13 +- src/qrcode/decoder/mode.rs | 6 +- src/qrcode/decoder/version.rs | 17 +- 14 files changed, 282 insertions(+), 272 deletions(-) create mode 100644 src/qrcode/cpp_port/qr_type.rs diff --git a/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs b/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs index 30919e2..143db83 100644 --- a/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs +++ b/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs @@ -1,43 +1,48 @@ -use crate::qrcode::decoder::{ - ErrorCorrectionLevel, FormatInformation, FORMAT_INFO_DECODE_LOOKUP, FORMAT_INFO_MASK_QR, +use crate::qrcode::{ + cpp_port::Type, + decoder::{ + ErrorCorrectionLevel, FormatInformation, FORMAT_INFO_DECODE_LOOKUP, + FORMAT_INFO_MASK_MODEL2, FORMAT_INFO_MASK_QR, + }, }; pub const FORMAT_INFO_MASK_QR_MODEL1: u32 = 0x2825; +pub const FORMAT_INFO_MASK_MICRO: u32 = 0x4445; -pub const FORMAT_INFO_DECODE_LOOKUP_MICRO: [[u32; 2]; 32] = [ - [0x4445, 0x00], - [0x4172, 0x01], - [0x4E2B, 0x02], - [0x4B1C, 0x03], - [0x55AE, 0x04], - [0x5099, 0x05], - [0x5FC0, 0x06], - [0x5AF7, 0x07], - [0x6793, 0x08], - [0x62A4, 0x09], - [0x6DFD, 0x0A], - [0x68CA, 0x0B], - [0x7678, 0x0C], - [0x734F, 0x0D], - [0x7C16, 0x0E], - [0x7921, 0x0F], - [0x06DE, 0x10], - [0x03E9, 0x11], - [0x0CB0, 0x12], - [0x0987, 0x13], - [0x1735, 0x14], - [0x1202, 0x15], - [0x1D5B, 0x16], - [0x186C, 0x17], - [0x2508, 0x18], - [0x203F, 0x19], - [0x2F66, 0x1A], - [0x2A51, 0x1B], - [0x34E3, 0x1C], - [0x31D4, 0x1D], - [0x3E8D, 0x1E], - [0x3BBA, 0x1F], -]; +// pub const FORMAT_INFO_DECODE_LOOKUP_MICRO: [u32 ;32] = [ +// 0x4445, +// 0x4172, +// 0x4E2B, +// 0x4B1C, +// 0x55AE, +// 0x5099, +// 0x5FC0, +// 0x5AF7, +// 0x6793, +// 0x62A4, +// 0x6DFD, +// 0x68CA, +// 0x7678, +// 0x734F, +// 0x7C16, +// 0x7921, +// 0x06DE, +// 0x03E9, +// 0x0CB0, +// 0x0987, +// 0x1735, +// 0x1202, +// 0x1D5B, +// 0x186C, +// 0x2508, +// 0x203F, +// 0x2F66, +// 0x2A51, +// 0x34E3, +// 0x31D4, +// 0x3E8D, +// 0x3BBA, +// ]; impl FormatInformation { /** @@ -51,9 +56,9 @@ impl FormatInformation { ); let formatInfoBits2 = ((formatInfoBits2 >> 1) & 0b111111100000000) | (formatInfoBits2 & 0b11111111); + // Some (Model2) QR codes apparently do not apply the XOR mask. Try with (standard) and without (quirk) masking. let mut fi = Self::FindBestFormatInfo( - &[0, FORMAT_INFO_MASK_QR], - FORMAT_INFO_DECODE_LOOKUP, + &[FORMAT_INFO_MASK_QR, 0, FORMAT_INFO_MASK_QR_MODEL1], &[ formatInfoBits1, formatInfoBits2, @@ -62,26 +67,10 @@ impl FormatInformation { ], ); - let mut fi_model1 = Self::FindBestFormatInfo( - &[FORMAT_INFO_MASK_QR ^ FORMAT_INFO_MASK_QR_MODEL1], - FORMAT_INFO_DECODE_LOOKUP, - &[ - formatInfoBits1, - formatInfoBits2, - Self::MirrorBits(formatInfoBits1), - mirroredFormatInfoBits2, - ], - ); - - if fi_model1.hammingDistance < fi.hammingDistance { - fi_model1.isModel1 = true; - fi = fi_model1; - } - // Use bits 3/4 for error correction, and 0-2 for mask. fi.error_correction_level = - ErrorCorrectionLevel::ECLevelFromBits((fi.index >> 3) & 0x03, false); - fi.data_mask = fi.index & 0x07; + ErrorCorrectionLevel::ECLevelFromBits((fi.data >> 3) as u8 & 0x03, false); + fi.data_mask = fi.data as u8 & 0x07; fi.isMirrored = fi.bitsIndex > 1; fi @@ -90,8 +79,7 @@ impl FormatInformation { pub fn DecodeMQR(formatInfoBits: u32) -> Self { // We don't use the additional masking (with 0x4445) to work around potentially non complying MicroQRCode encoders let mut fi = Self::FindBestFormatInfo( - &[0], - FORMAT_INFO_DECODE_LOOKUP_MICRO, + &[FORMAT_INFO_MASK_MICRO, 0], &[formatInfoBits, Self::MirrorBits(formatInfoBits)], ); @@ -99,9 +87,9 @@ impl FormatInformation { // Bits 2/3/4 contain both error correction level and version, 0/1 contain mask. fi.error_correction_level = - ErrorCorrectionLevel::ECLevelFromBits((fi.index >> 2) & 0x07, true); - fi.data_mask = fi.index & 0x03; - fi.microVersion = BITS_TO_VERSION[((fi.index >> 2) & 0x07) as usize] as u32; + ErrorCorrectionLevel::ECLevelFromBits((fi.data >> 2) as u8 & 0x07, true); + fi.data_mask = fi.data as u8 & 0x03; + fi.microVersion = BITS_TO_VERSION[((fi.data >> 2) as u8 & 0x07) as usize] as u32; fi.isMirrored = fi.bitsIndex == 1; fi @@ -112,21 +100,30 @@ impl FormatInformation { (bits.reverse_bits()) >> 17 } - pub fn FindBestFormatInfo(masks: &[u32], lookup: [[u32; 2]; 32], bits: &[u32]) -> Self { + pub fn FindBestFormatInfo(masks: &[u32], bits: &[u32]) -> Self { let mut fi = FormatInformation::default(); - // Some QR codes apparently do not apply the XOR mask. Try without and with additional masking. + // See ISO 18004:2015, Annex C, Table C.1 + const MODEL2_MASKED_PATTERNS: [u32; 32] = [ + 0x5412, 0x5125, 0x5E7C, 0x5B4B, 0x45F9, 0x40CE, 0x4F97, 0x4AA0, 0x77C4, 0x72F3, 0x7DAA, + 0x789D, 0x662F, 0x6318, 0x6C41, 0x6976, 0x1689, 0x13BE, 0x1CE7, 0x19D0, 0x0762, 0x0255, + 0x0D0C, 0x083B, 0x355F, 0x3068, 0x3F31, 0x3A06, 0x24B4, 0x2183, 0x2EDA, 0x2BED, + ]; + for mask in masks { - // for (auto mask : {0, mask}) - for (bitsIndex, bit_set) in bits.iter().enumerate() { + // for (auto mask : masks) + for bitsIndex in 0..bits.len() { // for (int bitsIndex = 0; bitsIndex < Size(bits); ++bitsIndex) - for [pattern, index] in lookup { - // for (const auto& [pattern, index] : lookup) { - // Find the int in lookup with fewest bits differing - let hammingDist = ((bit_set ^ mask) ^ pattern).count_ones(); + for ref_pattern in MODEL2_MASKED_PATTERNS { + // for (uint32_t pattern : MODEL2_MASKED_PATTERNS) { + // 'unmask' the pattern first to get the original 5-data bits + 10-ec bits back + let pattern = ref_pattern ^ FORMAT_INFO_MASK_MODEL2; + // Find the pattern with fewest bits differing + let hammingDist = ((bits[bitsIndex] ^ mask) ^ pattern).count_ones(); + // if (int hammingDist = BitHacks::CountBitsSet((bits[bitsIndex] ^ mask) ^ pattern); if hammingDist < fi.hammingDistance { - // if (int hammingDist = BitHacks::CountBitsSet((bits[bitsIndex] ^ mask) ^ pattern); hammingDist < fi.hammingDistance) { - fi.index = index as u8; + fi.mask = *mask; // store the used mask to discriminate between types/models + fi.data = pattern >> 10; // drop the 10 BCH error correction bits fi.hammingDistance = hammingDist; fi.bitsIndex = bitsIndex as u8; } @@ -134,9 +131,37 @@ impl FormatInformation { } } + // // Some QR codes apparently do not apply the XOR mask. Try without and with additional masking. + // for mask in masks { + // // for (auto mask : {0, mask}) + // for (bitsIndex, bit_set) in bits.iter().enumerate() { + // // for (int bitsIndex = 0; bitsIndex < Size(bits); ++bitsIndex) + // for [pattern, _index] in FORMAT_INFO_DECODE_LOOKUP { + // // for (const auto& [pattern, index] : lookup) { + // // Find the int in lookup with fewest bits differing + // let hammingDist = ((bit_set ^ mask) ^ pattern).count_ones(); + // if hammingDist < fi.hammingDistance { + // // if (int hammingDist = BitHacks::CountBitsSet((bits[bitsIndex] ^ mask) ^ pattern); hammingDist < fi.hammingDistance) { + // fi.mask = *mask; // store the used mask to discriminate between types/models + // fi.data = pattern >> 10; // drop the 10 BCH error correction bits + // fi.hammingDistance = hammingDist; + // fi.bitsIndex = bitsIndex as u8; + // } + // } + // } + // } + fi } + pub fn qr_type(&self) -> Type { + match self.mask { + FORMAT_INFO_MASK_QR_MODEL1 => Type::Model1, + FORMAT_INFO_MASK_MICRO => Type::Micro, + _ => Type::Model2, + } + } + // Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits differing means we found a match pub fn isValid(&self) -> bool { self.hammingDistance <= 3 @@ -145,5 +170,6 @@ impl FormatInformation { pub fn cpp_eq(&self, other: &Self) -> bool { self.data_mask == other.data_mask && self.error_correction_level == other.error_correction_level + && self.qr_type() == other.qr_type() } } diff --git a/src/common/cpp_essentials/base_extentions/qrcode_version.rs b/src/common/cpp_essentials/base_extentions/qrcode_version.rs index d11cc56..ca63868 100644 --- a/src/common/cpp_essentials/base_extentions/qrcode_version.rs +++ b/src/common/cpp_essentials/base_extentions/qrcode_version.rs @@ -1,60 +1,40 @@ -use crate::common::Result; +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +* Copyright 2023 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +use crate::common::{BitMatrix, Result}; +use crate::qrcode::cpp_port::Type; use crate::qrcode::decoder::{ Version, VersionRef, MICRO_VERSIONS, MODEL1_VERSIONS, VERSIONS, VERSION_DECODE_INFO, }; use crate::Exceptions; -// const Version* Version::AllMicroVersions() -// { -// /** -// * See ISO 18004:2006 6.5.1 Table 9 -// */ -// static const Version allVersions[] = { -// {1, {2, 1, 3, 0, 0}}, -// {2, {5, 1, 5, 0, 0, 6, 1, 4, 0, 0}}, -// {3, {6, 1, 11, 0, 0, 8, 1, 9, 0, 0}}, -// {4, {8, 1, 16, 0, 0, 10, 1, 14, 0, 0, 14, 1, 10, 0, 0}}}; -// return allVersions; -// } - impl Version { - pub fn FromDimension(dimension: u32, is_model1: bool) -> Result { - let isMicro = dimension < 21; - if dimension % Self::DimensionStep(isMicro) != 1 { - //throw std::invalid_argument("Unexpected dimension"); - return Err(Exceptions::ILLEGAL_ARGUMENT); + pub fn Model1(version_number: u32) -> Result { + if version_number < 1 || version_number > 14 { + Err(Exceptions::ILLEGAL_ARGUMENT) + } else { + Ok(&MODEL1_VERSIONS[version_number as usize - 1]) } - Self::FromNumber( - (dimension - Self::DimensionOffset(isMicro)) / Self::DimensionStep(isMicro), - isMicro, - is_model1, - ) } - pub fn FromNumber(versionNumber: u32, is_micro: bool, is_model1: bool) -> Result { - if versionNumber < 1 - || versionNumber - > (if is_micro { - 4 - } else { - if is_model1 { - 14 - } else { - 40 - } - }) - { - //throw std::invalid_argument("Version should be in range [1-40]."); - return Err(Exceptions::ILLEGAL_ARGUMENT); - } - - Ok(if is_micro { - &MICRO_VERSIONS[versionNumber as usize - 1] - } else if is_model1 { - &MODEL1_VERSIONS[versionNumber as usize - 1] + pub fn Model2(version_number: u32) -> Result { + if version_number < 1 || version_number > 40 { + Err(Exceptions::ILLEGAL_ARGUMENT) } else { - &VERSIONS[versionNumber as usize - 1] - }) + Ok(&VERSIONS[version_number as usize - 1]) + } + } + + pub fn Micro(version_number: u32) -> Result { + if version_number < 1 || version_number > 4 { + Err(Exceptions::ILLEGAL_ARGUMENT) + } else { + Ok(&MICRO_VERSIONS[version_number as usize - 1]) + } } pub fn DimensionOfVersion(version: u32, is_micro: bool) -> u32 { @@ -80,13 +60,6 @@ impl Version { let mut bestDifference = u32::MAX; let mut bestVersion = 0; for (i, targetVersion) in VERSION_DECODE_INFO.into_iter().enumerate() { - // for (int targetVersion : VERSION_DECODE_INFO) { - // Do the version info bits match exactly? done. - if targetVersion == versionBitsA as u32 || targetVersion == versionBitsB as u32 { - return Self::getVersionForNumber(i as u32 + 7); - } - // Otherwise see if this is the closest to a real version info bit string - // we have seen so far for bits in [versionBitsA, versionBitsB] { // for (int bits : {versionBitsA, versionBitsB}) { let bitsDifference = ((bits as u32) ^ targetVersion).count_ones(); //BitHacks::CountBitsSet(bits ^ targetVersion); @@ -95,6 +68,9 @@ impl Version { bestDifference = bitsDifference; } } + if bestDifference == 0 { + break; + } } // We can tolerate up to 3 bits of error since no two version info codewords will // differ in less than 8 bits. @@ -105,11 +81,34 @@ impl Version { Err(Exceptions::ILLEGAL_STATE) } - pub const fn isMicroQRCode(&self) -> bool { - self.is_micro + pub const fn isMicro(&self) -> bool { + Type::const_eq(self.qr_type, Type::Micro) } - pub const fn isQRCodeModel1(&self) -> bool { - self.is_model1 + pub const fn isModel1(&self) -> bool { + Type::const_eq(self.qr_type, Type::Model1) + } + + pub const fn isModel2(&self) -> bool { + Type::const_eq(self.qr_type, Type::Model2) + } + + pub fn HasMicroSize(bitMatrix: &BitMatrix) -> bool { + let size = bitMatrix.height(); + size >= 11 && size <= 17 && (size % 2) == 1 + } + + pub fn HasValidSize(bitMatrix: &BitMatrix) -> bool { + let size = bitMatrix.height(); + Self::HasMicroSize(bitMatrix) || (size >= 21 && size <= 177 && (size % 4) == 1) + } + + pub fn Number(bitMatrix: &BitMatrix) -> u32 { + if !Self::HasValidSize(bitMatrix) { + 0 + } else { + let isMicro = Self::HasMicroSize(bitMatrix); + (bitMatrix.height() - Self::DimensionOffset(isMicro)) / Self::DimensionStep(isMicro) + } } } diff --git a/src/qrcode/cpp_port/bitmatrix_parser.rs b/src/qrcode/cpp_port/bitmatrix_parser.rs index 15c3d7b..957f35c 100644 --- a/src/qrcode/cpp_port/bitmatrix_parser.rs +++ b/src/qrcode/cpp_port/bitmatrix_parser.rs @@ -10,7 +10,7 @@ use crate::{ Exceptions, }; -use super::{data_mask::GetDataMaskBit, detector::AppendBit}; +use super::{data_mask::GetDataMaskBit, detector::AppendBit, Type}; pub fn getBit(bitMatrix: &BitMatrix, x: u32, y: u32, mirrored: Option) -> bool { let mirrored = mirrored.unwrap_or(false); @@ -21,24 +21,27 @@ pub fn getBit(bitMatrix: &BitMatrix, x: u32, y: u32, mirrored: Option) -> } } -pub fn hasValidDimension(bitMatrix: &BitMatrix, isMicro: bool) -> bool { - let dimension = bitMatrix.height(); - if isMicro { - (11..=17).contains(&dimension) && (dimension % 2) == 1 - } else { - (21..=177).contains(&dimension) && (dimension % 4) == 1 +pub fn ReadVersion(bitMatrix: &BitMatrix, qr_type: Type) -> Result { + if !Version::HasValidSize(bitMatrix) { + return Err(Exceptions::FORMAT); } -} -pub fn ReadVersion(bitMatrix: &BitMatrix) -> Result { - let dimension = bitMatrix.height(); + let number = Version::Number(bitMatrix); - let mut version = Version::FromDimension(dimension, false)?; + match qr_type { + Type::Model1 => return Version::Model1(number), + + Type::Micro => return Version::Micro(number), + Type::Model2 => {} + } + let mut version = Version::Model2(number)?; if version.getVersionNumber() < 7 { return Ok(version); } + let dimension = bitMatrix.height(); + for mirror in [false, true] { // for (bool mirror : {false, true}) { // Read top-right/bottom-left version info: 3 wide by 6 tall (depending on mirrored) @@ -60,12 +63,8 @@ pub fn ReadVersion(bitMatrix: &BitMatrix) -> Result { Err(Exceptions::FORMAT) } -pub fn ReadFormatInformation(bitMatrix: &BitMatrix, isMicro: bool) -> Result { - if !hasValidDimension(bitMatrix, isMicro) { - return Err(Exceptions::FORMAT); - } - - if isMicro { +pub fn ReadFormatInformation(bitMatrix: &BitMatrix) -> Result { + if Version::HasMicroSize(bitMatrix) { // Read top-left format info bits let mut formatInfoBits = 0; for x in 1..9 { @@ -345,15 +344,9 @@ pub fn ReadCodewords( version: VersionRef, formatInfo: &FormatInformation, ) -> Result> { - if !hasValidDimension(bitMatrix, version.isMicroQRCode()) { - return Err(Exceptions::FORMAT); - } - - if version.isMicroQRCode() { - ReadMQRCodewords(bitMatrix, version, formatInfo) - } else if formatInfo.isModel1 { - ReadQRCodewordsModel1(bitMatrix, version, formatInfo) - } else { - ReadQRCodewords(bitMatrix, version, formatInfo) + match version.qr_type { + Type::Model1 => ReadQRCodewordsModel1(bitMatrix, version, formatInfo), + Type::Model2 => ReadQRCodewords(bitMatrix, version, formatInfo), + Type::Micro => ReadMQRCodewords(bitMatrix, version, formatInfo), } } diff --git a/src/qrcode/cpp_port/decoder.rs b/src/qrcode/cpp_port/decoder.rs index 0e0fceb..8b60af5 100644 --- a/src/qrcode/cpp_port/decoder.rs +++ b/src/qrcode/cpp_port/decoder.rs @@ -17,6 +17,8 @@ use crate::qrcode::cpp_port::bitmatrix_parser::{ use crate::qrcode::decoder::{DataBlock, ErrorCorrectionLevel, Mode, Version}; use crate::Exceptions; +use super::Type; + /** *

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

@@ -299,7 +301,7 @@ pub fn DecodeBitStream( let mut structuredAppend = StructuredAppendInfo::default(); let modeBitLength = Mode::get_codec_mode_bits_length(version); - if version.isQRCodeModel1() { + if version.isModel1() { bits.readBits(4)?; /* Model 1 is leading with 4 0-bits -> drop them */ } @@ -310,7 +312,7 @@ pub fn DecodeBitStream( } else { Mode::CodecModeForBits( bits.readBits(modeBitLength as usize)?, - Some(version.isMicroQRCode()), + Some(version.isMicro()), )? }; @@ -392,25 +394,23 @@ pub fn DecodeBitStream( .withEcLevel(ecLevel.to_string()) .withVersionNumber(version.getVersionNumber()) .withStructuredAppend(structuredAppend) - .withIsModel1(version.isQRCodeModel1())) + .withIsModel1(version.isModel1())) } pub fn Decode(bits: &BitMatrix) -> Result> { - let isMicroQRCode = bits.height() < 21; - let Ok(formatInfo) = ReadFormatInformation(bits, isMicroQRCode) else { + if !Version::HasValidSize(bits) { + return Err(Exceptions::format_with("Invalid symbol size")); + } + let Ok(formatInfo) = ReadFormatInformation(bits) else { return Err(Exceptions::format_with("Invalid format information")); }; - let Ok(pversion) = (if formatInfo.isModel1 { - Version::FromDimension(bits.height(), true) - } else { - ReadVersion(bits) - }) else { + let Ok(pversion) = ReadVersion(bits, formatInfo.qr_type()) else { return Err(Exceptions::format_with("Invalid version")); }; let version = pversion; - let Ok(formatInfo) = ReadFormatInformation(bits, version.isMicroQRCode()) else { + let Ok(formatInfo) = ReadFormatInformation(bits) else { return Err(Exceptions::format_with("Invalid format information")); }; diff --git a/src/qrcode/cpp_port/mod.rs b/src/qrcode/cpp_port/mod.rs index f18f038..8b9931e 100644 --- a/src/qrcode/cpp_port/mod.rs +++ b/src/qrcode/cpp_port/mod.rs @@ -7,5 +7,8 @@ pub use qr_cpp_reader::QrReader; mod bitmatrix_parser; +mod qr_type; +pub use qr_type::Type; + #[cfg(test)] mod test; diff --git a/src/qrcode/cpp_port/qr_type.rs b/src/qrcode/cpp_port/qr_type.rs new file mode 100644 index 0000000..610d741 --- /dev/null +++ b/src/qrcode/cpp_port/qr_type.rs @@ -0,0 +1,14 @@ +#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] +pub enum Type { + Model1, + Model2, + Micro, +} + +impl Type { + pub const fn const_eq(a: Type, b: Type) -> bool { + let (a, b) = (a as u8, b as u8); + + a == b + } +} diff --git a/src/qrcode/cpp_port/test/QRBitMatrixParserTest.rs b/src/qrcode/cpp_port/test/QRBitMatrixParserTest.rs index f6610e3..1c04e9a 100644 --- a/src/qrcode/cpp_port/test/QRBitMatrixParserTest.rs +++ b/src/qrcode/cpp_port/test/QRBitMatrixParserTest.rs @@ -33,10 +33,10 @@ XXX XX X X XXXX ) .expect("parse must parse"); - let version = ReadVersion(&bitMatrix).unwrap(); + let format = ReadFormatInformation(&bitMatrix).expect("could not read format information"); + let version = ReadVersion(&bitMatrix, format.qr_type()).expect("version found"); assert_eq!(3, version.getVersionNumber()); - let format = - ReadFormatInformation(&bitMatrix, true).expect("could not read format information"); + let codewords = ReadCodewords(&bitMatrix, version, &format).expect("could not read codewords"); assert_eq!(17, codewords.len()); assert_eq!(0x0, codewords[10]); @@ -67,10 +67,10 @@ X X XXXX XXX ) .unwrap(); - let version = ReadVersion(&bitMatrix).unwrap(); + let format = ReadFormatInformation(&bitMatrix).expect("could not read format information"); + let version = ReadVersion(&bitMatrix, format.qr_type()).expect("could not read version"); assert_eq!(3, version.getVersionNumber()); - let format = - ReadFormatInformation(&bitMatrix, true).expect("could not read format information"); + let codewords = ReadCodewords(&bitMatrix, version, &format).expect("could not read codewords"); assert_eq!(17, codewords.len()); assert_eq!(0x0, codewords[8]); diff --git a/src/qrcode/cpp_port/test/QRDecodedBitStreamParserTest.rs b/src/qrcode/cpp_port/test/QRDecodedBitStreamParserTest.rs index e777dc5..2b5bc74 100644 --- a/src/qrcode/cpp_port/test/QRDecodedBitStreamParserTest.rs +++ b/src/qrcode/cpp_port/test/QRDecodedBitStreamParserTest.rs @@ -42,7 +42,7 @@ fn SimpleByteMode() { let bytes: Vec = ba.into(); let result = DecodeBitStream( &bytes, - Version::FromNumber(1, false, false).expect("find_version"), + Version::Model2(1).expect("find_version"), ErrorCorrectionLevel::M, ) .expect("Decode") @@ -62,14 +62,10 @@ fn SimpleSJIS() { ba.appendBits(0xA3, 8).expect("append"); ba.appendBits(0xD0, 8).expect("append"); let bytes: Vec = ba.into(); - let result = DecodeBitStream( - &bytes, - Version::FromNumber(1, false, false).unwrap(), - ErrorCorrectionLevel::M, - ) - .unwrap() - .content() - .to_string(); + let result = DecodeBitStream(&bytes, Version::Model2(1).unwrap(), ErrorCorrectionLevel::M) + .unwrap() + .content() + .to_string(); assert_eq!("\u{ff61}\u{ff62}\u{ff63}\u{ff90}", result); } @@ -84,14 +80,10 @@ fn ECI() { ba.appendBits(0xA2, 8).expect("append"); ba.appendBits(0xA3, 8).expect("append"); let bytes: Vec = ba.into(); - let result = DecodeBitStream( - &bytes, - Version::FromNumber(1, false, false).unwrap(), - ErrorCorrectionLevel::M, - ) - .unwrap() - .content() - .to_string(); + let result = DecodeBitStream(&bytes, Version::Model2(1).unwrap(), ErrorCorrectionLevel::M) + .unwrap() + .content() + .to_string(); assert_eq!("\u{ED}\u{F3}\u{FA}", result); } @@ -103,14 +95,10 @@ fn Hanzi() { ba.appendBits(0x01, 8).expect("append"); // 1 characters ba.appendBits(0x03C1, 13).expect("append"); let bytes: Vec = ba.into(); - let result = DecodeBitStream( - &bytes, - Version::FromNumber(1, false, false).unwrap(), - ErrorCorrectionLevel::M, - ) - .unwrap() - .content() - .to_string(); + let result = DecodeBitStream(&bytes, Version::Model2(1).unwrap(), ErrorCorrectionLevel::M) + .unwrap() + .content() + .to_string(); assert_eq!("\u{963f}", result); } @@ -125,20 +113,16 @@ fn HanziLevel1() { let bytes: Vec = ba.into(); - let result = DecodeBitStream( - &bytes, - Version::FromNumber(1, false, false).unwrap(), - ErrorCorrectionLevel::M, - ) - .unwrap() - .content() - .to_string(); + let result = DecodeBitStream(&bytes, Version::Model2(1).unwrap(), ErrorCorrectionLevel::M) + .unwrap() + .content() + .to_string(); assert_eq!("\u{30a2}", result); } #[test] fn SymbologyIdentifier() { - let version = Version::FromNumber(1, false, false).unwrap(); + let version = Version::Model2(1).unwrap(); let ecLevel = ErrorCorrectionLevel::M; // Plain "ANUM(1) A" diff --git a/src/qrcode/cpp_port/test/QRFormatInformationTest.rs b/src/qrcode/cpp_port/test/QRFormatInformationTest.rs index 66b9c5f..352329c 100644 --- a/src/qrcode/cpp_port/test/QRFormatInformationTest.rs +++ b/src/qrcode/cpp_port/test/QRFormatInformationTest.rs @@ -4,7 +4,10 @@ */ // SPDX-License-Identifier: Apache-2.0 -use crate::qrcode::decoder::{ErrorCorrectionLevel, FormatInformation}; +use crate::qrcode::{ + cpp_port::Type, + decoder::{ErrorCorrectionLevel, FormatInformation}, +}; const MASKED_TEST_FORMAT_INFO: u32 = 0x2BED; const MASKED_TEST_FORMAT_INFO2: u32 = @@ -63,11 +66,12 @@ fn DecodeWithBitDifference() { MASKED_TEST_FORMAT_INFO2 ^ 0x07, ), ); - assert!(FormatInformation::DecodeQR( + let unexpected = FormatInformation::DecodeQR( MASKED_TEST_FORMAT_INFO ^ 0x0F, - MASKED_TEST_FORMAT_INFO2 ^ 0x0F - ) - .isValid()); + MASKED_TEST_FORMAT_INFO2 ^ 0x0F, + ); + assert!(!&expected.cpp_eq(&unexpected)); + assert!(!(unexpected.isValid() && unexpected.qr_type() == Type::Model2)); } #[test] diff --git a/src/qrcode/cpp_port/test/QRModeTest.rs b/src/qrcode/cpp_port/test/QRModeTest.rs index 03a3bae..324f06a 100644 --- a/src/qrcode/cpp_port/test/QRModeTest.rs +++ b/src/qrcode/cpp_port/test/QRModeTest.rs @@ -27,39 +27,27 @@ fn CharacterCount() { // Spot check a few values assert_eq!( 10, - Mode::CharacterCountBits( - &Mode::NUMERIC, - Version::FromNumber(5, false, false).unwrap() - ) + Mode::CharacterCountBits(&Mode::NUMERIC, Version::Model2(5).unwrap()) ); assert_eq!( 12, - Mode::CharacterCountBits( - &Mode::NUMERIC, - Version::FromNumber(26, false, false).unwrap() - ) + Mode::CharacterCountBits(&Mode::NUMERIC, Version::Model2(26).unwrap()) ); assert_eq!( 14, - Mode::CharacterCountBits( - &Mode::NUMERIC, - Version::FromNumber(40, false, false).unwrap() - ) + Mode::CharacterCountBits(&Mode::NUMERIC, Version::Model2(40).unwrap()) ); assert_eq!( 9, - Mode::CharacterCountBits( - &Mode::ALPHANUMERIC, - Version::FromNumber(6, false, false).unwrap() - ) + Mode::CharacterCountBits(&Mode::ALPHANUMERIC, Version::Model2(6).unwrap()) ); assert_eq!( 8, - Mode::CharacterCountBits(&Mode::BYTE, Version::FromNumber(7, false, false).unwrap()) + Mode::CharacterCountBits(&Mode::BYTE, Version::Model2(7).unwrap()) ); assert_eq!( 8, - Mode::CharacterCountBits(&Mode::KANJI, Version::FromNumber(8, false, false).unwrap()) + Mode::CharacterCountBits(&Mode::KANJI, Version::Model2(8).unwrap()) ); } @@ -122,29 +110,26 @@ fn MicroCharacterCount() { // Spot check a few values assert_eq!( 3, - Mode::CharacterCountBits(&Mode::NUMERIC, Version::FromNumber(1, true, false).unwrap()) + Mode::CharacterCountBits(&Mode::NUMERIC, Version::Micro(1).unwrap()) ); assert_eq!( 4, - Mode::CharacterCountBits(&Mode::NUMERIC, Version::FromNumber(2, true, false).unwrap()) + Mode::CharacterCountBits(&Mode::NUMERIC, Version::Micro(2).unwrap()) ); assert_eq!( 6, - Mode::CharacterCountBits(&Mode::NUMERIC, Version::FromNumber(4, true, false).unwrap()) + Mode::CharacterCountBits(&Mode::NUMERIC, Version::Micro(4).unwrap()) ); assert_eq!( 3, - Mode::CharacterCountBits( - &Mode::ALPHANUMERIC, - Version::FromNumber(2, true, false).unwrap() - ) + Mode::CharacterCountBits(&Mode::ALPHANUMERIC, Version::Micro(2).unwrap()) ); assert_eq!( 4, - Mode::CharacterCountBits(&Mode::BYTE, Version::FromNumber(3, true, false).unwrap()) + Mode::CharacterCountBits(&Mode::BYTE, Version::Micro(3).unwrap()) ); assert_eq!( 4, - Mode::CharacterCountBits(&Mode::KANJI, Version::FromNumber(4, true, false).unwrap()) + Mode::CharacterCountBits(&Mode::KANJI, Version::Micro(4).unwrap()) ); } diff --git a/src/qrcode/cpp_port/test/QRVersionTest.rs b/src/qrcode/cpp_port/test/QRVersionTest.rs index c02f348..d5d0b1e 100644 --- a/src/qrcode/cpp_port/test/QRVersionTest.rs +++ b/src/qrcode/cpp_port/test/QRVersionTest.rs @@ -12,7 +12,7 @@ use crate::{ fn CheckVersion(version: VersionRef, number: u32, dimension: u32) { // assert_ne!(version, nullptr); assert_eq!(number, version.getVersionNumber()); - if number > 1 && !version.isMicroQRCode() { + if number > 1 && version.isModel2() { assert!(!version.getAlignmentPatternCenters().is_empty()); } assert_eq!(dimension, version.getDimensionForVersion()); @@ -26,13 +26,13 @@ fn DoTestVersion(expectedVersion: u32, mask: i32) { #[test] fn VersionForNumber() { - let version = Version::FromNumber(0, false, false); + let version = Version::Model2(0); assert!(version.is_err(), "There is version with number 0"); for i in 1..=40 { // for (int i = 1; i <= 40; i++) { CheckVersion( - Version::FromNumber(i, false, false).expect("version number found"), + Version::Model2(i).expect("version number found"), i, 4 * i + 17, ); @@ -43,10 +43,13 @@ fn VersionForNumber() { fn GetProvisionalVersionForDimension() { for i in 1..=40 { // for (int i = 1; i <= 40; i++) { - let prov = Version::FromDimension(4 * i + 17, false) - .unwrap_or_else(|_| panic!("version should exist for {i}")); // assert_ne!(prov, nullptr); - assert_eq!(i, prov.getVersionNumber()); + assert_eq!( + i, + Version::Number( + &BitMatrix::with_single_dimension(4 * i + 17).expect("must create bitmatrix") + ) + ); } } @@ -63,14 +66,13 @@ fn DecodeVersionInformation() { #[test] fn MicroVersionForNumber() { - let version = Version::FromNumber(0, true, false); + let version = Version::Micro(0); assert!(version.is_err(), "There is version with number 0"); for i in 1..=4 { // for (int i = 1; i <= 4; i++) { CheckVersion( - Version::FromNumber(i, true, false) - .unwrap_or_else(|_| panic!("version for {i} should exist")), + Version::Micro(i).unwrap_or_else(|_| panic!("version for {i} should exist")), i, 2 * i + 9, ); @@ -81,10 +83,12 @@ fn MicroVersionForNumber() { fn GetProvisionalMicroVersionForDimension() { for i in 1..=4 { // for (int i = 1; i <= 4; i++) { - let prov = Version::FromDimension(2 * i + 9, false) - .unwrap_or_else(|_| panic!("version for micro {i} should exist")); - // assert_ne!(prov, nullptr); - assert_eq!(i, prov.getVersionNumber()); + assert_eq!( + i, + Version::Number( + &BitMatrix::with_single_dimension(2 * i + 9).expect("must create bitmatrix") + ) + ); } } @@ -101,7 +105,7 @@ fn FunctionPattern() { }; for i in 1..=4 { // for (int i = 1; i <= 4; i++) { - let version = Version::FromNumber(i, true, false).expect("version must be found"); + let version = Version::Micro(i).expect("version must be found"); let functionPattern = version .buildFunctionPattern() .expect("function pattern must be found"); diff --git a/src/qrcode/decoder/format_information.rs b/src/qrcode/decoder/format_information.rs index 8e7281c..4591cc9 100644 --- a/src/qrcode/decoder/format_information.rs +++ b/src/qrcode/decoder/format_information.rs @@ -19,6 +19,7 @@ use crate::common::Result; use super::ErrorCorrectionLevel; pub const FORMAT_INFO_MASK_QR: u32 = 0x5412; +pub const FORMAT_INFO_MASK_MODEL2: u32 = FORMAT_INFO_MASK_QR; /** * See ISO 18004:2006, Annex C, Table C.1 @@ -73,9 +74,9 @@ pub struct FormatInformation { pub data_mask: u8, pub microVersion: u32, pub isMirrored: bool, - pub isModel1: bool, - pub index: u8, // = 255; + pub mask: u32, // = 0 + pub data: u32, // = 255 pub bitsIndex: u8, // = 255; } @@ -87,8 +88,8 @@ impl Default for FormatInformation { data_mask: Default::default(), microVersion: 0, isMirrored: false, - isModel1: false, - index: 255, + mask: 0, + data: 255, bitsIndex: 255, } } @@ -106,9 +107,9 @@ impl FormatInformation { error_correction_level: errorCorrectionLevel, data_mask: dataMask, isMirrored: false, - isModel1: false, - index: 255, + mask: 0, bitsIndex: 255, + data: 255, }) } diff --git a/src/qrcode/decoder/mode.rs b/src/qrcode/decoder/mode.rs index 858f4ba..c6cb3ad 100644 --- a/src/qrcode/decoder/mode.rs +++ b/src/qrcode/decoder/mode.rs @@ -123,14 +123,14 @@ impl Mode { } pub const fn get_terminator_bit_length(version: &Version) -> u8 { - (if version.isMicroQRCode() { + (if version.isMicro() { version.getVersionNumber() * 2 + 1 } else { 4 }) as u8 } pub const fn get_codec_mode_bits_length(version: &Version) -> u8 { - (if version.isMicroQRCode() { + (if version.isMicro() { version.getVersionNumber() - 1 } else { 4 @@ -168,7 +168,7 @@ impl Mode { */ pub fn CharacterCountBits(&self, version: &Version) -> u32 { let number = version.getVersionNumber() as usize; - if version.isMicroQRCode() { + if version.isMicro() { match self { Mode::NUMERIC=> return [3, 4, 5, 6][number - 1], Mode::ALPHANUMERIC=> return [3, 4, 5][number - 2], diff --git a/src/qrcode/decoder/version.rs b/src/qrcode/decoder/version.rs index e580a50..59e22af 100755 --- a/src/qrcode/decoder/version.rs +++ b/src/qrcode/decoder/version.rs @@ -18,6 +18,7 @@ use std::fmt; use crate::{ common::{BitMatrix, Result}, + qrcode::cpp_port::Type, Exceptions, }; @@ -55,8 +56,7 @@ pub struct Version { alignmentPatternCenters: Vec, ecBlocks: Vec, totalCodewords: u32, - pub(crate) is_micro: bool, - pub(crate) is_model1: bool, + pub(crate) qr_type: Type, } impl Version { fn new(versionNumber: u32, alignmentPatternCenters: Vec, ecBlocks: [ECBlocks; 4]) -> Self { @@ -74,8 +74,7 @@ impl Version { alignmentPatternCenters, ecBlocks: ecBlocks.to_vec(), totalCodewords: total, - is_micro: false, - is_model1: false, + qr_type: Type::Model2, } } @@ -94,8 +93,7 @@ impl Version { alignmentPatternCenters: Vec::default(), ecBlocks, totalCodewords: total, - is_micro: true, - is_model1: false, + qr_type: Type::Micro, } } @@ -114,8 +112,7 @@ impl Version { alignmentPatternCenters: Vec::default(), ecBlocks, totalCodewords: total, - is_micro: false, - is_model1: true, + qr_type: Type::Model1, } } @@ -137,7 +134,7 @@ impl Version { } pub fn getDimensionForVersion(&self) -> u32 { - Self::DimensionOfVersion(self.versionNumber, self.is_micro) + Self::DimensionOfVersion(self.versionNumber, self.qr_type == Type::Micro) // 17 + 4 * self.versionNumber } @@ -205,7 +202,7 @@ impl Version { // Top left finder pattern + separator + format bitMatrix.setRegion(0, 0, 9, 9)?; - if !self.is_micro { + if self.qr_type != Type::Micro { // Top right finder pattern + separator + format bitMatrix.setRegion(dimension - 8, 0, 8, 9)?; // Bottom left finder pattern + separator + format