#![allow(dead_code)]
pub(crate) const TAB_174: [u32; 40] = [
347, 0, 12, 5, 7, 2, 87, 4, 1387, 348, 10, 7, 5, 4, 52, 20, 2947, 1388, 8, 9, 4, 5, 30, 52,
3987, 2948, 6, 11, 3, 6, 10, 104, 4191, 3988, 4, 13, 1, 8, 1, 204,
];
pub(crate) const FINDER_WIDTHS: [u8; 60] = [
1, 8, 4, 1, 1, 1, 1, 4, 8, 1, 3, 6, 4, 1, 1, 1, 1, 4, 6, 3, 3, 4, 6, 1, 1, 1, 1, 6, 4, 3, 3, 2, 8, 1, 1, 1, 1, 8, 2, 3, 2, 6, 5, 1, 1, 1, 1, 5, 6, 2, 2, 2, 9, 1, 1, 1, 1, 9, 2, 2, ];
pub(crate) const FINDER_SEQ: [&[u8]; 10] = [
&[0, 1], &[0, 3, 2], &[0, 5, 2, 7], &[0, 9, 2, 7, 4], &[0, 9, 2, 7, 6, 11], &[0, 9, 2, 7, 8, 11, 10], &[0, 1, 2, 3, 4, 5, 6, 7], &[0, 1, 2, 3, 4, 5, 6, 9, 8], &[0, 1, 2, 3, 4, 5, 6, 9, 10, 11], &[0, 1, 2, 3, 4, 7, 6, 9, 8, 11, 10], ];
pub(crate) const CHECK_WEIGHTS: [i16; 192] = [
-1, -1, -1, -1, -1, -1, -1, -1, 77, 96, 32, 81, 27, 9, 3, 1, 20, 60, 180, 118, 143, 7, 21, 63,
205, 209, 140, 117, 39, 13, 145, 189, 193, 157, 49, 147, 19, 57, 171, 91, 132, 44, 85, 169,
197, 136, 186, 62, 185, 133, 188, 142, 4, 12, 36, 108, 50, 87, 29, 80, 97, 173, 128, 113, 150,
28, 84, 41, 123, 158, 52, 156, 166, 196, 206, 139, 187, 203, 138, 46, 76, 17, 51, 153, 37, 111,
122, 155, 146, 119, 110, 107, 106, 176, 129, 43, 16, 48, 144, 10, 30, 90, 59, 177, 164, 125,
112, 178, 200, 137, 116, 109, 70, 210, 208, 202, 184, 130, 179, 115, 190, 204, 68, 93, 31, 151,
191, 134, 148, 22, 66, 198, 172, 94, 71, 2, 40, 154, 192, 64, 162, 54, 18, 6, 120, 149, 25, 75,
14, 42, 126, 167, 175, 199, 207, 69, 23, 78, 26, 79, 103, 98, 83, 38, 114, 131, 182, 124, 159,
53, 88, 170, 127, 183, 61, 161, 55, 165, 73, 8, 24, 72, 5, 15, 89, 100, 174, 58, 160, 194, 135,
45,
];
pub(crate) const FILL_PAT: [u8; 5] = [0, 0, 1, 0, 0];
pub(crate) const SEP_PAD: [u8; 4] = [0, 0, 0, 0];
pub(crate) const FNC1: i32 = -1;
pub(crate) const LATCH_NUMERIC: i32 = -2;
pub(crate) const LATCH_ALPHANUMERIC: i32 = -3;
pub(crate) const LATCH_ISO646: i32 = -4;
pub(crate) fn to_bin(n: u64, width: usize) -> Result<String, crate::error::Error> {
if width < 64 && n >= (1u64 << width) {
return Err(crate::error::Error::InvalidData(format!(
"DataBar Expanded: value {n} doesn't fit in {width} bits",
)));
}
let mut s = String::with_capacity(width);
for k in (0..width).rev() {
s.push(if (n >> k) & 1 == 1 { '1' } else { '0' });
}
Ok(s)
}
pub(crate) fn conv12to40(digits: &[u8]) -> Result<String, crate::error::Error> {
if digits.len() != 12 || !digits.iter().all(|b| b.is_ascii_digit()) {
return Err(crate::error::Error::InvalidData(format!(
"DataBar Expanded: conv12to40 needs exactly 12 ASCII digits, got {:?}",
std::str::from_utf8(digits).unwrap_or("<non-utf8>"),
)));
}
let mut out = String::with_capacity(40);
for group in 0..4 {
let chunk = &digits[group * 3..group * 3 + 3];
let n: u64 = std::str::from_utf8(chunk).unwrap().parse().unwrap();
out.push_str(&to_bin(n, 10)?);
}
Ok(out)
}
pub(crate) fn conv13to44(digits: &[u8]) -> Result<String, crate::error::Error> {
if digits.len() != 13 || !digits.iter().all(|b| b.is_ascii_digit()) {
return Err(crate::error::Error::InvalidData(format!(
"DataBar Expanded: conv13to44 needs exactly 13 ASCII digits, got {:?}",
std::str::from_utf8(digits).unwrap_or("<non-utf8>"),
)));
}
let head: u64 = u64::from(digits[0] - b'0');
let mut out = to_bin(head, 4)?;
out.push_str(&conv12to40(&digits[1..])?);
Ok(out)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Tab174Row {
pub gs: u32,
pub elo: u32,
pub ele: u32,
pub mwo: u32,
pub mwe: u32,
pub to: u32,
pub te: u32,
}
pub(crate) fn tab174_row_for(value: u32) -> Tab174Row {
let mut j = 0;
while j < TAB_174.len() {
if value <= TAB_174[j] {
return Tab174Row {
gs: TAB_174[j + 1],
elo: TAB_174[j + 2],
ele: TAB_174[j + 3],
mwo: TAB_174[j + 4],
mwe: TAB_174[j + 5],
to: TAB_174[j + 6],
te: TAB_174[j + 7],
};
}
j += 8;
}
panic!("DataBar Expanded: value {value} exceeds TAB_174's max threshold (4191)");
}
pub(crate) fn extract_data_character(d: u32, segment_index: usize) -> [u8; 8] {
let row = tab174_row_for(d);
let v = d - row.gs;
let odd_val = v / row.te;
let even_val = v % row.te;
let dwo = crate::symbology::databar::get_rss_widths(
i64::from(odd_val),
i64::from(row.elo),
i64::from(row.mwo),
4,
true,
);
let dwe = crate::symbology::databar::get_rss_widths(
i64::from(even_val),
i64::from(row.ele),
i64::from(row.mwe),
4,
false,
);
let mut out = [0u8; 8];
if segment_index % 2 == 0 {
for j in 0..4 {
out[7 - j * 2] = dwo[j];
out[6 - j * 2] = dwe[j];
}
} else {
for j in 0..4 {
out[j * 2] = dwo[j];
out[j * 2 + 1] = dwe[j];
}
}
out
}
pub(crate) fn extract_checksum_character(checksum: u32) -> [u8; 8] {
let row = tab174_row_for(checksum);
let v = checksum - row.gs;
let odd_val = v / row.te;
let even_val = v % row.te;
let cwo = crate::symbology::databar::get_rss_widths(
i64::from(odd_val),
i64::from(row.elo),
i64::from(row.mwo),
4,
true,
);
let cwe = crate::symbology::databar::get_rss_widths(
i64::from(even_val),
i64::from(row.ele),
i64::from(row.mwe),
4,
false,
);
let mut out = [0u8; 8];
for i in 0..4 {
out[i * 2] = cwo[i];
out[i * 2 + 1] = cwe[i];
}
out
}
pub(crate) fn pack_12_bits(slice: &[u8]) -> u32 {
assert_eq!(slice.len(), 12, "pack_12_bits requires exactly 12 bits");
let mut v = 0u32;
for &bit in slice {
v = (v << 1) | u32::from(bit);
}
v
}
pub(crate) fn rembits(unpadded_bits: usize, segments: usize) -> usize {
let rounded = unpadded_bits.div_ceil(12) * 12;
let mut target = rounded.max(48);
let cw_count = target / 12;
if cw_count % segments == 1 {
target = (cw_count + 1) * 12;
}
target - unpadded_bits
}
pub(crate) fn assemble_binval(
linkage: u8,
method: &[u8],
vlf_len: usize,
cdf: &[u8],
gpf: &[u8],
final_mode: CharsetMode,
segments: usize,
) -> Vec<u8> {
debug_assert!(vlf_len == 0 || vlf_len == 2);
debug_assert!(linkage <= 1);
let bj = 1 + 12 + method.len() + vlf_len + cdf.len() + gpf.len();
let pad_len = rembits(bj, segments);
let total_chars = (bj + pad_len) / 12;
let vlf_vals = if vlf_len == 2 {
let v0 = (total_chars % 2) as u8;
let v1 = if total_chars <= 14 { 0 } else { 1 };
[v0, v1]
} else {
[0, 0]
};
let mut out = Vec::with_capacity(1 + method.len() + vlf_len + cdf.len() + gpf.len() + pad_len);
out.push(linkage);
out.extend_from_slice(method);
if vlf_len == 2 {
out.push(vlf_vals[0]);
out.push(vlf_vals[1]);
}
out.extend_from_slice(cdf);
out.extend_from_slice(gpf);
if pad_len > 0 {
let shift = match final_mode {
CharsetMode::Numeric => 4,
_ => 0,
};
for i in 0..pad_len {
let bit = if i < shift {
0
} else {
FILL_PAT[(i - shift) % 5]
};
out.push(bit);
}
}
out
}
#[derive(Debug, Clone)]
pub(crate) struct MethodOutput {
pub method_bits: Vec<u8>,
pub vlf_len: usize,
pub cdf: Vec<u8>,
pub gpf_prefix_bytes: Vec<u8>,
pub consumed_ais: usize,
}
fn parse_two_ai_compressed_inputs<'a>(
elements: &'a [crate::util::gs1::Element],
expected_second_ai: &str,
) -> Option<(&'a [u8], u64)> {
if elements.len() != 2 || elements[0].ai != "01" || elements[1].ai != expected_second_ai {
return None;
}
let v0 = &elements[0].data;
let v1 = &elements[1].data;
if v0.len() != 14
|| !v0.bytes().all(|b| b.is_ascii_digit())
|| v0.as_bytes()[0] != b'9'
|| v1.len() != 6
|| !v1.bytes().all(|b| b.is_ascii_digit())
{
return None;
}
let v1_int: u64 = v1.parse().ok()?;
Some((&v0.as_bytes()[1..13], v1_int))
}
pub(crate) fn encode_method_0100(
elements: &[crate::util::gs1::Element],
) -> Result<Option<MethodOutput>, crate::error::Error> {
let Some((gtin12, v1)) = parse_two_ai_compressed_inputs(elements, "3103") else {
return Ok(None);
};
if v1 > 32767 {
return Ok(None);
}
let mut cdf_str = conv12to40(gtin12)?;
cdf_str.push_str(&to_bin(v1, 15)?);
let cdf: Vec<u8> = cdf_str.bytes().map(|b| b - b'0').collect();
Ok(Some(MethodOutput {
method_bits: vec![0, 1, 0, 0],
vlf_len: 0,
cdf,
gpf_prefix_bytes: Vec::new(),
consumed_ais: 2,
}))
}
pub(crate) fn encode_method_0101(
elements: &[crate::util::gs1::Element],
) -> Result<Option<MethodOutput>, crate::error::Error> {
let (gtin12, value_for_tobin) =
if let Some((g, v)) = parse_two_ai_compressed_inputs(elements, "3202") {
if v > 9999 {
return Ok(None);
}
(g, v)
} else if let Some((g, v)) = parse_two_ai_compressed_inputs(elements, "3203") {
if v > 22767 {
return Ok(None);
}
(g, v + 10000)
} else {
return Ok(None);
};
let mut cdf_str = conv12to40(gtin12)?;
cdf_str.push_str(&to_bin(value_for_tobin, 15)?);
let cdf: Vec<u8> = cdf_str.bytes().map(|b| b - b'0').collect();
Ok(Some(MethodOutput {
method_bits: vec![0, 1, 0, 1],
vlf_len: 0,
cdf,
gpf_prefix_bytes: Vec::new(),
consumed_ais: 2,
}))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum CharsetMode {
Numeric,
Alphanumeric,
Iso646,
}
pub(crate) const FNC1_SENTINEL_BYTE: u8 = b'^';
pub(crate) fn numeric_pair_bits(c0: u8, c1: u8) -> Option<(u32, u8)> {
fn digit(c: u8) -> Option<u32> {
match c {
b'0'..=b'9' => Some(u32::from(c - b'0')),
FNC1_SENTINEL_BYTE => Some(10),
_ => None,
}
}
let g = digit(c0)? * 11 + digit(c1)?;
Some((g + 8, 7))
}
pub(crate) fn alphanumeric_byte_bits(c: u8) -> Option<(u32, u8)> {
match c {
b'0'..=b'9' => Some((u32::from(c) - 43, 5)),
FNC1_SENTINEL_BYTE => Some((15, 5)),
b'A'..=b'Z' => Some((u32::from(c) - 33, 6)),
b'*' => Some((58, 6)),
b',' | b'-' | b'.' | b'/' => Some((u32::from(c) + 15, 6)),
_ => None,
}
}
pub(crate) fn iso646_byte_bits(c: u8) -> Option<(u32, u8)> {
match c {
b'0'..=b'9' => Some((u32::from(c) - 43, 5)),
FNC1_SENTINEL_BYTE => Some((15, 5)),
b'A'..=b'Z' => Some((u32::from(c) - 1, 7)),
b'a'..=b'z' => Some((u32::from(c) - 7, 7)),
b'!' => Some((232, 8)),
b'"' => Some((233, 8)),
b'%' | b'&' | b'\'' | b'(' | b')' | b'*' | b'+' | b',' | b'-' | b'.' | b'/' => {
Some((u32::from(c) + 197, 8))
}
b':' | b';' | b'<' | b'=' | b'>' | b'?' => Some((u32::from(c) + 187, 8)),
b'_' => Some((251, 8)),
b' ' => Some((252, 8)),
_ => None,
}
}
const LATCH_FROM_NUMERIC_TO_ALPHA: (u32, u8) = (0, 4);
const LATCH_FROM_ALPHA_TO_NUMERIC: (u32, u8) = (0, 3);
const LATCH_FROM_ALPHA_TO_ISO646: (u32, u8) = (4, 5);
const LATCH_FROM_ISO646_TO_NUMERIC: (u32, u8) = (0, 3);
const LATCH_FROM_ISO646_TO_ALPHA: (u32, u8) = (4, 5);
struct BitBuf {
bits: Vec<u8>,
}
impl BitBuf {
fn new() -> Self {
Self { bits: Vec::new() }
}
fn push(&mut self, value: u32, width: u8) {
for k in (0..width).rev() {
self.bits.push(u8::try_from((value >> k) & 1).unwrap());
}
}
fn len(&self) -> usize {
self.bits.len()
}
fn into_bits(self) -> Vec<u8> {
self.bits
}
}
#[derive(Debug, Clone)]
pub(crate) struct GpfLookahead {
pub numeric_runs: Vec<u32>,
pub alphanumeric_runs: Vec<u32>,
pub next_iso646_only: Vec<u32>,
}
pub(crate) fn compute_gpf_lookahead(gpf: &[u8]) -> GpfLookahead {
let n = gpf.len();
let mut numeric_runs = vec![0u32; n + 2];
let mut alphanumeric_runs = vec![0u32; n + 1];
let mut next_iso646_only = vec![9999u32; n + 1];
for i in (0..n).rev() {
let c = gpf[i];
let c_next = if i + 1 < n { Some(gpf[i + 1]) } else { None };
let numeric_pair_ok = match c_next {
Some(n2) => numeric_pair_bits(c, n2).is_some(),
None => false,
};
numeric_runs[i] = if numeric_pair_ok {
numeric_runs[i + 2] + 2
} else {
0
};
alphanumeric_runs[i] = if alphanumeric_byte_bits(c).is_some() {
alphanumeric_runs[i + 1] + 1
} else {
0
};
next_iso646_only[i] =
if iso646_byte_bits(c).is_some() && alphanumeric_byte_bits(c).is_none() {
0
} else {
next_iso646_only[i + 1].saturating_add(1)
};
}
GpfLookahead {
numeric_runs,
alphanumeric_runs,
next_iso646_only,
}
}
pub(crate) fn encode_general_purpose(
gpf: &[u8],
bits_before_gpf: usize,
segments: usize,
) -> Result<(Vec<u8>, CharsetMode), crate::error::Error> {
let look = compute_gpf_lookahead(gpf);
let mut out = BitBuf::new();
let mut i = 0usize;
let mut mode = CharsetMode::Numeric;
while i < gpf.len() {
match mode {
CharsetMode::Numeric => {
if i + 1 < gpf.len() {
let c0 = gpf[i];
let c1 = gpf[i + 1];
if let Some((value, width)) = numeric_pair_bits(c0, c1) {
out.push(value, width);
i += 2;
} else {
out.push(LATCH_FROM_NUMERIC_TO_ALPHA.0, LATCH_FROM_NUMERIC_TO_ALPHA.1);
mode = CharsetMode::Alphanumeric;
}
} else {
let c = gpf[i];
if !c.is_ascii_digit() {
out.push(LATCH_FROM_NUMERIC_TO_ALPHA.0, LATCH_FROM_NUMERIC_TO_ALPHA.1);
mode = CharsetMode::Alphanumeric;
} else {
let rem = rembits(bits_before_gpf + out.len(), segments);
if (4..=6).contains(&rem) {
let value = u32::from(c) - 47;
out.push(value, rem as u8);
break;
}
let (value, width) = numeric_pair_bits(c, FNC1_SENTINEL_BYTE)
.expect("digit + FNC1 always encodable in numeric");
out.push(value, width);
i += 1;
}
}
}
CharsetMode::Alphanumeric => {
let c = gpf[i];
if c == FNC1_SENTINEL_BYTE {
let (value, width) = alphanumeric_byte_bits(c).unwrap();
out.push(value, width);
mode = CharsetMode::Numeric;
i += 1;
continue;
}
if iso646_byte_bits(c).is_some() && alphanumeric_byte_bits(c).is_none() {
out.push(LATCH_FROM_ALPHA_TO_ISO646.0, LATCH_FROM_ALPHA_TO_ISO646.1);
mode = CharsetMode::Iso646;
continue;
}
let nr = look.numeric_runs[i];
if nr >= 6 || (nr >= 4 && i + nr as usize == gpf.len()) {
out.push(LATCH_FROM_ALPHA_TO_NUMERIC.0, LATCH_FROM_ALPHA_TO_NUMERIC.1);
mode = CharsetMode::Numeric;
continue;
}
let (value, width) = alphanumeric_byte_bits(c).ok_or_else(|| {
crate::error::Error::InvalidData(format!(
"DataBar Expanded: byte 0x{c:02x} not encodable in alphanumeric mode",
))
})?;
out.push(value, width);
i += 1;
}
CharsetMode::Iso646 => {
let c = gpf[i];
if c == FNC1_SENTINEL_BYTE {
let (value, width) = iso646_byte_bits(c).unwrap();
out.push(value, width);
mode = CharsetMode::Numeric;
i += 1;
continue;
}
let nr = look.numeric_runs[i];
let nio = look.next_iso646_only[i];
if nr >= 4 && nio >= 10 {
out.push(
LATCH_FROM_ISO646_TO_NUMERIC.0,
LATCH_FROM_ISO646_TO_NUMERIC.1,
);
mode = CharsetMode::Numeric;
continue;
}
let ar = look.alphanumeric_runs[i];
if ar >= 5 && nio >= 10 {
out.push(LATCH_FROM_ISO646_TO_ALPHA.0, LATCH_FROM_ISO646_TO_ALPHA.1);
mode = CharsetMode::Alphanumeric;
continue;
}
let (value, width) = iso646_byte_bits(c).ok_or_else(|| {
crate::error::Error::InvalidData(format!(
"DataBar Expanded: byte 0x{c:02x} not encodable in iso646 mode",
))
})?;
out.push(value, width);
i += 1;
}
}
}
Ok((out.into_bits(), mode))
}
fn match_310x_or_320x(ai: &str) -> Option<(bool, u8)> {
let bytes = ai.as_bytes();
if bytes.len() != 4 {
return None;
}
if bytes[3].is_ascii_digit() {
if &bytes[..3] == b"310" {
return Some((false, bytes[3] - b'0'));
}
if &bytes[..3] == b"320" {
return Some((true, bytes[3] - b'0'));
}
}
None
}
const DATE_AI_TABLE: &[(&str, u8)] = &[("11", 0), ("13", 1), ("15", 2), ("17", 3)];
pub(crate) fn encode_method_0111(
elements: &[crate::util::gs1::Element],
) -> Result<Option<MethodOutput>, crate::error::Error> {
if !(2..=3).contains(&elements.len()) || elements[0].ai != "01" {
return Ok(None);
}
let Some((is_320x, last_digit)) = match_310x_or_320x(&elements[1].ai) else {
return Ok(None);
};
let v0 = &elements[0].data;
let v1 = &elements[1].data;
if v0.len() != 14
|| !v0.bytes().all(|b| b.is_ascii_digit())
|| v0.as_bytes()[0] != b'9'
|| v1.len() != 6
|| !v1.bytes().all(|b| b.is_ascii_digit())
{
return Ok(None);
}
let v1_int: u64 = v1.parse().unwrap();
if v1_int > 99999 {
return Ok(None);
}
let (date_kind, date_encoded) = if elements.len() == 3 {
let Some(&(_, kind)) = DATE_AI_TABLE.iter().find(|(ai, _)| *ai == elements[2].ai) else {
return Ok(None);
};
let d = &elements[2].data;
if d.len() != 6 || !d.bytes().all(|b| b.is_ascii_digit()) {
return Ok(None);
}
let yy: u32 = d[0..2].parse().unwrap();
let mm: u32 = d[2..4].parse().unwrap();
let dd: u32 = d[4..6].parse().unwrap();
if !(1..=12).contains(&mm) || dd > 31 {
return Ok(None);
}
(kind, yy * 384 + (mm - 1) * 32 + dd)
} else {
(0u8, 38400u32)
};
let method_bits = vec![
0u8,
1,
1,
1,
(date_kind >> 1) & 1,
date_kind & 1,
u8::from(is_320x),
];
let mut cdf_str = conv12to40(&v0.as_bytes()[1..13])?;
let combined = u64::from(last_digit) * 100_000 + (v1_int % 100_000);
cdf_str.push_str(&to_bin(combined, 20)?);
cdf_str.push_str(&to_bin(u64::from(date_encoded), 16)?);
let cdf: Vec<u8> = cdf_str.bytes().map(|b| b - b'0').collect();
Ok(Some(MethodOutput {
method_bits,
vlf_len: 0,
cdf,
gpf_prefix_bytes: Vec::new(),
consumed_ais: elements.len(),
}))
}
pub(crate) fn encode_method_01100(
elements: &[crate::util::gs1::Element],
) -> Result<Option<MethodOutput>, crate::error::Error> {
if elements.len() < 2 || elements[0].ai != "01" {
return Ok(None);
}
let ai1 = &elements[1].ai;
let last_digit = match ai1.as_str() {
"3920" => 0u8,
"3921" => 1,
"3922" => 2,
"3923" => 3,
_ => return Ok(None),
};
let v0 = &elements[0].data;
if v0.len() != 14 || !v0.bytes().all(|b| b.is_ascii_digit()) || v0.as_bytes()[0] != b'9' {
return Ok(None);
}
let v1_bytes = elements[1].data.as_bytes();
if v1_bytes.is_empty() || !v1_bytes.iter().all(|b| b.is_ascii_digit()) {
return Ok(None);
}
let mut cdf_str = conv12to40(&v0.as_bytes()[1..13])?;
cdf_str.push_str(&to_bin(u64::from(last_digit), 2)?);
let cdf: Vec<u8> = cdf_str.bytes().map(|b| b - b'0').collect();
let mut gpf_prefix = v1_bytes.to_vec();
if elements.len() > 2 {
gpf_prefix.push(FNC1_SENTINEL_BYTE);
}
Ok(Some(MethodOutput {
method_bits: vec![0, 1, 1, 0, 0],
vlf_len: 2,
cdf,
gpf_prefix_bytes: gpf_prefix,
consumed_ais: 2,
}))
}
pub(crate) fn encode_method_01101(
elements: &[crate::util::gs1::Element],
) -> Result<Option<MethodOutput>, crate::error::Error> {
if elements.len() < 2 || elements[0].ai != "01" {
return Ok(None);
}
let ai1 = &elements[1].ai;
let last_digit = match ai1.as_str() {
"3930" => 0u8,
"3931" => 1,
"3932" => 2,
"3933" => 3,
_ => return Ok(None),
};
let v0 = &elements[0].data;
if v0.len() != 14 || !v0.bytes().all(|b| b.is_ascii_digit()) || v0.as_bytes()[0] != b'9' {
return Ok(None);
}
let v1_bytes = elements[1].data.as_bytes();
if v1_bytes.len() < 3 || !v1_bytes[..3].iter().all(|b| b.is_ascii_digit()) {
return Ok(None);
}
let currency: u64 = std::str::from_utf8(&v1_bytes[..3])
.unwrap()
.parse()
.unwrap();
let mut cdf_str = conv12to40(&v0.as_bytes()[1..13])?;
cdf_str.push_str(&to_bin(u64::from(last_digit), 2)?);
cdf_str.push_str(&to_bin(currency, 10)?);
let cdf: Vec<u8> = cdf_str.bytes().map(|b| b - b'0').collect();
let mut gpf_prefix = v1_bytes[3..].to_vec();
if elements.len() > 2 {
gpf_prefix.push(FNC1_SENTINEL_BYTE);
}
Ok(Some(MethodOutput {
method_bits: vec![0, 1, 1, 0, 1],
vlf_len: 2,
cdf,
gpf_prefix_bytes: gpf_prefix,
consumed_ais: 2,
}))
}
pub(crate) fn encode_method_1(
elements: &[crate::util::gs1::Element],
) -> Result<Option<MethodOutput>, crate::error::Error> {
if elements.is_empty() || elements[0].ai != "01" {
return Ok(None);
}
let data = &elements[0].data;
if data.len() != 14 || !data.bytes().all(|b| b.is_ascii_digit()) {
return Ok(None);
}
let cdf_str = conv13to44(&data.as_bytes()[..13])?;
let cdf: Vec<u8> = cdf_str.bytes().map(|b| b - b'0').collect();
Ok(Some(MethodOutput {
method_bits: vec![1],
vlf_len: 2,
cdf,
gpf_prefix_bytes: Vec::new(),
consumed_ais: 1,
}))
}
pub(crate) fn build_gpf_bytes(
elements: &[crate::util::gs1::Element],
) -> Result<Vec<u8>, crate::error::Error> {
let mut out = Vec::new();
for (i, e) in elements.iter().enumerate() {
out.extend_from_slice(e.ai.as_bytes());
out.extend_from_slice(e.data.as_bytes());
let variable = crate::util::gs1::ai_is_variable_length(&e.ai).ok_or_else(|| {
crate::error::Error::InvalidData(format!(
"DataBar Expanded: AI ({}) not in the GS1 table",
e.ai,
))
})?;
if variable && i + 1 < elements.len() {
out.push(FNC1_SENTINEL_BYTE);
}
}
Ok(out)
}
pub(crate) fn encode_method_00_skeleton(
_elements: &[crate::util::gs1::Element],
) -> Result<MethodOutput, crate::error::Error> {
Ok(MethodOutput {
method_bits: vec![0, 0],
vlf_len: 2,
cdf: Vec::new(),
gpf_prefix_bytes: Vec::new(),
consumed_ais: 0,
})
}
pub(crate) const DEFAULT_SEGMENTS: usize = 22;
pub(crate) const STACKED_SEGMENTS: usize = 4;
pub fn encode(
input: &str,
linkage: bool,
) -> Result<crate::encoding::LinearPattern, crate::error::Error> {
let elements = crate::util::gs1::parse(input).map_err(|e| {
crate::error::Error::InvalidData(format!("DataBar Expanded: GS1 parse failed: {e}",))
})?;
let method = if let Some(m) = encode_method_0100(&elements)? {
m
} else if let Some(m) = encode_method_0101(&elements)? {
m
} else if let Some(m) = encode_method_0111(&elements)? {
m
} else if let Some(m) = encode_method_01100(&elements)? {
m
} else if let Some(m) = encode_method_01101(&elements)? {
m
} else if let Some(m) = encode_method_1(&elements)? {
m
} else {
encode_method_00_skeleton(&elements)?
};
let mut gpf_bytes = method.gpf_prefix_bytes.clone();
for (i, e) in elements[method.consumed_ais..].iter().enumerate() {
gpf_bytes.extend_from_slice(e.ai.as_bytes());
gpf_bytes.extend_from_slice(e.data.as_bytes());
let remaining = elements.len() - method.consumed_ais;
let is_last = i + 1 == remaining;
let variable = crate::util::gs1::ai_is_variable_length(&e.ai).ok_or_else(|| {
crate::error::Error::InvalidData(format!(
"DataBar Expanded: AI ({}) not in the GS1 table",
e.ai,
))
})?;
if variable && !is_last {
gpf_bytes.push(FNC1_SENTINEL_BYTE);
}
}
let (gpf_bits, final_mode) = if gpf_bytes.is_empty() {
(Vec::new(), CharsetMode::Numeric)
} else {
let bits_before_gpf = 1 + 12 + method.method_bits.len() + method.vlf_len + method.cdf.len();
encode_general_purpose(&gpf_bytes, bits_before_gpf, DEFAULT_SEGMENTS)?
};
let (dxw, seq, _datalen) =
encode_to_dxw_and_seq(linkage, &method, &gpf_bits, final_mode, DEFAULT_SEGMENTS)?;
let sbs = assemble_sbs(&dxw, seq);
Ok(crate::encoding::LinearPattern {
bars: sbs,
text: None,
})
}
pub fn encode_stacked(
input: &str,
linkage: bool,
) -> Result<crate::encoding::BitMatrix, crate::error::Error> {
let elements = crate::util::gs1::parse(input).map_err(|e| {
crate::error::Error::InvalidData(
format!("DataBar Expanded Stacked: GS1 parse failed: {e}",),
)
})?;
let method = if let Some(m) = encode_method_0100(&elements)? {
m
} else if let Some(m) = encode_method_0101(&elements)? {
m
} else if let Some(m) = encode_method_0111(&elements)? {
m
} else if let Some(m) = encode_method_01100(&elements)? {
m
} else if let Some(m) = encode_method_01101(&elements)? {
m
} else if let Some(m) = encode_method_1(&elements)? {
m
} else {
encode_method_00_skeleton(&elements)?
};
let mut gpf_bytes = method.gpf_prefix_bytes.clone();
for (i, e) in elements[method.consumed_ais..].iter().enumerate() {
gpf_bytes.extend_from_slice(e.ai.as_bytes());
gpf_bytes.extend_from_slice(e.data.as_bytes());
let remaining = elements.len() - method.consumed_ais;
let is_last = i + 1 == remaining;
let variable = crate::util::gs1::ai_is_variable_length(&e.ai).ok_or_else(|| {
crate::error::Error::InvalidData(format!(
"DataBar Expanded Stacked: AI ({}) not in the GS1 table",
e.ai,
))
})?;
if variable && !is_last {
gpf_bytes.push(FNC1_SENTINEL_BYTE);
}
}
let (gpf_bits, final_mode) = if gpf_bytes.is_empty() {
(Vec::new(), CharsetMode::Numeric)
} else {
let bits_before_gpf = 1 + 12 + method.method_bits.len() + method.vlf_len + method.cdf.len();
encode_general_purpose(&gpf_bytes, bits_before_gpf, STACKED_SEGMENTS)?
};
let (dxw, seq, datalen) =
encode_to_dxw_and_seq(linkage, &method, &gpf_bits, final_mode, STACKED_SEGMENTS)?;
let data_chars = datalen - 1;
let fxw: Vec<[u8; 5]> = seq
.iter()
.map(|&id| {
let base = id as usize * 5;
[
FINDER_WIDTHS[base],
FINDER_WIDTHS[base + 1],
FINDER_WIDTHS[base + 2],
FINDER_WIDTHS[base + 3],
FINDER_WIDTHS[base + 4],
]
})
.collect();
let _ = data_chars;
let numrows = datalen.div_ceil(STACKED_SEGMENTS);
let row_sbs: Vec<Vec<u8>> = (0..numrows)
.map(|r| {
let mut row: Vec<u8> = Vec::new();
if STACKED_SEGMENTS % 4 != 0 && r % 2 == 1 {
row.push(0);
}
row.push(1);
row.push(1);
for q in 0..STACKED_SEGMENTS {
let pos = q + r * STACKED_SEGMENTS;
if pos < datalen {
row.extend_from_slice(&dxw[pos]);
if pos % 2 == 0 {
row.extend_from_slice(&fxw[pos / 2]);
}
}
}
row.push(1);
row.push(1);
row
})
.collect();
let mut row_bits: Vec<Vec<u8>> = row_sbs.iter().map(|sbs| expand_row_sbs(sbs)).collect();
let mut row_seps: Vec<Vec<u8>> = row_bits.iter().map(|r| compute_row_sep(r)).collect();
if STACKED_SEGMENTS % 4 == 0 {
let row0_len = row_bits[0].len();
for r in 0..numrows {
if r % 2 != 1 {
continue;
}
let finder_count = count_finder_positions(row_bits[r].len());
let length_differs = row_bits[r].len() != row0_len;
if length_differs && finder_count % 2 == 1 {
let mut new_row = vec![0u8];
new_row.extend_from_slice(&row_bits[r]);
row_bits[r] = new_row;
let mut new_sep = vec![0u8];
new_sep.extend_from_slice(&row_seps[r]);
row_seps[r] = new_sep;
} else {
row_bits[r].reverse();
row_seps[r].reverse();
}
}
}
let pixx = row_bits[0].len();
if numrows > 1 {
let last = numrows - 1;
if row_bits[last].len() < pixx {
row_bits[last].resize(pixx, 0);
}
if row_seps[last].len() < pixx {
row_seps[last].resize(pixx, 0);
}
}
let inter_sep: Vec<u8> = build_inter_row_sep(pixx);
let mut strips: Vec<&[u8]> = Vec::new();
let mut row_heights: Vec<usize> = Vec::new();
const DATA_ROW_HEIGHT: usize = 34;
const SEP_HEIGHT: usize = 1;
for r in 0..numrows {
if r != 0 {
strips.push(&row_seps[r]);
row_heights.push(SEP_HEIGHT);
}
strips.push(&row_bits[r]);
row_heights.push(DATA_ROW_HEIGHT);
if r + 1 != numrows {
strips.push(&row_seps[r]);
row_heights.push(SEP_HEIGHT);
strips.push(&inter_sep);
row_heights.push(SEP_HEIGHT);
}
}
let total_height: usize = row_heights.iter().sum();
let mut bm = crate::encoding::BitMatrix::new(pixx, total_height);
let mut y = 0;
for (strip, &height) in strips.iter().zip(row_heights.iter()) {
for _ in 0..height {
for (x, &bit) in strip.iter().enumerate() {
bm.set(x, y, bit != 0);
}
y += 1;
}
}
debug_assert_eq!(y, total_height);
Ok(bm)
}
fn expand_row_sbs(sbs: &[u8]) -> Vec<u8> {
let total: usize = sbs.iter().map(|&w| w as usize).sum();
let mut out = Vec::with_capacity(total);
for (i, &w) in sbs.iter().enumerate() {
let bit = if i % 2 == 0 { 0u8 } else { 1u8 };
for _ in 0..w {
out.push(bit);
}
}
out
}
fn compute_row_sep(row: &[u8]) -> Vec<u8> {
let n = row.len();
let mut sep: Vec<u8> = row.iter().map(|&b| 1 - b).collect();
let finder_positions: Vec<usize> = {
let mut v = Vec::new();
if n >= 13 {
let max = n - 13;
let mut p = 19;
while p <= max {
v.push(p);
p += 98;
}
let mut p = 68;
while p <= max {
v.push(p);
p += 98;
}
}
v
};
for &p in &finder_positions {
for i in p..=(p + 14).min(n - 1) {
let bit = if row[i] == 1 {
0
} else if i > 0 && (row[i - 1] == 1 || sep[i - 1] == 0) {
1
} else {
0
};
sep[i] = bit;
}
}
for slot in sep.iter_mut().take(4) {
*slot = 0;
}
if n >= 4 {
for slot in sep[n - 4..].iter_mut() {
*slot = 0;
}
}
sep
}
fn count_finder_positions(n: usize) -> usize {
if n < 13 {
return 0;
}
let max = n - 13;
let mut c = 0;
let mut p = 19;
while p <= max {
c += 1;
p += 98;
}
let mut p = 68;
while p <= max {
c += 1;
p += 98;
}
c
}
fn build_inter_row_sep(pixx: usize) -> Vec<u8> {
let mut sep: Vec<u8> = (0..pixx).map(|i| (i % 2) as u8).collect();
for slot in sep.iter_mut().take(4) {
*slot = 0;
}
if pixx >= 4 {
for slot in sep[pixx - 4..].iter_mut() {
*slot = 0;
}
}
sep
}
pub(crate) fn encode_to_dxw_and_seq(
linkage: bool,
method: &MethodOutput,
gpf_bits: &[u8],
final_mode: CharsetMode,
segments: usize,
) -> Result<(Vec<[u8; 8]>, &'static [u8], usize), crate::error::Error> {
let binval = assemble_binval(
u8::from(linkage),
&method.method_bits,
method.vlf_len,
&method.cdf,
gpf_bits,
final_mode,
segments,
);
debug_assert_eq!(binval.len() % 12, 0);
let data_chars = binval.len() / 12;
if !(2..=21).contains(&data_chars) {
return Err(crate::error::Error::InvalidData(format!(
"DataBar Expanded: encoded data needs {data_chars} symbol \
characters, but the symbology holds only 2..=21 (max 21 data \
+ 1 checksum) — input too long",
)));
}
let data_dxw: Vec<[u8; 8]> = (0..data_chars)
.map(|x| {
let d = pack_12_bits(&binval[x * 12..x * 12 + 12]);
extract_data_character(d, x)
})
.collect();
let seq = finder_sequence(data_chars);
let checksum = compute_checksum(&data_dxw, seq);
let mut dxw: Vec<[u8; 8]> = Vec::with_capacity(data_chars + 1);
dxw.push(extract_checksum_character(checksum));
dxw.extend(data_dxw);
let total_chars = dxw.len();
Ok((dxw, seq, total_chars))
}
pub(crate) fn finder_sequence(data_chars: usize) -> &'static [u8] {
assert!(
(2..=21).contains(&data_chars),
"DataBar Expanded data_chars must be 2..=21, got {data_chars}",
);
FINDER_SEQ[(data_chars - 2) / 2]
}
pub(crate) fn assemble_sbs(dxw: &[[u8; 8]], seq: &[u8]) -> Vec<u8> {
let datalen = dxw.len();
debug_assert_eq!(
seq.len(),
datalen.div_ceil(2),
"seq length should match datalen/2",
);
let mut sbs: Vec<u8> = Vec::with_capacity(1 + 8 * datalen + 5 * seq.len() + 2);
sbs.push(1); for (i, dw) in dxw.iter().enumerate() {
sbs.extend_from_slice(dw);
if i % 2 == 0 {
let finder_id = seq[i / 2] as usize;
sbs.extend_from_slice(&FINDER_WIDTHS[finder_id * 5..finder_id * 5 + 5]);
}
}
sbs.push(1); sbs.push(1); sbs
}
pub(crate) fn compute_checksum(dxw: &[[u8; 8]], seq: &[u8]) -> u32 {
let datalen = dxw.len();
let mut widths = Vec::with_capacity(datalen * 8);
for w in dxw {
widths.extend_from_slice(w);
}
let mut weights: Vec<i16> = Vec::with_capacity(seq.len() * 16);
for &finder_id in seq {
let base = (finder_id as usize) * 16;
weights.extend_from_slice(&CHECK_WEIGHTS[base..base + 16]);
}
let weights = &weights[8..];
let mut checksum: i32 = 0;
for (i, &w) in widths.iter().enumerate() {
checksum += i32::from(w) * i32::from(weights[i]);
}
let cs = checksum.rem_euclid(211);
let biased = cs + (datalen as i32 - 3) * 211;
u32::try_from(biased).expect("checksum bias overflow shouldn't happen for valid input")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tab_174_has_five_rows_of_eight_fields() {
assert_eq!(TAB_174.len(), 40);
assert_eq!(TAB_174[0], 347);
assert_eq!(TAB_174[8], 1387);
assert_eq!(TAB_174[16], 2947);
assert_eq!(TAB_174[24], 3987);
assert_eq!(TAB_174[32], 4191);
for row in 1..5 {
let prev_max = TAB_174[(row - 1) * 8];
let this_min = TAB_174[row * 8 + 1];
assert_eq!(this_min, prev_max + 1, "row {row} min ≠ prev max + 1");
}
}
#[test]
fn finder_widths_match_bwipp_shape() {
assert_eq!(FINDER_WIDTHS.len(), 60);
for f in 0..12 {
let s: u32 = FINDER_WIDTHS[f * 5..f * 5 + 5]
.iter()
.map(|&w| u32::from(w))
.sum();
assert_eq!(s, 15, "finder {f} widths don't sum to 15");
}
assert_eq!(&FINDER_WIDTHS[..5], &[1u8, 8, 4, 1, 1]);
assert_eq!(&FINDER_WIDTHS[55..], &[1u8, 1, 9, 2, 2]);
}
#[test]
fn finder_seq_length_grows_with_datalen() {
assert_eq!(FINDER_SEQ.len(), 10);
let expected_lens = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
for (i, expected) in expected_lens.into_iter().enumerate() {
assert_eq!(FINDER_SEQ[i].len(), expected, "seq[{i}].len()");
}
for (i, seq) in FINDER_SEQ.iter().enumerate() {
for (j, &id) in seq.iter().enumerate() {
assert!(id < 12, "seq[{i}][{j}] = {id} ≥ 12");
}
}
}
#[test]
fn check_weights_has_sentinel_row_plus_eleven_data_rows() {
assert_eq!(CHECK_WEIGHTS.len(), 192);
for (k, &v) in CHECK_WEIGHTS.iter().enumerate().take(8) {
assert_eq!(v, -1, "sentinel cell {k}");
}
assert_eq!(CHECK_WEIGHTS[8], 77);
assert_eq!(CHECK_WEIGHTS[191], 45);
for (k, &w) in CHECK_WEIGHTS.iter().enumerate().skip(8) {
assert!((0..211).contains(&w), "weight {k} = {w} outside [0, 210]",);
}
}
#[test]
fn fill_and_sep_pat_shapes() {
assert_eq!(FILL_PAT, [0, 0, 1, 0, 0]);
assert_eq!(SEP_PAD, [0, 0, 0, 0]);
}
#[test]
fn sentinels_are_distinct() {
let mut s = [FNC1, LATCH_NUMERIC, LATCH_ALPHANUMERIC, LATCH_ISO646];
for v in s {
assert!(v < 0, "{v} should be a negative sentinel");
}
s.sort_unstable();
for i in 1..s.len() {
assert_ne!(s[i - 1], s[i], "sentinel collision at {i}");
}
}
#[test]
fn to_bin_pads_with_leading_zeros() {
assert_eq!(to_bin(0, 4).unwrap(), "0000");
assert_eq!(to_bin(5, 4).unwrap(), "0101");
assert_eq!(to_bin(15, 4).unwrap(), "1111");
assert_eq!(to_bin(255, 8).unwrap(), "11111111");
assert_eq!(to_bin(0, 1).unwrap(), "0");
assert_eq!(to_bin(1, 1).unwrap(), "1");
assert_eq!(to_bin(4095, 12).unwrap(), "111111111111");
let msg = match to_bin(4096, 12).unwrap_err() {
crate::error::Error::InvalidData(m) => m,
other => panic!(
"to_bin(4096, 12) must reject as InvalidData (value-overflows-width); got {other:?}"
),
};
assert!(
msg.contains("DataBar Expanded:"),
"overflow diagnostic must carry symbology tag; got {msg:?}"
);
assert!(
msg.contains("4096"),
"overflow diagnostic must echo the offending value; got {msg:?}"
);
assert!(
msg.contains("12 bits"),
"overflow diagnostic must echo the requested width; got {msg:?}"
);
assert!(
msg.contains("doesn't fit"),
"overflow diagnostic must use the 'doesn't fit' predicate; got {msg:?}"
);
match to_bin(1, 0).unwrap_err() {
crate::error::Error::InvalidData(msg) => {
assert!(
msg.contains("DataBar Expanded:"),
"width-0 overflow diagnostic must carry symbology tag; got {msg:?}"
);
assert!(
msg.contains("value 1"),
"width-0 overflow diagnostic must echo n=1 (not 4096); got {msg:?}"
);
assert!(
msg.contains("0 bits"),
"width-0 overflow diagnostic must echo width=0 (not 12); got {msg:?}"
);
assert!(
msg.contains("doesn't fit"),
"width-0 overflow diagnostic must use the 'doesn't fit' predicate; got {msg:?}"
);
}
other => panic!("to_bin(1, 0) must yield InvalidData; got {other:?}"),
}
assert_eq!(
to_bin(0, 0).unwrap(),
"",
"to_bin(0, 0) must return empty string (0 < 1<<0)"
);
}
#[test]
fn conv12to40_matches_bwipp_examples() {
assert_eq!(
conv12to40(b"000111222333").unwrap(),
"0000000000000110111100110111100101001101",
);
assert_eq!(conv12to40(b"000000000000").unwrap(), "0".repeat(40),);
assert_eq!(conv12to40(b"999999999999").unwrap(), "1111100111".repeat(4),);
}
#[test]
fn conv12to40_rejects_bad_input() {
for input in [b"" as &[u8], b"12345", b"1234567890123", b"12345678901a"] {
let err = conv12to40(input).unwrap_err();
let crate::error::Error::InvalidData(msg) = err else {
panic!("conv12to40({input:?}) must yield InvalidData; got other variant");
};
assert!(
msg.contains("DataBar Expanded:"),
"diagnostic for {input:?} must carry the symbology tag; got {msg:?}"
);
assert!(
msg.contains("conv12to40"),
"diagnostic for {input:?} must name conv12to40; got {msg:?}"
);
assert!(
msg.contains("12 ASCII digits"),
"diagnostic for {input:?} must mention '12 ASCII digits'; got {msg:?}"
);
assert!(
!msg.contains("conv13to44"),
"conv12to40 diagnostic must not leak conv13to44 name; got {msg:?}"
);
}
}
#[test]
fn conv13to44_uses_head_4bits_plus_conv12to40() {
assert_eq!(
conv13to44(b"9001234567890").unwrap(),
"10010000000001001110101010001101111101111010",
);
assert_eq!(conv13to44(b"0000000000000").unwrap(), "0".repeat(44));
assert_eq!(
conv13to44(b"0012345678905").unwrap(),
"00000000001100010101100110101001101110001001",
);
}
#[test]
fn conv13to44_rejects_bad_input() {
for input in [b"" as &[u8], b"12345", b"12345678901234", b"a012345678901"] {
let err = conv13to44(input).unwrap_err();
let crate::error::Error::InvalidData(msg) = err else {
panic!("conv13to44({input:?}) must yield InvalidData; got other variant");
};
assert!(
msg.contains("DataBar Expanded:"),
"diagnostic for {input:?} must carry the symbology tag; got {msg:?}"
);
assert!(
msg.contains("conv13to44"),
"diagnostic for {input:?} must name conv13to44; got {msg:?}"
);
assert!(
msg.contains("13 ASCII digits"),
"diagnostic for {input:?} must mention '13 ASCII digits' (kills 13→12/14 \
off-by-one); got {msg:?}"
);
assert!(
!msg.contains("conv12to40"),
"conv13to44 diagnostic must not leak conv12to40 name (cross-function swap); \
got {msg:?}"
);
}
}
#[test]
fn tab174_row_for_picks_first_threshold_above_value() {
assert_eq!(tab174_row_for(0).gs, 0);
assert_eq!(tab174_row_for(347).gs, 0);
assert_eq!(tab174_row_for(348).gs, 348);
assert_eq!(tab174_row_for(1387).gs, 348);
assert_eq!(tab174_row_for(1388).gs, 1388);
assert_eq!(tab174_row_for(1680).gs, 1388);
assert_eq!(tab174_row_for(2947).gs, 1388);
assert_eq!(tab174_row_for(4191).gs, 3988);
}
#[test]
fn tab174_row_for_pins_all_seven_fields_per_row() {
let r0 = tab174_row_for(0);
assert_eq!(r0.gs, 0, "row 0 gs");
assert_eq!(r0.elo, 12, "row 0 elo");
assert_eq!(r0.ele, 5, "row 0 ele");
assert_eq!(r0.mwo, 7, "row 0 mwo");
assert_eq!(r0.mwe, 2, "row 0 mwe");
assert_eq!(r0.to, 87, "row 0 to");
assert_eq!(r0.te, 4, "row 0 te");
let r2 = tab174_row_for(1680);
assert_eq!(r2.gs, 1388, "row 2 gs");
assert_eq!(r2.elo, 8, "row 2 elo");
assert_eq!(r2.ele, 9, "row 2 ele");
assert_eq!(r2.mwo, 4, "row 2 mwo");
assert_eq!(r2.mwe, 5, "row 2 mwe");
assert_eq!(r2.to, 30, "row 2 to");
assert_eq!(r2.te, 52, "row 2 te");
let r4 = tab174_row_for(4000);
assert_eq!(r4.gs, 3988, "row 4 gs");
assert_eq!(r4.elo, 4, "row 4 elo");
assert_eq!(r4.ele, 13, "row 4 ele");
assert_eq!(r4.mwo, 1, "row 4 mwo");
assert_eq!(r4.mwe, 8, "row 4 mwe");
assert_eq!(r4.to, 1, "row 4 to");
assert_eq!(r4.te, 204, "row 4 te");
}
#[test]
fn pack_12_bits_msb_first() {
let v = pack_12_bits(&[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(v, 0b1000_0000_0000);
assert_eq!(pack_12_bits(&[1u8; 12]), 4095);
assert_eq!(pack_12_bits(&[0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0]), 1680,);
}
#[test]
fn extract_data_character_matches_oracle_segments_for_input_a() {
let binval: &[u8] = &[
0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1,
0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0,
];
assert_eq!(binval.len(), 48);
let mut codewords = [0u32; 4];
for x in 0..4 {
codewords[x] = pack_12_bits(&binval[x * 12..x * 12 + 12]);
}
let expected: &[[u8; 8]] = &[
[1, 2, 1, 3, 5, 2, 2, 1], [1, 1, 4, 2, 2, 1, 5, 1], [3, 1, 1, 2, 4, 2, 1, 3], [3, 4, 1, 2, 1, 1, 1, 4], ];
for x in 0..4 {
let got = extract_data_character(codewords[x], x);
assert_eq!(
got, expected[x],
"data segment {x} mismatch — d={}, expected {:?}, got {:?}",
codewords[x], expected[x], got
);
}
}
#[test]
fn extract_checksum_character_matches_oracle_input_a() {
let got = extract_checksum_character(246);
assert_eq!(got, [3, 1, 2, 2, 1, 1, 6, 1]);
}
#[test]
fn rembits_matches_bwipp_examples() {
assert_eq!(rembits(60, 22), 0);
assert_eq!(rembits(12, 22), 36);
assert_eq!(rembits(48, 22), 0);
assert_eq!(rembits(49, 22), 11);
assert_eq!(rembits(13, 22), 35);
assert_eq!(rembits(60, 4), 12);
}
#[test]
fn parse_two_ai_compressed_inputs_strict_predicates() {
use crate::util::gs1::Element;
let mk = |ai: &str, data: &str| Element {
ai: ai.into(),
data: data.into(),
};
let elements = vec![mk("01", "90012345678905"), mk("3103", "001234")];
let got = parse_two_ai_compressed_inputs(&elements, "3103");
assert_eq!(
got,
Some((b"001234567890".as_slice(), 1234)),
"happy path: returns GTIN[1..13] + parsed v1"
);
assert_eq!(
parse_two_ai_compressed_inputs(&[mk("01", "90012345678905")], "3103"),
None,
"1 element rejected"
);
let three = vec![
mk("01", "90012345678905"),
mk("3103", "001234"),
mk("10", "X"),
];
assert_eq!(
parse_two_ai_compressed_inputs(&three, "3103"),
None,
"3+ elements rejected"
);
let wrong_a0 = vec![mk("02", "90012345678905"), mk("3103", "001234")];
assert_eq!(parse_two_ai_compressed_inputs(&wrong_a0, "3103"), None);
let wrong_a1 = vec![mk("01", "90012345678905"), mk("3104", "001234")];
assert_eq!(parse_two_ai_compressed_inputs(&wrong_a1, "3103"), None);
let no9 = vec![mk("01", "80012345678905"), mk("3103", "001234")];
assert_eq!(parse_two_ai_compressed_inputs(&no9, "3103"), None);
let short = vec![mk("01", "9001234567890"), mk("3103", "001234")];
assert_eq!(parse_two_ai_compressed_inputs(&short, "3103"), None);
let bad_d = vec![mk("01", "9001234567890A"), mk("3103", "001234")];
assert_eq!(parse_two_ai_compressed_inputs(&bad_d, "3103"), None);
let short_v1 = vec![mk("01", "90012345678905"), mk("3103", "12345")];
assert_eq!(parse_two_ai_compressed_inputs(&short_v1, "3103"), None);
let bad_v1 = vec![mk("01", "90012345678905"), mk("3103", "12345A")];
assert_eq!(parse_two_ai_compressed_inputs(&bad_v1, "3103"), None);
}
#[test]
fn iso646_byte_bits_per_arm_anchors() {
assert_eq!(iso646_byte_bits(b'0'), Some((5, 5)));
assert_eq!(iso646_byte_bits(b'9'), Some((14, 5)));
assert_eq!(iso646_byte_bits(FNC1_SENTINEL_BYTE), Some((15, 5)));
assert_eq!(iso646_byte_bits(b'A'), Some((64, 7)));
assert_eq!(iso646_byte_bits(b'Z'), Some((89, 7)));
assert_eq!(iso646_byte_bits(b'a'), Some((90, 7)));
assert_eq!(iso646_byte_bits(b'z'), Some((115, 7)));
assert_eq!(iso646_byte_bits(b'!'), Some((232, 8)));
assert_eq!(iso646_byte_bits(b'"'), Some((233, 8)));
assert_eq!(iso646_byte_bits(b'%'), Some((234, 8)));
assert_eq!(iso646_byte_bits(b'/'), Some((244, 8)));
assert_eq!(iso646_byte_bits(b'*'), Some((239, 8)));
assert_eq!(iso646_byte_bits(b':'), Some((245, 8)));
assert_eq!(iso646_byte_bits(b'?'), Some((250, 8)));
assert_eq!(iso646_byte_bits(b'_'), Some((251, 8)));
assert_eq!(iso646_byte_bits(b' '), Some((252, 8)));
assert_eq!(iso646_byte_bits(b'#'), None, "'#' not in iso646");
assert_eq!(iso646_byte_bits(b'$'), None, "'$' gap between '\"' and '%'");
assert_eq!(iso646_byte_bits(b'@'), None, "'@' gap between '?' and 'A'");
assert_eq!(iso646_byte_bits(b'['), None, "'[' between 'Z' and '_'");
assert_eq!(iso646_byte_bits(b'\\'), None);
assert_eq!(iso646_byte_bits(b']'), None);
assert_eq!(iso646_byte_bits(b'`'), None, "'`' between '_' and 'a'");
assert_eq!(iso646_byte_bits(b'{'), None);
assert_eq!(iso646_byte_bits(b'~'), None);
assert_eq!(iso646_byte_bits(0x00), None);
}
#[test]
fn alphanumeric_byte_bits_per_arm_anchors() {
assert_eq!(alphanumeric_byte_bits(b'0'), Some((5, 5)));
assert_eq!(alphanumeric_byte_bits(b'9'), Some((14, 5)));
assert_eq!(alphanumeric_byte_bits(b'5'), Some((10, 5)));
assert_eq!(alphanumeric_byte_bits(FNC1_SENTINEL_BYTE), Some((15, 5)));
assert_eq!(alphanumeric_byte_bits(b'A'), Some((32, 6)));
assert_eq!(alphanumeric_byte_bits(b'Z'), Some((57, 6)));
assert_eq!(alphanumeric_byte_bits(b'M'), Some((44, 6)));
assert_eq!(alphanumeric_byte_bits(b'*'), Some((58, 6)));
assert_eq!(alphanumeric_byte_bits(b','), Some((59, 6)));
assert_eq!(alphanumeric_byte_bits(b'-'), Some((60, 6)));
assert_eq!(alphanumeric_byte_bits(b'.'), Some((61, 6)));
assert_eq!(alphanumeric_byte_bits(b'/'), Some((62, 6)));
assert_eq!(alphanumeric_byte_bits(b'a'), None, "lowercase rejected");
assert_eq!(alphanumeric_byte_bits(b'!'), None);
assert_eq!(alphanumeric_byte_bits(b':'), None, "':' above digit range");
assert_eq!(alphanumeric_byte_bits(b'@'), None, "'@' below 'A'");
assert_eq!(alphanumeric_byte_bits(b'['), None, "'[' above 'Z'");
assert_eq!(alphanumeric_byte_bits(b' '), None);
assert_eq!(alphanumeric_byte_bits(b'+'), None, "'+' not in punct block");
}
#[test]
fn numeric_pair_bits_formula_and_fnc1_branch() {
assert_eq!(numeric_pair_bits(b'0', b'0'), Some((8, 7)));
assert_eq!(numeric_pair_bits(b'0', b'1'), Some((9, 7)));
assert_eq!(numeric_pair_bits(b'1', b'0'), Some((19, 7)));
assert_eq!(numeric_pair_bits(b'9', b'9'), Some((116, 7)));
assert_eq!(
numeric_pair_bits(b'0', FNC1_SENTINEL_BYTE),
Some((18, 7)),
"d1=FNC1 → 0*11+10+8=18"
);
assert_eq!(
numeric_pair_bits(FNC1_SENTINEL_BYTE, b'0'),
Some((118, 7)),
"d0=FNC1 → 10*11+0+8=118"
);
assert_eq!(
numeric_pair_bits(FNC1_SENTINEL_BYTE, FNC1_SENTINEL_BYTE),
Some((128, 7)),
"FNC1/FNC1 → 10*11+10+8=128"
);
assert_eq!(numeric_pair_bits(b'A', b'0'), None);
assert_eq!(numeric_pair_bits(b'0', b'A'), None);
assert_eq!(numeric_pair_bits(b' ', b' '), None);
assert_eq!(numeric_pair_bits(b'/', b'0'), None, "'/' just below '0'");
assert_eq!(numeric_pair_bits(b'9', b':'), None, "':' just above '9'");
}
#[test]
fn alphanumeric_byte_bits_per_arm_with_boundary_discriminators() {
assert_eq!(
alphanumeric_byte_bits(b'0'),
Some((5, 5)),
"'0' → ('0'-43, 5) = (5, 5)"
);
assert_eq!(alphanumeric_byte_bits(b'5'), Some((10, 5)), "'5' → (10, 5)");
assert_eq!(
alphanumeric_byte_bits(b'9'),
Some((14, 5)),
"'9' → ('9'-43, 5) = (14, 5)"
);
assert_eq!(
alphanumeric_byte_bits(b'/'),
Some((62, 6)),
"'/' lands in the ,-./ arm, NOT the digit arm"
);
assert_eq!(
alphanumeric_byte_bits(FNC1_SENTINEL_BYTE),
Some((15, 5)),
"FNC1 sentinel '^' → (15, 5)"
);
assert_eq!(
alphanumeric_byte_bits(b'A'),
Some((32, 6)),
"'A' → ('A'-33, 6) = (32, 6)"
);
assert_eq!(
alphanumeric_byte_bits(b'M'),
Some((44, 6)),
"'M' (77) → (44, 6)"
);
assert_eq!(
alphanumeric_byte_bits(b'Z'),
Some((57, 6)),
"'Z' → ('Z'-33, 6) = (57, 6)"
);
assert_eq!(
alphanumeric_byte_bits(b'*'),
Some((58, 6)),
"'*' → (58, 6) — own arm, distinct from letter range"
);
assert_eq!(alphanumeric_byte_bits(b','), Some((59, 6)), "',' → (59, 6)");
assert_eq!(alphanumeric_byte_bits(b'-'), Some((60, 6)), "'-' → (60, 6)");
assert_eq!(alphanumeric_byte_bits(b'.'), Some((61, 6)), "'.' → (61, 6)");
assert_eq!(alphanumeric_byte_bits(b'/'), Some((62, 6)), "'/' → (62, 6)");
assert_eq!(alphanumeric_byte_bits(b'a'), None, "'a' lowercase: None");
assert_eq!(alphanumeric_byte_bits(b'z'), None, "'z' lowercase: None");
assert_eq!(alphanumeric_byte_bits(b' '), None, "space: None");
assert_eq!(alphanumeric_byte_bits(b'!'), None, "'!': None");
assert_eq!(alphanumeric_byte_bits(b'_'), None, "'_': None");
assert_eq!(
alphanumeric_byte_bits(b'+'),
None,
"'+' (43) is one below ','; must be None"
);
assert_eq!(
alphanumeric_byte_bits(b':'),
None,
"':' (58) just above '9'; must be None"
);
assert_eq!(
alphanumeric_byte_bits(b'@'),
None,
"'@' (64) just below 'A'; must be None"
);
assert_eq!(
alphanumeric_byte_bits(b'['),
None,
"'[' (91) just above 'Z'; must be None"
);
assert_eq!(alphanumeric_byte_bits(0), None);
assert_eq!(alphanumeric_byte_bits(0x7F), None);
for w in b'0'..b'9' {
let (v_w, _) = alphanumeric_byte_bits(w).unwrap();
let (v_next, _) = alphanumeric_byte_bits(w + 1).unwrap();
assert_eq!(
v_next,
v_w + 1,
"digit-arm value(c+1) must equal value(c)+1 (c={})",
w as char
);
}
for w in b'A'..b'Z' {
let (v_w, _) = alphanumeric_byte_bits(w).unwrap();
let (v_next, _) = alphanumeric_byte_bits(w + 1).unwrap();
assert_eq!(
v_next,
v_w + 1,
"letter-arm value(c+1) must equal value(c)+1 (c={})",
w as char
);
}
}
#[test]
fn assemble_binval_matches_oracle_input_a() {
let method_bits = vec![1u8];
let cdf_str = conv13to44(b"9001234567890").expect(
"conv13to44(b\"9001234567890\") (DataBar Expanded 13-digit GTIN body → 44-bit cdf binary; method-1 helper) must succeed",
);
let cdf: Vec<u8> = cdf_str.bytes().map(|b| b - b'0').collect();
let binval = assemble_binval(
0,
&method_bits,
2,
&cdf,
&[],
CharsetMode::Numeric,
DEFAULT_SEGMENTS,
);
let want: &[u8] = &[
0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1,
0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0,
];
assert_eq!(binval, want);
}
#[test]
fn encode_to_dxw_and_seq_pipeline_for_input_a() {
let method_bits = vec![1u8];
let cdf_str = conv13to44(b"9001234567890").expect("conv13to44 must succeed");
let cdf: Vec<u8> = cdf_str.bytes().map(|b| b - b'0').collect();
let method = MethodOutput {
method_bits,
vlf_len: 2,
cdf,
gpf_prefix_bytes: vec![],
consumed_ais: 0,
};
let (dxw, seq, total_chars) = encode_to_dxw_and_seq(
false, &method,
&[], CharsetMode::Numeric,
DEFAULT_SEGMENTS,
)
.expect("input A (data_chars=4) is within the 2..=21 capacity");
assert_eq!(total_chars, 5, "data_chars=4 + 1 checksum = 5 total");
assert_eq!(dxw.len(), 5);
assert_eq!(
dxw[0],
[3, 1, 2, 2, 1, 1, 6, 1],
"dxw[0] must be the checksum character (prepended via push BEFORE extend)"
);
assert_eq!(
dxw[1],
[1, 2, 1, 3, 5, 2, 2, 1],
"dxw[1] = data seg 0 (even parity)"
);
assert_eq!(
dxw[2],
[1, 1, 4, 2, 2, 1, 5, 1],
"dxw[2] = data seg 1 (odd parity)"
);
assert_eq!(
dxw[3],
[3, 1, 1, 2, 4, 2, 1, 3],
"dxw[3] = data seg 2 (even parity)"
);
assert_eq!(
dxw[4],
[3, 4, 1, 2, 1, 1, 1, 4],
"dxw[4] = data seg 3 (odd parity)"
);
assert_eq!(
seq,
&[0u8, 3, 2],
"finder_sequence(4) = bracket 1 [0, 3, 2]"
);
}
#[test]
fn encode_method_1_falls_through_for_non_gtin_inputs() {
let els = vec![crate::util::gs1::Element {
ai: "10".to_string(),
data: "ABC".to_string(),
}];
let result = encode_method_1(&els)
.expect("encode_method_1(non-AI-01) must not error, only return None");
assert!(
result.is_none(),
"AI=10 should NOT match method 1, got Some({result:?}) — method 1 only handles AI=01"
);
let els = vec![crate::util::gs1::Element {
ai: "01".to_string(),
data: "12345".to_string(),
}];
let result = encode_method_1(&els)
.expect("encode_method_1(AI=01 wrong length) must not error, only return None");
assert!(
result.is_none(),
"AI=01 with 5-digit body should NOT match method 1, got Some({result:?}) — method 1 requires exactly 14 digits"
);
let els = vec![
crate::util::gs1::Element {
ai: "01".to_string(),
data: "90012345678908".to_string(),
},
crate::util::gs1::Element {
ai: "10".to_string(),
data: "ABC".to_string(),
},
];
let m = encode_method_1(&els)
.expect("encode_method_1((01)+GTIN multi-AI) (DataBar Expanded method-1 partial-consume; outer Result Ok) must succeed")
.expect("encode_method_1((01)+GTIN multi-AI) (DataBar Expanded method-1 must match; inner Option Some with method_bits=[1] + consumed_ais=1)");
assert_eq!(m.method_bits, vec![1]);
assert_eq!(m.consumed_ais, 1);
}
#[test]
fn encode_input_a_end_to_end_matches_oracle_sbs() {
let pat = encode("(01)90012345678908", false).expect(
"encode(\"(01)90012345678908\", linkage=false) (DataBar Expanded end-to-end Input-A oracle: method-1 + 14-digit GTIN → 59-element sbs) must succeed",
);
let want: &[u8] = &[
1, 3, 1, 2, 2, 1, 1, 6, 1, 1, 8, 4, 1, 1, 1, 2, 1, 3, 5, 2, 2, 1, 1, 1, 4, 2, 2, 1, 5,
1, 1, 1, 4, 6, 3, 3, 1, 1, 2, 4, 2, 1, 3, 3, 4, 1, 2, 1, 1, 1, 4, 3, 6, 4, 1, 1, 1, 1,
];
assert_eq!(pat.bars, want);
assert!(pat.text.is_none());
}
#[test]
fn encode_method_0100_matches_oracle() {
let pat = encode("(01)90012345678908(3103)032000", false).expect(
"encode(\"(01)90012345678908(3103)032000\", linkage=false) (DataBar Expanded method-0100 oracle: 01+3103 net-weight → 66-element sbs, datalen=6, 3 finders + 6 chars) must succeed",
);
let want: &[u8] = &[
1, 1, 1, 3, 2, 4, 1, 2, 3, 1, 8, 4, 1, 1, 3, 4, 1, 2, 2, 3, 1, 1, 1, 1, 4, 1, 3, 2, 2,
3, 1, 1, 4, 6, 3, 2, 1, 1, 3, 3, 3, 1, 3, 1, 4, 1, 3, 1, 2, 3, 2, 3, 6, 4, 1, 1, 2, 2,
2, 1, 4, 2, 3, 1, 1, 1,
];
assert_eq!(pat.bars, want);
}
#[test]
fn encode_method_0101_3202_matches_oracle() {
let pat = encode("(01)90012345678908(3202)001234", false).unwrap();
let want: &[u8] = &[
1, 1, 1, 2, 1, 4, 3, 3, 2, 1, 8, 4, 1, 1, 1, 2, 3, 3, 1, 4, 2, 1, 1, 1, 4, 1, 3, 2, 2,
3, 1, 1, 4, 6, 3, 2, 1, 1, 3, 3, 3, 1, 3, 1, 4, 1, 1, 1, 4, 3, 2, 3, 6, 4, 1, 1, 1, 3,
3, 2, 2, 1, 1, 4, 1, 1,
];
assert_eq!(pat.bars, want);
}
#[test]
fn encode_method_0101_3203_uses_plus_10000_offset() {
let pat = encode("(01)90012345678908(3203)001234", false).unwrap();
let want: &[u8] = &[
1, 1, 1, 3, 3, 5, 1, 1, 2, 1, 8, 4, 1, 1, 1, 2, 3, 3, 1, 4, 2, 1, 1, 1, 4, 1, 3, 2, 2,
3, 1, 1, 4, 6, 3, 2, 1, 1, 3, 3, 3, 1, 3, 1, 4, 1, 2, 1, 1, 3, 4, 3, 6, 4, 1, 1, 1, 3,
3, 1, 2, 1, 5, 1, 1, 1,
];
assert_eq!(pat.bars, want);
}
#[test]
fn encode_method_0100_falls_through_when_weight_too_large() {
let pat = encode("(01)90012345678908(3103)999999", false).unwrap();
assert!(
!pat.bars.is_empty(),
"encode(\"(01)90012345678908(3103)999999\") (weight 999999 > 32767 → 0100 declines → falls through to method 1 + gpf tail) must produce non-empty DataBar Expanded bars; got len={}",
pat.bars.len()
);
assert_eq!(pat.bars.first(), Some(&1)); }
#[test]
fn encode_method_0101_falls_through_when_gtin_does_not_start_with_9() {
let pat = encode("(01)10012345678907(3202)001234", false).unwrap();
assert!(
!pat.bars.is_empty(),
"encode(\"(01)10012345678907(3202)001234\") (GTIN starts with '1' not '9' → 0101 precondition fails → falls through to method 1 + gpf tail) must produce non-empty DataBar Expanded bars; got len={}",
pat.bars.len()
);
}
#[test]
fn encode_method_0111000_310x_no_date_matches_oracle() {
let pat = encode("(01)90012345678908(3103)099999", false).unwrap();
let want: &[u8] = &[
1, 2, 2, 5, 1, 2, 3, 1, 1, 1, 8, 4, 1, 1, 1, 3, 2, 1, 3, 4, 1, 2, 1, 1, 4, 2, 2, 1, 5,
1, 1, 1, 6, 4, 3, 3, 1, 1, 2, 4, 2, 1, 3, 3, 4, 1, 2, 1, 1, 1, 4, 3, 6, 4, 1, 1, 5, 4,
1, 1, 1, 2, 2, 1, 2, 2, 1, 3, 1, 3, 4, 1, 1, 1, 8, 2, 3, 2, 2, 2, 4, 1, 1, 4, 1, 1, 1,
];
assert_eq!(pat.bars, want);
}
#[test]
fn encode_method_0111001_320x_no_date_matches_oracle() {
let pat = encode("(01)90012345678908(3203)099999", false).unwrap();
let want: &[u8] = &[
1, 3, 3, 1, 1, 3, 2, 3, 1, 1, 8, 4, 1, 1, 3, 1, 1, 3, 2, 4, 1, 2, 1, 1, 4, 2, 2, 1, 5,
1, 1, 1, 6, 4, 3, 3, 1, 1, 2, 4, 2, 1, 3, 3, 4, 1, 2, 1, 1, 1, 4, 3, 6, 4, 1, 1, 5, 4,
1, 1, 1, 2, 2, 1, 2, 2, 1, 3, 1, 3, 4, 1, 1, 1, 8, 2, 3, 2, 2, 2, 4, 1, 1, 4, 1, 1, 1,
];
assert_eq!(pat.bars, want);
}
#[test]
fn encode_method_0111000_310x_with_date_11_matches_oracle() {
let pat = encode("(01)90012345678908(3103)001750(11)250101", false).unwrap();
let want: &[u8] = &[
1, 2, 2, 4, 3, 3, 1, 1, 1, 1, 8, 4, 1, 1, 1, 3, 2, 1, 3, 4, 1, 2, 1, 1, 4, 2, 2, 1, 5,
1, 1, 1, 6, 4, 3, 3, 1, 1, 2, 4, 2, 1, 3, 3, 4, 1, 2, 1, 1, 1, 4, 3, 6, 4, 1, 1, 3, 1,
1, 2, 1, 4, 2, 3, 4, 2, 2, 1, 1, 1, 1, 5, 1, 1, 8, 2, 3, 2, 4, 4, 2, 1, 1, 2, 1, 1, 1,
];
assert_eq!(pat.bars, want);
}
#[test]
fn encode_stacked_matches_oracle_for_input_a() {
let bm = encode_stacked("(01)90012345678908", false).unwrap();
assert_eq!(bm.width(), 102);
assert_eq!(bm.height(), 71);
let row0: &[u8] = &[
0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0,
0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1,
0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1,
];
let sep0: &[u8] = &[
0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1,
0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1,
1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0,
1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0,
];
let inter_sep: &[u8] = &[
0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0,
];
let sep1: &[u8] = &[
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
let row1: &[u8] = &[
0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
for y in 0..34 {
for (x, &expected) in row0.iter().enumerate() {
assert_eq!(bm.get(x, y) as u8, expected, "row0 y={y} x={x}");
}
}
for (x, &expected) in sep0.iter().enumerate() {
assert_eq!(bm.get(x, 34) as u8, expected, "sep0 x={x}");
}
for (x, &expected) in inter_sep.iter().enumerate() {
assert_eq!(bm.get(x, 35) as u8, expected, "inter_sep x={x}");
}
for (x, &expected) in sep1.iter().enumerate() {
assert_eq!(bm.get(x, 36) as u8, expected, "sep1 x={x}");
}
for y in 37..71 {
for (x, &expected) in row1.iter().enumerate() {
assert_eq!(bm.get(x, y) as u8, expected, "row1 y={y} x={x}");
}
}
}
#[test]
fn encode_method_01100_392x_matches_oracle() {
let pat = encode("(01)90012345678908(3922)1234", false).unwrap();
let want: &[u8] = &[
1, 1, 1, 5, 4, 2, 1, 2, 1, 1, 8, 4, 1, 1, 1, 2, 3, 5, 1, 1, 2, 2, 1, 1, 4, 2, 2, 1, 5,
1, 1, 1, 6, 4, 3, 3, 1, 1, 2, 4, 2, 1, 3, 3, 4, 1, 2, 1, 1, 1, 4, 3, 6, 4, 1, 1, 1, 1,
2, 4, 1, 1, 5, 2, 1, 3, 2, 5, 1, 1, 2, 2, 1, 1, 8, 2, 3, 1, 1,
];
assert_eq!(pat.bars, want);
}
#[test]
fn encode_method_01100_with_trailing_lot_ai_matches_oracle() {
let pat = encode("(01)90012345678908(3922)1234(10)abc", false).unwrap();
let want: &[u8] = &[
1, 1, 4, 1, 3, 2, 1, 4, 1, 1, 8, 4, 1, 1, 4, 3, 1, 4, 1, 1, 1, 2, 1, 1, 4, 2, 2, 1, 5,
1, 1, 1, 5, 6, 2, 3, 1, 1, 2, 4, 2, 1, 3, 3, 4, 1, 2, 1, 1, 1, 4, 3, 6, 4, 1, 1, 1, 1,
2, 4, 1, 1, 5, 2, 1, 6, 3, 1, 1, 1, 1, 3, 1, 1, 8, 2, 3, 1, 4, 1, 3, 2, 4, 1, 1, 1, 1,
1, 3, 3, 3, 3, 2, 3, 4, 6, 1, 1, 1, 1, 1, 3, 3, 3, 4, 1, 1, 1,
];
assert_eq!(pat.bars, want);
}
#[test]
fn encode_method_01101_393x_matches_oracle() {
let pat = encode("(01)90012345678908(3932)84012345", false).unwrap();
let want: &[u8] = &[
1, 3, 1, 1, 2, 4, 2, 2, 2, 1, 8, 4, 1, 1, 3, 1, 1, 5, 2, 2, 1, 2, 1, 1, 4, 2, 2, 1, 5,
1, 1, 1, 6, 4, 3, 3, 1, 1, 2, 4, 2, 1, 3, 3, 4, 1, 2, 1, 1, 1, 4, 3, 6, 4, 1, 1, 2, 1,
2, 2, 1, 1, 4, 4, 1, 2, 5, 3, 1, 1, 3, 1, 1, 1, 8, 2, 3, 1, 3, 4, 2, 3, 2, 1, 1, 1, 1,
];
assert_eq!(pat.bars, want);
}
#[test]
fn encode_stacked_three_rows_with_reversed_middle_row() {
let bm = encode_stacked("(01)90012345678908(11)250101(10)xyz", false).unwrap();
assert_eq!(bm.width(), 102);
assert_eq!(bm.height(), 108);
let row0_first15: &[u8] = &[0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0];
let row1_first15_reversed: &[u8] = &[1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0];
let row2_first15: &[u8] = &[0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0];
for (x, &b) in row0_first15.iter().enumerate() {
assert_eq!(bm.get(x, 0) as u8, b, "row0 x={x}");
}
for (x, &b) in row1_first15_reversed.iter().enumerate() {
assert_eq!(bm.get(x, 37) as u8, b, "row1 (reversed) x={x}");
}
for (x, &b) in row2_first15.iter().enumerate() {
assert_eq!(bm.get(x, 74) as u8, b, "row2 x={x}");
}
}
#[test]
fn encode_method_00_pure_numeric_sscc_matches_oracle() {
let pat = encode("(00)123456789012345675", false).unwrap();
let want: &[u8] = &[
1, 2, 1, 3, 1, 1, 2, 4, 3, 1, 8, 4, 1, 1, 2, 5, 1, 5, 1, 1, 1, 1, 1, 2, 5, 3, 1, 1, 3,
1, 1, 1, 6, 4, 3, 3, 4, 1, 1, 2, 2, 3, 1, 1, 4, 3, 1, 1, 2, 1, 4, 3, 6, 4, 1, 1, 1, 1,
1, 2, 2, 3, 3, 4, 1, 1, 1, 2, 3, 5, 1, 3, 1, 1, 8, 2, 3, 4, 2, 1, 4, 1, 1, 1, 3, 1, 1,
];
assert_eq!(pat.bars, want);
}
#[test]
fn encode_method_1_with_uppercase_trailing_ai() {
let pat = encode("(01)90012345678908(10)ABC123", false).unwrap();
let want: &[u8] = &[
1, 3, 2, 3, 1, 1, 3, 3, 1, 1, 8, 4, 1, 1, 1, 2, 1, 3, 5, 2, 2, 1, 1, 1, 4, 2, 2, 1, 5,
1, 1, 1, 5, 6, 2, 3, 1, 1, 2, 4, 2, 1, 3, 3, 4, 1, 2, 1, 1, 1, 4, 3, 6, 4, 1, 1, 3, 3,
2, 2, 1, 4, 1, 1, 1, 2, 3, 1, 6, 1, 2, 1, 1, 1, 8, 2, 3, 2, 1, 1, 2, 1, 6, 1, 3, 2, 2,
1, 3, 2, 1, 1, 5, 3, 4, 6, 1, 1, 1, 1,
];
assert_eq!(pat.bars, want);
}
#[test]
fn encode_method_1_with_three_ais_date_then_lot() {
let pat = encode("(01)90012345678908(11)250101(10)xyz", false).unwrap();
let want: &[u8] = &[
1, 1, 3, 2, 1, 1, 1, 4, 4, 1, 8, 4, 1, 1, 1, 2, 1, 3, 5, 2, 2, 1, 1, 1, 4, 2, 2, 1, 5,
1, 1, 1, 5, 6, 2, 3, 1, 1, 2, 4, 2, 1, 3, 3, 4, 1, 2, 1, 1, 1, 4, 3, 6, 4, 1, 1, 4, 1,
1, 4, 1, 4, 1, 1, 1, 5, 1, 2, 2, 1, 2, 3, 1, 1, 8, 2, 3, 5, 2, 1, 1, 1, 3, 2, 2, 1, 1,
4, 2, 2, 1, 5, 1, 3, 2, 8, 1, 1, 1, 5, 4, 1, 1, 1, 1, 3, 2, 2, 4, 1, 1, 3, 1, 3, 1, 1,
9, 2, 2, 1, 1,
];
assert_eq!(pat.bars, want);
}
#[test]
fn encode_method_1_with_iso646_punctuation_trailing_ai() {
let pat = encode("(01)90012345678908(10)A!b%c", false).unwrap();
let want: &[u8] = &[
1, 1, 1, 1, 2, 3, 5, 3, 1, 1, 8, 4, 1, 1, 4, 1, 1, 2, 1, 4, 1, 3, 1, 1, 4, 2, 2, 1, 5,
1, 1, 1, 5, 6, 2, 3, 1, 1, 2, 4, 2, 1, 3, 3, 4, 1, 2, 1, 1, 1, 4, 3, 6, 4, 1, 1, 3, 3,
2, 2, 1, 4, 1, 1, 1, 2, 1, 1, 7, 1, 3, 1, 1, 1, 8, 2, 3, 2, 1, 4, 3, 1, 1, 2, 3, 3, 2,
1, 3, 1, 4, 1, 2, 3, 4, 6, 1, 1, 5, 2, 2, 2, 1, 1, 3, 1, 1, 1,
];
assert_eq!(pat.bars, want);
}
#[test]
fn encode_handles_method_1_with_trailing_variable_ai_via_general_purpose() {
let pat = encode("(01)90012345678908(10)abc", false).unwrap();
let want: &[u8] = &[
1, 3, 1, 3, 2, 1, 1, 3, 3, 1, 8, 4, 1, 1, 1, 2, 1, 3, 5, 2, 2, 1, 1, 1, 4, 2, 2, 1, 5,
1, 1, 1, 5, 6, 2, 3, 1, 1, 2, 4, 2, 1, 3, 3, 4, 1, 2, 1, 1, 1, 4, 3, 6, 4, 1, 1, 4, 3,
1, 2, 1, 4, 1, 1, 3, 3, 5, 1, 1, 2, 1, 1, 1, 1, 8, 2, 3, 1, 3, 5, 1, 1, 3, 2, 1, 3, 1,
4, 1, 4, 1, 1, 2, 3, 4, 6, 1, 1, 1, 1,
];
assert_eq!(pat.bars, want);
}
#[test]
fn finder_sequence_returns_bwipp_row() {
assert_eq!(finder_sequence(2), &[0, 1]);
assert_eq!(finder_sequence(3), &[0, 1]);
assert_eq!(finder_sequence(4), &[0, 3, 2]);
assert_eq!(finder_sequence(5), &[0, 3, 2]);
assert_eq!(finder_sequence(14), &[0, 1, 2, 3, 4, 5, 6, 7]);
assert_eq!(finder_sequence(21), &[0, 1, 2, 3, 4, 7, 6, 9, 8, 11, 10]);
}
#[test]
fn assemble_sbs_matches_oracle_input_a() {
let binval: &[u8] = &[
0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1,
0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0,
];
let mut dxw: Vec<[u8; 8]> = Vec::with_capacity(5);
let data_dxw: Vec<[u8; 8]> = (0..4)
.map(|x| {
let d = pack_12_bits(&binval[x * 12..x * 12 + 12]);
extract_data_character(d, x)
})
.collect();
let seq = finder_sequence(4);
let checksum = compute_checksum(&data_dxw, seq);
dxw.push(extract_checksum_character(checksum));
dxw.extend(data_dxw);
let sbs = assemble_sbs(&dxw, seq);
let want: &[u8] = &[
1, 3, 1, 2, 2, 1, 1, 6, 1, 1, 8, 4, 1, 1, 1, 2, 1, 3, 5, 2, 2, 1, 1, 1, 4, 2, 2, 1, 5,
1, 1, 1, 4, 6, 3, 3, 1, 1, 2, 4, 2, 1, 3, 3, 4, 1, 2, 1, 1, 1, 4, 3, 6, 4, 1, 1, 1, 1,
];
assert_eq!(sbs, want);
}
#[test]
fn compute_checksum_matches_oracle_input_a() {
let binval: &[u8] = &[
0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1,
0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0,
];
let dxw: Vec<[u8; 8]> = (0..4)
.map(|x| {
let d = pack_12_bits(&binval[x * 12..x * 12 + 12]);
extract_data_character(d, x)
})
.collect();
let seq: &[u8] = &[0, 3, 2]; let checksum = compute_checksum(&dxw, seq);
assert_eq!(checksum, 246);
}
#[test]
fn to_bin_boundary_widths() {
assert_eq!(to_bin(0, 4).unwrap(), "0000");
assert_eq!(to_bin(0, 1).unwrap(), "0");
assert_eq!(to_bin(1, 4).unwrap(), "0001");
assert_eq!(to_bin(0b1010, 4).unwrap(), "1010");
assert_eq!(to_bin(0xFF, 8).unwrap(), "11111111");
assert_eq!(to_bin(0x80, 8).unwrap(), "10000000");
for (n, width) in [(16u64, 4usize), (256, 8)] {
let err = to_bin(n, width).unwrap_err();
let crate::error::Error::InvalidData(msg) = err else {
panic!("to_bin({n}, {width}) must yield InvalidData; got {err:?}");
};
assert!(
msg.contains("DataBar Expanded:") && msg.contains("doesn't fit"),
"to_bin({n}, {width}) must pin symbology tag + 'doesn't fit'; got {msg:?}"
);
assert!(
msg.contains(&n.to_string()) && msg.contains(&format!("{width} bits")),
"to_bin({n}, {width}) must echo value + width; got {msg:?}"
);
}
let s = to_bin(0xFFFF_FFFF_FFFF_FFFF, 64).unwrap();
assert_eq!(s.len(), 64);
assert!(s.chars().all(|c| c == '1'));
}
#[test]
fn rembits_codeword_alignment() {
assert_eq!(rembits(0, 4), 48);
assert_eq!(rembits(12, 4), 36);
assert_eq!(rembits(48, 4), 0);
assert_eq!(rembits(60, 4), 12);
assert_eq!(rembits(72, 4), 0);
assert_eq!(rembits(1, 4), 47);
}
#[test]
fn match_310x_or_320x_arms_and_length_guards() {
assert_eq!(
match_310x_or_320x("3100"),
Some((false, 0)),
"310x boundary: last digit 0"
);
assert_eq!(
match_310x_or_320x("3105"),
Some((false, 5)),
"310x mid: last digit 5"
);
assert_eq!(
match_310x_or_320x("3109"),
Some((false, 9)),
"310x top: last digit 9"
);
assert_eq!(match_310x_or_320x("3200"), Some((true, 0)), "320x boundary");
assert_eq!(match_310x_or_320x("3203"), Some((true, 3)), "320x mid");
assert_eq!(match_310x_or_320x("3209"), Some((true, 9)), "320x top");
assert_eq!(match_310x_or_320x(""), None, "empty input");
assert_eq!(match_310x_or_320x("310"), None, "length 3 (one too short)");
assert_eq!(match_310x_or_320x("31000"), None, "length 5 (one too long)");
assert_eq!(match_310x_or_320x("310A"), None, "non-digit last char");
assert_eq!(match_310x_or_320x("320X"), None);
assert_eq!(match_310x_or_320x("310-"), None);
assert_eq!(match_310x_or_320x("3115"), None, "wrong prefix 311");
assert_eq!(match_310x_or_320x("3305"), None, "wrong prefix 330");
assert_eq!(match_310x_or_320x("4100"), None, "wrong prefix 410");
assert_eq!(match_310x_or_320x("31X0"), None);
assert_eq!(match_310x_or_320x("3105"), Some((false, 5)));
assert_eq!(match_310x_or_320x("3205"), Some((true, 5)));
}
#[test]
fn to_bin_msb_first_with_overflow_guard() {
assert_eq!(to_bin(0, 0).unwrap(), "");
assert_eq!(to_bin(0, 4).unwrap(), "0000");
assert_eq!(to_bin(15, 4).unwrap(), "1111");
assert_eq!(
to_bin(5, 4).unwrap(),
"0101",
"MSB-first: removing .rev() would give \"1010\""
);
assert_eq!(to_bin(130, 8).unwrap(), "10000010");
let err = to_bin(16, 4).unwrap_err();
let crate::error::Error::InvalidData(msg) = err else {
panic!("to_bin(16, 4) must yield InvalidData; got {err:?}");
};
assert!(
msg.contains("DataBar Expanded:")
&& msg.contains("16")
&& msg.contains("4 bits")
&& msg.contains("doesn't fit"),
"to_bin(16, 4) must pin tag + value + width + 'doesn't fit'; got {msg:?}"
);
assert!(to_bin(15, 4).is_ok(), "15 fits in 4 bits");
let err = to_bin(u64::MAX, 63).unwrap_err();
let crate::error::Error::InvalidData(msg) = err else {
panic!("to_bin(u64::MAX, 63) must yield InvalidData; got {err:?}");
};
assert!(
msg.contains("DataBar Expanded:")
&& msg.contains(&u64::MAX.to_string())
&& msg.contains("63 bits")
&& msg.contains("doesn't fit"),
"to_bin(u64::MAX, 63) must pin tag + u64::MAX value + 63 bits width; got {msg:?}"
);
let s = to_bin(u64::MAX, 64).expect("width=64 always fits");
assert_eq!(s.len(), 64);
assert!(s.chars().all(|c| c == '1'));
assert_eq!(to_bin(0, 64).unwrap(), "0".repeat(64));
for w in 0..=12 {
for v in 0..=7u64 {
if let Ok(s) = to_bin(v, w) {
assert_eq!(s.len(), w, "to_bin({v}, {w}) length should be {w}");
}
}
}
}
#[test]
fn conv12to40_four_groups_of_three_digits_to_ten_bit_chunks() {
let z = conv12to40(b"000000000000").unwrap();
assert_eq!(z, "0".repeat(40));
let n = conv12to40(b"999999999999").unwrap();
assert_eq!(n, "1111100111".repeat(4));
let s = conv12to40(b"000000000001").unwrap();
assert_eq!(s, "0".repeat(30) + "0000000001");
let s = conv12to40(b"100000000000").unwrap();
assert_eq!(s, format!("{}{}", "0001100100", "0".repeat(30)));
for (input, scenario) in [
(b"00000000000" as &[u8], "length 11"),
(b"0000000000000", "length 13"),
(b"", "empty"),
(b"12345678901A", "trailing non-digit"),
(b"A12345678901", "leading non-digit"),
] {
let err = conv12to40(input).unwrap_err();
let crate::error::Error::InvalidData(msg) = err else {
panic!(
"conv12to40({input:?}, {scenario}) must yield InvalidData; got other variant"
);
};
assert!(
msg.contains("DataBar Expanded:")
&& msg.contains("conv12to40")
&& msg.contains("12 ASCII digits"),
"{input:?} ({scenario}) must pin tag + function name + '12 ASCII digits'; \
got {msg:?}"
);
assert!(
!msg.contains("conv13to44"),
"{input:?} ({scenario}) diagnostic must not leak conv13to44 name; got {msg:?}"
);
}
let cases: &[&[u8]] = &[
b"123456789012",
b"012345678901",
b"555555555555",
b"111222333444",
];
for c in cases {
let s = conv12to40(c).unwrap();
assert_eq!(s.len(), 40, "conv12to40 length on {c:?}");
assert!(s.chars().all(|c| c == '0' || c == '1'));
}
}
#[test]
fn conv13to44_head_nibble_plus_conv12to40_tail() {
assert_eq!(conv13to44(b"0000000000000").unwrap(), "0".repeat(44));
let s = conv13to44(b"9999999999999").unwrap();
assert_eq!(s, format!("{}{}", "1001", "1111100111".repeat(4)));
assert_eq!(s.len(), 44);
let s = conv13to44(b"1000000000000").unwrap();
assert_eq!(s, format!("{}{}", "0001", "0".repeat(40)));
let s = conv13to44(b"0000000000001").unwrap();
assert_eq!(s, format!("{}{}{}", "0000", "0".repeat(30), "0000000001"));
let s = conv13to44(b"1000000000000").unwrap();
assert_eq!(&s[..14], "00010000000000", "head goes FIRST");
let s = conv13to44(b"5000000000000").unwrap();
assert_eq!(&s[..4], "0101", "head=5 → 4-bit nibble '0101'");
let s = conv13to44(b"9000000000000").unwrap();
assert_eq!(&s[..4], "1001");
for (input, scenario) in [
(b"000000000000" as &[u8], "length 12"),
(b"00000000000000", "length 14"),
(b"", "empty"),
(b"A000000000000", "leading non-digit"),
(b"000000000000A", "trailing non-digit"),
] {
let err = conv13to44(input).unwrap_err();
let crate::error::Error::InvalidData(msg) = err else {
panic!(
"conv13to44({input:?}, {scenario}) must yield InvalidData; got other variant"
);
};
assert!(
msg.contains("DataBar Expanded:")
&& msg.contains("conv13to44")
&& msg.contains("13 ASCII digits"),
"{input:?} ({scenario}) must pin tag + function name + '13 ASCII digits'; \
got {msg:?}"
);
assert!(
!msg.contains("conv12to40"),
"{input:?} ({scenario}) diagnostic must not leak conv12to40 (cross-fn swap); got {msg:?}"
);
}
let cases: &[&[u8]] = &[
b"1234567890123",
b"0123456789012",
b"5555555555555",
b"7111222333444",
];
for c in cases {
let s = conv13to44(c).unwrap();
assert_eq!(s.len(), 44, "conv13to44 length on {c:?}");
assert!(s.chars().all(|c| c == '0' || c == '1'));
}
}
#[test]
fn compute_row_sep_short_row_complement_with_seppad() {
assert_eq!(compute_row_sep(&[]), Vec::<u8>::new());
assert_eq!(
compute_row_sep(&[1, 0, 1]),
vec![0, 0, 0],
"n=3: first-4 zeroes all entries; second-4 skipped"
);
assert_eq!(
compute_row_sep(&[1, 0, 1, 0]),
vec![0, 0, 0, 0],
"n=4: first-4 + second-4 both zero the whole vec"
);
assert_eq!(
compute_row_sep(&[1, 1, 1, 1, 0, 0, 0, 0]),
vec![0, 0, 0, 0, 0, 0, 0, 0],
"n=8: complement at index 4..=7 zeroed by last-4 seppad"
);
assert_eq!(
compute_row_sep(&[0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0]),
vec![0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0],
"n=12: middle (indices 4..=7) keeps the complement; \
indices 5 and 7 survive as 1 (1 - 0 = 1)"
);
let cases: &[&[u8]] = &[&[], &[0], &[1], &[1, 0, 1], &[0u8; 12], &[1u8; 12]];
for row in cases {
let sep = compute_row_sep(row);
assert_eq!(sep.len(), row.len(), "row {row:?}: length must match");
if row.len() >= 4 {
assert_eq!(&sep[..4], &[0, 0, 0, 0], "row {row:?}: first 4 must be 0");
assert_eq!(
&sep[row.len() - 4..],
&[0, 0, 0, 0],
"row {row:?}: last 4 must be 0"
);
}
}
}
#[test]
fn build_inter_row_sep_alternates_with_seppad() {
assert_eq!(build_inter_row_sep(0), Vec::<u8>::new());
assert_eq!(build_inter_row_sep(1), vec![0u8]);
assert_eq!(build_inter_row_sep(3), vec![0, 0, 0]);
assert_eq!(build_inter_row_sep(4), vec![0, 0, 0, 0]);
assert_eq!(build_inter_row_sep(5), vec![0, 0, 0, 0, 0]);
assert_eq!(build_inter_row_sep(8), vec![0u8; 8]);
assert_eq!(
build_inter_row_sep(10),
vec![0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
"pixx=10: middle index 5 = 1; indices 6..=9 zeroed"
);
assert_eq!(
build_inter_row_sep(12),
vec![0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0],
"pixx=12: middle indices 4..=7 keep alternation"
);
assert_eq!(
build_inter_row_sep(20),
vec![
0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, ],
"pixx=20: 12-wide alternation centered; first/last 4 zeroed"
);
for pixx in 0..=30 {
assert_eq!(
build_inter_row_sep(pixx).len(),
pixx,
"pixx={pixx}: output length must equal input pixx"
);
}
for pixx in 4..=30 {
let sep = build_inter_row_sep(pixx);
assert_eq!(
&sep[..4],
&[0, 0, 0, 0],
"pixx={pixx}: first 4 must be zero"
);
assert_eq!(
&sep[pixx - 4..],
&[0, 0, 0, 0],
"pixx={pixx}: last 4 must be zero"
);
}
}
#[test]
fn count_finder_positions_offset_19_and_68_with_stride_98() {
assert_eq!(count_finder_positions(0), 0, "n=0 → 0");
assert_eq!(count_finder_positions(12), 0, "n=12 → 0 (under floor)");
assert_eq!(count_finder_positions(13), 0, "n=13 → 0 (floor, max=0)");
assert_eq!(
count_finder_positions(31),
0,
"n=31 → 0 (max=18, both p=19 and p=68 > 18)"
);
assert_eq!(
count_finder_positions(32),
1,
"n=32 → 1 (max=19, p=19 ≤ 19 exact hit)"
);
assert_eq!(
count_finder_positions(80),
1,
"n=80 → 1 (max=67, p=19 fits; p=68 > 67)"
);
assert_eq!(
count_finder_positions(81),
2,
"n=81 → 2 (max=68, p=19 and p=68 both fit exactly)"
);
assert_eq!(
count_finder_positions(130),
3,
"n=130 → 3 (p=19, p=117, p=68 all ≤ 117)"
);
assert_eq!(
count_finder_positions(180),
4,
"n=180 → 4 (p=19,117 ≤ 167 and p=68,166 ≤ 167)"
);
}
#[test]
fn expand_row_sbs_alternates_bit_per_index_parity() {
assert_eq!(expand_row_sbs(&[]), Vec::<u8>::new());
assert_eq!(expand_row_sbs(&[3]), vec![0, 0, 0]);
assert_eq!(
expand_row_sbs(&[3, 2]),
vec![0, 0, 0, 1, 1],
"i=0 (even) → 3 zeros; i=1 (odd) → 2 ones"
);
assert_eq!(
expand_row_sbs(&[2, 1, 3]),
vec![0, 0, 1, 0, 0, 0],
"i=0 → 0x2; i=1 → 1x1; i=2 → 0x3 (even parity → bit 0)"
);
assert_eq!(
expand_row_sbs(&[1, 1, 1, 1]),
vec![0, 1, 0, 1],
"1-1-1-1 alternation must produce [0,1,0,1]"
);
assert_eq!(
expand_row_sbs(&[0, 0, 5]),
vec![0, 0, 0, 0, 0],
"leading zeros produce no output; i=2 emits 5 zeros"
);
assert_eq!(
expand_row_sbs(&[5, 0, 3]),
vec![0, 0, 0, 0, 0, 0, 0, 0],
"i=0 emits 5 zeros; i=1 zero-width emits nothing; i=2 emits 3 zeros"
);
let cases: &[&[u8]] = &[
&[],
&[3],
&[3, 2],
&[1, 1, 1, 1, 1, 1],
&[5, 4, 3, 2, 1],
&[0, 7, 0, 0, 9],
];
for sbs in cases {
let out = expand_row_sbs(sbs);
let total: usize = sbs.iter().map(|&w| w as usize).sum();
assert_eq!(
out.len(),
total,
"sbs {sbs:?}: output length must equal sum of widths"
);
assert!(out.iter().all(|&b| b == 0 || b == 1));
}
}
#[test]
fn compute_gpf_lookahead_reverse_walk_invariants() {
let look = compute_gpf_lookahead(b"");
assert_eq!(look.numeric_runs, vec![0u32, 0]);
assert_eq!(look.alphanumeric_runs, vec![0u32]);
assert_eq!(look.next_iso646_only, vec![9999u32]);
let look = compute_gpf_lookahead(b"12");
assert_eq!(
look.numeric_runs,
vec![2u32, 0, 0, 0],
"digit pair must give numeric_runs[0]=2"
);
assert_eq!(
look.alphanumeric_runs,
vec![2u32, 1, 0],
"both digits alphanumeric → run length 2, 1, 0"
);
assert_eq!(look.next_iso646_only, vec![10001u32, 10000, 9999]);
let look = compute_gpf_lookahead(b"A");
assert_eq!(look.numeric_runs, vec![0u32, 0, 0]);
assert_eq!(
look.alphanumeric_runs,
vec![1u32, 0],
"uppercase is alphanumeric → run length 1, 0"
);
assert_eq!(look.next_iso646_only, vec![10000u32, 9999]);
let look = compute_gpf_lookahead(b"a");
assert_eq!(look.numeric_runs, vec![0u32, 0, 0]);
assert_eq!(
look.alphanumeric_runs,
vec![0u32, 0],
"lowercase is NOT in alphanumeric alphabet → run length 0, 0"
);
assert_eq!(
look.next_iso646_only,
vec![0u32, 9999],
"lowercase IS iso646-only → distance 0 at position 0"
);
let look = compute_gpf_lookahead(b"1");
assert_eq!(
look.numeric_runs[0], 0,
"lone digit cannot form a pair → numeric_runs[0]=0"
);
assert_eq!(look.alphanumeric_runs[0], 1);
}
#[test]
fn build_gpf_bytes_fnc1_insertion_per_variable_ai_with_follower() {
use crate::util::gs1::Element;
let mk = |ai: &str, data: &str| Element {
ai: ai.to_string(),
data: data.to_string(),
};
assert_eq!(build_gpf_bytes(&[]).unwrap(), Vec::<u8>::new());
let out = build_gpf_bytes(&[mk("01", "04012345123456")]).unwrap();
assert_eq!(out, b"0104012345123456".to_vec());
let out = build_gpf_bytes(&[mk("10", "BATCH1")]).unwrap();
assert_eq!(out, b"10BATCH1".to_vec());
let out = build_gpf_bytes(&[mk("10", "BATCH1"), mk("01", "04012345123456")]).unwrap();
assert_eq!(
out,
[
b'1', b'0', b'B', b'A', b'T', b'C', b'H', b'1', b'^', b'0', b'1', b'0', b'4', b'0',
b'1', b'2', b'3', b'4', b'5', b'1', b'2', b'3', b'4', b'5', b'6',
],
"FNC1 must separate variable AI from following AI"
);
let out = build_gpf_bytes(&[mk("01", "04012345123456"), mk("10", "BATCH1")]).unwrap();
assert_eq!(
out,
b"0104012345123456"
.iter()
.chain(b"10BATCH1")
.copied()
.collect::<Vec<u8>>(),
"fixed AI must NOT insert FNC1 — only variable AIs do"
);
let out = build_gpf_bytes(&[mk("10", "BATCH1"), mk("21", "SERIAL")]).unwrap();
assert!(
out.contains(&FNC1_SENTINEL_BYTE),
"FNC1 missing between variable AIs"
);
assert_eq!(out[8], FNC1_SENTINEL_BYTE);
assert_eq!(&out[..8], b"10BATCH1");
assert_eq!(&out[9..], b"21SERIAL");
let err = build_gpf_bytes(&[mk("99999", "X")]).unwrap_err();
let crate::error::Error::InvalidData(msg) = err else {
panic!("unknown AI must surface as InvalidData; got {err:?}");
};
assert!(
msg.contains("DataBar Expanded:"),
"diagnostic must carry the symbology tag; got {msg:?}"
);
assert!(
msg.contains("AI (99999)"),
"diagnostic must echo the offending AI; got {msg:?}"
);
assert!(
msg.contains("not in the GS1 table"),
"diagnostic must carry the table-membership phrase; got {msg:?}"
);
}
#[test]
fn dbexp_encode_and_stacked_fingerprint_pinned() {
fn fp_lin(p: &crate::encoding::LinearPattern) -> (usize, u64) {
let mut s: u64 = 0;
for (i, &b) in p.bars.iter().enumerate() {
s = s.wrapping_add(
(b as u64).wrapping_mul((i as u64).wrapping_add(1).wrapping_mul(2_654_435_761)),
);
}
(p.bars.len(), s)
}
fn fp_bm(bm: &crate::encoding::BitMatrix) -> (usize, usize, u64) {
let w = bm.width();
let h = bm.height();
let mut s: u64 = 0;
for y in 0..h {
for x in 0..w {
let v = u64::from(bm.get(x, y));
let idx = (y as u64) * (w as u64) + (x as u64);
s = s.wrapping_add(
v.wrapping_mul(idx.wrapping_add(1).wrapping_mul(2_654_435_761)),
);
}
}
(w, h, s)
}
let lin_cases: &[(&str, &str, bool, (usize, u64))] = &[
("gtin_only", "(01)90012345678908", false, FP_LIN_GTIN),
(
"gtin_weight",
"(01)90012345678908(3103)001750",
false,
FP_LIN_GW,
),
("gtin_ai10", "(01)90012345678908(10)BATCH", false, FP_LIN_GA),
("non_gtin", "(10)BATCH123(99)9876543", false, FP_LIN_NG),
("linkage", "(01)90012345678908", true, FP_LIN_LINK),
];
for (tag, input, link, want) in lin_cases {
let p = encode(input, *link).unwrap_or_else(|e| panic!("encode({tag}) ok: {e:?}"));
let got = fp_lin(&p);
assert_eq!(got, *want, "linear fingerprint changed for {tag}");
}
let stk_cases: &[(&str, &str, bool, (usize, usize, u64))] = &[
("stk_gtin", "(01)90012345678908", false, FP_STK_GTIN),
(
"stk_long",
"(01)90012345678908(3103)001750(10)BATCH123",
false,
FP_STK_LONG,
),
(
"stk_linkage",
"(01)90012345678908(10)BATCH",
true,
FP_STK_LINK,
),
];
for (tag, input, link, want) in stk_cases {
let bm = encode_stacked(input, *link)
.unwrap_or_else(|e| panic!("encode_stacked({tag}) ok: {e:?}"));
let got = fp_bm(&bm);
assert_eq!(got, *want, "stacked fingerprint changed for {tag}");
}
}
const FP_LIN_GTIN: (usize, u64) = (58, 10461131334101);
const FP_LIN_GW: (usize, u64) = (66, 13256252190434);
const FP_LIN_GA: (usize, u64) = (100, 30956029844782);
const FP_LIN_NG: (usize, u64) = (100, 31104678247398);
const FP_LIN_LINK: (usize, u64) = (58, 10381498261271);
const FP_STK_GTIN: (usize, usize, u64) = (102, 71, 21699701777190963);
const FP_STK_LONG: (usize, usize, u64) = (102, 145, 113339721915581835);
const FP_STK_LINK: (usize, usize, u64) = (102, 108, 60900775407773981);
#[test]
fn encode_general_purpose_state_machine_fingerprint_pinned() {
fn fp(out: &[u8], mode: CharsetMode) -> (u8, usize, u64) {
let mut s: u64 = 0;
for (i, &b) in out.iter().enumerate() {
s = s.wrapping_add(
(b as u64).wrapping_mul((i as u64).wrapping_add(1).wrapping_mul(2_654_435_761)),
);
}
let mode_tag = match mode {
CharsetMode::Numeric => 0u8,
CharsetMode::Alphanumeric => 1,
CharsetMode::Iso646 => 2,
};
(mode_tag, out.len(), s)
}
const F: u8 = FNC1_SENTINEL_BYTE;
let cases: &[(&str, &[u8], usize, usize, (u8, usize, u64))] = &[
("pure_num_pair", b"12", 24, STACKED_SEGMENTS, FP_EGP_PURE),
(
"lone_num_rem456",
b"7",
30,
STACKED_SEGMENTS,
FP_EGP_LONE_R456,
),
(
"lone_num_pad_fnc1",
b"7",
0,
STACKED_SEGMENTS,
FP_EGP_LONE_PAD,
),
("num_to_alpha", b"AB", 24, STACKED_SEGMENTS, FP_EGP_N_TO_A),
(
"alpha_fnc1_back",
&[b'A', b'B', F, b'1', b'2'],
24,
STACKED_SEGMENTS,
FP_EGP_ALPHA_FNC1,
),
(
"alpha_to_iso_only",
b"AB!Z",
24,
STACKED_SEGMENTS,
FP_EGP_A_TO_I,
),
(
"alpha_nr6_latch",
b"AB123456789012345",
24,
STACKED_SEGMENTS,
FP_EGP_NR6,
),
(
"alpha_nr4_eom",
b"AB12345678",
24,
STACKED_SEGMENTS,
FP_EGP_NR4_EOM,
),
(
"iso_fnc1_back",
&[b'!', b'a', F, b'1', b'2'],
24,
STACKED_SEGMENTS,
FP_EGP_ISO_FNC1,
),
(
"iso_to_num",
b"!a12345678",
24,
STACKED_SEGMENTS,
FP_EGP_I_TO_N,
),
(
"iso_to_alpha",
b"!aABCDEZ",
24,
STACKED_SEGMENTS,
FP_EGP_I_TO_A,
),
(
"mixed_round_trip",
b"12AB!Z01234567",
24,
STACKED_SEGMENTS,
FP_EGP_ROUND,
),
];
for (tag, gpf, bbg, seg, want) in cases {
let (bits, mode) = encode_general_purpose(gpf, *bbg, *seg)
.unwrap_or_else(|e| panic!("encode_general_purpose({tag}) ok: {e:?}"));
let got = fp(&bits, mode);
eprintln!("CAP encode_general_purpose/{tag} -> {got:?}");
assert_eq!(got, *want, "fingerprint changed for {tag}");
}
}
const FP_EGP_PURE: (u8, usize, u64) = (0, 7, 39816536415);
const FP_EGP_LONE_R456: (u8, usize, u64) = (0, 7, 69015329786);
const FP_EGP_LONE_PAD: (u8, usize, u64) = (0, 7, 69015329786);
const FP_EGP_N_TO_A: (u8, usize, u64) = (1, 16, 84941944352);
const FP_EGP_ALPHA_FNC1: (u8, usize, u64) = (0, 28, 499033923068);
const FP_EGP_A_TO_I: (u8, usize, u64) = (2, 36, 735278705797);
const FP_EGP_NR6: (u8, usize, u64) = (0, 72, 3697629015073);
const FP_EGP_NR4_EOM: (u8, usize, u64) = (0, 47, 1481175154638);
const FP_EGP_ISO_FNC1: (u8, usize, u64) = (0, 36, 923743644828);
const FP_EGP_I_TO_N: (u8, usize, u64) = (0, 55, 2075768765102);
const FP_EGP_I_TO_A: (u8, usize, u64) = (1, 65, 2309359112070);
const FP_EGP_ROUND: (u8, usize, u64) = (0, 78, 3084454354282);
#[test]
fn encode_stacked_state_machine_fingerprint_pinned() {
fn fp_bm(bm: &crate::encoding::BitMatrix) -> (usize, usize, u64) {
let w = bm.width();
let h = bm.height();
let mut s: u64 = 0;
for y in 0..h {
for x in 0..w {
let v = u64::from(bm.get(x, y));
let idx = (y as u64) * (w as u64) + (x as u64);
s = s.wrapping_add(
v.wrapping_mul(idx.wrapping_add(1).wrapping_mul(2_654_435_761)),
);
}
}
(w, h, s)
}
let cases: &[(&str, &str, bool, (usize, usize, u64))] = &[
(
"single_gtin",
"(01)90012345678908",
false,
FP_ESK_SINGLE_GTIN,
),
("gtin_link", "(01)90012345678908", true, FP_ESK_GTIN_LINK),
(
"two_row_balanced",
"(01)90012345678908(3103)001750",
false,
FP_ESK_TWO_BAL,
),
(
"two_row_short",
"(01)90012345678908(10)A",
false,
FP_ESK_TWO_SHORT,
),
(
"three_row_short",
"(01)90012345678908(3103)001750(10)BATCH12345",
false,
FP_ESK_THREE_SHORT,
),
(
"three_row_full",
"(01)90012345678908(3103)001750(10)BATCHLOT1234X",
false,
FP_ESK_THREE_FULL,
),
("vlf_path", "(10)LOT123(99)9876543", false, FP_ESK_VLF),
(
"cdf_long",
"(01)90012345678908(3103)001750(13)260101",
false,
FP_ESK_CDF,
),
(
"gpf_alpha_run",
"(01)90012345678908(10)ABCDEFGH",
false,
FP_ESK_GPF_ALPHA,
),
(
"gpf_iso_mid",
"(01)90012345678908(10)AB!CD",
false,
FP_ESK_GPF_ISO,
),
];
for (tag, input, link, want) in cases {
let bm = encode_stacked(input, *link)
.unwrap_or_else(|e| panic!("encode_stacked({tag}) ok: {e:?}"));
let got = fp_bm(&bm);
eprintln!("CAP encode_stacked/{tag} -> {got:?}");
assert_eq!(got, *want, "fingerprint changed for {tag}");
}
}
const FP_ESK_SINGLE_GTIN: (usize, usize, u64) = (102, 71, 21699701777190963);
const FP_ESK_GTIN_LINK: (usize, usize, u64) = (102, 71, 22286127688818366);
const FP_ESK_TWO_BAL: (usize, usize, u64) = (102, 71, 23659652201169011);
const FP_ESK_TWO_SHORT: (usize, usize, u64) = (102, 71, 35298684095342239);
const FP_ESK_THREE_SHORT: (usize, usize, u64) = (102, 145, 108701318395838259);
const FP_ESK_THREE_FULL: (usize, usize, u64) = (102, 145, 138945001205362538);
const FP_ESK_VLF: (usize, usize, u64) = (102, 71, 34602011503253223);
const FP_ESK_CDF: (usize, usize, u64) = (102, 71, 37155586668642506);
const FP_ESK_GPF_ALPHA: (usize, usize, u64) = (102, 108, 60258898333099288);
const FP_ESK_GPF_ISO: (usize, usize, u64) = (102, 108, 64523294508553725);
#[test]
fn oversized_input_returns_error_not_panic() {
let long = format!("(10){}", "A".repeat(60));
let lin = encode(&long, false);
assert!(
matches!(lin, Err(crate::error::Error::InvalidData(_))),
"oversized linear input must be InvalidData, got {lin:?}"
);
let stk = encode_stacked(&long, false);
assert!(
matches!(stk, Err(crate::error::Error::InvalidData(_))),
"oversized stacked input must be InvalidData, got {stk:?}"
);
assert!(
encode("(01)90012345678908", false).is_ok(),
"in-capacity GTIN must still encode"
);
}
fn el(ai: &str, data: &str) -> crate::util::gs1::Element {
crate::util::gs1::Element {
ai: ai.to_string(),
data: data.to_string(),
}
}
#[test]
fn method_0100_weight_boundary_32767() {
let at = vec![el("01", "90012345678908"), el("3103", "032767")];
let r = encode_method_0100(&at).expect("0100 ok").expect(
"weight 32767 is the inclusive max — `> 32767` is false so 0100 must encode; \
the `>= 32767` mutant wrongly declines here",
);
assert_eq!(r.method_bits, vec![0, 1, 0, 0]);
let over = vec![el("01", "90012345678908"), el("3103", "032768")];
assert!(
encode_method_0100(&over).expect("0100 ok").is_none(),
"weight 32768 exceeds the cap — both original and mutant decline"
);
}
#[test]
fn method_0101_weight_boundaries() {
let at = vec![el("01", "90012345678908"), el("3202", "009999")];
let r = encode_method_0101(&at).expect("0101 ok").expect(
"3202 weight 9999 is the inclusive max — `> 9999` false so must encode; \
`>= 9999` and `== 9999` mutants wrongly decline",
);
assert_eq!(r.method_bits, vec![0, 1, 0, 1]);
let over = vec![el("01", "90012345678908"), el("3202", "010000")];
assert!(
encode_method_0101(&over).expect("0101 ok").is_none(),
"3202 weight 10000 must decline — the `== 9999` mutant would encode"
);
let at3 = vec![el("01", "90012345678908"), el("3203", "022767")];
let r3 = encode_method_0101(&at3).expect("0101 ok").expect(
"3203 weight 22767 is the inclusive max — `> 22767` false so must encode; \
`>= 22767` mutant wrongly declines",
);
assert_eq!(r3.method_bits, vec![0, 1, 0, 1]);
}
#[test]
fn method_0111_validation_disjunction_each_operand() {
let mk = |gtin: &str, w: &str| vec![el("01", gtin), el("3103", w)];
assert!(
matches!(encode_method_0111(&mk("9001234567890", "000123")), Ok(None)),
"L937: 13-digit GTIN must decline; `&&` mutant slips through"
);
assert!(
matches!(
encode_method_0111(&mk("900123456789X8", "000123")),
Ok(None)
),
"L938: non-digit GTIN must Ok(None)-decline; `&&` mutant errors/proceeds"
);
assert!(
matches!(
encode_method_0111(&mk("10012345678908", "000123")),
Ok(None)
),
"L939: GTIN not starting '9' must decline; `&&` mutant slips through"
);
assert!(
matches!(encode_method_0111(&mk("90012345678908", "12345")), Ok(None)),
"L940: 5-digit (3103) value must decline; `&&` mutant slips through"
);
let date7 = vec![
el("01", "90012345678908"),
el("3103", "001750"),
el("11", "2501011"),
];
assert!(
matches!(encode_method_0111(&date7), Ok(None)),
"L953: 7-digit date must decline; `&&` mutant slips through"
);
let badmonth = vec![
el("01", "90012345678908"),
el("3103", "001750"),
el("11", "251301"),
];
assert!(
matches!(encode_method_0111(&badmonth), Ok(None)),
"L959:36: month 13 must decline; `&&` mutant slips through"
);
let day31 = vec![
el("01", "90012345678908"),
el("3103", "001750"),
el("11", "250131"),
];
assert!(
encode_method_0111(&day31).expect("0111 ok").is_some(),
"L959:42: day 31 is valid (`dd > 31` false) — `== 31`/`>= 31` mutants reject it"
);
}
#[test]
fn method_0111_date_encode_arithmetic_exact_cdf() {
let els = vec![
el("01", "90012345678908"),
el("3103", "001750"),
el("13", "250601"),
];
let m = encode_method_0111(&els)
.expect("0111 ok")
.expect("must encode");
let want_cdf: Vec<u8> = vec![
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1,
1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1,
1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1,
];
assert_eq!(
m.cdf, want_cdf,
"date_encoded = 25*384 + (6-1)*32 + 1 = 9761; the `+ → -` and `* → /` mutants \
change the last 16 cdf bits"
);
}
#[test]
fn method_01100_392x_arm_coverage() {
for (ai, bits) in [
("3920", [0u8, 0]),
("3921", [0, 1]),
("3922", [1, 0]),
("3923", [1, 1]),
] {
let els = vec![el("01", "90012345678908"), el(ai, "1234")];
let m = encode_method_01100(&els)
.expect("01100 ok")
.unwrap_or_else(|| {
panic!("({ai}) must match method 01100 (deleted arm regresses to None)")
});
assert_eq!(
&m.cdf[40..42],
&bits,
"({ai}) last-digit cdf bits 40..42 pin the match arm"
);
}
}
#[test]
fn method_01100_validation_disjunction() {
assert!(
matches!(
encode_method_01100(&[el("01", "9001234567890"), el("3920", "1234")]),
Ok(None)
),
"L1012:23: 13-digit GTIN must decline"
);
assert!(
matches!(
encode_method_01100(&[el("01", "900123456789X8"), el("3920", "1234")]),
Ok(None)
),
"L1012:66: non-digit GTIN must Ok(None)-decline"
);
assert!(
matches!(
encode_method_01100(&[el("01", "90012345678908"), el("3920", "")]),
Ok(None)
),
"L1016:28: empty (392x) value must decline"
);
}
#[test]
fn method_01101_393x_arm_coverage() {
for (ai, bits) in [
("3930", [0u8, 0]),
("3931", [0, 1]),
("3932", [1, 0]),
("3933", [1, 1]),
] {
let els = vec![el("01", "90012345678908"), el(ai, "840123")];
let m = encode_method_01101(&els)
.expect("01101 ok")
.unwrap_or_else(|| {
panic!("({ai}) must match method 01101 (deleted arm regresses to None)")
});
assert_eq!(
&m.cdf[40..42],
&bits,
"({ai}) last-digit cdf bits 40..42 pin the match arm"
);
}
}
#[test]
fn method_01101_validation_disjunction() {
assert!(
matches!(
encode_method_01101(&[el("01", "9001234567890"), el("3930", "840123")]),
Ok(None)
),
"L1062:23: 13-digit GTIN must decline"
);
assert!(
matches!(
encode_method_01101(&[el("01", "900123456789X8"), el("3930", "840123")]),
Ok(None)
),
"L1062:66: non-digit GTIN must Ok(None)-decline"
);
let three = vec![el("01", "90012345678908"), el("3930", "840")];
assert!(
encode_method_01101(&three).expect("01101 ok").is_some(),
"L1066:23: a 3-digit (393x) value (currency only) must encode; `==3`/`<=3` reject it"
);
assert!(
matches!(
encode_method_01101(&[el("01", "90012345678908"), el("3930", "84")]),
Ok(None)
),
"L1066:27: a 2-digit value must Ok(None)-decline (mutant panics on [..3] slice)"
);
}
#[test]
fn method_01101_trailing_fnc1_gate() {
let two = vec![el("01", "90012345678908"), el("3932", "840123")];
let m2 = encode_method_01101(&two).expect("ok").expect("some");
assert_eq!(
m2.gpf_prefix_bytes,
vec![b'1', b'2', b'3'],
"len==2: gpf prefix is v1[3..] with NO trailing FNC1; `==2`/`>=2` mutants append one"
);
let three = vec![
el("01", "90012345678908"),
el("3932", "840123"),
el("10", "AB"),
];
let m3 = encode_method_01101(&three).expect("ok").expect("some");
assert_eq!(
m3.gpf_prefix_bytes,
vec![b'1', b'2', b'3', FNC1_SENTINEL_BYTE],
"len==3: gpf prefix ends with the FNC1 sentinel; `< 2` mutant omits it"
);
}
#[test]
fn method_1_empty_elements_ok_none() {
assert!(
matches!(encode_method_1(&[]), Ok(None)),
"empty element list must Ok(None)-decline; the `&&` mutant panics on elements[0]"
);
}
#[test]
fn encode_bits_before_gpf_lone_digit_golden() {
let pat = encode("(01)90012345678908(10)1", false).expect("encode ok");
let want: &[u8] = &[
1, 1, 2, 3, 1, 1, 2, 5, 2, 1, 8, 4, 1, 1, 4, 1, 1, 2, 1, 4, 1, 3, 1, 1, 4, 2, 2, 1, 5,
1, 1, 1, 4, 6, 3, 3, 1, 1, 2, 4, 2, 1, 3, 3, 4, 1, 2, 1, 1, 1, 4, 3, 6, 4, 1, 1, 2, 3,
3, 2, 1, 4, 1, 1, 1, 1,
];
assert_eq!(
pat.bars, want,
"method-1 + lone trailing digit: the `len * vlf` mutant shifts bits_before_gpf \
from 60 to 59, changing the lone-digit rembits encoding"
);
}
#[test]
fn encode_stacked_bits_before_gpf_lone_digit_fingerprint() {
fn fp_bm(bm: &crate::encoding::BitMatrix) -> (usize, usize, u64) {
let w = bm.width();
let h = bm.height();
let mut s: u64 = 0;
for y in 0..h {
for x in 0..w {
let v = u64::from(bm.get(x, y));
let idx = (y as u64) * (w as u64) + (x as u64);
s = s.wrapping_add(
v.wrapping_mul(idx.wrapping_add(1).wrapping_mul(2_654_435_761)),
);
}
}
(w, h, s)
}
let bm = encode_stacked("(01)90012345678908(10)1", false).expect("stacked ok");
assert_eq!(
fp_bm(&bm),
(102, 71, 22671862330299603),
"stacked method-1 + lone trailing digit: `len * vlf` mutant changes bits_before_gpf \
→ different module grid"
);
}
#[test]
fn compute_row_sep_max_bound_n129() {
let z = vec![0u8; 129];
let ones: Vec<usize> = compute_row_sep(&z)
.iter()
.enumerate()
.filter(|(_, &b)| b == 1)
.map(|(i, _)| i)
.collect();
let want: Vec<usize> = vec![
4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 22, 24, 26, 28, 30, 32, 34,
35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 69, 71, 73, 75, 77, 79, 81, 83, 84, 85, 86,
87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106,
107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123,
124,
];
assert_eq!(
ones, want,
"n=129: only finders 19 and 68 fire (max=116 excludes 117); the `n + 13` mutant \
also fires finder 117, alternating the 117.. region"
);
}
#[test]
fn compute_row_sep_finder_stride_additive() {
let ones = |n: usize| -> Vec<usize> {
compute_row_sep(&vec![0u8; n])
.iter()
.enumerate()
.filter(|(_, &b)| b == 1)
.map(|(i, _)| i)
.collect::<Vec<_>>()
};
let want140: Vec<usize> = vec![
4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 22, 24, 26, 28, 30, 32, 34,
35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 69, 71, 73, 75, 77, 79, 81, 83, 84, 85, 86,
87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106,
107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 118, 120, 122, 124, 126, 128, 130,
132, 133, 134, 135,
];
assert_eq!(
ones(140),
want140,
"n=140: the 19-chain must step 19→117 via `+= 98`; the `*= 98` mutant drops finder 117"
);
let want185: Vec<usize> = vec![
4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 22, 24, 26, 28, 30, 32, 34,
35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 69, 71, 73, 75, 77, 79, 81, 83, 84, 85, 86,
87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106,
107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 118, 120, 122, 124, 126, 128, 130,
132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148,
149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165,
167, 169, 171, 173, 175, 177, 179,
];
assert_eq!(
ones(185),
want185,
"n=185: the 68-chain must step 68→166 via `+= 98`; the `*= 98` mutant drops finder 166"
);
}
#[test]
fn compute_row_sep_finder_clamp_n33() {
let z = vec![0u8; 33];
let ones: Vec<usize> = compute_row_sep(&z)
.iter()
.enumerate()
.filter(|(_, &b)| b == 1)
.map(|(i, _)| i)
.collect();
assert_eq!(
ones,
vec![4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 22, 24, 26, 28],
"n=33: finder 19 region clamps to n-1=32; the `n+1`/`n/1` mutants index out of bounds \
(panic) instead of completing"
);
}
#[test]
fn databar_expanded_equivalence_notes() {
assert_eq!(
tab174_row_for(4191).gs,
3988,
"top threshold returns before tail"
);
assert_eq!(tab174_row_for(0).gs, 0, "first threshold returns at j=0");
assert_eq!(
pack_12_bits(&[1u8; 12]),
4095,
"OR == XOR when low bit is clear"
);
assert_eq!(pack_12_bits(&[0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0]), 1680);
assert_eq!(
FILL_PAT[0], 0,
"FILL_PAT[0]==0 makes the i<=shift boundary a no-op"
);
let n = assemble_binval(
0,
&[1],
2,
&[0u8; 44],
&[],
CharsetMode::Numeric,
DEFAULT_SEGMENTS,
);
assert_eq!(
n.len() % 12,
0,
"numeric-mode pad assembles to whole codewords"
);
let a = assemble_binval(
0,
&[1],
2,
&[0u8; 44],
&[],
CharsetMode::Alphanumeric,
DEFAULT_SEGMENTS,
);
assert_eq!(
a.len() % 12,
0,
"non-numeric pad assembles to whole codewords"
);
assert!(encode_stacked("(01)90012345678908", false).is_ok());
assert_eq!(
STACKED_SEGMENTS % 4,
0,
"left conjunct is always false → RHS unreached"
);
let bm = encode_stacked("(01)90012345678908(10)A", false).expect("ok");
assert_eq!(
bm.width(),
102,
"BitMatrix width is pixx; short last strip pads with 0 = default"
);
let mut row = vec![0u8; 40];
row[18] = 1;
let sep = compute_row_sep(&row);
assert_eq!(
sep[19], 1,
"i=19: row[18]=1 forces sep[18]=0, both predicates agree → sep[19]=1"
);
assert_eq!(count_finder_positions(13), 0, "n=13 counts 0 via the loop");
assert_eq!(
count_finder_positions(12),
0,
"n=12 counts 0 via early return"
);
assert_eq!(count_finder_positions(31), 0, "n=31: max=18 < 19, still 0");
assert_eq!(count_finder_positions(32), 1, "n=32: first finder appears");
}
}