use rxing_one_d_proc_derive::OneDReader;
use crate::{RXingResultMetadataType, RXingResultMetadataValue};
use crate::{
common::{BitArray, Result},
point, BarcodeFormat, Exceptions, RXingResult,
};
use super::{one_d_reader, OneDReader};
const MAX_AVG_VARIANCE: f32 = 0.38;
const MAX_INDIVIDUAL_VARIANCE: f32 = 0.5;
const W: u32 = 3; const W_LOWER: u32 = 2; const N: u32 = 1;
const DEFAULT_ALLOWED_LENGTHS: [u32; 5] = [6, 8, 10, 12, 14];
const START_PATTERN: [u32; 4] = [N, N, N, N];
const END_PATTERN_REVERSED: [[u32; 3]; 2] = [
[N, N, W_LOWER], [N, N, W], ];
const PATTERNS: [[u32; 5]; 20] = [
[N, N, W_LOWER, W_LOWER, N], [W_LOWER, N, N, N, W_LOWER], [N, W_LOWER, N, N, W_LOWER], [W_LOWER, W_LOWER, N, N, N], [N, N, W_LOWER, N, W_LOWER], [W_LOWER, N, W_LOWER, N, N], [N, W_LOWER, W_LOWER, N, N], [N, N, N, W_LOWER, W_LOWER], [W_LOWER, N, N, W_LOWER, N], [N, W_LOWER, N, W_LOWER, N], [N, N, W, W, N], [W, N, N, N, W], [N, W, N, N, W], [W, W, N, N, N], [N, N, W, N, W], [W, N, W, N, N], [N, W, W, N, N], [N, N, N, W, W], [W, N, N, W, N], [N, W, N, W, N], ];
#[derive(OneDReader)]
pub struct ITFReader {
narrowLineWidth: i32,
}
impl Default for ITFReader {
fn default() -> Self {
Self {
narrowLineWidth: -1,
}
}
}
impl OneDReader for ITFReader {
fn decode_row(
&mut self,
rowNumber: u32,
row: &crate::common::BitArray,
hints: &crate::DecodeHints,
) -> Result<crate::RXingResult> {
let mut row = row.clone();
let startRange = self.decodeStart(&row)?;
let endRange = self.decodeEnd(&mut row)?;
let mut result = String::with_capacity(20); self.decodeMiddle(&row, startRange[1], endRange[0], &mut result)?;
let resultString = result;
let allowedLengths = if let Some(al) = &hints.AllowedLengths {
al.clone()
} else {
DEFAULT_ALLOWED_LENGTHS.to_vec()
};
let length = resultString.chars().count();
let mut lengthOK = false;
let mut maxAllowedLength = 0;
for allowedLength in allowedLengths {
if length == allowedLength as usize {
lengthOK = true;
break;
}
maxAllowedLength = std::cmp::max(allowedLength, maxAllowedLength);
}
if !lengthOK && length > maxAllowedLength as usize {
lengthOK = true;
}
if !lengthOK {
return Err(Exceptions::FORMAT);
}
let mut resultObject = RXingResult::new(
&resultString,
Vec::new(), vec![
point(startRange[1] as f32, rowNumber as f32),
point(endRange[0] as f32, rowNumber as f32),
],
BarcodeFormat::ITF,
);
resultObject.putMetadata(
RXingResultMetadataType::SYMBOLOGY_IDENTIFIER,
RXingResultMetadataValue::SymbologyIdentifier("]I0".to_owned()),
);
Ok(resultObject)
}
}
impl ITFReader {
fn decodeMiddle(
&self,
row: &BitArray,
payloadStart: usize,
payloadEnd: usize,
resultString: &mut String,
) -> Result<()> {
let mut payloadStart = payloadStart;
let mut counterDigitPair = [0_u32; 10]; let mut counterBlack = [0_u32; 5]; let mut counterWhite = [0_u32; 5];
while payloadStart < payloadEnd {
one_d_reader::record_pattern(row, payloadStart, &mut counterDigitPair)?;
for k in 0..5 {
let twoK = 2 * k;
counterBlack[k] = counterDigitPair[twoK];
counterWhite[k] = counterDigitPair[twoK + 1];
}
let mut bestMatch = self.decodeDigit(&counterBlack)?;
resultString.push(char::from_u32('0' as u32 + bestMatch).ok_or(Exceptions::PARSE)?);
bestMatch = self.decodeDigit(&counterWhite)?;
resultString.push(char::from_u32('0' as u32 + bestMatch).ok_or(Exceptions::PARSE)?);
payloadStart += counterDigitPair.iter().sum::<u32>() as usize;
}
Ok(())
}
fn decodeStart(&mut self, row: &BitArray) -> Result<[usize; 2]> {
let endStart = Self::skipWhiteSpace(row)?;
let startPattern = self.findGuardPattern(row, endStart, &START_PATTERN)?;
self.narrowLineWidth = (startPattern[1] - startPattern[0]) as i32 / 4;
self.validateQuietZone(row, startPattern[0])?;
Ok(startPattern)
}
fn validateQuietZone(&self, row: &BitArray, startPattern: usize) -> Result<()> {
let mut quietCount = self.narrowLineWidth * 10;
quietCount = quietCount.min(startPattern as i32);
let mut i = startPattern as isize - 1;
while quietCount > 0 && i >= 0 {
if row.get(i as usize) {
break;
}
quietCount -= 1;
i -= 1;
}
if quietCount != 0 {
Err(Exceptions::NOT_FOUND)
} else {
Ok(())
}
}
fn skipWhiteSpace(row: &BitArray) -> Result<usize> {
let width = row.get_size();
let endStart = row.getNextSet(0);
if endStart == width {
return Err(Exceptions::NOT_FOUND);
}
Ok(endStart)
}
fn decodeEnd(&self, row: &mut BitArray) -> Result<[usize; 2]> {
row.reverse();
let interim_function = || -> Result<[usize; 2]> {
let endStart = Self::skipWhiteSpace(row)?;
let mut endPattern =
if let Ok(ptrn) = self.findGuardPattern(row, endStart, &END_PATTERN_REVERSED[0]) {
ptrn
} else {
self.findGuardPattern(row, endStart, &END_PATTERN_REVERSED[1])?
};
self.validateQuietZone(row, endPattern[0])?;
let temp = endPattern[0];
endPattern[0] = row.get_size() - endPattern[1];
endPattern[1] = row.get_size() - temp;
Ok(endPattern)
};
let res = interim_function();
row.reverse();
res
}
fn findGuardPattern(
&self,
row: &BitArray,
rowOffset: usize,
pattern: &[u32],
) -> Result<[usize; 2]> {
let patternLength = pattern.len();
let mut counters = vec![0u32; patternLength]; let width = row.get_size();
let mut isWhite = false;
let mut counterPosition = 0;
let mut patternStart = rowOffset;
for x in rowOffset..width {
if row.get(x) != isWhite {
counters[counterPosition] += 1;
} else {
if counterPosition == patternLength - 1 {
if one_d_reader::pattern_match_variance(
&counters,
pattern,
MAX_INDIVIDUAL_VARIANCE,
) < MAX_AVG_VARIANCE
{
return Ok([patternStart, x]);
}
patternStart += (counters[0] + counters[1]) as usize;
counters.copy_within(2..(counterPosition - 1 + 2), 0);
counters[counterPosition - 1] = 0;
counters[counterPosition] = 0;
counterPosition -= 1;
} else {
counterPosition += 1;
}
counters[counterPosition] = 1;
isWhite = !isWhite;
}
}
Err(Exceptions::NOT_FOUND)
}
fn decodeDigit(&self, counters: &[u32]) -> Result<u32> {
let mut bestVariance = MAX_AVG_VARIANCE; let mut bestMatch = -1_isize;
for (i, pattern) in PATTERNS.iter().enumerate() {
let variance =
one_d_reader::pattern_match_variance(counters, pattern, MAX_INDIVIDUAL_VARIANCE);
if variance < bestVariance {
bestVariance = variance;
bestMatch = i as isize;
} else if variance == bestVariance {
bestMatch = -1;
continue;
}
}
if bestMatch >= 0 {
Ok(bestMatch as u32 % 10)
} else {
Err(Exceptions::NOT_FOUND)
}
}
}