use alloc::string::String;
use alloc::vec::Vec;
use crate::symbology::{BitMatrix, Decoder, LinearCharacter};
const MAX_START_CANDIDATES: usize = 3;
const MAX_TRUNCATIONS: usize = 2;
#[derive(Clone, Copy, Debug)]
struct Run {
dark: bool,
len: u32,
}
pub(crate) fn decode_row<D: Decoder + ?Sized>(
decoder: &D,
character: LinearCharacter,
row: &[bool],
) -> Option<String> {
let elements = character.elements as usize;
let stop = character.stop_elements as usize;
if elements == 0 || stop == 0 {
return None;
}
let runs = runs(row);
let runs = trim(&runs);
if runs.len() < elements * 2 + stop {
return None;
}
for start in (0..MAX_START_CANDIDATES).map(|i| i * 2) {
let Some(available) = runs.len().checked_sub(start) else {
break;
};
let Some(max_characters) = available.checked_sub(stop).map(|rest| rest / elements) else {
continue;
};
for dropped in 0..=MAX_TRUNCATIONS {
let Some(characters) = max_characters.checked_sub(dropped).filter(|k| *k >= 2) else {
break;
};
let window = &runs[start..start + characters * elements + stop];
let Some(modules) = quantize(window, character) else {
continue;
};
if let Ok(payload) = decoder.decode(&BitMatrix::from_row(modules)) {
return Some(payload);
}
}
}
None
}
fn runs(row: &[bool]) -> Vec<Run> {
let mut runs: Vec<Run> = Vec::new();
for &dark in row {
match runs.last_mut() {
Some(last) if last.dark == dark => last.len += 1,
_ => runs.push(Run { dark, len: 1 }),
}
}
runs
}
fn trim(runs: &[Run]) -> &[Run] {
let start = runs.iter().position(|r| r.dark);
let end = runs.iter().rposition(|r| r.dark);
match (start, end) {
(Some(s), Some(e)) => &runs[s..=e],
_ => &[],
}
}
fn quantize(window: &[Run], character: LinearCharacter) -> Option<Vec<bool>> {
let elements = character.elements as usize;
let stop = character.stop_elements as usize;
let characters = window.len().checked_sub(stop)? / elements;
let mut modules = Vec::new();
for c in 0..characters {
let group = &window[c * elements..(c + 1) * elements];
push_group(&mut modules, group, character.modules)?;
}
push_group(
&mut modules,
&window[characters * elements..],
character.stop_modules,
)?;
if modules.is_empty() {
return None;
}
Some(modules)
}
fn push_group(out: &mut Vec<bool>, group: &[Run], modules: u32) -> Option<()> {
let count = group.len();
if count == 0 || (modules as usize) < count {
return None;
}
let total: u64 = group.iter().map(|r| u64::from(r.len)).sum();
if total == 0 {
return None;
}
let mut widths: Vec<u32> = Vec::with_capacity(count);
let mut remainders: Vec<(u64, usize)> = Vec::with_capacity(count);
for (i, run) in group.iter().enumerate() {
let scaled = u64::from(run.len) * u64::from(modules);
widths.push((scaled / total) as u32);
remainders.push((scaled % total, i));
}
let assigned: u32 = widths.iter().sum();
let mut leftover = modules.checked_sub(assigned)?;
remainders.sort_by(|a, b| b.0.cmp(&a.0).then(a.1.cmp(&b.1)));
for &(_, i) in remainders.iter() {
if leftover == 0 {
break;
}
widths[i] += 1;
leftover -= 1;
}
while let Some(starved) = widths.iter().position(|w| *w == 0) {
let (widest, _) = widths
.iter()
.enumerate()
.max_by_key(|(i, w)| (**w, core::cmp::Reverse(*i)))?;
if widths[widest] <= 1 {
return None;
}
widths[widest] -= 1;
widths[starved] += 1;
}
for (run, width) in group.iter().zip(widths) {
out.extend(core::iter::repeat_n(run.dark, width as usize));
}
Some(())
}
#[cfg(all(test, feature = "code128"))]
mod tests {
use super::*;
use crate::symbology::{Code128, Symbology, SymbologyKind};
use alloc::vec;
fn character() -> LinearCharacter {
SymbologyKind::Code128.linear_character().unwrap()
}
fn stretch(modules: &[bool], scale: usize, margin: usize) -> Vec<bool> {
let mut row = vec![false; margin];
for &m in modules {
row.extend(core::iter::repeat_n(m, scale));
}
row.extend(core::iter::repeat_n(false, margin));
row
}
#[test]
fn reads_a_symbol_at_several_scales() {
let symbol = Code128.encode("PKG-9ED9285C").unwrap();
let modules = symbol.modules().row(0).to_vec();
for scale in [1, 2, 3, 4, 7, 11] {
let row = stretch(&modules, scale, 10 * scale);
let got = decode_row(&Code128, character(), &row);
assert_eq!(got.as_deref(), Some("PKG-9ED9285C"), "at scale {scale}");
}
}
#[test]
fn reads_a_symbol_with_no_quiet_zone_at_all() {
let symbol = Code128.encode("PKG-9ED9285C").unwrap();
let row = stretch(symbol.modules().row(0), 3, 0);
assert_eq!(
decode_row(&Code128, character(), &row).as_deref(),
Some("PKG-9ED9285C")
);
}
#[test]
fn survives_module_widths_that_do_not_divide_evenly() {
let symbol = Code128.encode("PKG-9ED9285C").unwrap();
let modules = symbol.modules().row(0);
let mut row = vec![false; 35];
for (i, &m) in modules.iter().enumerate() {
row.extend(core::iter::repeat_n(m, if i % 2 == 0 { 4 } else { 3 }));
}
row.extend(core::iter::repeat_n(false, 35));
assert_eq!(
decode_row(&Code128, character(), &row).as_deref(),
Some("PKG-9ED9285C")
);
}
#[test]
fn skips_junk_before_and_after_the_symbol() {
let symbol = Code128.encode("PKG-9ED9285C").unwrap();
let mut row = vec![true; 6];
row.extend(core::iter::repeat_n(false, 20));
row.extend(stretch(symbol.modules().row(0), 3, 30));
row.extend(core::iter::repeat_n(true, 5));
assert_eq!(
decode_row(&Code128, character(), &row).as_deref(),
Some("PKG-9ED9285C")
);
}
#[test]
fn refuses_a_line_that_carries_no_symbol() {
assert!(decode_row(&Code128, character(), &[false; 200]).is_none());
assert!(decode_row(&Code128, character(), &[true; 200]).is_none());
let noise: Vec<bool> = (0..400).map(|i| i % 2 == 0).collect();
assert!(decode_row(&Code128, character(), &noise).is_none());
}
#[test]
fn refuses_an_empty_line() {
assert!(decode_row(&Code128, character(), &[]).is_none());
}
#[test]
fn a_character_always_comes_out_at_its_nominal_width() {
for widths in [
[1u32, 1, 1, 1, 1, 1],
[40, 1, 9, 3, 21, 2],
[1, 1, 1, 1, 1, 100],
[7, 7, 7, 7, 7, 7],
] {
let group: Vec<Run> = widths
.iter()
.enumerate()
.map(|(i, &len)| Run {
dark: i % 2 == 0,
len,
})
.collect();
let mut out = Vec::new();
push_group(&mut out, &group, 11).unwrap();
assert_eq!(out.len(), 11, "widths {widths:?} did not total 11 modules");
assert_eq!(runs(&out).len(), 6, "an element was lost for {widths:?}");
}
}
}