Files
rxing/src/oned/code_39_reader.rs

420 lines
19 KiB
Rust
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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 one_d_reader_derive::OneDReader;
use crate::common::BitArray;
use crate::{BarcodeFormat, Exceptions, RXingResult};
use super::OneDReader;
/**
* <p>Decodes Code 39 barcodes. Supports "Full ASCII Code 39" if USE_CODE_39_EXTENDED_MODE is set.</p>
*
* @author Sean Owen
* @see Code93Reader
*/
#[derive(OneDReader)]
pub struct Code39Reader {
usingCheckDigit: bool,
extendedMode: bool,
decodeRowRXingResult: String,
counters: Vec<u32>,
}
impl OneDReader for Code39Reader {
fn decodeRow(
&mut self,
rowNumber: u32,
row: &crate::common::BitArray,
_hints: &DecodingHintDictionary,
) -> Result<crate::RXingResult, Exceptions> {
// let theCounters = self.counters;
self.counters.fill(0);
// let result = self.decodeRowRXingResult;
// result.setLength(0);
self.decodeRowRXingResult.clear();
let start = Self::findAsteriskPattern(row, &mut self.counters)?;
// Read off white space
let mut nextStart = row.getNextSet(start[1] as usize);
let end = row.getSize();
let mut decodedChar;
let mut lastStart;
loop {
Self::recordPattern(row, nextStart, &mut self.counters)?;
let pattern = Self::toNarrowWidePattern(&self.counters);
if pattern < 0 {
return Err(Exceptions::NotFoundException("".to_owned()));
}
decodedChar = Self::patternToChar(pattern as u32)?;
self.decodeRowRXingResult.push(decodedChar);
lastStart = nextStart;
for counter in &self.counters {
// for (int counter : theCounters) {
nextStart += *counter as usize;
}
// Read off white space
nextStart = row.getNextSet(nextStart);
if !(decodedChar != '*') {
break;
}
} //(decodedChar != '*');
self.decodeRowRXingResult
.truncate(self.decodeRowRXingResult.len() - 1); // remove asterisk
// Look for whitespace after pattern:
let mut lastPatternSize = 0;
for counter in &self.counters {
// for (int counter : self.counters) {
lastPatternSize += *counter;
}
let whiteSpaceAfterEnd = nextStart - lastStart - lastPatternSize as usize;
// If 50% of last pattern size, following last pattern, is not whitespace, fail
// (but if it's whitespace to the very end of the image, that's OK)
if nextStart != end && (whiteSpaceAfterEnd * 2) < lastPatternSize as usize {
return Err(Exceptions::NotFoundException("".to_owned()));
}
if self.usingCheckDigit {
let max = self.decodeRowRXingResult.chars().count() - 1;
let mut total = 0;
for i in 0..max {
// for (int i = 0; i < max; i++) {
if let Some(pos) =
Self::ALPHABET_STRING.find(self.decodeRowRXingResult.chars().nth(i).unwrap())
{
total += pos;
}
// total += Self::ALPHABET_STRING.indexOf(self.decodeRowRXingResult.chars().nth(i).unwrap());
}
if self.decodeRowRXingResult.chars().nth(max).unwrap()
!= Self::ALPHABET_STRING.chars().nth(total % 43).unwrap()
{
return Err(Exceptions::NotFoundException("".to_owned()));
}
self.decodeRowRXingResult.truncate(max);
}
if self.decodeRowRXingResult.chars().count() == 0 {
// false positive
return Err(Exceptions::NotFoundException("".to_owned()));
}
let resultString;
if self.extendedMode {
resultString = Self::decodeExtended(&self.decodeRowRXingResult)?;
} else {
resultString = self.decodeRowRXingResult.clone();
}
let left = (start[1] + start[0]) as f32 / 2.0;
let right = (lastStart + lastPatternSize as usize) as f32 / 2.0;
let mut resultObject = RXingResult::new(
&resultString,
Vec::new(),
vec![
RXingResultPoint::new(left, rowNumber as f32),
RXingResultPoint::new(right, rowNumber as f32),
],
BarcodeFormat::CODE_39,
);
resultObject.putMetadata(
RXingResultMetadataType::SYMBOLOGY_IDENTIFIER,
RXingResultMetadataValue::SymbologyIdentifier("]A0".to_owned()),
);
Ok(resultObject)
}
}
impl Code39Reader {
const ALPHABET_STRING: &str = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%";
/**
* These represent the encodings of characters, as patterns of wide and narrow bars.
* The 9 least-significant bits of each int correspond to the pattern of wide and narrow,
* with 1s representing "wide" and 0s representing narrow.
*/
const CHARACTER_ENCODINGS: [u32; 43] = [
0x034, 0x121, 0x061, 0x160, 0x031, 0x130, 0x070, 0x025, 0x124, 0x064, // 0-9
0x109, 0x049, 0x148, 0x019, 0x118, 0x058, 0x00D, 0x10C, 0x04C, 0x01C, // A-J
0x103, 0x043, 0x142, 0x013, 0x112, 0x052, 0x007, 0x106, 0x046, 0x016, // K-T
0x181, 0x0C1, 0x1C0, 0x091, 0x190, 0x0D0, 0x085, 0x184, 0x0C4, 0x0A8, // U-$
0x0A2, 0x08A, 0x02A, // /-%
];
const ASTERISK_ENCODING: u32 = 0x094;
/**
* Creates a reader that assumes all encoded data is data, and does not treat the final
* character as a check digit. It will not decoded "extended Code 39" sequences.
*/
pub fn new() -> Self {
Self::with_use_check_digit(false)
}
/**
* Creates a reader that can be configured to check the last character as a check digit.
* It will not decoded "extended Code 39" sequences.
*
* @param usingCheckDigit if true, treat the last data character as a check digit, not
* data, and verify that the checksum passes.
*/
pub fn with_use_check_digit(usingCheckDigit: bool) -> Self {
Self::with_all_config(usingCheckDigit, false)
}
/**
* Creates a reader that can be configured to check the last character as a check digit,
* or optionally attempt to decode "extended Code 39" sequences that are used to encode
* the full ASCII character set.
*
* @param usingCheckDigit if true, treat the last data character as a check digit, not
* data, and verify that the checksum passes.
* @param extendedMode if true, will attempt to decode extended Code 39 sequences in the
* text.
*/
pub fn with_all_config(usingCheckDigit: bool, extendedMode: bool) -> Self {
Self {
usingCheckDigit,
extendedMode,
decodeRowRXingResult: String::with_capacity(20),
counters: vec![0; 9],
}
}
fn findAsteriskPattern(row: &BitArray, counters: &mut [u32]) -> Result<Vec<u32>, Exceptions> {
let width = row.getSize();
let rowOffset = row.getNextSet(0);
let mut counterPosition = 0;
let mut patternStart = rowOffset;
let mut isWhite = false;
let patternLength = counters.len();
for i in rowOffset..width {
// for (int i = rowOffset; i < width; i++) {
if row.get(i) != isWhite {
counters[counterPosition] += 1;
} else {
if counterPosition == patternLength - 1 {
// Look for whitespace before start pattern, >= 50% of width of start pattern
if Self::toNarrowWidePattern(counters) == (Self::ASTERISK_ENCODING as i32)
&& row.isRange(
0.max(
patternStart as isize - ((i as isize - patternStart as isize) / 2),
) as usize,
patternStart,
false,
)?
{
return Ok(vec![patternStart as u32, i as u32]);
// return new int[]{patternStart, i};
}
patternStart += (counters[0] + counters[1]) as usize;
let slc = counters[0..(counterPosition - 1)].to_vec();
counters[2..(counterPosition - 1 + 2)].clone_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;
}
}
return Err(Exceptions::NotFoundException("".to_owned()));
}
// For efficiency, returns -1 on failure. Not throwing here saved as many as 700 exceptions
// per image when using some of our blackbox images.
fn toNarrowWidePattern(counters: &[u32]) -> i32 {
let numCounters = counters.len();
let mut maxNarrowCounter = 0;
let mut wideCounters;
loop {
let mut minCounter = u32::MAX; //Integer.MAX_VALUE;
for counter in counters {
// for (int counter : counters) {
if counter < &minCounter && counter > &maxNarrowCounter {
minCounter = *counter;
}
}
maxNarrowCounter = minCounter;
wideCounters = 0;
let mut totalWideCountersWidth = 0;
let mut pattern = 0;
for i in 0..numCounters {
// for (int i = 0; i < numCounters; i++) {
let counter = counters[i];
if counter > maxNarrowCounter {
pattern |= 1 << (numCounters - 1 - i);
wideCounters += 1;
totalWideCountersWidth += counter;
}
}
if wideCounters == 3 {
// Found 3 wide counters, but are they close enough in width?
// We can perform a cheap, conservative check to see if any individual
// counter is more than 1.5 times the average:
let mut i = 0;
while i < numCounters && wideCounters > 0 {
// for (int i = 0; i < numCounters && wideCounters > 0; i++) {
let counter = counters[i];
if counter > maxNarrowCounter {
wideCounters -= 1;
// totalWideCountersWidth = 3 * average, so this checks if counter >= 3/2 * average
if (counter * 2) >= totalWideCountersWidth {
return -1;
}
}
i += 1;
}
return pattern;
}
if !(wideCounters > 3) {
break;
}
} //while ;
return -1;
}
fn patternToChar(pattern: u32) -> Result<char, Exceptions> {
for i in 0..Self::CHARACTER_ENCODINGS.len() {
// for (int i = 0; i < CHARACTER_ENCODINGS.length; i++) {
if Self::CHARACTER_ENCODINGS[i] == pattern {
return Ok(Self::ALPHABET_STRING.chars().nth(i).unwrap());
}
}
if pattern == Self::ASTERISK_ENCODING {
return Ok('*');
}
return Err(Exceptions::NotFoundException("".to_owned()));
}
fn decodeExtended(encoded: &str) -> Result<String, Exceptions> {
let length = encoded.chars().count();
let mut decoded = String::with_capacity(length); //new StringBuilder(length);
let mut i = 0;
while i < length {
// for i in 0..length {
// for (int i = 0; i < length; i++) {
let c = encoded.chars().nth(i).unwrap();
if c == '+' || c == '$' || c == '%' || c == '/' {
let next = encoded.chars().nth(i + 1).unwrap();
let mut decodedChar = '\0';
match c {
'+' => {
// +A to +Z map to a to z
if next >= 'A' && next <= 'Z' {
decodedChar = char::from_u32(next as u32 + 32).unwrap();
} else {
return Err(Exceptions::NotFoundException("".to_owned()));
}
}
'$' => {
// $A to $Z map to control codes SH to SB
if next >= 'A' && next <= 'Z' {
decodedChar = char::from_u32(next as u32 - 64).unwrap();
} else {
return Err(Exceptions::NotFoundException("".to_owned()));
}
}
'%' => {
// %A to %E map to control codes ESC to US
if next >= 'A' && next <= 'E' {
decodedChar = char::from_u32(next as u32 - 38).unwrap();
} else if next >= 'F' && next <= 'J' {
decodedChar = char::from_u32(next as u32 - 11).unwrap();
} else if next >= 'K' && next <= 'O' {
decodedChar = char::from_u32(next as u32 + 16).unwrap();
} else if next >= 'P' && next <= 'T' {
decodedChar = char::from_u32(next as u32 + 43).unwrap();
} else if next == 'U' {
decodedChar = 0 as char;
} else if next == 'V' {
decodedChar = '@';
} else if next == 'W' {
decodedChar = '`';
} else if next == 'X' || next == 'Y' || next == 'Z' {
decodedChar = 127 as char;
} else {
return Err(Exceptions::NotFoundException("".to_owned()));
}
}
'/' => {
// /A to /O map to ! to , and /Z maps to :
if next >= 'A' && next <= 'O' {
decodedChar = char::from_u32(next as u32 - 32).unwrap();
} else if next == 'Z' {
decodedChar = ':';
} else {
return Err(Exceptions::NotFoundException("".to_owned()));
}
}
_ => {}
}
decoded.push(decodedChar);
// bump up i again since we read two characters
i += 1;
} else {
decoded.push(c);
}
i += 1;
}
Ok(decoded)
}
}
#[cfg(test)]
mod code_39_extended_mode_test_case {
use std::collections::HashMap;
use crate::{
common::{BitArray, BitMatrix},
oned::{Code39Reader, OneDReader},
};
#[test]
fn testDecodeExtendedMode() {
// \b -> 2408 \f -> 000c
doTest("\u{0000}\u{0001}\u{0002}\u{0003}\u{0004}\u{0005}\u{0006}\u{0007}\u{0008}\t\n\u{000b}\u{000C}\r\u{000e}\u{000f}\u{0010}\u{0011}\u{0012}\u{0013}\u{0014}\u{0015}\u{0016}\u{0017}\u{0018}\u{0019}\u{001a}\u{001b}\u{001c}\u{001d}\u{001e}\u{001f}",
"000001001011011010101001001001011001010101101001001001010110101001011010010010010101011010010110100100100101011011010010101001001001010101011001011010010010010101101011001010100100100101010110110010101001001001010101010011011010010010010101101010011010100100100101010110100110101001001001010101011001101010010010010101101010100110100100100101010110101001101001001001010110110101001010010010010101010110100110100100100101011010110100101001001001010101101101001010010010010101010101100110100100100101011010101100101001001001010101101011001010010010010101010110110010100100100101011001010101101001001001010100110101011010010010010101100110101010100100100101010010110101101001001001010110010110101010010010010101001101101010101001001001011010100101101010010010010101101001011010100100100101101101001010101001001001010101100101101010010010010110101100101010010110110100000");
doTest(" !\"#$%&'()*+,-./0123456789:;<=>?",
"00000100101101101010011010110101001001010010110101001011010010010100101011010010110100100101001011011010010101001001010010101011001011010010010100101101011001010100100101001010110110010101001001010010101010011011010010010100101101010011010100100101001010110100110101001001010010101011001101010010010100101101010100110100100101001010110101001101001010110110110010101101010010010100101101011010010101001101101011010010101101011001010110110110010101010100110101101101001101010101100110101010100101101101101001011010101100101101010010010100101001101101010101001001001010110110010101010010010010101010011011010100100100101101010011010101001001001010110100110101010010010010101011001101010010110110100000");
doTest("@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_",
"000010010110110101010010010010100110101011011010100101101011010010110110110100101010101100101101101011001010101101100101010101001101101101010011010101101001101010101100110101101010100110101101010011011011010100101010110100110110101101001010110110100101010101100110110101011001010110101100101010110110010110010101011010011010101101100110101010100101101011011001011010101001101101010101001001001011010101001101010010010010101101010011010100100100101101101010010101001001001010101101001101010010010010110101101001010010110110100000");
doTest("`abcdefghijklmnopqrstuvwxyz{|}~",
"000001001011011010101001001001011001101010101001010010010110101001011010010100100101011010010110100101001001011011010010101001010010010101011001011010010100100101101011001010100101001001010110110010101001010010010101010011011010010100100101101010011010100101001001010110100110101001010010010101011001101010010100100101101010100110100101001001010110101001101001010010010110110101001010010100100101010110100110100101001001011010110100101001010010010101101101001010010100100101010101100110100101001001011010101100101001010010010101101011001010010100100101010110110010100101001001011001010101101001010010010100110101011010010100100101100110101010100101001001010010110101101001010010010110010110101010010100100101001101101010101001001001010110110100101010010010010101010110011010100100100101101010110010101001001001010110101100101010010010010101011011001010010110110100000");
}
fn doTest(expectedRXingResult: &str, encodedRXingResult: &str) {
let mut sut = Code39Reader::with_all_config(false, true);
let matrix =
BitMatrix::parse_strings(encodedRXingResult, "1", "0").expect("bitmatrix parse");
let row = BitArray::with_size(matrix.getWidth() as usize);
let row = matrix.getRow(0, row);
let result = sut.decodeRow(0, &row, &HashMap::new()).expect("decode row");
assert_eq!(expectedRXingResult, result.getText());
}
}