#![forbid(unsafe_code)]
use crate::common::{
buffer::SliceWriter, errors::EncodeError, traits::BarcodeEncoder, types::Encoded,
};
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;
fn encode_into(input: &str, buf: &mut [bool]) -> Result<Encoded, EncodeError> {
if input.is_empty() {
return Err(EncodeError::InvalidInput("Code 39 input must not be empty"));
}
for ch in input.chars() {
if lookup_pattern(ch).is_none() {
return Err(EncodeError::InvalidCharacter(ch));
}
}
let len = encode_bars(input, buf)?;
Ok(Encoded::Linear { len, height: 50 })
}
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(w: &mut SliceWriter, pattern: &[bool; 9]) -> Result<(), EncodeError> {
for (i, &wide) in pattern.iter().enumerate() {
let is_bar = i % 2 == 0; let width = if wide { 3 } else { 1 };
w.push_run(is_bar, width)?; }
Ok(())
}
fn encode_bars(input: &str, buf: &mut [bool]) -> Result<usize, EncodeError> {
let mut w = SliceWriter::new(buf);
let star = lookup_pattern('*').expect("star pattern must exist");
append_char(&mut w, star)?;
for ch in input.chars() {
w.push(false)?;
let pattern = lookup_pattern(ch).expect("already validated");
append_char(&mut w, pattern)?;
}
w.push(false)?;
append_char(&mut w, star)?;
Ok(w.len())
}
#[cfg(test)]
mod tests {
use super::*;
fn encode_len(input: &str) -> usize {
let mut buf = [false; 1024];
match Code39::encode_into(input, &mut buf).unwrap() {
Encoded::Linear { len, .. } => len,
_ => panic!("expected linear"),
}
}
#[test]
fn test_encode_basic() {
assert!(encode_len("CODE39") > 0);
}
#[test]
fn test_encode_digits() {
assert!(encode_len("12345") > 0);
}
#[test]
fn test_encode_special_chars() {
assert!(encode_len("HELLO WORLD") > 0);
}
#[test]
fn test_invalid_character() {
let mut buf = [false; 1024];
assert!(Code39::encode_into("hello", &mut buf).is_err());
}
#[test]
fn test_invalid_char_symbol() {
let mut buf = [false; 1024];
assert!(Code39::encode_into("ABC!DEF", &mut buf).is_err());
}
#[test]
fn test_empty_input() {
let mut buf = [false; 1024];
assert!(Code39::encode_into("", &mut buf).is_err());
}
#[test]
fn test_buffer_too_small() {
let mut buf = [false; 8];
assert_eq!(
Code39::encode_into("A", &mut buf),
Err(EncodeError::BufferTooSmall)
);
}
#[test]
fn test_symbology_name() {
assert_eq!(Code39::symbology_name(), "Code 39");
}
#[test]
fn test_bar_count_single_char() {
assert_eq!(encode_len("A"), 47);
}
#[cfg(feature = "alloc")]
#[test]
fn test_svg_output() {
let svg = Code39::encode("TEST").unwrap().to_svg_string();
assert!(svg.starts_with("<svg "));
}
}