From 81f2298dd0f13d037c22d8b66e222fe5d8ecb601 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Fri, 9 Dec 2022 18:15:52 -0600 Subject: [PATCH] ean 8 integration passes --- one-d-reader-derive/src/lib.rs | 4 +- src/common/bit_array.rs | 4 +- src/oned/EAN8Reader.java | 75 -- src/oned/UPCEANExtension2Support.java | 112 --- src/oned/UPCEANExtension5Support.java | 180 ---- src/oned/UPCEANExtensionSupport.java | 40 - src/oned/ean_8_reader.rs | 85 ++ src/oned/ean_manufacturer_org_support.rs | 11 +- src/oned/mod.rs | 13 +- src/oned/upc_ean_extension_2_support.rs | 148 +++ src/oned/upc_ean_extension_5_support.rs | 224 +++++ src/oned/upc_ean_extension_support.rs | 61 ++ src/oned/upc_ean_reader.rs | 855 ++++++++++-------- ...TestCase.java => EAN8BlackBox1TestCase.rs} | 27 +- 14 files changed, 1059 insertions(+), 780 deletions(-) delete mode 100644 src/oned/EAN8Reader.java delete mode 100644 src/oned/UPCEANExtension2Support.java delete mode 100644 src/oned/UPCEANExtension5Support.java delete mode 100644 src/oned/UPCEANExtensionSupport.java create mode 100644 src/oned/ean_8_reader.rs create mode 100644 src/oned/upc_ean_extension_2_support.rs create mode 100644 src/oned/upc_ean_extension_5_support.rs create mode 100644 src/oned/upc_ean_extension_support.rs rename tests/{EAN8BlackBox1TestCase.java => EAN8BlackBox1TestCase.rs} (55%) diff --git a/one-d-reader-derive/src/lib.rs b/one-d-reader-derive/src/lib.rs index 522b957..153e8a4 100644 --- a/one-d-reader-derive/src/lib.rs +++ b/one-d-reader-derive/src/lib.rs @@ -92,14 +92,14 @@ pub fn ean_reader_derive(input: TokenStream) -> TokenStream { fn impl_ean_reader_macro(ast: &syn::DeriveInput) -> TokenStream { let name = &ast.ident; let gen = quote! { - impl OneDReader for #name { + impl super::OneDReader for #name { fn decodeRow( &mut self, rowNumber: u32, row: &crate::common::BitArray, hints: &crate::DecodingHintDictionary, ) -> Result { - self.decodeRowWithGuardRange(rowNumber, row, Self::findStartGuardPattern(row), hints) + self.decodeRowWithGuardRange(rowNumber, row, &Self::findStartGuardPattern(row)?, hints) } } }; diff --git a/src/common/bit_array.rs b/src/common/bit_array.rs index ac7225d..3834730 100644 --- a/src/common/bit_array.rs +++ b/src/common/bit_array.rs @@ -137,7 +137,7 @@ impl BitArray { return self.size; } let mut bitsOffset = from / 32; - let mut currentBits = !self.bits[bitsOffset] as i32; + let mut currentBits = !self.bits[bitsOffset] as i64; // mask off lesser bits first currentBits &= -(1 << (from & 0x1F)); while currentBits == 0 { @@ -145,7 +145,7 @@ impl BitArray { if bitsOffset == self.bits.len() { return self.size; } - currentBits = !self.bits[bitsOffset] as i32; + currentBits = !self.bits[bitsOffset] as i64; } let result = (bitsOffset * 32) + currentBits.trailing_zeros() as usize; return cmp::min(result, self.size); diff --git a/src/oned/EAN8Reader.java b/src/oned/EAN8Reader.java deleted file mode 100644 index 8d0b7e2..0000000 --- a/src/oned/EAN8Reader.java +++ /dev/null @@ -1,75 +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.oned; - -import com.google.zxing.BarcodeFormat; -import com.google.zxing.NotFoundException; -import com.google.zxing.common.BitArray; - -/** - *

Implements decoding of the EAN-8 format.

- * - * @author Sean Owen - */ -public final class EAN8Reader extends UPCEANReader { - - private final int[] decodeMiddleCounters; - - public EAN8Reader() { - decodeMiddleCounters = new int[4]; - } - - @Override - protected int decodeMiddle(BitArray row, - int[] startRange, - StringBuilder result) throws NotFoundException { - int[] counters = decodeMiddleCounters; - counters[0] = 0; - counters[1] = 0; - counters[2] = 0; - counters[3] = 0; - int end = row.getSize(); - int rowOffset = startRange[1]; - - for (int x = 0; x < 4 && rowOffset < end; x++) { - int bestMatch = decodeDigit(row, counters, rowOffset, L_PATTERNS); - result.append((char) ('0' + bestMatch)); - for (int counter : counters) { - rowOffset += counter; - } - } - - int[] middleRange = findGuardPattern(row, rowOffset, true, MIDDLE_PATTERN); - rowOffset = middleRange[1]; - - for (int x = 0; x < 4 && rowOffset < end; x++) { - int bestMatch = decodeDigit(row, counters, rowOffset, L_PATTERNS); - result.append((char) ('0' + bestMatch)); - for (int counter : counters) { - rowOffset += counter; - } - } - - return rowOffset; - } - - @Override - BarcodeFormat getBarcodeFormat() { - return BarcodeFormat.EAN_8; - } - -} diff --git a/src/oned/UPCEANExtension2Support.java b/src/oned/UPCEANExtension2Support.java deleted file mode 100644 index b1dd000..0000000 --- a/src/oned/UPCEANExtension2Support.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2012 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.oned; - -import com.google.zxing.BarcodeFormat; -import com.google.zxing.NotFoundException; -import com.google.zxing.RXingResult; -import com.google.zxing.RXingResultMetadataType; -import com.google.zxing.RXingResultPoint; -import com.google.zxing.common.BitArray; - -import java.util.EnumMap; -import java.util.Map; - -/** - * @see UPCEANExtension5Support - */ -final class UPCEANExtension2Support { - - private final int[] decodeMiddleCounters = new int[4]; - private final StringBuilder decodeRowStringBuffer = new StringBuilder(); - - RXingResult decodeRow(int rowNumber, BitArray row, int[] extensionStartRange) throws NotFoundException { - - StringBuilder result = decodeRowStringBuffer; - result.setLength(0); - int end = decodeMiddle(row, extensionStartRange, result); - - String resultString = result.toString(); - Map extensionData = parseExtensionString(resultString); - - RXingResult extensionRXingResult = - new RXingResult(resultString, - null, - new RXingResultPoint[] { - new RXingResultPoint((extensionStartRange[0] + extensionStartRange[1]) / 2.0f, rowNumber), - new RXingResultPoint(end, rowNumber), - }, - BarcodeFormat.UPC_EAN_EXTENSION); - if (extensionData != null) { - extensionRXingResult.putAllMetadata(extensionData); - } - return extensionRXingResult; - } - - private int decodeMiddle(BitArray row, int[] startRange, StringBuilder resultString) throws NotFoundException { - int[] counters = decodeMiddleCounters; - counters[0] = 0; - counters[1] = 0; - counters[2] = 0; - counters[3] = 0; - int end = row.getSize(); - int rowOffset = startRange[1]; - - int checkParity = 0; - - for (int x = 0; x < 2 && rowOffset < end; x++) { - int bestMatch = UPCEANReader.decodeDigit(row, counters, rowOffset, UPCEANReader.L_AND_G_PATTERNS); - resultString.append((char) ('0' + bestMatch % 10)); - for (int counter : counters) { - rowOffset += counter; - } - if (bestMatch >= 10) { - checkParity |= 1 << (1 - x); - } - if (x != 1) { - // Read off separator if not last - rowOffset = row.getNextSet(rowOffset); - rowOffset = row.getNextUnset(rowOffset); - } - } - - if (resultString.length() != 2) { - throw NotFoundException.getNotFoundInstance(); - } - - if (Integer.parseInt(resultString.toString()) % 4 != checkParity) { - throw NotFoundException.getNotFoundInstance(); - } - - return rowOffset; - } - - /** - * @param raw raw content of extension - * @return formatted interpretation of raw content as a {@link Map} mapping - * one {@link RXingResultMetadataType} to appropriate value, or {@code null} if not known - */ - private static Map parseExtensionString(String raw) { - if (raw.length() != 2) { - return null; - } - Map result = new EnumMap<>(RXingResultMetadataType.class); - result.put(RXingResultMetadataType.ISSUE_NUMBER, Integer.valueOf(raw)); - return result; - } - -} diff --git a/src/oned/UPCEANExtension5Support.java b/src/oned/UPCEANExtension5Support.java deleted file mode 100644 index dff97c8..0000000 --- a/src/oned/UPCEANExtension5Support.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright (C) 2010 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.zxing.oned; - -import com.google.zxing.BarcodeFormat; -import com.google.zxing.NotFoundException; -import com.google.zxing.RXingResult; -import com.google.zxing.RXingResultMetadataType; -import com.google.zxing.RXingResultPoint; -import com.google.zxing.common.BitArray; - -import java.util.EnumMap; -import java.util.Map; - -/** - * @see UPCEANExtension2Support - */ -final class UPCEANExtension5Support { - - private static final int[] CHECK_DIGIT_ENCODINGS = { - 0x18, 0x14, 0x12, 0x11, 0x0C, 0x06, 0x03, 0x0A, 0x09, 0x05 - }; - - private final int[] decodeMiddleCounters = new int[4]; - private final StringBuilder decodeRowStringBuffer = new StringBuilder(); - - RXingResult decodeRow(int rowNumber, BitArray row, int[] extensionStartRange) throws NotFoundException { - - StringBuilder result = decodeRowStringBuffer; - result.setLength(0); - int end = decodeMiddle(row, extensionStartRange, result); - - String resultString = result.toString(); - Map extensionData = parseExtensionString(resultString); - - RXingResult extensionRXingResult = - new RXingResult(resultString, - null, - new RXingResultPoint[] { - new RXingResultPoint((extensionStartRange[0] + extensionStartRange[1]) / 2.0f, rowNumber), - new RXingResultPoint(end, rowNumber), - }, - BarcodeFormat.UPC_EAN_EXTENSION); - if (extensionData != null) { - extensionRXingResult.putAllMetadata(extensionData); - } - return extensionRXingResult; - } - - private int decodeMiddle(BitArray row, int[] startRange, StringBuilder resultString) throws NotFoundException { - int[] counters = decodeMiddleCounters; - counters[0] = 0; - counters[1] = 0; - counters[2] = 0; - counters[3] = 0; - int end = row.getSize(); - int rowOffset = startRange[1]; - - int lgPatternFound = 0; - - for (int x = 0; x < 5 && rowOffset < end; x++) { - int bestMatch = UPCEANReader.decodeDigit(row, counters, rowOffset, UPCEANReader.L_AND_G_PATTERNS); - resultString.append((char) ('0' + bestMatch % 10)); - for (int counter : counters) { - rowOffset += counter; - } - if (bestMatch >= 10) { - lgPatternFound |= 1 << (4 - x); - } - if (x != 4) { - // Read off separator if not last - rowOffset = row.getNextSet(rowOffset); - rowOffset = row.getNextUnset(rowOffset); - } - } - - if (resultString.length() != 5) { - throw NotFoundException.getNotFoundInstance(); - } - - int checkDigit = determineCheckDigit(lgPatternFound); - if (extensionChecksum(resultString.toString()) != checkDigit) { - throw NotFoundException.getNotFoundInstance(); - } - - return rowOffset; - } - - private static int extensionChecksum(CharSequence s) { - int length = s.length(); - int sum = 0; - for (int i = length - 2; i >= 0; i -= 2) { - sum += s.charAt(i) - '0'; - } - sum *= 3; - for (int i = length - 1; i >= 0; i -= 2) { - sum += s.charAt(i) - '0'; - } - sum *= 3; - return sum % 10; - } - - private static int determineCheckDigit(int lgPatternFound) - throws NotFoundException { - for (int d = 0; d < 10; d++) { - if (lgPatternFound == CHECK_DIGIT_ENCODINGS[d]) { - return d; - } - } - throw NotFoundException.getNotFoundInstance(); - } - - /** - * @param raw raw content of extension - * @return formatted interpretation of raw content as a {@link Map} mapping - * one {@link RXingResultMetadataType} to appropriate value, or {@code null} if not known - */ - private static Map parseExtensionString(String raw) { - if (raw.length() != 5) { - return null; - } - Object value = parseExtension5String(raw); - if (value == null) { - return null; - } - Map result = new EnumMap<>(RXingResultMetadataType.class); - result.put(RXingResultMetadataType.SUGGESTED_PRICE, value); - return result; - } - - private static String parseExtension5String(String raw) { - String currency; - switch (raw.charAt(0)) { - case '0': - currency = "£"; - break; - case '5': - currency = "$"; - break; - case '9': - // Reference: http://www.jollytech.com - switch (raw) { - case "90000": - // No suggested retail price - return null; - case "99991": - // Complementary - return "0.00"; - case "99990": - return "Used"; - } - // Otherwise... unknown currency? - currency = ""; - break; - default: - currency = ""; - break; - } - int rawAmount = Integer.parseInt(raw.substring(1)); - String unitsString = String.valueOf(rawAmount / 100); - int hundredths = rawAmount % 100; - String hundredthsString = hundredths < 10 ? "0" + hundredths : String.valueOf(hundredths); - return currency + unitsString + '.' + hundredthsString; - } - -} diff --git a/src/oned/UPCEANExtensionSupport.java b/src/oned/UPCEANExtensionSupport.java deleted file mode 100644 index 9493584..0000000 --- a/src/oned/UPCEANExtensionSupport.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2010 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.zxing.oned; - -import com.google.zxing.NotFoundException; -import com.google.zxing.ReaderException; -import com.google.zxing.RXingResult; -import com.google.zxing.common.BitArray; - -final class UPCEANExtensionSupport { - - private static final int[] EXTENSION_START_PATTERN = {1,1,2}; - - private final UPCEANExtension2Support twoSupport = new UPCEANExtension2Support(); - private final UPCEANExtension5Support fiveSupport = new UPCEANExtension5Support(); - - RXingResult decodeRow(int rowNumber, BitArray row, int rowOffset) throws NotFoundException { - int[] extensionStartRange = UPCEANReader.findGuardPattern(row, rowOffset, false, EXTENSION_START_PATTERN); - try { - return fiveSupport.decodeRow(rowNumber, row, extensionStartRange); - } catch (ReaderException ignored) { - return twoSupport.decodeRow(rowNumber, row, extensionStartRange); - } - } - -} diff --git a/src/oned/ean_8_reader.rs b/src/oned/ean_8_reader.rs new file mode 100644 index 0000000..a89d0e1 --- /dev/null +++ b/src/oned/ean_8_reader.rs @@ -0,0 +1,85 @@ +/* + * 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 super::OneDReader; +use crate::{BarcodeFormat, Exceptions}; +use one_d_reader_derive::{EANReader, OneDReader}; + +use super::upc_ean_reader; +use super::UPCEANReader; + +/** + *

Implements decoding of the EAN-8 format.

+ * + * @author Sean Owen + */ +#[derive(OneDReader, EANReader)] +pub struct EAN8Reader; + +impl UPCEANReader for EAN8Reader { + fn getBarcodeFormat(&self) -> crate::BarcodeFormat { + BarcodeFormat::EAN_8 + } + + fn decodeMiddle( + &self, + row: &crate::common::BitArray, + startRange: &[usize; 2], + resultString: &mut String, + ) -> Result { + let mut counters = [0_u32; 4]; //decodeMiddleCounters; + // counters[0] = 0; + // counters[1] = 0; + // counters[2] = 0; + // counters[3] = 0; + let end = row.getSize(); + let mut rowOffset = startRange[1]; + + let mut x = 0; + while x < 4 && rowOffset < end { + // for (int x = 0; x < 4 && rowOffset < end; x++) { + let bestMatch = + Self::decodeDigit(row, &mut counters, rowOffset, &upc_ean_reader::L_PATTERNS)?; + resultString.push(char::from_u32('0' as u32 + bestMatch as u32).unwrap()); + // for (int counter : counters) { + // rowOffset += counter; + // } + + rowOffset += counters.iter().sum::() as usize; + + x += 1; + } + + let middleRange = + Self::findGuardPattern(row, rowOffset, true, &upc_ean_reader::MIDDLE_PATTERN)?; + rowOffset = middleRange[1]; + + let mut x = 0; + while x < 4 && rowOffset < end { + // for (int x = 0; x < 4 && rowOffset < end; x++) { + let bestMatch = + Self::decodeDigit(row, &mut counters, rowOffset, &upc_ean_reader::L_PATTERNS)?; + resultString.push(char::from_u32('0' as u32 + bestMatch as u32).unwrap()); + // for (int counter : counters) { + // rowOffset += counter; + // } + rowOffset += counters.iter().sum::() as usize; + x += 1; + } + + Ok(rowOffset) + } +} diff --git a/src/oned/ean_manufacturer_org_support.rs b/src/oned/ean_manufacturer_org_support.rs index b6156ff..33cfde7 100644 --- a/src/oned/ean_manufacturer_org_support.rs +++ b/src/oned/ean_manufacturer_org_support.rs @@ -30,16 +30,17 @@ pub struct EANManufacturerOrgSupport { impl Default for EANManufacturerOrgSupport { fn default() -> Self { - Self { + let mut slf = Self { ranges: Default::default(), countryIdentifiers: Default::default(), - } + }; + slf.initIfNeeded(); + slf } } impl EANManufacturerOrgSupport { - pub fn lookupCountryIdentifier(&mut self, productCode: &str) -> Option { - self.initIfNeeded(); + pub fn lookupCountryIdentifier(&self, productCode: &str) -> Option { let prefix = productCode[0..3].parse::().expect("must parse prefix"); // let prefix = Integer.parseInt(productCode.substring(0, 3)); let max = self.ranges.len(); @@ -193,7 +194,7 @@ mod EANManufacturerOrgSupportTest { #[test] fn testLookup() { - let mut support = EANManufacturerOrgSupport::default(); + let support = EANManufacturerOrgSupport::default(); assert!(support.lookupCountryIdentifier("472000").is_none()); assert_eq!( "US/CA", diff --git a/src/oned/mod.rs b/src/oned/mod.rs index f03984d..928e467 100644 --- a/src/oned/mod.rs +++ b/src/oned/mod.rs @@ -25,4 +25,15 @@ mod itf_reader; pub use itf_reader::*; mod upc_ean_reader; -pub use upc_ean_reader::*; \ No newline at end of file +pub use upc_ean_reader::*; + +mod upc_ean_extension_2_support; +mod upc_ean_extension_5_support; +mod upc_ean_extension_support; + +pub use upc_ean_extension_2_support::*; +pub use upc_ean_extension_5_support::*; +pub use upc_ean_extension_support::*; + +mod ean_8_reader; +pub use ean_8_reader::*; diff --git a/src/oned/upc_ean_extension_2_support.rs b/src/oned/upc_ean_extension_2_support.rs new file mode 100644 index 0000000..bfbc347 --- /dev/null +++ b/src/oned/upc_ean_extension_2_support.rs @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2012 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::BitArray, BarcodeFormat, Exceptions, RXingResult, RXingResultMetadataType, + RXingResultMetadataValue, RXingResultPoint, +}; + +use super::{upc_ean_reader, StandIn, UPCEANReader}; + +/** + * @see UPCEANExtension5Support + */ +pub struct UPCEANExtension2Support { + decodeMiddleCounters: [u32; 4], +} +impl Default for UPCEANExtension2Support { + fn default() -> Self { + Self { + decodeMiddleCounters: Default::default(), + } + } +} + +impl UPCEANExtension2Support { + pub fn decodeRow( + &self, + rowNumber: u32, + row: &BitArray, + extensionStartRange: &[u32; 3], + ) -> Result { + let mut result = String::new(); + let end = self.decodeMiddle(row, extensionStartRange, &mut result)?; + + let resultString = result; + let extensionData = Self::parseExtensionString(&resultString); + + let mut extensionRXingResult = RXingResult::new( + &resultString, + Vec::new(), + vec![ + RXingResultPoint::new( + (extensionStartRange[0] + extensionStartRange[1]) as f32 / 2.0, + rowNumber as f32, + ), + RXingResultPoint::new(end as f32, rowNumber as f32), + ], + BarcodeFormat::UPC_EAN_EXTENSION, + ); + if let Some(ed) = extensionData { + extensionRXingResult.putAllMetadata(ed); + } + + Ok(extensionRXingResult) + } + + fn decodeMiddle( + &self, + row: &BitArray, + startRange: &[u32; 3], + resultString: &mut String, + ) -> Result { + let mut counters = self.decodeMiddleCounters; + counters[0] = 0; + counters[1] = 0; + counters[2] = 0; + counters[3] = 0; + let end = row.getSize(); + let mut rowOffset = startRange[1] as usize; + + let mut checkParity = 0; + + let mut x = 0; + while x < 2 && rowOffset < end { + // for (int x = 0; x < 2 && rowOffset < end; x++) { + let bestMatch = StandIn::decodeDigit( + row, + &mut counters, + rowOffset, + &upc_ean_reader::L_AND_G_PATTERNS, + )?; + resultString.push(char::from_u32('0' as u32 + bestMatch as u32 % 10).unwrap()); + // for counter in counters { + // // for (int counter : counters) { + // rowOffset += counter; + // } + + rowOffset += counters.iter().sum::() as usize; + + if bestMatch >= 10 { + checkParity |= 1 << (1 - x); + } + if x != 1 { + // Read off separator if not last + rowOffset = row.getNextSet(rowOffset); + rowOffset = row.getNextUnset(rowOffset); + } + x += 1; + } + + if resultString.chars().count() != 2 { + return Err(Exceptions::NotFoundException("".to_owned())); + } + + if resultString.parse::().unwrap() % 4 != checkParity { + // if (Integer.parseInt(resultString.toString()) % 4 != checkParity) { + return Err(Exceptions::NotFoundException("".to_owned())); + } + + Ok(rowOffset as u32) + } + + /** + * @param raw raw content of extension + * @return formatted interpretation of raw content as a {@link Map} mapping + * one {@link RXingResultMetadataType} to appropriate value, or {@code null} if not known + */ + fn parseExtensionString( + raw: &str, + ) -> Option> { + if raw.chars().count() != 2 { + return None; + } + let mut result = HashMap::new(); + + result.insert( + RXingResultMetadataType::ISSUE_NUMBER, + RXingResultMetadataValue::IssueNumber(raw.parse::().unwrap()), + ); + + Some(result) + } +} diff --git a/src/oned/upc_ean_extension_5_support.rs b/src/oned/upc_ean_extension_5_support.rs new file mode 100644 index 0000000..5b166e6 --- /dev/null +++ b/src/oned/upc_ean_extension_5_support.rs @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2010 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use std::collections::HashMap; + +use crate::{ + common::BitArray, BarcodeFormat, Exceptions, RXingResult, RXingResultMetadataType, + RXingResultMetadataValue, RXingResultPoint, +}; + +use super::{upc_ean_reader, StandIn, UPCEANReader}; + +/** + * @see UPCEANExtension2Support + */ +pub struct UPCEANExtension5Support { + //decodeMiddleCounters : [u32;4], + // decodeRowStringBuffer : String, +} + +impl Default for UPCEANExtension5Support { + fn default() -> Self { + Self {} + } +} + +impl UPCEANExtension5Support { + const CHECK_DIGIT_ENCODINGS: [usize; 10] = + [0x18, 0x14, 0x12, 0x11, 0x0C, 0x06, 0x03, 0x0A, 0x09, 0x05]; + + pub fn decodeRow( + &self, + rowNumber: u32, + row: &BitArray, + extensionStartRange: &[usize; 2], + ) -> Result { + let mut result = String::new(); //self.decodeRowStringBuffer; + // result.setLength(0); + let end = Self::decodeMiddle(row, extensionStartRange, &mut result)?; + + let resultString = result; + let extensionData = Self::parseExtensionString(&resultString); + + let mut extensionRXingResult = RXingResult::new( + &resultString, + Vec::new(), + vec![ + RXingResultPoint::new( + (extensionStartRange[0] + extensionStartRange[1]) as f32 / 2.0, + rowNumber as f32, + ), + RXingResultPoint::new(end as f32, rowNumber as f32), + ], + BarcodeFormat::UPC_EAN_EXTENSION, + ); + if let Some(ed) = extensionData { + extensionRXingResult.putAllMetadata(ed); + } + Ok(extensionRXingResult) + } + + fn decodeMiddle( + row: &BitArray, + startRange: &[usize; 2], + resultString: &mut String, + ) -> Result { + let mut counters = [0_u32; 4]; //decodeMiddleCounters; + // counters[0] = 0; + // counters[1] = 0; + // counters[2] = 0; + // counters[3] = 0; + let end = row.getSize(); + let mut rowOffset = startRange[1] as usize; + + let mut lgPatternFound = 0; + + let mut x = 0; + while x < 5 && rowOffset < end { + // for (int x = 0; x < 5 && rowOffset < end; x++) { + let bestMatch = StandIn::decodeDigit( + row, + &mut counters, + rowOffset, + &upc_ean_reader::L_AND_G_PATTERNS, + )?; + resultString.push(char::from_u32('0' as u32 + bestMatch as u32 % 10).unwrap()); + // for (int counter : counters) { + // rowOffset += counter; + // } + rowOffset += counters.iter().sum::() as usize; + + if bestMatch >= 10 { + lgPatternFound |= 1 << (4 - x); + } + if x != 4 { + // Read off separator if not last + rowOffset = row.getNextSet(rowOffset); + rowOffset = row.getNextUnset(rowOffset); + } + + x += 1; + } + + if resultString.chars().count() != 5 { + return Err(Exceptions::NotFoundException("".to_owned())); + } + + let checkDigit = Self::determineCheckDigit(lgPatternFound)?; + if Self::extensionChecksum(resultString) != checkDigit as u32 { + return Err(Exceptions::NotFoundException("".to_owned())); + } + + Ok(rowOffset as u32) + } + + fn extensionChecksum(s: &str) -> u32 { + let length = s.chars().count(); + let mut sum = 0; + let mut i = length as isize - 2; + while i >= 0 { + // for (int i = length - 2; i >= 0; i -= 2) { + sum += s.chars().nth(i as usize).unwrap() as u32 - '0' as u32; + + i -= 2; + } + sum *= 3; + + let mut i = length as isize - 1; + while i >= 0 { + // for (int i = length - 1; i >= 0; i -= 2) { + sum += s.chars().nth(i as usize).unwrap() as u32 - '0' as u32; + + i -= 2; + } + sum *= 3; + return sum % 10; + } + + fn determineCheckDigit(lgPatternFound: usize) -> Result { + for d in 0..10 { + // for (int d = 0; d < 10; d++) { + if lgPatternFound == Self::CHECK_DIGIT_ENCODINGS[d] { + return Ok(d); + } + } + return Err(Exceptions::NotFoundException("".to_owned())); + } + + /** + * @param raw raw content of extension + * @return formatted interpretation of raw content as a {@link Map} mapping + * one {@link RXingResultMetadataType} to appropriate value, or {@code null} if not known + */ + fn parseExtensionString( + raw: &str, + ) -> Option> { + if raw.chars().count() != 5 { + return None; + } + let Some(value) = Self::parseExtension5String(raw) else { + return None; + }; + // Map result = new EnumMap<>(RXingResultMetadataType.class); + let mut result = HashMap::new(); + result.insert( + RXingResultMetadataType::SUGGESTED_PRICE, + RXingResultMetadataValue::SuggestedPrice(value), + ); + + Some(result) + } + + fn parseExtension5String(raw: &str) -> Option { + let currency = match raw.chars().nth(0).unwrap() { + '0' => "£", + '5' => "$", + '9' => { + // Reference: http://www.jollytech.com + match raw { + "90000" => + // No suggested retail price + { + return None + } + "99991" => + // Complementary + { + return Some("0.00".to_string()) + } + "99990" => return Some("Used".to_owned()), + _ => {} + } + // Otherwise... unknown currency? + "" + } + _ => "", + }; + + let rawAmount = raw[1..].parse::().unwrap(); + // let rawAmount = Integer.parseInt(raw.substring(1)); + let unitsString = (rawAmount / 100).to_string(); + let hundredths = rawAmount % 100; + let hundredthsString = if hundredths < 10 { + format!("0{}", hundredths) + } else { + hundredths.to_string() + }; + + Some(format!("{}{}.{}", currency, unitsString, hundredthsString)) + } +} diff --git a/src/oned/upc_ean_extension_support.rs b/src/oned/upc_ean_extension_support.rs new file mode 100644 index 0000000..0d0f6c9 --- /dev/null +++ b/src/oned/upc_ean_extension_support.rs @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2010 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::{common::BitArray, Exceptions, RXingResult}; + +use super::{StandIn, UPCEANExtension2Support, UPCEANExtension5Support, UPCEANReader}; + +pub struct UPCEANExtensionSupport { + twoSupport: UPCEANExtension2Support, + fiveSupport: UPCEANExtension5Support, +} + +impl Default for UPCEANExtensionSupport { + fn default() -> Self { + Self { + twoSupport: Default::default(), + fiveSupport: Default::default(), + } + } +} +impl UPCEANExtensionSupport { + const EXTENSION_START_PATTERN: [u32; 3] = [1, 1, 2]; + + pub fn decodeRow( + &self, + rowNumber: u32, + row: &BitArray, + rowOffset: usize, + ) -> Result { + let extensionStartRange = + StandIn::findGuardPattern(row, rowOffset, false, &Self::EXTENSION_START_PATTERN)?; + if let Ok(res_1) = self + .fiveSupport + .decodeRow(rowNumber, row, &extensionStartRange) + { + Ok(res_1) + } else { + self.twoSupport + .decodeRow(rowNumber, row, &Self::EXTENSION_START_PATTERN) + } + // let res_2 = twoSupport.decodeRow(rowNumber, row, extensionStartRange); + // try { + // return fiveSupport.decodeRow(rowNumber, row, extensionStartRange); + // } catch (ReaderException ignored) { + // return twoSupport.decodeRow(rowNumber, row, extensionStartRange); + // } + } +} diff --git a/src/oned/upc_ean_reader.rs b/src/oned/upc_ean_reader.rs index e4a85f2..6221ef7 100644 --- a/src/oned/upc_ean_reader.rs +++ b/src/oned/upc_ean_reader.rs @@ -14,73 +14,88 @@ * limitations under the License. */ -use crate::{Exceptions, common::BitArray, RXingResult, BarcodeFormat}; +use crate::{ + common::BitArray, BarcodeFormat, DecodeHintType, DecodeHintValue, Exceptions, RXingResult, + RXingResultMetadataType, RXingResultMetadataValue, RXingResultPoint, Reader, +}; -use super::OneDReader; +use super::{EANManufacturerOrgSupport, OneDReader, UPCEANExtensionSupport}; +use lazy_static::lazy_static; +lazy_static! { + pub static ref EAN_MANUFACTURER_SUPPORT: EANManufacturerOrgSupport = + EANManufacturerOrgSupport::default(); + pub static ref UPC_EAN_EXTENSION_SUPPORT: UPCEANExtensionSupport = + UPCEANExtensionSupport::default(); +} - // These two values are critical for determining how permissive the decoding will be. - // We've arrived at these values through a lot of trial and error. Setting them any higher - // lets false positives creep in quickly. - const MAX_AVG_VARIANCE : f32= 0.48; - const MAX_INDIVIDUAL_VARIANCE : f32= 0.7; +// These two values are critical for determining how permissive the decoding will be. +// We've arrived at these values through a lot of trial and error. Setting them any higher +// lets false positives creep in quickly. +pub const MAX_AVG_VARIANCE: f32 = 0.48; +pub const MAX_INDIVIDUAL_VARIANCE: f32 = 0.7; - /** - * Start/end guard pattern. - */ - const START_END_PATTERN : [u32;3]= [1, 1, 1,]; +/** + * Start/end guard pattern. + */ +pub const START_END_PATTERN: [u32; 3] = [1, 1, 1]; - /** - * Pattern marking the middle of a UPC/EAN pattern, separating the two halves. - */ - const MIDDLE_PATTERN : [u32;5]= [1, 1, 1, 1, 1]; - /** - * end guard pattern. - */ - const END_PATTERN : [u32;6]= [1, 1, 1, 1, 1, 1]; - /** - * "Odd", or "L" patterns used to encode UPC/EAN digits. - */ - const L_PATTERNS : [[u32;4];10]= [ - [3, 2, 1, 1], // 0 - [2, 2, 2, 1], // 1 - [2, 1, 2, 2], // 2 - [1, 4, 1, 1], // 3 - [1, 1, 3, 2], // 4 - [1, 2, 3, 1], // 5 - [1, 1, 1, 4], // 6 - [1, 3, 1, 2], // 7 - [1, 2, 1, 3], // 8 - [3, 1, 1, 2] // 9 - ]; +/** + * Pattern marking the middle of a UPC/EAN pattern, separating the two halves. + */ +pub const MIDDLE_PATTERN: [u32; 5] = [1, 1, 1, 1, 1]; +/** + * end guard pattern. + */ +pub const END_PATTERN: [u32; 6] = [1, 1, 1, 1, 1, 1]; +/** + * "Odd", or "L" patterns used to encode UPC/EAN digits. + */ +pub const L_PATTERNS: [[u32; 4]; 10] = [ + [3, 2, 1, 1], // 0 + [2, 2, 2, 1], // 1 + [2, 1, 2, 2], // 2 + [1, 4, 1, 1], // 3 + [1, 1, 3, 2], // 4 + [1, 2, 3, 1], // 5 + [1, 1, 1, 4], // 6 + [1, 3, 1, 2], // 7 + [1, 2, 1, 3], // 8 + [3, 1, 1, 2], // 9 +]; - /** - * As above but also including the "even", or "G" patterns used to encode UPC/EAN digits. - */ - const L_AND_G_PATTERNS : [[u32;4];20] = { - let new_array = [[0_u32;4];20];//new int[20][]; - new_array[0..10].copy_from_slice(&L_PATTERNS[0..10]); +/** + * As above but also including the "even", or "G" patterns used to encode UPC/EAN digits. + */ +pub const L_AND_G_PATTERNS: [[u32; 4]; 20] = { + let mut new_array = [[0_u32; 4]; 20]; //new int[20][]; + let mut i = 0; + while i < 10 { + new_array[i] = L_PATTERNS[i]; + i += 1; + } + // new_array[0..10].copy_from_slice(&L_PATTERNS[0..10]); // System.arraycopy(L_PATTERNS, 0, L_AND_G_PATTERNS, 0, 10); let mut i = 10; while i < 20 { - // for (int i = 10; i < 20; i++) { - let widths = &L_PATTERNS[i - 10]; - let reversedWidths = [0_u32;4];//new int[widths.length]; - let mut j = 0; - while j < 4 { - // for (int j = 0; j < widths.length; j++) { - reversedWidths[j] = widths[4 - j - 1]; - - j+=1; - } - new_array[i] = reversedWidths; + // for (int i = 10; i < 20; i++) { + let widths = &L_PATTERNS[i - 10]; + let mut reversedWidths = [0_u32; 4]; //new int[widths.length]; + let mut j = 0; + while j < 4 { + // for (int j = 0; j < widths.length; j++) { + reversedWidths[j] = widths[4 - j - 1]; - i+=1; + j += 1; + } + new_array[i] = reversedWidths; + + i += 1; } new_array - }; +}; /** *

Encapsulates functionality and implementation that is common to UPC and EAN families @@ -91,327 +106,461 @@ use super::OneDReader; * @author alasdair@google.com (Alasdair Mackintosh) */ pub trait UPCEANReader: OneDReader { + // private final StringBuilder decodeRowStringBuffer; + // private final UPCEANExtensionSupport extensionReader; + // private final EANManufacturerOrgSupport eanManSupport; - // private final StringBuilder decodeRowStringBuffer; - // private final UPCEANExtensionSupport extensionReader; - // private final EANManufacturerOrgSupport eanManSupport; + // protected UPCEANReader() { + // decodeRowStringBuffer = new StringBuilder(20); + // extensionReader = new UPCEANExtensionSupport(); + // eanManSupport = new EANManufacturerOrgSupport(); + // } - // protected UPCEANReader() { - // decodeRowStringBuffer = new StringBuilder(20); - // extensionReader = new UPCEANExtensionSupport(); - // eanManSupport = new EANManufacturerOrgSupport(); - // } - - fn findStartGuardPattern( row:&BitArray) -> Result,Exceptions> { - let foundStart = false; - let startRange ;//= null; - let nextStart = 0; - let counters = vec![0_u32;START_END_PATTERN.len()]; - while (!foundStart) { - Arrays.fill(counters, 0, START_END_PATTERN.len(), 0); - startRange = Self::findGuardPattern(row, nextStart, false, START_END_PATTERN, counters); - let start = startRange[0]; - nextStart = startRange[1]; - // Make sure there is a quiet zone at least as big as the start pattern before the barcode. - // If this check would run off the left edge of the image, do not accept this barcode, - // as it is very likely to be a false positive. - let quietStart = start - (nextStart - start); - if (quietStart >= 0) { - foundStart = row.isRange(quietStart, start, false); - } - } - return startRange; - } - - // @Override - // public RXingResult decodeRow(int rowNumber, BitArray row, Map hints) - // throws NotFoundException, ChecksumException, FormatException { - // return decodeRow(rowNumber, row, findStartGuardPattern(row), hints); - // } - - /** - *

Like {@link #decodeRow(int, BitArray, Map)}, but - * allows caller to inform method about where the UPC/EAN start pattern is - * found. This allows this to be computed once and reused across many implementations.

- * - * @param rowNumber row index into the image - * @param row encoding of the row of the barcode image - * @param startGuardRange start/end column where the opening start pattern was found - * @param hints optional hints that influence decoding - * @return {@link RXingResult} encapsulating the result of decoding a barcode in the row - * @throws NotFoundException if no potential barcode is found - * @throws ChecksumException if a potential barcode is found but does not pass its checksum - * @throws FormatException if a potential barcode is found but format is invalid - */ - fn decodeRowWithGuardRange(rowNumber:u32, - row:&BitArray, - startGuardRange:&[u32;2], - hints:&crate::DecodingHintDictionary) - -> Result { - - RXingResultPointCallback resultPointCallback = hints == null ? null : - (RXingResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK); - int symbologyIdentifier = 0; - - if (resultPointCallback != null) { - resultPointCallback.foundPossibleRXingResultPoint(new RXingResultPoint( - (startGuardRange[0] + startGuardRange[1]) / 2.0f, rowNumber - )); - } - - StringBuilder result = decodeRowStringBuffer; - result.setLength(0); - int endStart = decodeMiddle(row, startGuardRange, result); - - if (resultPointCallback != null) { - resultPointCallback.foundPossibleRXingResultPoint(new RXingResultPoint( - endStart, rowNumber - )); - } - - int[] endRange = decodeEnd(row, endStart); - - if (resultPointCallback != null) { - resultPointCallback.foundPossibleRXingResultPoint(new RXingResultPoint( - (endRange[0] + endRange[1]) / 2.0f, rowNumber - )); - } - - - // Make sure there is a quiet zone at least as big as the end pattern after the barcode. The - // spec might want more whitespace, but in practice this is the maximum we can count on. - int end = endRange[1]; - int quietEnd = end + (end - endRange[0]); - if (quietEnd >= row.getSize() || !row.isRange(end, quietEnd, false)) { - throw NotFoundException.getNotFoundInstance(); - } - - String resultString = result.toString(); - // UPC/EAN should never be less than 8 chars anyway - if (resultString.length() < 8) { - throw FormatException.getFormatInstance(); - } - if (!checkChecksum(resultString)) { - throw ChecksumException.getChecksumInstance(); - } - - float left = (startGuardRange[1] + startGuardRange[0]) / 2.0f; - float right = (endRange[1] + endRange[0]) / 2.0f; - BarcodeFormat format = getBarcodeFormat(); - RXingResult decodeRXingResult = new RXingResult(resultString, - null, // no natural byte representation for these barcodes - new RXingResultPoint[]{ - new RXingResultPoint(left, rowNumber), - new RXingResultPoint(right, rowNumber)}, - format); - - int extensionLength = 0; - - try { - RXingResult extensionRXingResult = extensionReader.decodeRow(rowNumber, row, endRange[1]); - decodeRXingResult.putMetadata(RXingResultMetadataType.UPC_EAN_EXTENSION, extensionRXingResult.getText()); - decodeRXingResult.putAllMetadata(extensionRXingResult.getRXingResultMetadata()); - decodeRXingResult.addRXingResultPoints(extensionRXingResult.getRXingResultPoints()); - extensionLength = extensionRXingResult.getText().length(); - } catch (ReaderException re) { - // continue - } - - int[] allowedExtensions = - hints == null ? null : (int[]) hints.get(DecodeHintType.ALLOWED_EAN_EXTENSIONS); - if (allowedExtensions != null) { - boolean valid = false; - for (int length : allowedExtensions) { - if (extensionLength == length) { - valid = true; - break; + fn findStartGuardPattern(row: &BitArray) -> Result<[usize; 2], Exceptions> + where + Self: Sized, + { + let mut foundStart = false; + let mut startRange = [0; 2]; //= null; + let mut nextStart = 0; + let mut counters = [0_u32; 3]; //vec![0_u32;START_END_PATTERN.len()]; + while !foundStart { + counters.fill(0); + // Arrays.fill(counters, 0, START_END_PATTERN.len(), 0); + startRange = Self::findGuardPatternWithCounters( + row, + nextStart, + false, + &START_END_PATTERN, + &mut counters, + )?; + let start = startRange[0]; + nextStart = startRange[1]; + // Make sure there is a quiet zone at least as big as the start pattern before the barcode. + // If this check would run off the left edge of the image, do not accept this barcode, + // as it is very likely to be a false positive. + let quietStart = start as isize - (nextStart as isize - start as isize); + if quietStart >= 0 { + foundStart = row.isRange(quietStart as usize, start, false)?; + } } - } - if (!valid) { - throw NotFoundException.getNotFoundInstance(); - } + + Ok(startRange) } - if (format == BarcodeFormat.EAN_13 || format == BarcodeFormat.UPC_A) { - String countryID = eanManSupport.lookupCountryIdentifier(resultString); - if (countryID != null) { - decodeRXingResult.putMetadata(RXingResultMetadataType.POSSIBLE_COUNTRY, countryID); - } - } - if (format == BarcodeFormat.EAN_8) { - symbologyIdentifier = 4; + // @Override + // public RXingResult decodeRow(int rowNumber, BitArray row, Map hints) + // throws NotFoundException, ChecksumException, FormatException { + // return decodeRow(rowNumber, row, findStartGuardPattern(row), hints); + // } + + /** + *

Like {@link #decodeRow(int, BitArray, Map)}, but + * allows caller to inform method about where the UPC/EAN start pattern is + * found. This allows this to be computed once and reused across many implementations.

+ * + * @param rowNumber row index into the image + * @param row encoding of the row of the barcode image + * @param startGuardRange start/end column where the opening start pattern was found + * @param hints optional hints that influence decoding + * @return {@link RXingResult} encapsulating the result of decoding a barcode in the row + * @throws NotFoundException if no potential barcode is found + * @throws ChecksumException if a potential barcode is found but does not pass its checksum + * @throws FormatException if a potential barcode is found but format is invalid + */ + fn decodeRowWithGuardRange( + &self, + rowNumber: u32, + row: &BitArray, + startGuardRange: &[usize; 2], + hints: &crate::DecodingHintDictionary, + ) -> Result + where + Self: Sized, + { + let resultPointCallback = hints.get(&DecodeHintType::NEED_RESULT_POINT_CALLBACK); + let mut symbologyIdentifier = 0; + + if let Some(DecodeHintValue::NeedResultPointCallback(cb)) = resultPointCallback { + cb(&RXingResultPoint::new( + (startGuardRange[0] + startGuardRange[1]) as f32 / 2.0, + rowNumber as f32, + )); + } + + let mut result = String::new(); //decodeRowStringBuffer; + let endStart = self.decodeMiddle(row, startGuardRange, &mut result)?; + + if let Some(DecodeHintValue::NeedResultPointCallback(cb)) = resultPointCallback { + cb(&RXingResultPoint::new(endStart as f32, rowNumber as f32)); + } + + let endRange = Self::decodeEnd(row, endStart)?; + + if let Some(DecodeHintValue::NeedResultPointCallback(cb)) = resultPointCallback { + cb(&RXingResultPoint::new( + (endRange[0] + endRange[1]) as f32 / 2.0, + rowNumber as f32, + )); + } + + // Make sure there is a quiet zone at least as big as the end pattern after the barcode. The + // spec might want more whitespace, but in practice this is the maximum we can count on. + let end = endRange[1]; + let quietEnd = end + (end - endRange[0]); + if quietEnd >= row.getSize() || !row.isRange(end, quietEnd, false)? { + return Err(Exceptions::NotFoundException("".to_owned())); + } + + let resultString = result; + // UPC/EAN should never be less than 8 chars anyway + if resultString.chars().count() < 8 { + return Err(Exceptions::FormatException("".to_owned())); + } + if !self.checkChecksum(&resultString)? { + return Err(Exceptions::ChecksumException("".to_owned())); + } + + let left = (startGuardRange[1] + startGuardRange[0]) as f32 / 2.0; + let right: f32 = (endRange[1] + endRange[0]) as f32 / 2.0; + let format = self.getBarcodeFormat(); + let mut decodeRXingResult = RXingResult::new( + &resultString, + Vec::new(), // no natural byte representation for these barcodes + vec![ + RXingResultPoint::new(left, rowNumber as f32), + RXingResultPoint::new(right, rowNumber as f32), + ], + format, + ); + + let mut extensionLength = 0; + + let mut attempt = || -> Result<(), Exceptions> { + let extensionRXingResult = + UPC_EAN_EXTENSION_SUPPORT.decodeRow(rowNumber, row, endRange[1])?; + + decodeRXingResult.putMetadata( + RXingResultMetadataType::UPC_EAN_EXTENSION, + RXingResultMetadataValue::UpcEanExtension(extensionRXingResult.getText().clone()), + ); + decodeRXingResult.putAllMetadata(extensionRXingResult.getRXingResultMetadata().clone()); + decodeRXingResult + .addRXingResultPoints(&mut extensionRXingResult.getRXingResultPoints().clone()); + extensionLength = extensionRXingResult.getText().chars().count(); + Ok(()) + }; + + let try_result = attempt(); + // if let Err(Exceptions::ReaderException(_)) = try_result { + // } else if try_result.is_err() { + // return Err(try_result.err().unwrap()); + // } + + // try { + // RXingResult extensionRXingResult = extensionReader.decodeRow(rowNumber, row, endRange[1]); + // decodeRXingResult.putMetadata(RXingResultMetadataType.UPC_EAN_EXTENSION, extensionRXingResult.getText()); + // decodeRXingResult.putAllMetadata(extensionRXingResult.getRXingResultMetadata()); + // decodeRXingResult.addRXingResultPoints(extensionRXingResult.getRXingResultPoints()); + // extensionLength = extensionRXingResult.getText().length(); + // } catch (ReaderException re) { + // // continue + // } + + if let Some(DecodeHintValue::AllowedEanExtensions(allowedExtensions)) = + hints.get(&DecodeHintType::ALLOWED_EAN_EXTENSIONS) + { + let mut valid = false; + for length in allowedExtensions { + // for (int length : allowedExtensions) { + if extensionLength == *length as usize { + valid = true; + break; + } + } + if !valid { + return Err(Exceptions::NotFoundException("".to_owned())); + } + } + // let allowedExtensions = + // hints == null ? null : (int[]) hints.get(DecodeHintType.ALLOWED_EAN_EXTENSIONS); + // if (allowedExtensions != null) { + // let valid = false; + // for (int length : allowedExtensions) { + // if (extensionLength == length) { + // valid = true; + // break; + // } + // } + // if (!valid) { + // return Err(Exceptions::NotFoundException("".to_owned())); + // } + // } + + if format == BarcodeFormat::EAN_13 || format == BarcodeFormat::UPC_A { + let countryID = EAN_MANUFACTURER_SUPPORT.lookupCountryIdentifier(&resultString); + if let Some(cid) = countryID { + decodeRXingResult.putMetadata( + RXingResultMetadataType::POSSIBLE_COUNTRY, + RXingResultMetadataValue::PossibleCountry(cid), + ); + } + } + if format == BarcodeFormat::EAN_8 { + symbologyIdentifier = 4; + } + + decodeRXingResult.putMetadata( + RXingResultMetadataType::SYMBOLOGY_IDENTIFIER, + RXingResultMetadataValue::SymbologyIdentifier(format!("]E{}", symbologyIdentifier)), + ); + + Ok(decodeRXingResult) } - decodeRXingResult.putMetadata(RXingResultMetadataType.SYMBOLOGY_IDENTIFIER, "]E" + symbologyIdentifier); - - return decodeRXingResult; - } - - /** - * @param s string of digits to check - * @return {@link #checkStandardUPCEANChecksum(CharSequence)} - * @throws FormatException if the string does not contain only digits - */ - fn checkChecksum(&self, s:&str) -> Result { - Self::checkStandardUPCEANChecksum(s) - } - - /** - * Computes the UPC/EAN checksum on a string of digits, and reports - * whether the checksum is correct or not. - * - * @param s string of digits to check - * @return true iff string of digits passes the UPC/EAN checksum algorithm - * @throws FormatException if the string does not contain only digits - */ - fn checkStandardUPCEANChecksum( s:&str) -> Result { - let length = s.len(); - if length == 0 { - return Ok(false); + /** + * @param s string of digits to check + * @return {@link #checkStandardUPCEANChecksum(CharSequence)} + * @throws FormatException if the string does not contain only digits + */ + fn checkChecksum(&self, s: &str) -> Result { + Self::checkStandardUPCEANChecksum(s) } - let check = Character.digit(s.charAt(length - 1), 10); - return getStandardUPCEANChecksum(s.subSequence(0, length - 1)) == check; - } - fn getStandardUPCEANChecksum( s:&str) -> Result { - let length = s.chars().count(); - let sum = 0; - let mut i = length - 1; - while i >= 0 { - // for (int i = length - 1; i >= 0; i -= 2) { - let digit = s.charAt(i) - '0'; - if (digit < 0 || digit > 9) { - throw FormatException.getFormatInstance(); - } - sum += digit; + /** + * Computes the UPC/EAN checksum on a string of digits, and reports + * whether the checksum is correct or not. + * + * @param s string of digits to check + * @return true iff string of digits passes the UPC/EAN checksum algorithm + * @throws FormatException if the string does not contain only digits + */ + fn checkStandardUPCEANChecksum(s: &str) -> Result { + let length = s.len(); + if length == 0 { + return Ok(false); + } + let char_in_question = s.chars().nth(length - 1).unwrap(); + let check = char_in_question.is_digit(10); + // let check = Character.digit(s.charAt(length - 1), 10); - i -= 2; + let check_against = &s[..length - 1]; //s.subSequence(0, length - 1); + let calculated_checksum = Self::getStandardUPCEANChecksum(check_against)?; + + Ok( calculated_checksum == if check { char_in_question.to_digit(10).unwrap() } else { u32::MAX }) } - sum *= 3; - let mut i = length - 2; - while i >= 0 { - // for (int i = length - 2; i >= 0; i -= 2) { - let digit = s.charAt(i) - '0'; - if (digit < 0 || digit > 9) { - throw FormatException.getFormatInstance(); - } - sum += digit; - i -= 2; + fn getStandardUPCEANChecksum(s: &str) -> Result { + let length = s.chars().count(); + let mut sum = 0; + let mut i = length as isize - 1; + while i >= 0 { + // for (int i = length - 1; i >= 0; i -= 2) { + let digit = (s.chars().nth(i as usize).unwrap() as i32) - ('0' as i32); + if digit < 0 || digit > 9 { + return Err(Exceptions::FormatException("".to_owned())); + } + sum += digit; + + i -= 2; + } + sum *= 3; + let mut i = length as isize - 2; + while i >= 0 { + // for (int i = length - 2; i >= 0; i -= 2) { + let digit = (s.chars().nth(i as usize).unwrap() as i32) - ('0' as i32); + if digit < 0 || digit > 9 { + return Err(Exceptions::FormatException("".to_owned())); + } + sum += digit; + + i -= 2; + } + Ok(((1000 - sum) % 10) as u32) } - return (1000 - sum) % 10; - } - fn decodeEnd(&self, row:&BitArray, endStart:usize) -> Result<[usize;2],Exceptions> { - Self::findGuardPattern(row, endStart, false, START_END_PATTERN) - } + fn decodeEnd(row: &BitArray, endStart: usize) -> Result<[usize; 2], Exceptions> + where + Self: Sized, + { + Self::findGuardPattern(row, endStart, false, &START_END_PATTERN) + } - fn findGuardPattern( row:&BitArray, - rowOffset:usize, - whiteFirst:bool, - pattern:[u32;3]) -> Result<[usize;2],Exceptions> { - Self::findGuardPatternWithCounters(row, rowOffset, whiteFirst, pattern, vec![0u32;pattern.len()]) - } + fn findGuardPattern( + row: &BitArray, + rowOffset: usize, + whiteFirst: bool, + pattern: &[u32], + ) -> Result<[usize; 2], Exceptions> + where + Self: Sized, + { + Self::findGuardPatternWithCounters(row, rowOffset, whiteFirst, pattern, &mut vec![0u32; pattern.len()]) + } - /** - * @param row row of black/white values to search - * @param rowOffset position to start search - * @param whiteFirst if true, indicates that the pattern specifies white/black/white/... - * pixel counts, otherwise, it is interpreted as black/white/black/... - * @param pattern pattern of counts of number of black and white pixels that are being - * searched for as a pattern - * @param counters array of counters, as long as pattern, to re-use - * @return start/end horizontal offset of guard pattern, as an array of two ints - * @throws NotFoundException if pattern is not found - */ - fn findGuardPatternWithCounters( row:&BitArray, - rowOffset:usize, - whiteFirst:bool, - pattern:&[u32;3], - counters:&Vec) -> Result<[usize;2],Exceptions> { - let width = row.getSize(); - rowOffset = if whiteFirst {row.getNextUnset(rowOffset)} else {row.getNextSet(rowOffset)}; - let counterPosition = 0; - let patternStart = rowOffset; - let patternLength = pattern.len(); - let isWhite = whiteFirst; - for x in rowOffset ..width { - // for (int x = rowOffset; x < width; x++) { - if (row.get(x) != isWhite) { - counters[counterPosition]+=1; - } else { - if (counterPosition == patternLength - 1) { - if (Self::patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE) { - return [patternStart, x]; - } - patternStart += counters[0] + counters[1]; - System.arraycopy(counters, 2, counters, 0, counterPosition - 1); - counters[counterPosition - 1] = 0; - counters[counterPosition] = 0; - counterPosition-=1; + /** + * @param row row of black/white values to search + * @param rowOffset position to start search + * @param whiteFirst if true, indicates that the pattern specifies white/black/white/... + * pixel counts, otherwise, it is interpreted as black/white/black/... + * @param pattern pattern of counts of number of black and white pixels that are being + * searched for as a pattern + * @param counters array of counters, as long as pattern, to re-use + * @return start/end horizontal offset of guard pattern, as an array of two ints + * @throws NotFoundException if pattern is not found + */ + fn findGuardPatternWithCounters( + row: &BitArray, + rowOffset: usize, + whiteFirst: bool, + pattern: &[u32], + counters: &mut [u32], + ) -> Result<[usize; 2], Exceptions> + where + Self: Sized, + { + let width = row.getSize(); + let rowOffset = if whiteFirst { + row.getNextUnset(rowOffset) } else { - counterPosition+=1; + row.getNextSet(rowOffset) + }; + let mut counterPosition = 0; + let mut patternStart = rowOffset; + let patternLength = pattern.len(); + let mut isWhite = whiteFirst; + for x in rowOffset..width { + // for (int x = rowOffset; x < width; x++) { + if row.get(x) != isWhite { + counters[counterPosition] += 1; + } else { + if counterPosition == patternLength - 1 { + if Self::patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) + < MAX_AVG_VARIANCE + { + return Ok([patternStart, x]); + } + patternStart += (counters[0] + counters[1]) as usize; + let slc = &counters[2..(counterPosition - 1 + 2)].to_vec(); + counters[..(counterPosition - 1)].copy_from_slice(slc); + // System.arraycopy(counters, 2, counters, 0, counterPosition - 1); + counters[counterPosition - 1] = 0; + counters[counterPosition] = 0; + counterPosition -= 1; + } else { + counterPosition += 1; + } + counters[counterPosition] = 1; + isWhite = !isWhite; + } } - counters[counterPosition] = 1; - isWhite = !isWhite; - } + + Err(Exceptions::NotFoundException("".to_owned())) } - throw NotFoundException.getNotFoundInstance(); - } - /** - * Attempts to decode a single UPC/EAN-encoded digit. - * - * @param row row of black/white values to decode - * @param counters the counts of runs of observed black/white/black/... values - * @param rowOffset horizontal offset to start decoding from - * @param patterns the set of patterns to use to decode -- sometimes different encodings - * for the digits 0-9 are used, and this indicates the encodings for 0 to 9 that should - * be used - * @return horizontal offset of first pixel beyond the decoded digit - * @throws NotFoundException if digit cannot be decoded - */ - fn decodeDigit( row:&BitArray, counters:&Vec, rowOffset:usize, patterns:&Vec>) - -> Result { - Self::recordPattern(row, rowOffset, counters); - let bestVariance = MAX_AVG_VARIANCE; // worst variance we'll accept - let bestMatch = -1_isize; - let max = patterns.len(); - for i in 0..max { - // for (int i = 0; i < max; i++) { - let pattern = patterns[i]; - let variance:f32 = Self::patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE); - if (variance < bestVariance) { - bestVariance = variance; - bestMatch = i; - } + /** + * Attempts to decode a single UPC/EAN-encoded digit. + * + * @param row row of black/white values to decode + * @param counters the counts of runs of observed black/white/black/... values + * @param rowOffset horizontal offset to start decoding from + * @param patterns the set of patterns to use to decode -- sometimes different encodings + * for the digits 0-9 are used, and this indicates the encodings for 0 to 9 that should + * be used + * @return horizontal offset of first pixel beyond the decoded digit + * @throws NotFoundException if digit cannot be decoded + */ + fn decodeDigit( + row: &BitArray, + counters: &mut [u32; 4], + rowOffset: usize, + patterns: &[[u32; 4]], + ) -> Result + where + Self: Sized, + { + Self::recordPattern(row, rowOffset, counters)?; + let mut bestVariance = MAX_AVG_VARIANCE; // worst variance we'll accept + let mut bestMatch = -1_isize; + let max = patterns.len(); + for i in 0..max { + // for (int i = 0; i < max; i++) { + let pattern = &patterns[i]; + let variance: f32 = + Self::patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE); + if variance < bestVariance { + bestVariance = variance; + bestMatch = i as isize; + } + } + if bestMatch >= 0 { + Ok(bestMatch as usize) + } else { + Err(Exceptions::NotFoundException("".to_owned())) + } } - if (bestMatch >= 0) { - return bestMatch; - } else { - throw NotFoundException.getNotFoundInstance(); - } - } - /** - * Get the format of this decoder. - * - * @return The 1D format. - */ - fn getBarcodeFormat() -> BarcodeFormat; - - /** - * Subclasses override this to decode the portion of a barcode between the start - * and end guard patterns. - * - * @param row row of black/white values to search - * @param startRange start/end offset of start guard pattern - * @param resultString {@link StringBuilder} to append decoded chars to - * @return horizontal offset of first pixel after the "middle" that was decoded - * @throws NotFoundException if decoding could not complete successfully - */ - fn decodeMiddle( row:&BitArray, - startRange:&[u32;2], - resultString:&mut String) -> Result; + /** + * Get the format of this decoder. + * + * @return The 1D format. + */ + fn getBarcodeFormat(&self) -> BarcodeFormat; + /** + * Subclasses override this to decode the portion of a barcode between the start + * and end guard patterns. + * + * @param row row of black/white values to search + * @param startRange start/end offset of start guard pattern + * @param resultString {@link StringBuilder} to append decoded chars to + * @return horizontal offset of first pixel after the "middle" that was decoded + * @throws NotFoundException if decoding could not complete successfully + */ + fn decodeMiddle( + &self, + row: &BitArray, + startRange: &[usize; 2], + resultString: &mut String, + ) -> Result; +} + +pub struct StandIn; +impl UPCEANReader for StandIn { + fn getBarcodeFormat(&self) -> BarcodeFormat { + todo!() + } + + fn decodeMiddle( + &self, + _row: &BitArray, + _startRange: &[usize; 2], + _resultString: &mut String, + ) -> Result { + todo!() + } +} +impl OneDReader for StandIn { + fn decodeRow( + &mut self, + _rowNumber: u32, + _row: &BitArray, + _hints: &crate::DecodingHintDictionary, + ) -> Result { + todo!() + } +} + +impl Reader for StandIn { + fn decode(&mut self, _image: &crate::BinaryBitmap) -> Result { + todo!() + } + + fn decode_with_hints( + &mut self, + _image: &crate::BinaryBitmap, + _hints: &crate::DecodingHintDictionary, + ) -> Result { + todo!() + } } diff --git a/tests/EAN8BlackBox1TestCase.java b/tests/EAN8BlackBox1TestCase.rs similarity index 55% rename from tests/EAN8BlackBox1TestCase.java rename to tests/EAN8BlackBox1TestCase.rs index f9fc6b9..66d6720 100644 --- a/tests/EAN8BlackBox1TestCase.java +++ b/tests/EAN8BlackBox1TestCase.rs @@ -14,21 +14,28 @@ * limitations under the License. */ -package com.google.zxing.oned; +use rxing::{ + BarcodeFormat, MultiFormatReader, oned::EAN8Reader, +}; -import com.google.zxing.BarcodeFormat; -import com.google.zxing.MultiFormatReader; -import com.google.zxing.common.AbstractBlackBoxTestCase; +mod common; /** * @author Sean Owen */ -public final class EAN8BlackBox1TestCase extends AbstractBlackBoxTestCase { +#[test] + fn ean8_black_box1_test_case() { + let mut tester = common::AbstractBlackBoxTestCase::new( + "test_resources/blackbox/ean8-1", + // MultiFormatReader::default(), + EAN8Reader{}, + BarcodeFormat::EAN_8, + ); - public EAN8BlackBox1TestCase() { - super("src/test/resources/blackbox/ean8-1", new MultiFormatReader(), BarcodeFormat.EAN_8); - addTest(8, 8, 0.0f); - addTest(8, 8, 180.0f); + // super("src/test/resources/blackbox/ean8-1", new MultiFormatReader(), BarcodeFormat.EAN_8); + tester.add_test(8, 8, 0.0); + tester.add_test(8, 8, 180.0); + + tester.test_black_box() } -} \ No newline at end of file