#![forbid(unsafe_code)]
extern crate alloc;
use alloc::vec::Vec;
use crate::common::{
errors::EncodeError,
traits::BarcodeEncoder,
types::{BarcodeOutput, LinearBarcode},
};
const CODE93_CHARS: &[u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%";
const CODE93_PATTERNS: [u16; 48] = [
0b100010100, 0b101001000, 0b101000100, 0b101000010, 0b100101000, 0b100100100, 0b100100010, 0b101010000, 0b100010010, 0b100001010, 0b110101000, 0b110100100, 0b110100010, 0b110010100, 0b110010010, 0b110001010, 0b101101000, 0b101100100, 0b101100010, 0b100110100, 0b100011010, 0b101011000, 0b101001100, 0b101000110, 0b100101100, 0b100010110, 0b110110100, 0b110110010, 0b110101100, 0b110100110, 0b110010110, 0b110011010, 0b101101100, 0b101100110, 0b100110110, 0b100111010, 0b100101110, 0b111010100, 0b111010010, 0b111001010, 0b101101110, 0b101110110, 0b110101110, 0b100100110, 0b111011010, 0b111010110, 0b100110010, 0b101011110, ];
const START_STOP: usize = 47;
pub struct Code93;
impl BarcodeEncoder for Code93 {
type Input = str;
type Error = EncodeError;
fn encode(input: &str) -> Result<BarcodeOutput, EncodeError> {
if input.is_empty() {
return Err(EncodeError::InvalidInput(
"Code 93 input must not be empty".into(),
));
}
let mut values: Vec<usize> = Vec::with_capacity(input.len());
for ch in input.chars() {
match char_value(ch) {
Some(v) => values.push(v),
None => {
return Err(EncodeError::InvalidInput(alloc::format!(
"character '{ch}' is not valid in Code 93"
)));
}
}
}
let c = check_value(&values, 20);
values.push(c);
let k = check_value(&values, 15);
values.push(k);
let bars = encode_bars(&values);
Ok(BarcodeOutput::Linear(LinearBarcode {
bars,
height: 50,
text: Some(input.into()),
}))
}
fn symbology_name() -> &'static str {
"Code 93"
}
}
fn char_value(ch: char) -> Option<usize> {
let byte = u8::try_from(ch as u32).ok()?;
CODE93_CHARS.iter().position(|&c| c == byte)
}
fn check_value(values: &[usize], max_weight: u32) -> usize {
let mut weight = 1u32;
let mut sum = 0u32;
for &v in values.iter().rev() {
sum += weight * v as u32;
weight = if weight == max_weight { 1 } else { weight + 1 };
}
(sum % 47) as usize
}
fn append_pattern(bars: &mut Vec<bool>, value: usize) {
let pattern = CODE93_PATTERNS[value];
for i in (0..9).rev() {
bars.push((pattern >> i) & 1 == 1);
}
}
fn encode_bars(values: &[usize]) -> Vec<bool> {
let mut bars: Vec<bool> = Vec::with_capacity((values.len() + 2) * 9 + 1);
append_pattern(&mut bars, START_STOP);
for &v in values {
append_pattern(&mut bars, v);
}
append_pattern(&mut bars, START_STOP);
bars.push(true);
bars
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encode_basic() {
let out = Code93::encode("CODE93").unwrap();
assert!(matches!(out, BarcodeOutput::Linear(_)));
}
#[test]
fn test_encode_special_chars() {
let out = Code93::encode("HELLO WORLD").unwrap();
assert!(matches!(out, BarcodeOutput::Linear(_)));
}
#[test]
fn test_invalid_lowercase() {
assert!(Code93::encode("hello").is_err());
}
#[test]
fn test_invalid_symbol() {
assert!(Code93::encode("ABC!DEF").is_err());
}
#[test]
fn test_empty_input() {
assert!(Code93::encode("").is_err());
}
#[test]
fn test_symbology_name() {
assert_eq!(Code93::symbology_name(), "Code 93");
}
#[test]
fn test_bar_count() {
let out = Code93::encode("A").unwrap();
match out {
BarcodeOutput::Linear(lb) => assert_eq!(lb.bars.len(), 5 * 9 + 1),
_ => panic!("expected linear"),
}
}
#[test]
fn test_check_values_known() {
let values: Vec<usize> = "CODE93".chars().map(|c| char_value(c).unwrap()).collect();
let c = check_value(&values, 20);
assert_eq!(c, char_value('P').unwrap());
let mut with_c = values.clone();
with_c.push(c);
let k = check_value(&with_c, 15);
assert_eq!(k, char_value('V').unwrap());
}
#[test]
fn test_svg_output() {
let svg = Code93::encode("TEST").unwrap().to_svg_string();
assert!(svg.starts_with("<svg "));
}
}