#![forbid(unsafe_code)]
extern crate alloc;
use alloc::vec::Vec;
use crate::common::{
errors::EncodeError,
traits::BarcodeEncoder,
types::{BarcodeOutput, LinearBarcode},
};
const CODE39_TABLE: &[(char, [bool; 9])] = &[
(
'0',
[false, false, false, true, true, false, true, false, false],
), (
'1',
[true, false, false, true, false, false, false, false, true],
), (
'2',
[false, false, true, true, false, false, false, false, true],
), (
'3',
[true, false, true, true, false, false, false, false, false],
), (
'4',
[false, false, false, true, true, false, false, false, true],
), (
'5',
[true, false, false, true, true, false, false, false, false],
), (
'6',
[false, false, true, true, true, false, false, false, false],
), (
'7',
[false, false, false, true, false, false, true, false, true],
), (
'8',
[true, false, false, true, false, false, true, false, false],
), (
'9',
[false, false, true, true, false, false, true, false, false],
), (
'A',
[true, false, false, false, false, true, false, false, true],
), (
'B',
[false, false, true, false, false, true, false, false, true],
), (
'C',
[true, false, true, false, false, true, false, false, false],
), (
'D',
[false, false, false, false, true, true, false, false, true],
), (
'E',
[true, false, false, false, true, true, false, false, false],
), (
'F',
[false, false, true, false, true, true, false, false, false],
), (
'G',
[false, false, false, false, false, true, true, false, true],
), (
'H',
[true, false, false, false, false, true, true, false, false],
), (
'I',
[false, false, true, false, false, true, true, false, false],
), (
'J',
[false, false, false, false, true, true, true, false, false],
), (
'K',
[true, false, false, false, false, false, false, true, true],
), (
'L',
[false, false, true, false, false, false, false, true, true],
), (
'M',
[true, false, true, false, false, false, false, true, false],
), (
'N',
[false, false, false, false, true, false, false, true, true],
), (
'O',
[true, false, false, false, true, false, false, true, false],
), (
'P',
[false, false, true, false, true, false, false, true, false],
), (
'Q',
[false, false, false, false, false, false, true, true, true],
), (
'R',
[true, false, false, false, false, false, true, true, false],
), (
'S',
[false, false, true, false, false, false, true, true, false],
), (
'T',
[false, false, false, false, true, false, true, true, false],
), (
'U',
[true, true, false, false, false, false, false, false, true],
), (
'V',
[false, true, true, false, false, false, false, false, true],
), (
'W',
[true, true, true, false, false, false, false, false, false],
), (
'X',
[false, true, false, false, true, false, false, false, true],
), (
'Y',
[true, true, false, false, true, false, false, false, false],
), (
'Z',
[false, true, true, false, true, false, false, false, false],
), (
'-',
[false, true, false, false, false, false, true, false, true],
), (
'.',
[true, true, false, false, false, false, true, false, false],
), (
' ',
[false, true, true, false, false, false, true, false, false],
), (
'$',
[false, true, false, true, false, true, false, false, false],
), (
'/',
[false, true, false, true, false, false, false, true, false],
), (
'+',
[false, true, false, false, false, true, false, true, false],
), (
'%',
[false, false, false, true, false, true, false, true, false],
), (
'*',
[false, true, false, false, true, false, true, false, false],
), ];
pub struct Code39;
impl BarcodeEncoder for Code39 {
type Input = str;
type Error = EncodeError;
fn encode(input: &str) -> Result<BarcodeOutput, EncodeError> {
if input.is_empty() {
return Err(EncodeError::InvalidInput(
"Code 39 input must not be empty".into(),
));
}
for ch in input.chars() {
if lookup_pattern(ch).is_none() {
return Err(EncodeError::InvalidInput(alloc::format!(
"character '{ch}' is not valid in Code 39"
)));
}
}
let bars = encode_bars(input);
let text = input.into();
Ok(BarcodeOutput::Linear(LinearBarcode {
bars,
height: 50,
text: Some(text),
}))
}
fn symbology_name() -> &'static str {
"Code 39"
}
}
fn lookup_pattern(ch: char) -> Option<&'static [bool; 9]> {
CODE39_TABLE.iter().find(|(c, _)| *c == ch).map(|(_, p)| p)
}
fn append_char(bars: &mut Vec<bool>, pattern: &[bool; 9]) {
for (i, &wide) in pattern.iter().enumerate() {
let is_bar = i % 2 == 0; let width = if wide { 3 } else { 1 };
let module = is_bar; for _ in 0..width {
bars.push(module);
}
}
}
fn encode_bars(input: &str) -> Vec<bool> {
let mut bars: Vec<bool> = Vec::new();
let star = lookup_pattern('*').expect("star pattern must exist");
append_char(&mut bars, star);
for ch in input.chars() {
bars.push(false);
let pattern = lookup_pattern(ch).expect("already validated");
append_char(&mut bars, pattern);
}
bars.push(false);
append_char(&mut bars, star);
bars
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encode_basic() {
let out = Code39::encode("CODE39").unwrap();
assert!(matches!(out, BarcodeOutput::Linear(_)));
}
#[test]
fn test_encode_digits() {
let out = Code39::encode("12345").unwrap();
assert!(matches!(out, BarcodeOutput::Linear(_)));
}
#[test]
fn test_encode_special_chars() {
let out = Code39::encode("HELLO WORLD").unwrap();
assert!(matches!(out, BarcodeOutput::Linear(_)));
}
#[test]
fn test_invalid_character() {
assert!(Code39::encode("hello").is_err());
}
#[test]
fn test_invalid_char_symbol() {
assert!(Code39::encode("ABC!DEF").is_err());
}
#[test]
fn test_empty_input() {
assert!(Code39::encode("").is_err());
}
#[test]
fn test_symbology_name() {
assert_eq!(Code39::symbology_name(), "Code 39");
}
#[test]
fn test_bar_count_single_char() {
let out = Code39::encode("A").unwrap();
match out {
BarcodeOutput::Linear(lb) => {
assert_eq!(lb.bars.len(), 47);
}
_ => panic!("expected linear"),
}
}
#[test]
fn test_svg_output() {
let svg = Code39::encode("TEST").unwrap().to_svg_string();
assert!(svg.starts_with("<svg "));
}
}