#![forbid(unsafe_code)]
use crate::common::{errors::EncodeError, traits::BarcodeEncoder, types::Encoded};
use crate::linear::code128::{FNC1, MAX_SYMBOLS, START_B, STOP, compute_check, symbols_to_bars};
const MAX_SEGMENTS: usize = 32;
fn is_fixed_length_ai(ai: &str) -> bool {
matches!(
ai,
"00" | "01"
| "02"
| "03"
| "04"
| "11"
| "12"
| "13"
| "14"
| "15"
| "16"
| "17"
| "18"
| "19"
| "20"
| "31"
| "32"
| "33"
| "34"
| "35"
| "36"
| "41"
)
}
pub struct Gs1_128;
impl BarcodeEncoder for Gs1_128 {
type Input = str;
fn encode_into(input: &str, buf: &mut [bool]) -> Result<Encoded, EncodeError> {
if input.trim().is_empty() {
return Err(EncodeError::InvalidInput("GS1-128 input must not be empty"));
}
let mut segments = [AiSegment { ai: "", data: "" }; MAX_SEGMENTS];
let count = parse_gs1(input.trim(), &mut segments)?;
let len = build_barcode(&segments[..count], buf)?;
Ok(Encoded::Linear { len, height: 50 })
}
fn symbology_name() -> &'static str {
"GS1-128"
}
}
#[derive(Clone, Copy)]
struct AiSegment<'a> {
ai: &'a str,
data: &'a str,
}
fn parse_gs1<'a>(
input: &'a str,
out: &mut [AiSegment<'a>; MAX_SEGMENTS],
) -> Result<usize, EncodeError> {
let bytes = input.as_bytes();
let mut pos = 0;
let mut count = 0;
while pos < bytes.len() {
if bytes[pos] != b'(' {
return Err(EncodeError::InvalidInput("expected '(' at start of AI"));
}
pos += 1;
let ai_start = pos;
while pos < bytes.len() && bytes[pos] != b')' {
if !bytes[pos].is_ascii_digit() {
return Err(EncodeError::InvalidInput("AI must contain only digits"));
}
pos += 1;
}
if pos >= bytes.len() {
return Err(EncodeError::InvalidInput(
"unclosed '(' in AI specification",
));
}
let ai = &input[ai_start..pos];
pos += 1;
let data_start = pos;
while pos < bytes.len() && bytes[pos] != b'(' {
pos += 1;
}
let data = &input[data_start..pos];
if data.is_empty() {
return Err(EncodeError::InvalidInput("AI has no data"));
}
if count >= MAX_SEGMENTS {
return Err(EncodeError::DataTooLong);
}
out[count] = AiSegment { ai, data };
count += 1;
}
if count == 0 {
return Err(EncodeError::InvalidInput("no valid AIs found in input"));
}
Ok(count)
}
fn build_barcode(segments: &[AiSegment], buf: &mut [bool]) -> Result<usize, EncodeError> {
let mut symbols = [0u8; MAX_SYMBOLS];
let mut n = 0;
macro_rules! push {
($v:expr) => {{
if n >= MAX_SYMBOLS {
return Err(EncodeError::DataTooLong);
}
symbols[n] = $v;
n += 1;
}};
}
push!(START_B);
push!(FNC1);
for (i, seg) in segments.iter().enumerate() {
for byte in seg.ai.bytes() {
push!(byte - 0x20);
}
for &byte in seg.data.as_bytes() {
if (0x20..=0x7E).contains(&byte) {
push!(byte - 0x20);
}
}
if i + 1 < segments.len() && !is_fixed_length_ai(seg.ai) {
push!(FNC1);
}
}
let check = compute_check(&symbols[..n]);
push!(check);
push!(STOP);
symbols_to_bars(&symbols[..n], buf)
}
#[cfg(test)]
mod tests {
use super::*;
fn encode_len(input: &str) -> usize {
let mut buf = [false; 2048];
match Gs1_128::encode_into(input, &mut buf).unwrap() {
Encoded::Linear { len, .. } => len,
_ => panic!("expected linear"),
}
}
fn parse(input: &str) -> ([AiSegment<'_>; MAX_SEGMENTS], usize) {
let mut segs = [AiSegment { ai: "", data: "" }; MAX_SEGMENTS];
let n = parse_gs1(input, &mut segs).unwrap();
(segs, n)
}
#[test]
fn test_encode_single_ai() {
assert!(encode_len("(01)12345678901231") > 0);
}
#[test]
fn test_encode_multiple_ai() {
assert!(encode_len("(01)12345678901231(10)ABC123") > 0);
}
#[test]
fn test_parse_ai_digits_only() {
let (segs, n) = parse("(01)12345678901231");
assert_eq!(n, 1);
assert_eq!(segs[0].ai, "01");
assert_eq!(segs[0].data, "12345678901231");
}
#[test]
fn test_parse_multiple_ais() {
let (segs, n) = parse("(01)12345678901231(10)LOT123");
assert_eq!(n, 2);
assert_eq!(segs[0].ai, "01");
assert_eq!(segs[1].ai, "10");
assert_eq!(segs[1].data, "LOT123");
}
#[test]
fn test_invalid_no_parens() {
let mut buf = [false; 2048];
assert!(Gs1_128::encode_into("0112345678901231", &mut buf).is_err());
}
#[test]
fn test_empty_input() {
let mut buf = [false; 2048];
assert!(Gs1_128::encode_into("", &mut buf).is_err());
}
#[test]
fn test_symbology_name() {
assert_eq!(Gs1_128::symbology_name(), "GS1-128");
}
#[cfg(feature = "alloc")]
#[test]
fn test_svg_output() {
let svg = Gs1_128::encode("(01)12345678901231")
.unwrap()
.to_svg_string();
assert!(svg.starts_with("<svg "));
}
#[test]
fn test_fixed_length_ai() {
assert!(is_fixed_length_ai("01"));
assert!(is_fixed_length_ai("02"));
assert!(!is_fixed_length_ai("10"));
assert!(!is_fixed_length_ai("21"));
}
}