use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;
use super::{BitMatrix, Decoder, Symbol, Symbology, SymbologyKind};
use crate::error::{Error, Result};
const UPSTREAM_QUIET_ZONE: u32 = 10;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct Code128;
impl Symbology for Code128 {
fn kind(&self) -> SymbologyKind {
SymbologyKind::Code128
}
fn encode(&self, data: &str) -> Result<Symbol> {
if data.is_empty() {
return Err(Error::EmptyPayload);
}
let code = ::code128::Code128::encode_str(data).ok_or_else(|| Error::Unencodable {
symbology: SymbologyKind::Code128.name(),
reason: "payload contains characters outside ISO/IEC 8859-1 (Latin-1)".to_string(),
})?;
let total = code.len() as u32;
let width = total
.checked_sub(2 * UPSTREAM_QUIET_ZONE)
.filter(|w| *w > 0)
.ok_or_else(|| Error::Unencodable {
symbology: SymbologyKind::Code128.name(),
reason: "encoder produced a degenerate symbol".to_string(),
})?;
let mut row = vec![false; width as usize];
for bar in code.bar_coordinates() {
let start = bar.x.saturating_sub(UPSTREAM_QUIET_ZONE) as usize;
if start >= row.len() {
continue;
}
let end = (start + bar.width as usize).min(row.len());
row[start..end].fill(true);
}
Ok(Symbol::new(
SymbologyKind::Code128,
BitMatrix::from_row(row),
data.to_string(),
))
}
}
impl Decoder for Code128 {
fn kind(&self) -> SymbologyKind {
SymbologyKind::Code128
}
fn decode(&self, modules: &BitMatrix) -> Result<String> {
if modules.height() != 1 {
return Err(Error::Decode(
"expected a single-row linear symbol".to_string(),
));
}
let bars = to_bars(modules.row(0));
if bars.is_empty() {
return Err(Error::Decode("symbol contains no bars".to_string()));
}
let bytes = ::code128::decode(&bars).map_err(|e| Error::Decode(alloc::format!("{e:?}")))?;
Ok(bytes.into_iter().map(|b| b as char).collect())
}
}
pub fn decode(symbol: &Symbol) -> Result<String> {
if symbol.kind() != SymbologyKind::Code128 {
return Err(Error::Decode(alloc::format!(
"expected a Code 128 symbol, found {}",
symbol.kind()
)));
}
Code128.decode(symbol.modules())
}
fn to_bars(row: &[bool]) -> Vec<::code128::Bar> {
let mut bars = Vec::new();
let mut i = 0;
while i < row.len() {
if !row[i] {
i += 1;
continue;
}
let bar_start = i;
while i < row.len() && row[i] {
i += 1;
}
let width = (i - bar_start) as u8;
let space_start = i;
while i < row.len() && !row[i] {
i += 1;
}
let space = (i - space_start) as u8;
bars.push(::code128::Bar { width, space });
}
bars
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::format;
const START_A: &str = "11010000100";
const START_B: &str = "11010010000";
const START_C: &str = "11010011100";
const STOP: &str = "1100011101011";
fn as_bits(symbol: &Symbol) -> String {
symbol
.modules()
.row(0)
.iter()
.map(|d| if *d { '1' } else { '0' })
.collect()
}
#[test]
fn starts_with_a_valid_start_pattern() {
let bits = as_bits(&Code128.encode("PKG-9ED9285C").unwrap());
let start = &bits[..11];
assert!(
start == START_A || start == START_B || start == START_C,
"unexpected start pattern {start}"
);
}
#[test]
fn numeric_payloads_use_the_set_c_start_pattern() {
let bits = as_bits(&Code128.encode("1234567890").unwrap());
assert_eq!(&bits[..11], START_C, "digit runs should start in Set C");
}
#[test]
fn ends_with_the_stop_pattern() {
let bits = as_bits(&Code128.encode("PKG-9ED9285C").unwrap());
assert!(bits.ends_with(STOP), "missing or malformed stop pattern");
}
#[test]
fn width_is_a_whole_number_of_symbols_plus_the_stop_bars() {
let bits = as_bits(&Code128.encode("PKG-9ED9285C").unwrap());
assert_eq!(
(bits.len() - 2) % 11,
0,
"symbol width {} is not 11n + 2",
bits.len()
);
}
#[test]
fn set_c_halves_the_width_of_long_digit_runs() {
let digits = "12345678901234567890"; let letters = "ABCDEFGHIJKLMNOPQRST"; let numeric = Code128.encode(digits).unwrap().modules().width();
let alpha = Code128.encode(letters).unwrap().modules().width();
assert!(
numeric < alpha,
"Set C compression not applied: {numeric} modules vs {alpha}"
);
}
#[test]
fn round_trips_through_the_module_grid() {
for payload in [
"PKG-9ED9285C",
"1234567890123456789012",
"A",
"Mixed 123 Case!",
"~$%^&*()_+",
] {
let symbol = Code128.encode(payload).unwrap();
let decoded = decode(&symbol).unwrap_or_else(|e| panic!("{payload}: {e}"));
assert_eq!(decoded, payload);
}
}
#[test]
fn rejects_an_empty_payload() {
assert!(matches!(Code128.encode(""), Err(Error::EmptyPayload)));
}
#[test]
fn rejects_characters_outside_latin1() {
let err = Code128.encode("PKG-\u{4e2d}\u{6587}").unwrap_err();
assert!(matches!(err, Error::Unencodable { .. }), "got {err:?}");
}
#[test]
fn quiet_zone_is_not_part_of_the_symbol() {
let symbol = Code128.encode("A").unwrap();
let row = symbol.modules().row(0);
assert!(row[0], "symbol must begin with a bar, not a quiet zone");
assert!(
*row.last().unwrap(),
"symbol must end with a bar, not a quiet zone"
);
}
#[test]
fn payload_is_preserved_on_the_symbol() {
let symbol = Code128.encode("PKG-9ED9285C").unwrap();
assert_eq!(symbol.payload(), "PKG-9ED9285C");
assert_eq!(format!("{}", symbol.kind()), "Code 128");
}
}