use crate::error::{Error, Result};
use crate::output::{Encoding, LinearPattern};
use crate::segment::Segment;
use crate::symbol::{Symbol, SymbolMeta};
use crate::symbology::Symbology;
use crate::traits::{Decode, Encode};
const QUIET_ZONE: usize = 10;
const BAR_WIDTHS: [[u32; 5]; 10] = [
[1, 1, 3, 3, 1],
[3, 1, 1, 1, 3],
[1, 3, 1, 1, 3],
[3, 3, 1, 1, 1],
[1, 1, 3, 1, 3],
[3, 1, 3, 1, 1],
[1, 3, 3, 1, 1],
[1, 1, 1, 3, 3],
[3, 1, 1, 3, 1],
[1, 3, 1, 3, 1],
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Variant {
Standard,
Iata,
Matrix,
}
impl Variant {
fn from_symbology(s: Symbology) -> Result<Self> {
match s {
Symbology::Std2of5 => Ok(Variant::Standard),
Symbology::Iata2of5 => Ok(Variant::Iata),
Symbology::Matrix2of5 => Ok(Variant::Matrix),
_ => Err(Error::invalid_parameter("not a 2-of-5 symbology")),
}
}
fn symbology(self) -> Symbology {
match self {
Variant::Standard => Symbology::Std2of5,
Variant::Iata => Symbology::Iata2of5,
Variant::Matrix => Symbology::Matrix2of5,
}
}
fn start_stop(self) -> (&'static [u32], &'static [u32]) {
match self {
Variant::Standard => (&[3, 1, 3, 1, 1, 1], &[3, 1, 1, 1, 3]),
Variant::Iata => (&[1, 1, 1, 1], &[3, 1, 1]),
Variant::Matrix => (&[4, 1, 1, 1, 1, 1], &[4, 1, 1, 1, 1]),
}
}
fn digit_widths(self, d: usize) -> Vec<u32> {
let bars = &BAR_WIDTHS[d];
match self {
Variant::Standard | Variant::Iata => {
let mut v = Vec::with_capacity(10);
for &b in bars {
v.push(b);
v.push(1);
}
v
}
Variant::Matrix => vec![bars[0], bars[1], bars[2], bars[3], bars[4], 1],
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct TwoOf5Meta;
fn ensure_digits(digits: &[u8]) -> Result<()> {
if digits.is_empty() {
return Err(Error::invalid_data("2-of-5 payload is empty"));
}
if digits.iter().all(u8::is_ascii_digit) {
Ok(())
} else {
Err(Error::invalid_data("2-of-5 payload must be ASCII digits"))
}
}
fn push_run(modules: &mut Vec<bool>, bar: bool, width: u32) {
modules.extend(std::iter::repeat_n(bar, width as usize));
}
fn render_widths(modules: &mut Vec<bool>, widths: &[u32]) {
for (i, &w) in widths.iter().enumerate() {
push_run(modules, i % 2 == 0, w);
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct TwoOf5Encoder;
impl TwoOf5Encoder {
pub fn new() -> Self {
Self
}
pub fn build(&self, symbology: Symbology, digits: &[u8]) -> Result<Symbol> {
Variant::from_symbology(symbology)?;
ensure_digits(digits)?;
Ok(Symbol::new(
symbology,
vec![Segment::numeric(digits.to_vec())],
SymbolMeta::TwoOf5(TwoOf5Meta),
))
}
}
impl Encode for TwoOf5Encoder {
fn encode(&self, symbol: &Symbol) -> Result<Encoding> {
let variant = Variant::from_symbology(symbol.symbology)?;
if !matches!(symbol.meta, SymbolMeta::TwoOf5(_)) {
return Err(Error::invalid_parameter("2-of-5 symbol missing TwoOf5Meta"));
}
let digits = symbol.payload_bytes();
ensure_digits(&digits)?;
let (start, stop) = variant.start_stop();
let mut modules = Vec::new();
render_widths(&mut modules, start);
for &d in &digits {
render_widths(&mut modules, &variant.digit_widths((d - b'0') as usize));
}
render_widths(&mut modules, stop);
Ok(Encoding::Linear(LinearPattern {
modules,
quiet_zone: QUIET_ZONE,
}))
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct TwoOf5Decoder;
impl TwoOf5Decoder {
pub fn new() -> Self {
Self
}
}
fn rle(modules: &[bool]) -> Result<Vec<u32>> {
if modules.is_empty() || !modules[0] {
return Err(Error::undecodable("linear pattern must start with a bar"));
}
let mut runs = Vec::new();
let mut cur = modules[0];
let mut len = 0u32;
for &m in modules {
if m == cur {
len += 1;
} else {
runs.push(len);
cur = m;
len = 1;
}
}
runs.push(len);
Ok(runs)
}
fn matches_widths(runs: &[u32], pattern: &[u32]) -> bool {
runs.len() == pattern.len() && runs.iter().zip(pattern).all(|(&r, &p)| (r > 1) == (p > 1))
}
fn digit_from_bars(bars: &[u32]) -> Result<u8> {
for (d, pat) in BAR_WIDTHS.iter().enumerate() {
if (0..5).all(|i| (bars[i] > 1) == (pat[i] > 1)) {
return Ok(d as u8);
}
}
Err(Error::undecodable("invalid 2-of-5 digit pattern"))
}
impl Decode for TwoOf5Decoder {
fn decode(&self, encoding: &Encoding) -> Result<Symbol> {
let pattern = match encoding {
Encoding::Linear(p) => p,
Encoding::Matrix(_) => {
return Err(Error::invalid_parameter("2-of-5 expects a linear pattern"));
}
};
let runs = rle(&pattern.modules)?;
for variant in [Variant::Standard, Variant::Iata, Variant::Matrix] {
let (start, stop) = variant.start_stop();
if runs.len() < start.len() + stop.len() {
continue;
}
if variant == Variant::Matrix && runs[0] < 4 {
continue;
}
if variant != Variant::Matrix && runs[0] >= 4 {
continue;
}
if !matches_widths(&runs[..start.len()], start) {
continue;
}
let stop_at = runs.len() - stop.len();
if !matches_widths(&runs[stop_at..], stop) {
continue;
}
let body = &runs[start.len()..stop_at];
let per_digit = match variant {
Variant::Standard | Variant::Iata => 10,
Variant::Matrix => 6,
};
if body.is_empty() || body.len() % per_digit != 0 {
continue;
}
let mut digits = Vec::new();
let mut ok = true;
for chunk in body.chunks(per_digit) {
let bars: Vec<u32> = match variant {
Variant::Standard | Variant::Iata => chunk.iter().step_by(2).copied().collect(),
Variant::Matrix => chunk[..5].to_vec(),
};
match digit_from_bars(&bars) {
Ok(d) => digits.push(b'0' + d),
Err(_) => {
ok = false;
break;
}
}
}
if !ok {
continue;
}
return Ok(Symbol::new(
variant.symbology(),
vec![Segment::numeric(digits)],
SymbolMeta::TwoOf5(TwoOf5Meta),
));
}
Err(Error::undecodable("no matching 2-of-5 variant"))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn roundtrip(sym: Symbology, digits: &[u8]) {
let enc = TwoOf5Encoder::new();
let symbol = enc.build(sym, digits).unwrap();
let encoding = enc.encode(&symbol).unwrap();
let back = TwoOf5Decoder::new().decode(&encoding).unwrap();
assert_eq!(back.symbology, sym);
assert_eq!(back.segments, symbol.segments);
assert_eq!(enc.encode(&back).unwrap(), encoding);
}
#[test]
fn roundtrip_all_variants() {
for sym in [
Symbology::Std2of5,
Symbology::Iata2of5,
Symbology::Matrix2of5,
] {
roundtrip(sym, b"0123456789");
roundtrip(sym, b"42");
}
}
#[test]
fn variant_is_recovered_from_start_stop() {
let enc = TwoOf5Encoder::new();
for sym in [
Symbology::Std2of5,
Symbology::Iata2of5,
Symbology::Matrix2of5,
] {
let encoding = enc.encode(&enc.build(sym, b"1234").unwrap()).unwrap();
assert_eq!(
TwoOf5Decoder::new().decode(&encoding).unwrap().symbology,
sym
);
}
}
}