#![forbid(unsafe_code)]
extern crate alloc;
use alloc::vec::Vec;
use crate::common::{
errors::EncodeError,
traits::BarcodeEncoder,
types::{BarcodeOutput, LinearBarcode},
};
pub(crate) const PATTERNS: &[[u8; 6]; 107] = &[
[2, 1, 2, 2, 2, 2], [2, 2, 2, 1, 2, 2], [2, 2, 2, 2, 2, 1], [1, 2, 1, 2, 2, 3], [1, 2, 1, 3, 2, 2], [1, 3, 1, 2, 2, 2], [1, 2, 2, 2, 1, 3], [1, 2, 2, 3, 1, 2], [1, 3, 2, 2, 1, 2], [2, 2, 1, 2, 1, 3], [2, 2, 1, 3, 1, 2], [2, 3, 1, 2, 1, 2], [1, 1, 2, 2, 3, 2], [1, 2, 2, 1, 3, 2], [1, 2, 2, 2, 3, 1], [1, 1, 3, 2, 2, 2], [1, 2, 3, 1, 2, 2], [1, 2, 3, 2, 2, 1], [2, 2, 3, 2, 1, 1], [2, 2, 1, 1, 3, 2], [2, 2, 1, 2, 3, 1], [2, 1, 3, 2, 1, 2], [2, 2, 3, 1, 1, 2], [3, 1, 2, 1, 3, 1], [3, 1, 1, 2, 2, 2], [3, 2, 1, 1, 2, 2], [3, 2, 1, 2, 2, 1], [3, 1, 2, 2, 1, 2], [3, 2, 2, 1, 1, 2], [3, 2, 2, 2, 1, 1], [2, 1, 2, 1, 2, 3], [2, 1, 2, 3, 2, 1], [2, 3, 2, 1, 2, 1], [1, 1, 1, 3, 2, 3], [1, 3, 1, 1, 2, 3], [1, 3, 1, 3, 2, 1], [1, 1, 2, 3, 1, 3], [1, 3, 2, 1, 1, 3], [1, 3, 2, 3, 1, 1], [2, 1, 1, 3, 1, 3], [2, 3, 1, 1, 1, 3], [2, 3, 1, 3, 1, 1], [1, 1, 2, 1, 3, 3], [1, 1, 2, 3, 3, 1], [1, 3, 2, 1, 3, 1], [1, 1, 3, 1, 2, 3], [1, 1, 3, 3, 2, 1], [1, 3, 3, 1, 2, 1], [3, 1, 3, 1, 2, 1], [2, 1, 1, 3, 3, 1], [2, 3, 1, 1, 3, 1], [2, 1, 3, 1, 1, 3], [2, 1, 3, 3, 1, 1], [2, 1, 3, 1, 3, 1], [3, 1, 1, 1, 2, 3], [3, 1, 1, 3, 2, 1], [3, 3, 1, 1, 2, 1], [3, 1, 2, 1, 1, 3], [3, 1, 2, 3, 1, 1], [3, 3, 2, 1, 1, 1], [3, 1, 4, 1, 1, 1], [2, 2, 1, 4, 1, 1], [4, 3, 1, 1, 1, 1], [1, 1, 1, 2, 2, 4], [1, 1, 1, 4, 2, 2], [1, 2, 1, 1, 2, 4], [1, 2, 1, 4, 2, 1], [1, 4, 1, 1, 2, 2], [1, 4, 1, 2, 2, 1], [1, 1, 2, 2, 1, 4], [1, 1, 2, 4, 1, 2], [1, 2, 2, 1, 1, 4], [1, 2, 2, 4, 1, 1], [1, 4, 2, 1, 1, 2], [1, 4, 2, 2, 1, 1], [2, 4, 1, 2, 1, 1], [2, 2, 1, 1, 1, 4], [4, 1, 3, 1, 1, 1], [2, 4, 1, 1, 1, 2], [1, 3, 4, 1, 1, 1], [1, 1, 1, 2, 4, 2], [1, 2, 1, 1, 4, 2], [1, 2, 1, 2, 4, 1], [1, 1, 4, 2, 1, 2], [1, 2, 4, 1, 1, 2], [1, 2, 4, 2, 1, 1], [4, 1, 1, 2, 1, 2], [4, 2, 1, 1, 1, 2], [4, 2, 1, 2, 1, 1], [2, 1, 2, 1, 4, 1], [2, 1, 4, 1, 2, 1], [4, 1, 2, 1, 2, 1], [1, 1, 1, 1, 4, 3], [1, 1, 1, 3, 4, 1], [1, 3, 1, 1, 4, 1], [1, 1, 4, 1, 1, 3], [1, 1, 4, 3, 1, 1], [4, 1, 1, 1, 1, 3], [4, 1, 1, 3, 1, 1], [1, 1, 3, 1, 4, 1], [1, 1, 4, 1, 3, 1], [3, 1, 1, 1, 4, 1], [4, 1, 1, 1, 3, 1], [2, 1, 1, 4, 1, 2], [2, 1, 1, 2, 1, 4], [2, 1, 1, 2, 3, 2], [2, 3, 3, 1, 1, 1], ];
pub(crate) const STOP_TERMINATION: u8 = 2;
pub(crate) const START_A: u8 = 103;
pub(crate) const START_B: u8 = 104;
pub(crate) const START_C: u8 = 105;
pub(crate) const STOP: u8 = 106;
pub(crate) const FNC1: u8 = 102;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Subset {
A,
B,
C,
}
fn best_subset(input: &[u8]) -> Result<Subset, EncodeError> {
if input.len() >= 2
&& input.len().is_multiple_of(2)
&& input.iter().all(|&b| b.is_ascii_digit())
{
return Ok(Subset::C);
}
if input.iter().any(|&b| b < 0x20 || b == 0x7F) {
if input.iter().all(|&b| b <= 0x5F || b == 0x7F) {
return Ok(Subset::A);
}
return Err(EncodeError::InvalidInput(
"input contains characters not encodable in Code 128A".into(),
));
}
if input.iter().all(|&b| (0x20..=0x7E).contains(&b)) {
return Ok(Subset::B);
}
Err(EncodeError::InvalidInput(
"input contains characters outside the Code 128 character set".into(),
))
}
fn symbol_value_a(byte: u8) -> u8 {
if byte <= 0x1F { byte + 64 } else { byte - 0x20 }
}
fn symbol_value_b(byte: u8) -> u8 {
byte - 0x20
}
pub struct Code128;
impl BarcodeEncoder for Code128 {
type Input = str;
type Error = EncodeError;
fn encode(input: &str) -> Result<BarcodeOutput, EncodeError> {
if input.is_empty() {
return Err(EncodeError::InvalidInput(
"Code 128 input must not be empty".into(),
));
}
let bytes = input.as_bytes();
let subset = best_subset(bytes)?;
let mut symbol_indices: Vec<u8> = Vec::with_capacity(bytes.len() + 4);
let start = match subset {
Subset::A => START_A,
Subset::B => START_B,
Subset::C => START_C,
};
symbol_indices.push(start);
match subset {
Subset::A => {
for &b in bytes {
symbol_indices.push(symbol_value_a(b));
}
}
Subset::B => {
for &b in bytes {
symbol_indices.push(symbol_value_b(b));
}
}
Subset::C => {
let mut i = 0;
while i + 1 < bytes.len() {
let tens = bytes[i] - b'0';
let units = bytes[i + 1] - b'0';
symbol_indices.push(tens * 10 + units);
i += 2;
}
}
}
let check = compute_check(&symbol_indices);
symbol_indices.push(check);
symbol_indices.push(STOP);
let bars = symbols_to_bars(&symbol_indices);
Ok(BarcodeOutput::Linear(LinearBarcode {
bars,
height: 50,
text: Some(input.into()),
}))
}
fn symbology_name() -> &'static str {
"Code 128"
}
}
pub(crate) fn compute_check(symbols: &[u8]) -> u8 {
let start_val = symbols[0] as u32;
let weighted: u32 = symbols[1..]
.iter()
.enumerate()
.map(|(i, &s)| (i as u32 + 1) * s as u32)
.sum();
((start_val + weighted) % 103) as u8
}
pub(crate) fn symbols_to_bars(symbols: &[u8]) -> Vec<bool> {
let mut bars: Vec<bool> = Vec::new();
for &sym in symbols.iter() {
let is_stop = sym == STOP;
let pattern = &PATTERNS[sym as usize];
let mut dark = true;
for &width in pattern.iter() {
for _ in 0..width {
bars.push(dark);
}
dark = !dark;
}
if is_stop {
bars.extend(core::iter::repeat_n(true, STOP_TERMINATION as usize));
}
}
bars
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encode_subset_b_basic() {
let out = Code128::encode("Hello").unwrap();
match out {
BarcodeOutput::Linear(lb) => {
assert_eq!(lb.bars.len(), 90);
}
_ => panic!("expected linear"),
}
}
#[test]
fn test_encode_subset_c() {
let out = Code128::encode("123456").unwrap();
match out {
BarcodeOutput::Linear(lb) => {
assert_eq!(lb.bars.len(), 68);
}
_ => panic!("expected linear"),
}
}
#[test]
fn test_encode_subset_a_control() {
let input = "\x07ABC";
let out = Code128::encode(input).unwrap();
assert!(matches!(out, BarcodeOutput::Linear(_)));
}
#[test]
fn test_empty_input_error() {
assert!(Code128::encode("").is_err());
}
#[test]
fn test_invalid_high_byte() {
assert!(Code128::encode("caf\u{00E9}").is_err());
}
#[test]
fn test_check_computation() {
let out = Code128::encode("PJJ123C").unwrap();
assert!(matches!(out, BarcodeOutput::Linear(_)));
}
#[test]
fn test_symbology_name() {
assert_eq!(Code128::symbology_name(), "Code 128");
}
#[test]
fn test_svg_output() {
let svg = Code128::encode("Test").unwrap().to_svg_string();
assert!(svg.starts_with("<svg "));
}
}