const PATTERNS: [&str; 107] = [
"212222", "222122", "222221", "121223", "121322", "131222", "122213", "122312", "132212", "221213",
"221312", "231212", "112232", "122132", "122231", "113222", "123122", "123221", "223211", "221132",
"221231", "213212", "223112", "312131", "311222", "321122", "321221", "312212", "322112", "322211",
"212123", "212321", "232121", "111323", "131123", "131321", "112313", "132113", "132311", "211313",
"231113", "231311", "112133", "112331", "132131", "113123", "113321", "133121", "313121", "211331",
"231131", "213113", "213311", "213131", "311123", "311321", "331121", "312113", "312311", "332111",
"314111", "221411", "431111", "111224", "111422", "121124", "121421", "141122", "141221", "112214",
"112412", "122114", "122411", "142112", "142211", "241211", "221114", "413111", "241112", "134111",
"111242", "121142", "121241", "114212", "124112", "124211", "411212", "421112", "421211", "212141",
"214121", "412121", "111143", "111341", "131141", "114113", "114311", "411113", "411311", "113141",
"114131", "311141", "411131", "211412", "211214", "211232", "2331112",
];
const CODE_C: u16 = 99;
const CODE_B: u16 = 100;
const START_B: u16 = 104;
const START_C: u16 = 105;
const STOP: u16 = 106;
pub fn values(text: &str) -> Result<Vec<u16>, char> {
if let Some(bad) = text.chars().find(|c| !(' '..='\u{7f}').contains(c)) {
return Err(bad);
}
let bytes = text.as_bytes();
let digit_run = |from: usize| bytes[from..].iter().take_while(|b| b.is_ascii_digit()).count();
let mut out = Vec::with_capacity(bytes.len() + 3);
let mut i = 0;
let first_run = digit_run(0);
let mut in_c = first_run >= 4 && first_run % 2 == 0;
out.push(if in_c { START_C } else { START_B });
while i < bytes.len() {
let run = digit_run(i);
if in_c {
if run >= 2 {
out.push(((bytes[i] - b'0') * 10 + (bytes[i + 1] - b'0')) as u16);
i += 2;
continue;
}
out.push(CODE_B);
in_c = false;
}
if run >= 4 && run % 2 == 0 {
out.push(CODE_C);
in_c = true;
continue;
}
out.push((bytes[i] - b' ') as u16);
i += 1;
}
let check = out.iter().enumerate().fold(0u32, |acc, (pos, &v)| acc + v as u32 * (pos.max(1) as u32)) % 103;
out.push(check as u16);
out.push(STOP);
Ok(out)
}
pub fn modules(values: &[u16]) -> Vec<bool> {
let mut row = Vec::with_capacity(values.len() * 11 + 2);
for &v in values {
for (k, width) in PATTERNS[v as usize].bytes().enumerate() {
row.extend(std::iter::repeat_n(k % 2 == 0, (width - b'0') as usize));
}
}
row
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_pattern_is_eleven_modules_and_stop_thirteen() {
for (v, p) in PATTERNS.iter().enumerate() {
let sum: u32 = p.bytes().map(|b| (b - b'0') as u32).sum();
assert_eq!(sum, if v == 106 { 13 } else { 11 }, "value {v}");
}
}
#[test]
fn the_check_symbol_is_the_weighted_sum_mod_103() {
assert_eq!(values("PJJ123C").unwrap(), vec![104, 48, 42, 42, 17, 18, 19, 35, 55, 106]);
}
#[test]
fn digit_runs_pack_two_to_a_symbol() {
assert_eq!(&values("12345678").unwrap()[..5], &[105, 12, 34, 56, 78]);
assert_eq!(&values("A12345").unwrap()[..6], &[104, 33, 17, 99, 23, 45]);
assert_eq!(&values("A12").unwrap()[..4], &[104, 33, 17, 18]);
}
#[test]
fn a_character_outside_ascii_is_refused_by_name() {
assert_eq!(values("café"), Err('é'));
}
}
#[cfg(test)]
#[test]
#[ignore]
fn write_barcodes_for_an_outside_decoder() {
use super::super::render::Drawing;
let dir = std::env::temp_dir().join("zebflow-code128-check");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("dir");
let cases = ["CERT-2026-0412", "T-000481", "12345678", "A12345", "Hello, world!", "0012", "x", "PJJ123C", "Order #88213 / batch 7"];
for (i, text) in cases.iter().enumerate() {
let row = vec![modules(&values(text).expect("ascii"))];
let png = Drawing { rows: &row, margin: 10, module_px: 3, row_px: 120, dark: [0, 0, 0], light: [255, 255, 255] }.png().expect("png");
std::fs::write(dir.join(format!("{i:03}.png")), png).expect("png");
std::fs::write(dir.join(format!("{i:03}.txt")), text).expect("txt");
}
}