use super::{BarState, PostalVariant};
use crate::error::{Error, Result};
const POSTNET: [[bool; 5]; 10] = [
[true, true, false, false, false], [false, false, false, true, true], [false, false, true, false, true], [false, false, true, true, false], [false, true, false, false, true], [false, true, false, true, false], [false, true, true, false, false], [true, false, false, false, true], [true, false, false, true, false], [true, false, true, false, false], ];
fn bar(tall: bool) -> BarState {
if tall {
BarState::Ascender
} else {
BarState::Tracker
}
}
fn pattern(digit: u8, planet: bool) -> [bool; 5] {
let mut p = POSTNET[digit as usize];
if planet {
for b in &mut p {
*b = !*b;
}
}
p
}
pub(super) fn validate_digits(digits: &[u8]) -> Result<()> {
if digits.is_empty() {
return Err(Error::invalid_data("POSTNET/PLANET data must not be empty"));
}
if !digits.iter().all(u8::is_ascii_digit) {
return Err(Error::invalid_data(
"POSTNET/PLANET data must be ASCII digits",
));
}
Ok(())
}
fn check_digit(values: &[u8]) -> u8 {
let sum: u32 = values.iter().map(|&v| v as u32).sum();
((10 - (sum % 10)) % 10) as u8
}
pub(super) fn encode(planet: bool, digits: &[u8]) -> Result<Vec<BarState>> {
validate_digits(digits)?;
let values: Vec<u8> = digits.iter().map(|&b| b - b'0').collect();
let check = check_digit(&values);
let mut bars = Vec::with_capacity(2 + 5 * (values.len() + 1));
bars.push(bar(true)); for &v in values.iter().chain(std::iter::once(&check)) {
for &tall in &pattern(v, planet) {
bars.push(bar(tall));
}
}
bars.push(bar(true)); Ok(bars)
}
pub(super) fn decode(bars: &[BarState]) -> Result<(PostalVariant, Vec<u8>)> {
let n = bars.len();
if n < 7 || !(n - 2).is_multiple_of(5) {
return Err(Error::undecodable("POSTNET/PLANET bar count invalid"));
}
if bars.iter().any(|b| b.has_descender()) {
return Err(Error::undecodable("POSTNET/PLANET bars must be 2-state"));
}
if bars[0] != BarState::Ascender || bars[n - 1] != BarState::Ascender {
return Err(Error::undecodable(
"POSTNET/PLANET framing bars must be tall",
));
}
let groups = &bars[1..n - 1];
for planet in [false, true] {
if let Some(values) = decode_groups(groups, planet)
&& check_digit(&values[..values.len() - 1]) == values[values.len() - 1]
{
let variant = if planet {
PostalVariant::Planet
} else {
PostalVariant::Postnet
};
let digits = values[..values.len() - 1]
.iter()
.map(|&v| v + b'0')
.collect();
return Ok((variant, digits));
}
}
Err(Error::undecodable("POSTNET/PLANET pattern not recognized"))
}
fn decode_groups(groups: &[BarState], planet: bool) -> Option<Vec<u8>> {
groups
.chunks_exact(5)
.map(|chunk| {
let tall = [
chunk[0].has_ascender(),
chunk[1].has_ascender(),
chunk[2].has_ascender(),
chunk[3].has_ascender(),
chunk[4].has_ascender(),
];
(0u8..10).find(|&d| pattern(d, planet) == tall)
})
.collect()
}