use crate::SymbolType;
use std::collections::{HashMap, HashSet};
pub(crate) mod internal;
pub mod symbologies;
pub use symbologies::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Enable(pub bool);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AddChecksum(pub bool);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EmitChecksum(pub bool);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Binary(pub bool);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MinLength(pub u32);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MaxLength(pub u32);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Uncertainty(pub u32);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PositionTracking(pub bool);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TestInverted(pub bool);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct XDensity(pub u32);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct YDensity(pub u32);
pub trait SupportsEnable: Symbology {}
pub trait SupportsChecksum: Symbology {}
pub trait SupportsLengthLimits: Symbology {}
pub trait SupportsUncertainty: Symbology {}
pub trait Symbology: Sized {
const TYPE: SymbolType;
const NAME: &'static str;
}
#[derive(Debug, Clone)]
pub struct DecoderConfig {
pub(crate) enabled: HashSet<SymbolType>,
pub(crate) checksum_flags: HashMap<SymbolType, (bool, bool)>,
pub(crate) length_limits: HashMap<SymbolType, (u32, u32)>,
pub(crate) uncertainty: HashMap<SymbolType, u32>,
pub(crate) position_tracking: bool,
pub(crate) test_inverted: bool,
pub(crate) x_density: u32,
pub(crate) y_density: u32,
pub(crate) retry_undecoded_regions: bool,
}
impl Default for DecoderConfig {
fn default() -> Self {
Self::new()
}
}
impl DecoderConfig {
pub fn new() -> Self {
let mut config = Self {
enabled: HashSet::new(),
checksum_flags: HashMap::new(),
length_limits: HashMap::new(),
uncertainty: HashMap::new(),
position_tracking: true,
test_inverted: false,
x_density: 1,
y_density: 1,
retry_undecoded_regions: false,
};
config.enabled.insert(SymbolType::Ean13);
config.enabled.insert(SymbolType::Ean8);
config.enabled.insert(SymbolType::I25);
config.enabled.insert(SymbolType::Databar);
config.enabled.insert(SymbolType::DatabarExp);
config.enabled.insert(SymbolType::Codabar);
config.enabled.insert(SymbolType::Code39);
config.enabled.insert(SymbolType::Code93);
config.enabled.insert(SymbolType::Code128);
config.enabled.insert(SymbolType::QrCode);
config.enabled.insert(SymbolType::SqCode);
for sym in [SymbolType::Ean13, SymbolType::Ean8] {
config.checksum_flags.insert(sym, (false, true)); }
for sym in [
SymbolType::Upca,
SymbolType::Upce,
SymbolType::Isbn10,
SymbolType::Isbn13,
] {
config.checksum_flags.insert(sym, (false, true)); }
config.length_limits.insert(SymbolType::I25, (6, 256));
config.length_limits.insert(SymbolType::Codabar, (4, 256));
config.length_limits.insert(SymbolType::Code39, (1, 256));
config.uncertainty.insert(SymbolType::Codabar, 1);
config
}
pub fn enable<S: Symbology + SupportsEnable>(mut self, _: S) -> Self {
self.enabled.insert(S::TYPE);
self
}
pub fn disable<S: Symbology + SupportsEnable>(mut self, _: S) -> Self {
self.enabled.remove(&S::TYPE);
self
}
pub fn disable_all(mut self) -> Self {
self.enabled.clear();
self
}
pub fn is_enabled(&self, sym: SymbolType) -> bool {
self.enabled.contains(&sym)
}
pub fn set_checksum<S: Symbology + SupportsChecksum>(
mut self,
_: S,
add_check: bool,
emit_check: bool,
) -> Self {
self.checksum_flags.insert(S::TYPE, (add_check, emit_check));
self
}
pub fn set_length_limits<S: Symbology + SupportsLengthLimits>(
mut self,
_: S,
min: u32,
max: u32,
) -> Self {
assert!(min <= max, "min length must be <= max length");
assert!(max <= 256, "max length must be <= 256");
self.length_limits.insert(S::TYPE, (min, max));
self
}
pub fn set_uncertainty<S: Symbology + SupportsUncertainty>(
mut self,
_: S,
threshold: u32,
) -> Self {
self.uncertainty.insert(S::TYPE, threshold);
self
}
pub fn position_tracking(mut self, enabled: bool) -> Self {
self.position_tracking = enabled;
self
}
pub fn test_inverted(mut self, enabled: bool) -> Self {
self.test_inverted = enabled;
self
}
pub fn scan_density(mut self, x: u32, y: u32) -> Self {
assert!(x > 0, "x density must be > 0");
assert!(y > 0, "y density must be > 0");
self.x_density = x;
self.y_density = y;
self
}
pub fn retry_undecoded_regions(mut self, enabled: bool) -> Self {
self.retry_undecoded_regions = enabled;
self
}
pub fn x_density(mut self, density: u32) -> Self {
assert!(density > 0, "density must be > 0");
self.x_density = density;
self
}
pub fn y_density(mut self, density: u32) -> Self {
assert!(density > 0, "density must be > 0");
self.y_density = density;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = DecoderConfig::new();
assert!(config.is_enabled(SymbolType::Ean13));
assert!(config.is_enabled(SymbolType::Code39));
assert!(config.position_tracking);
assert!(!config.test_inverted);
assert_eq!(config.x_density, 1);
assert_eq!(config.y_density, 1);
}
#[test]
fn test_builder_pattern() {
let config = DecoderConfig::new()
.enable(Ean13)
.disable(Code39)
.set_checksum(Code39, true, false)
.position_tracking(false)
.scan_density(2, 2);
assert!(config.is_enabled(SymbolType::Ean13));
assert!(!config.is_enabled(SymbolType::Code39));
assert!(!config.position_tracking);
assert_eq!(config.x_density, 2);
assert_eq!(config.y_density, 2);
}
#[test]
fn test_type_safe_length_limits() {
let config = DecoderConfig::new()
.set_length_limits(Code39, 5, 20)
.set_length_limits(Code128, 1, 50)
.set_length_limits(I25, 6, 30);
assert_eq!(
config.length_limits.get(&SymbolType::Code39),
Some(&(5, 20))
);
assert_eq!(
config.length_limits.get(&SymbolType::Code128),
Some(&(1, 50))
);
assert_eq!(config.length_limits.get(&SymbolType::I25), Some(&(6, 30)));
}
#[test]
fn test_checksum_configuration() {
let config = DecoderConfig::new()
.set_checksum(Codabar, true, false) .set_checksum(Ean13, false, true);
assert_eq!(
config.checksum_flags.get(&SymbolType::Codabar),
Some(&(true, false))
);
assert_eq!(
config.checksum_flags.get(&SymbolType::Ean13),
Some(&(false, true))
);
}
#[test]
fn test_uncertainty_configuration() {
let config = DecoderConfig::new()
.set_uncertainty(Code39, 2)
.set_uncertainty(QrCode, 0)
.set_uncertainty(Codabar, 1);
assert_eq!(config.uncertainty.get(&SymbolType::Code39), Some(&2));
assert_eq!(config.uncertainty.get(&SymbolType::QrCode), Some(&0));
assert_eq!(config.uncertainty.get(&SymbolType::Codabar), Some(&1));
}
#[test]
fn test_config_to_state_conversion() {
let config = DecoderConfig::new()
.enable(Ean13)
.set_checksum(Ean13, false, true)
.position_tracking(true)
.scan_density(2, 3);
let state: internal::DecoderState = (&config).into();
assert!(state.is_enabled(SymbolType::Ean13));
assert!(state.ean_enabled());
let ean13_config = state.get(SymbolType::Ean13).unwrap();
assert!(!ean13_config.checksum.add_check);
assert!(ean13_config.checksum.emit_check);
assert_eq!(state.scanner.x_density, 2);
assert_eq!(state.scanner.y_density, 3);
assert!(state.scanner.position_tracking);
}
}