#![forbid(unsafe_code)]
extern crate alloc;
use alloc::{string::String, vec, vec::Vec};
use crate::common::{
errors::EncodeError,
traits::BarcodeEncoder,
types::{BarcodeOutput, LinearBarcode},
};
const ITF_TABLE: [[bool; 5]; 10] = [
[false, false, true, true, false], [true, false, false, false, true], [false, true, false, false, true], [true, true, false, false, false], [false, false, true, false, true], [true, false, true, false, false], [false, true, true, false, false], [false, false, false, true, true], [true, false, false, true, false], [false, true, false, true, false], ];
pub struct Itf;
impl BarcodeEncoder for Itf {
type Input = str;
type Error = EncodeError;
fn encode(input: &str) -> Result<BarcodeOutput, EncodeError> {
let trimmed = input.trim();
if trimmed.is_empty() {
return Err(EncodeError::InvalidInput(
"ITF input must not be empty".into(),
));
}
if !trimmed.chars().all(|c| c.is_ascii_digit()) {
return Err(EncodeError::InvalidInput(
"ITF input must contain digits only".into(),
));
}
let padded: String = if !trimmed.len().is_multiple_of(2) {
let mut s = String::with_capacity(trimmed.len() + 1);
s.push('0');
s.push_str(trimmed);
s
} else {
trimmed.into()
};
let digits: Vec<u8> = padded.bytes().map(|b| b - b'0').collect();
let bars = encode_bars(&digits);
Ok(BarcodeOutput::Linear(LinearBarcode {
bars,
height: 50,
text: Some(trimmed.into()),
}))
}
fn symbology_name() -> &'static str {
"ITF"
}
}
#[inline]
fn push_module(bars: &mut Vec<bool>, dark: bool, wide: bool) {
let width = if wide { 3 } else { 1 };
for _ in 0..width {
bars.push(dark);
}
}
fn encode_bars(digits: &[u8]) -> Vec<bool> {
let mut bars: Vec<bool> = vec![true, false, true, false];
let mut i = 0;
while i + 1 < digits.len() {
let d1 = digits[i] as usize; let d2 = digits[i + 1] as usize;
let p1 = &ITF_TABLE[d1];
let p2 = &ITF_TABLE[d2];
for j in 0..5 {
push_module(&mut bars, true, p1[j]); push_module(&mut bars, false, p2[j]); }
i += 2;
}
bars.push(true); bars.push(true);
bars.push(true);
bars.push(false); bars.push(true);
bars
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encode_even_digits() {
let out = Itf::encode("12345678").unwrap();
assert!(matches!(out, BarcodeOutput::Linear(_)));
}
#[test]
fn test_encode_odd_digits_padded() {
let out_odd = Itf::encode("1234567").unwrap();
let out_even = Itf::encode("01234567").unwrap();
assert!(matches!(out_odd, BarcodeOutput::Linear(_)));
match (out_odd, out_even) {
(BarcodeOutput::Linear(odd), BarcodeOutput::Linear(even)) => {
assert_eq!(odd.bars, even.bars);
}
_ => panic!("expected linear"),
}
}
#[test]
fn test_invalid_characters() {
assert!(Itf::encode("1234A678").is_err());
}
#[test]
fn test_empty_input() {
assert!(Itf::encode("").is_err());
}
#[test]
fn test_symbology_name() {
assert_eq!(Itf::symbology_name(), "ITF");
}
#[test]
fn test_bar_length_two_digits() {
let out = Itf::encode("12").unwrap();
match out {
BarcodeOutput::Linear(lb) => {
assert_eq!(lb.bars.len(), 27);
}
_ => panic!("expected linear"),
}
}
#[test]
fn test_svg_output() {
let svg = Itf::encode("1234").unwrap().to_svg_string();
assert!(svg.starts_with("<svg "));
}
}