use super::tables::{
ADDON_LEAD, ADDON_SEP, CENTER_GUARD, EAN2_PARITY, EAN5_PARITY, EAN13_FIRST_PARITY,
NORMAL_GUARD, UPCE_END_GUARD, check_digit, ean5_checksum, match_left, match_right, upce_expand,
upce_from_parity,
};
use super::{AddOn, AddOnKind, EanMeta, EanVariant};
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;
#[derive(Debug, Default, Clone, Copy)]
pub struct EanDecoder;
impl EanDecoder {
pub fn new() -> Self {
EanDecoder
}
}
impl Decode for EanDecoder {
fn decode(&self, encoding: &Encoding) -> Result<Symbol> {
match encoding {
Encoding::Linear(lp) => decode_linear(lp),
Encoding::Matrix(_) => Err(Error::Unsupported {
what: "EAN/UPC decode of a matrix",
}),
}
}
}
fn decode_linear(lp: &LinearPattern) -> Result<Symbol> {
let m = trim(&lp.modules);
if m.len() < 3 || m[0..3] != NORMAL_GUARD {
return Err(Error::undecodable("no EAN/UPC start guard"));
}
if m.len() >= 4 && m[3] {
let (addon, consumed) =
parse_addon(m).ok_or_else(|| Error::undecodable("malformed add-on"))?;
if consumed != m.len() {
return Err(Error::undecodable("trailing data after add-on"));
}
let (symbology, variant) = match addon.kind {
AddOnKind::Two => (Symbology::Ean2, EanVariant::Ean2),
AddOnKind::Five => (Symbology::Ean5, EanVariant::Ean5),
};
return Ok(Symbol::new(
symbology,
vec![Segment::numeric(addon.digits)],
SymbolMeta::Ean(EanMeta::new(variant)),
));
}
for parser in [try_ean13, try_ean8, try_upce] {
if let Some((variant, digits, consumed)) = parser(m)
&& let Some(addon) = parse_trailing_addon(&m[consumed..])
{
let symbology = symbology_of(variant);
return Ok(Symbol::new(
symbology,
vec![Segment::numeric(digits)],
SymbolMeta::Ean(EanMeta { variant, addon }),
));
}
}
Err(Error::undecodable("unrecognized EAN/UPC symbol"))
}
fn trim(modules: &[bool]) -> &[bool] {
let start = modules.iter().position(|&b| b).unwrap_or(modules.len());
let end = modules.iter().rposition(|&b| b).map_or(start, |p| p + 1);
&modules[start..end]
}
fn symbology_of(variant: EanVariant) -> Symbology {
match variant {
EanVariant::Ean13 => Symbology::Ean13,
EanVariant::Ean8 => Symbology::Ean8,
EanVariant::UpcA => Symbology::UpcA,
EanVariant::UpcE => Symbology::UpcE,
EanVariant::Ean2 => Symbology::Ean2,
EanVariant::Ean5 => Symbology::Ean5,
}
}
fn to_ascii(vals: &[u8]) -> Vec<u8> {
vals.iter().map(|&v| b'0' + v).collect()
}
fn try_ean13(m: &[bool]) -> Option<(EanVariant, Vec<u8>, usize)> {
if m.len() < 95
|| m[0..3] != NORMAL_GUARD
|| m[45..50] != CENTER_GUARD
|| m[92..95] != NORMAL_GUARD
{
return None;
}
let mut left = [0u8; 6];
let mut parity = [false; 6];
for i in 0..6 {
let (d, even) = match_left(&m[3 + i * 7..10 + i * 7])?;
left[i] = d;
parity[i] = even;
}
let mut right = [0u8; 6];
for i in 0..6 {
right[i] = match_right(&m[50 + i * 7..57 + i * 7])?;
}
let first = (0..10u8).find(|&f| EAN13_FIRST_PARITY[f as usize] == parity)?;
let mut vals = Vec::with_capacity(13);
vals.push(first);
vals.extend_from_slice(&left);
vals.extend_from_slice(&right);
if check_digit(&vals[..12]) != vals[12] {
return None;
}
if first == 0 {
Some((EanVariant::UpcA, to_ascii(&vals[1..]), 95))
} else {
Some((EanVariant::Ean13, to_ascii(&vals), 95))
}
}
fn try_ean8(m: &[bool]) -> Option<(EanVariant, Vec<u8>, usize)> {
if m.len() < 67
|| m[0..3] != NORMAL_GUARD
|| m[31..36] != CENTER_GUARD
|| m[64..67] != NORMAL_GUARD
{
return None;
}
let mut vals = [0u8; 8];
for i in 0..4 {
let (d, even) = match_left(&m[3 + i * 7..10 + i * 7])?;
if even {
return None; }
vals[i] = d;
}
for i in 0..4 {
vals[4 + i] = match_right(&m[36 + i * 7..43 + i * 7])?;
}
if check_digit(&vals[..7]) != vals[7] {
return None;
}
Some((EanVariant::Ean8, to_ascii(&vals), 67))
}
fn try_upce(m: &[bool]) -> Option<(EanVariant, Vec<u8>, usize)> {
if m.len() < 51 || m[0..3] != NORMAL_GUARD || m[45..51] != UPCE_END_GUARD {
return None;
}
let mut payload = [0u8; 6];
let mut parity = [false; 6];
for i in 0..6 {
let (d, even) = match_left(&m[3 + i * 7..10 + i * 7])?;
payload[i] = d;
parity[i] = even;
}
let (ns, check) = upce_from_parity(&parity)?;
if check_digit(&upce_expand(ns, &payload)) != check {
return None;
}
let mut vals = Vec::with_capacity(8);
vals.push(ns);
vals.extend_from_slice(&payload);
vals.push(check);
Some((EanVariant::UpcE, to_ascii(&vals), 51))
}
fn parse_trailing_addon(rest: &[bool]) -> Option<Option<AddOn>> {
if rest.is_empty() {
return Some(None);
}
let start = rest.iter().position(|&b| b)?;
if start == 0 {
return None;
}
let tail = &rest[start..];
let (addon, consumed) = parse_addon(tail)?;
if consumed == tail.len() {
Some(Some(addon))
} else {
None
}
}
fn parse_addon(m: &[bool]) -> Option<(AddOn, usize)> {
try_addon5(m).or_else(|| try_addon2(m))
}
fn try_addon2(m: &[bool]) -> Option<(AddOn, usize)> {
if m.len() < 20 || m[0..4] != ADDON_LEAD || m[11..13] != ADDON_SEP {
return None;
}
let (d0, p0) = match_left(&m[4..11])?;
let (d1, p1) = match_left(&m[13..20])?;
let value = d0 * 10 + d1;
if EAN2_PARITY[(value % 4) as usize] != [p0, p1] {
return None;
}
Some((
AddOn {
kind: AddOnKind::Two,
digits: to_ascii(&[d0, d1]),
},
20,
))
}
fn try_addon5(m: &[bool]) -> Option<(AddOn, usize)> {
if m.len() < 47 || m[0..4] != ADDON_LEAD {
return None;
}
let mut vals = [0u8; 5];
let mut parity = [false; 5];
for i in 0..5 {
let pos = 4 + i * 9;
if i > 0 && m[pos - 2..pos] != ADDON_SEP {
return None;
}
let (d, even) = match_left(&m[pos..pos + 7])?;
vals[i] = d;
parity[i] = even;
}
if EAN5_PARITY[ean5_checksum(&vals) as usize] != parity {
return None;
}
Some((
AddOn {
kind: AddOnKind::Five,
digits: to_ascii(&vals),
},
47,
))
}