#![forbid(unsafe_code)]
extern crate alloc;
use alloc::{format, string::String, vec::Vec};
use crate::common::{
errors::EncodeError,
traits::BarcodeEncoder,
types::{BarcodeOutput, LinearBarcode},
};
const FINDER_PATTERN: [u8; 6] = [3, 1, 1, 1, 1, 3];
const DATABAR_TABLE: &[[u8; 4]] = &[
[1, 1, 1, 12], [1, 1, 2, 11], [1, 1, 3, 10], [1, 1, 4, 9], [1, 1, 5, 8], [1, 1, 6, 7], [1, 1, 7, 6], [1, 1, 8, 5], [1, 1, 9, 4], [1, 1, 10, 3], [1, 1, 11, 2], [1, 1, 12, 1], [1, 2, 1, 11], [1, 2, 2, 10], [1, 2, 3, 9], [1, 2, 4, 8], [1, 2, 5, 7], [1, 2, 6, 6], [1, 2, 7, 5], [1, 2, 8, 4], [1, 2, 9, 3], [1, 2, 10, 2], [1, 2, 11, 1], [1, 3, 1, 10], [1, 3, 2, 9], [1, 3, 3, 8], [1, 3, 4, 7], [1, 3, 5, 6], [1, 3, 6, 5], [1, 3, 7, 4], [1, 3, 8, 3], [1, 3, 9, 2], [1, 3, 10, 1], [1, 4, 1, 9], [1, 4, 2, 8], [1, 4, 3, 7], [1, 4, 4, 6], [1, 4, 5, 5], [1, 4, 6, 4], [1, 4, 7, 3], [1, 4, 8, 2], [1, 4, 9, 1], [1, 5, 1, 8], [1, 5, 2, 7], [1, 5, 3, 6], [1, 5, 4, 5], [1, 5, 5, 4], [1, 5, 6, 3], [1, 5, 7, 2], [1, 5, 8, 1], [1, 6, 1, 7], [1, 6, 2, 6], [1, 6, 3, 5], [1, 6, 4, 4], [1, 6, 5, 3], [1, 6, 6, 2], [1, 6, 7, 1], [1, 7, 1, 6], [1, 7, 2, 5], [1, 7, 3, 4], [1, 7, 4, 3], [1, 7, 5, 2], [1, 7, 6, 1], [1, 8, 1, 5], [1, 8, 2, 4], [1, 8, 3, 3], [1, 8, 4, 2], [1, 8, 5, 1], [1, 9, 1, 4], [1, 9, 2, 3], [1, 9, 3, 2], [1, 9, 4, 1], [1, 10, 1, 3], [1, 10, 2, 2], [1, 10, 3, 1], [1, 11, 1, 2], [1, 11, 2, 1], [1, 12, 1, 1], [2, 1, 1, 11], [2, 1, 2, 10], [2, 1, 3, 9], [2, 1, 4, 8], [2, 1, 5, 7], [2, 1, 6, 6], [2, 1, 7, 5], [2, 1, 8, 4], [2, 1, 9, 3], [2, 1, 10, 2], [2, 1, 11, 1], [2, 2, 1, 10], [2, 2, 2, 9], [2, 2, 3, 8], [2, 2, 4, 7], [2, 2, 5, 6], [2, 2, 6, 5], [2, 2, 7, 4], [2, 2, 8, 3], [2, 2, 9, 2], [2, 2, 10, 1], [2, 3, 1, 9], [2, 3, 2, 8], [2, 3, 3, 7], [2, 3, 4, 6], [2, 3, 5, 5], [2, 3, 6, 4], [2, 3, 7, 3], [2, 3, 8, 2], [2, 3, 9, 1], [2, 4, 1, 8], [2, 4, 2, 7], [2, 4, 3, 6], [2, 4, 4, 5], [2, 4, 5, 4], [2, 4, 6, 3], [2, 4, 7, 2], [2, 4, 8, 1], ];
pub struct DataBar;
impl BarcodeEncoder for DataBar {
type Input = str;
type Error = EncodeError;
fn encode(input: &str) -> Result<BarcodeOutput, EncodeError> {
let digits = parse_and_validate(input)?;
let bars = encode_bars(&digits);
let text = format_text(&digits);
Ok(BarcodeOutput::Linear(LinearBarcode {
bars,
height: 33,
text: Some(text),
}))
}
fn symbology_name() -> &'static str {
"GS1 DataBar"
}
}
fn parse_and_validate(input: &str) -> Result<[u8; 14], EncodeError> {
let trimmed = input.trim();
if !trimmed.chars().all(|c| c.is_ascii_digit()) {
return Err(EncodeError::InvalidInput(
"GS1 DataBar input must contain digits only".into(),
));
}
match trimmed.len() {
13 => {
let mut digits = [0u8; 14];
digits[0] = 0;
for (i, c) in trimmed.chars().enumerate() {
digits[i + 1] = c as u8 - b'0';
}
digits[13] = gtin_check_digit(&digits[..13]);
Ok(digits)
}
14 => {
let mut digits = [0u8; 14];
for (i, c) in trimmed.chars().enumerate() {
digits[i] = c as u8 - b'0';
}
let expected = gtin_check_digit(&digits[..13]);
if digits[13] != expected {
return Err(EncodeError::InvalidInput(format!(
"GTIN check digit mismatch: got {}, expected {expected}",
digits[13]
)));
}
Ok(digits)
}
_ => Err(EncodeError::InvalidInput(
"GS1 DataBar input must be 13 or 14 digits".into(),
)),
}
}
pub(crate) fn gtin_check_digit(digits: &[u8]) -> u8 {
let sum: u32 = digits
.iter()
.enumerate()
.map(|(i, &d)| {
let from_right = digits.len() - i; let weight = if from_right.is_multiple_of(2) {
1u32
} else {
3u32
};
weight * d as u32
})
.sum();
((10 - (sum % 10)) % 10) as u8
}
fn encode_bars(digits: &[u8; 14]) -> Vec<bool> {
let mut value: u64 = 0;
for &d in digits.iter() {
value = value * 10 + d as u64;
}
let left_value = value / 4_537_077;
let right_value = value % 4_537_077;
let mut bars: Vec<bool> = Vec::new();
encode_half(&mut bars, left_value, true);
bars.push(false);
encode_half(&mut bars, right_value, false);
bars
}
fn encode_half(bars: &mut Vec<bool>, value: u64, is_left: bool) {
if is_left {
bars.push(true);
}
let char_a = (value / 1349) as usize % 116;
let char_b = (value % 1349) as usize;
let char_b = if char_b >= 116 { 115 } else { char_b };
encode_databar_char(bars, char_a, true);
let mut dark = false;
for &w in &FINDER_PATTERN {
for _ in 0..w {
bars.push(dark);
}
dark = !dark;
}
encode_databar_char(bars, char_b, false);
if !is_left {
bars.push(true);
}
}
fn encode_databar_char(bars: &mut Vec<bool>, idx: usize, start_dark: bool) {
let pattern = &DATABAR_TABLE[idx.min(DATABAR_TABLE.len() - 1)];
let mut dark = start_dark;
for &w in pattern.iter() {
for _ in 0..w {
bars.push(dark);
}
dark = !dark;
}
}
fn format_text(digits: &[u8; 14]) -> String {
digits.iter().map(|d| (b'0' + d) as char).collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gtin_check_digit() {
let digits: [u8; 13] = [0, 0, 6, 1, 4, 1, 4, 1, 1, 2, 3, 4, 5];
assert_eq!(gtin_check_digit(&digits), 2);
}
#[test]
fn test_encode_14_digits() {
let out = DataBar::encode("00614141123452").unwrap();
assert!(matches!(out, BarcodeOutput::Linear(_)));
}
#[test]
fn test_encode_13_digits_auto_check() {
let out = DataBar::encode("0061414112345").unwrap();
assert!(matches!(out, BarcodeOutput::Linear(_)));
}
#[test]
fn test_invalid_check_digit() {
assert!(DataBar::encode("00614141123453").is_err());
}
#[test]
fn test_invalid_chars() {
assert!(DataBar::encode("0061414112345X").is_err());
}
#[test]
fn test_wrong_length() {
assert!(DataBar::encode("0061414").is_err());
}
#[test]
fn test_symbology_name() {
assert_eq!(DataBar::symbology_name(), "GS1 DataBar");
}
#[test]
fn test_svg_output() {
let svg = DataBar::encode("00614141123452").unwrap().to_svg_string();
assert!(svg.starts_with("<svg "));
}
}