use crate::{
BarcodeFormat, DecodeHints, Exceptions, PointI, RXingResult,
common::{
BitArray,
cpp_essentials::{FindLeftGuardBy, FixedPattern, IsRightGuard, PatternView, ToIntPos},
},
point,
};
use super::row_reader::{DecodingState, RowReader};
use crate::common::Result;
const CLOCK_LENGTH_FN: usize = 31;
const CLOCK_LENGTH_NO_FN: usize = 23;
const DATA_LENGTH_FN: u32 = 23;
const DATA_LENGTH_NO_FN: u32 = 15;
const CLOCK_PATTERN_FN: FixedPattern<25, CLOCK_LENGTH_FN> = FixedPattern::new([
5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3,
]);
const CLOCK_PATTERN_NO_FN: FixedPattern<17, CLOCK_LENGTH_NO_FN> =
FixedPattern::new([5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3]);
const DATA_START_PATTERN: FixedPattern<5, 5> = FixedPattern::new([1, 1, 1, 1, 1]);
const DATA_STOP_PATTERN: FixedPattern<3, 3> = FixedPattern::new([1, 1, 1]);
pub struct DXFilmEdgeReader<'a> {
options: &'a DecodeHints,
}
impl<'a> DXFilmEdgeReader<'_> {
pub fn new(hints: &'a DecodeHints) -> DXFilmEdgeReader<'a> {
DXFilmEdgeReader { options: hints }
}
}
fn IsPattern<const N: usize, const SUM: usize>(
view: &mut PatternView,
pattern: &FixedPattern<N, SUM>,
minQuietZone: f32,
) -> bool {
const E2E: bool = false;
*view = view.subView(0, Some(N));
view.isValid()
&& crate::common::cpp_essentials::pattern::IsPattern::<E2E, N, SUM, false>(
view,
pattern,
Some(if view.isAtFirstBar() {
u32::MAX as f32
} else {
view[-1] as f32
}),
minQuietZone,
0.0,
) != 0.0
}
fn DistIsBelowThreshold(a: PointI, b: PointI, threshold: PointI) -> bool {
(a.x - b.x).abs() <= threshold.x && (a.y - b.y).abs() <= threshold.y
}
#[derive(Debug, Clone, Default)]
pub(super) struct Clock {
hasFrameNr: bool, rowNumber: u32, xStart: u32, xStop: u32, }
impl Clock {
pub const fn dataLength(&self) -> u32 {
if self.hasFrameNr {
DATA_LENGTH_FN
} else {
DATA_LENGTH_NO_FN
}
}
pub fn moduleSize(&self) -> f32 {
(self.xStop as f32 + 1.0 - self.xStart as f32)
/ (if self.hasFrameNr {
CLOCK_LENGTH_FN
} else {
CLOCK_LENGTH_NO_FN
}) as f32
}
pub fn isCloseTo(&self, p: PointI, x: u32) -> bool {
DistIsBelowThreshold(
p,
point(x as i32, self.rowNumber as i32),
(self.moduleSize() * point(0.5, 4.0)).into(),
)
}
pub fn isCloseToStart(&self, x: u32, y: u32) -> bool {
self.isCloseTo(point(x as i32, y as i32), self.xStart)
}
pub fn isCloseToStop(&self, x: u32, y: u32) -> bool {
self.isCloseTo(point(x as i32, y as i32), self.xStop)
}
}
impl DecodingState {
pub fn findClock(&mut self, x: u32, y: u32) -> Option<&mut Clock> {
let start = point(x, y);
if let Some(i) = self
.clocks
.iter()
.position(|c| c.rowNumber != start.y && c.isCloseToStart(start.x, start.y))
{
self.clocks.get_mut(i) } else {
None
}
}
pub fn addClock(&mut self, clock: Clock) {
if let Some(clockf) = self.findClock(clock.xStart, clock.rowNumber) {
*clockf = clock
} else {
self.clocks.push(clock)
}
}
}
fn CheckForClock(rowNumber: u32, view: &mut PatternView) -> Option<Clock> {
let mut clock = Clock::default();
if IsPattern(view, &CLOCK_PATTERN_FN, 0.5)
{
clock.hasFrameNr = true;
} else if IsPattern(view, &CLOCK_PATTERN_NO_FN, 2.0) {
clock.hasFrameNr = false;
} else {
return None;
}
clock.rowNumber = rowNumber;
clock.xStart = view.pixelsInFront() as u32;
clock.xStop = view.pixelsTillEnd() as u32;
Some(clock)
}
impl RowReader for DXFilmEdgeReader<'_> {
fn decodePattern(
&self,
rowNumber: u32,
next: &mut PatternView,
state: &mut Option<DecodingState>,
) -> Result<RXingResult> {
if state.is_none() {
*state = Some(DecodingState::default())
};
let dxState = state.as_mut().unwrap();
if self.options.TryHarder != Some(true) && rowNumber < dxState.centerRow {
return Err(Exceptions::NOT_FOUND);
}
let Is4x1 = |view: &PatternView, spaceInPixel: Option<f32>| {
let spaceInPixel = spaceInPixel.unwrap_or_default();
let tmp_arr: [u16; 4] = [view[1], view[2], view[3], view[4]];
let m = *tmp_arr.iter().min().unwrap_or(&0);
let M = *tmp_arr.iter().max().unwrap_or(&0);
M <= m * 4 / 3 + 1 && spaceInPixel > m as f32 / 2.0
};
*next = FindLeftGuardBy::<4, _>(*next, 10, Is4x1)?;
if !next.isValid() {
return Err(Exceptions::NOT_FOUND);
}
if let Some(clock) = CheckForClock(rowNumber, next) {
dxState.addClock(clock);
next.skipSymbol();
return Err(Exceptions::NOT_FOUND);
}
if dxState.clocks.is_empty() {
return Err(Exceptions::NOT_FOUND);
}
let minDataQuietZone: f32 = 0.5;
if !IsPattern(next, &DATA_START_PATTERN, minDataQuietZone) {
return Err(Exceptions::NOT_FOUND);
}
let xStart = next.pixelsInFront();
let Some(clock) = dxState.findClock(xStart as u32, rowNumber) else {
return Err(Exceptions::NOT_FOUND);
};
next.skipSymbol();
let mut dataBits = BitArray::default();
while next.isValidWithN(1) && dataBits.get_size() < clock.dataLength() as usize {
let modules = (next[0] as f32 / clock.moduleSize() + 0.5) as i32;
if (1..=20).contains(&modules) {
dataBits.appendBits(
if next.index() % 2 == 0 {
0xFFFFFFFF_usize
} else {
0x0
},
modules as usize,
)?;
} else {
return Err(Exceptions::NOT_FOUND);
}
next.shift(1);
}
if dataBits.get_size() != clock.dataLength() as usize {
return Err(Exceptions::NOT_FOUND);
}
*next = next.subView(0, Some(DATA_STOP_PATTERN.size()));
if !next.isValid() || !IsRightGuard(next, &DATA_STOP_PATTERN, minDataQuietZone, 0.0) {
return Err(Exceptions::NOT_FOUND);
}
if dataBits.get(0) || dataBits.get(8) || (if clock.hasFrameNr {
dataBits.get(20) || dataBits.get(22)
} else {
dataBits.get(14) })
{
return Err(Exceptions::NOT_FOUND);
}
let db_hld = Into::<Vec<bool>>::into(&dataBits); let signalSum = db_hld
.iter()
.rev()
.skip(2)
.fold(0, |acc, e| acc + u8::from(*e)); let parityBit = u8::from(db_hld[db_hld.len() - 2]);
if signalSum % 2 != parityBit {
return Err(Exceptions::NOT_FOUND);
}
let dataBitsU8: Vec<u8> = Into::<Vec<bool>>::into(&dataBits)
.iter()
.map(|b| u8::from(*b))
.collect();
let Some(productNumber) = ToIntPos(&dataBitsU8, 1, 7) else {
return Err(Exceptions::NOT_FOUND);
};
let Some(generationNumber) = ToIntPos(&dataBitsU8, 9, 4) else {
return Err(Exceptions::NOT_FOUND);
};
let mut txt; txt = (productNumber.to_string()) + "-" + (&generationNumber.to_string());
if clock.hasFrameNr {
let frameNr = ToIntPos(&dataBitsU8, 13, 6).unwrap_or(0);
txt += &("/".to_owned() + &(frameNr.to_string()));
if dataBits.get(19) {
txt += "A";
}
}
let xStop = next.pixelsTillEnd();
if !clock.isCloseToStop(xStop as u32, rowNumber) {
return Err(Exceptions::NOT_FOUND);
}
clock.xStart = xStart as u32;
clock.xStop = xStop as u32;
Ok(RXingResult::new(
&txt,
dataBits.into(),
vec![
point(xStart as f32, rowNumber as f32),
point(xStop as f32, rowNumber as f32),
point(xStart as f32, rowNumber as f32),
point(xStop as f32, rowNumber as f32),
],
BarcodeFormat::DXFilmEdge,
))
}
}