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 START_BITS: [bool; 8] = [true, true, true, true, true, false, true, false];
const STOP_BITS: [bool; 8] = [false, true, false, true, true, true, true, true];
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct TelepenMeta {
pub check: bool,
}
fn even_parity(value: u8) -> u8 {
if value.count_ones() % 2 == 1 {
value | 0x80
} else {
value
}
}
fn check_value(data: &[u8]) -> u8 {
let sum: u32 = data.iter().map(|&b| b as u32).sum();
((127 - (sum % 127)) % 127) as u8
}
fn push_byte(bits: &mut Vec<bool>, byte: u8) {
for i in 0..8 {
bits.push((byte >> i) & 1 == 1);
}
}
fn bits_to_widths(bits: &[bool]) -> Vec<u32> {
let mut out = Vec::new();
let mut i = 0;
while i < bits.len() {
if bits[i] {
out.push(1); out.push(1); i += 1;
} else {
let mut j = i + 1;
while j < bits.len() && bits[j] {
j += 1;
}
let ones = j - (i + 1);
match ones {
0 => {
out.push(3); out.push(1); }
1 => {
out.push(3); out.push(3); }
_ => {
out.push(1); out.push(3); for _ in 0..ones - 2 {
out.push(1);
out.push(1);
}
out.push(1); out.push(3); }
}
i = j + 1;
}
}
out
}
fn widths_to_modules(widths: &[u32]) -> Vec<bool> {
let mut modules = Vec::new();
for (i, &w) in widths.iter().enumerate() {
modules.extend(std::iter::repeat_n(i % 2 == 0, w as usize));
}
modules
}
fn widths_to_bits(widths: &[u32]) -> Result<Vec<bool>> {
if !widths.len().is_multiple_of(2) {
return Err(Error::undecodable("odd Telepen element count"));
}
let mut bits = Vec::new();
let mut in_block = false;
for pair in widths.chunks_exact(2) {
match (pair[0] > 1, pair[1] > 1) {
(false, false) => bits.push(true), (true, false) => {
bits.push(false);
bits.push(false);
}
(true, true) => {
bits.push(false);
bits.push(true);
bits.push(false);
}
(false, true) => {
if in_block {
bits.push(true);
bits.push(false);
in_block = false;
} else {
bits.push(false);
bits.push(true);
in_block = true;
}
}
}
}
if in_block {
return Err(Error::undecodable("unterminated Telepen block"));
}
Ok(bits)
}
#[derive(Debug, Default, Clone, Copy)]
pub struct TelepenEncoder;
impl TelepenEncoder {
pub fn new() -> Self {
Self
}
pub fn build(&self, data: &[u8], check: bool) -> Result<Symbol> {
if !data.iter().all(|&b| b < 128) {
return Err(Error::invalid_data(
"Telepen full-ASCII data must be 0..=127",
));
}
Ok(Symbol::new(
Symbology::Telepen,
vec![Segment::byte(data.to_vec())],
SymbolMeta::Telepen(TelepenMeta { check }),
))
}
}
impl Encode for TelepenEncoder {
fn encode(&self, symbol: &Symbol) -> Result<Encoding> {
if symbol.symbology != Symbology::Telepen {
return Err(Error::invalid_parameter(
"TelepenEncoder given a non-Telepen symbol",
));
}
let meta = match &symbol.meta {
SymbolMeta::Telepen(m) => m,
_ => {
return Err(Error::invalid_parameter(
"Telepen symbol missing TelepenMeta",
));
}
};
let data = symbol.payload_bytes();
if !data.iter().all(|&b| b < 128) {
return Err(Error::invalid_data(
"Telepen full-ASCII data must be 0..=127",
));
}
let mut bits = Vec::new();
bits.extend_from_slice(&START_BITS);
for &b in &data {
push_byte(&mut bits, even_parity(b));
}
if meta.check {
push_byte(&mut bits, even_parity(check_value(&data)));
}
bits.extend_from_slice(&STOP_BITS);
Ok(Encoding::Linear(LinearPattern {
modules: widths_to_modules(&bits_to_widths(&bits)),
quiet_zone: QUIET_ZONE,
}))
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct TelepenDecoder {
check: bool,
}
impl TelepenDecoder {
pub fn new() -> Self {
Self { check: false }
}
pub fn with_check(check: bool) -> Self {
Self { check }
}
}
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)
}
impl Decode for TelepenDecoder {
fn decode(&self, encoding: &Encoding) -> Result<Symbol> {
let pattern = match encoding {
Encoding::Linear(p) => p,
Encoding::Matrix(_) => {
return Err(Error::invalid_parameter("Telepen expects a linear pattern"));
}
};
let widths = rle(&pattern.modules)?;
let bits = widths_to_bits(&widths)?;
if bits.len() < 16 || bits[..8] != START_BITS || bits[bits.len() - 8..] != STOP_BITS {
return Err(Error::undecodable("bad Telepen guards"));
}
let body = &bits[8..bits.len() - 8];
if body.len() % 8 != 0 {
return Err(Error::undecodable("Telepen body not byte-aligned"));
}
let mut bytes = Vec::with_capacity(body.len() / 8);
for group in body.chunks_exact(8) {
let mut e = 0u8;
for (i, &bit) in group.iter().enumerate() {
if bit {
e |= 1 << i;
}
}
if !e.count_ones().is_multiple_of(2) {
return Err(Error::undecodable("Telepen parity error"));
}
bytes.push(e & 0x7F);
}
if self.check {
let Some((&chk, data)) = bytes.split_last() else {
return Err(Error::undecodable("Telepen missing check character"));
};
if chk != check_value(data) {
return Err(Error::undecodable("Telepen check mismatch"));
}
bytes.truncate(bytes.len() - 1);
}
Ok(Symbol::new(
Symbology::Telepen,
vec![Segment::byte(bytes)],
SymbolMeta::Telepen(TelepenMeta { check: self.check }),
))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn check_reference_abc() {
assert_eq!(check_value(b"ABC"), 56);
}
#[test]
fn start_stop_guard_shapes() {
let start = bits_to_widths(&START_BITS);
assert_eq!(start, vec![1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3]);
let stop = bits_to_widths(&STOP_BITS);
assert_eq!(stop, vec![3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
}
#[test]
fn roundtrip_with_and_without_check() {
let enc = TelepenEncoder::new();
for check in [false, true] {
for data in [&b"ABC"[..], b"Hello, World!", b"12345", b"\x00\x7f"] {
let sym = enc.build(data, check).unwrap();
let encoding = enc.encode(&sym).unwrap();
let back = TelepenDecoder::with_check(check).decode(&encoding).unwrap();
assert_eq!(back.segments, sym.segments);
assert_eq!(back.meta, sym.meta);
assert_eq!(enc.encode(&back).unwrap(), encoding);
}
}
}
}