use super::highlevel::{self, unstuff_bits};
use super::layout::Layout;
use super::tables::{total_bits_in_layer, word_size};
use super::{AztecMeta, full_size};
use crate::codes::aztec::gf::{Gf, rs_decode};
use crate::error::{Error, Result};
use crate::output::{BitMatrix, Encoding};
use crate::segment::Segment;
use crate::symbol::{Symbol, SymbolMeta};
use crate::symbology::Symbology;
use crate::traits::Decode;
#[derive(Debug, Default, Clone, Copy)]
pub struct AztecDecoder;
impl AztecDecoder {
pub fn new() -> Self {
AztecDecoder
}
pub fn decode_matrix(&self, m: &BitMatrix) -> Result<Symbol> {
let size = m.width();
if m.height() != size {
return Err(Error::undecodable("Aztec grid is not square"));
}
let (compact, layers) = detect_size(size, m)?;
let layout = Layout::new(compact, layers);
let mode_bits: Vec<bool> = layout
.mode_positions()
.iter()
.map(|&(x, y)| m.get(x, y))
.collect();
let (mode_layers, data_words) = read_mode_message(&mode_bits, compact)?;
if mode_layers != layers {
return Err(Error::undecodable(
"Aztec mode message disagrees with symbol size",
));
}
let w = word_size(layers);
let capacity = total_bits_in_layer(layers, compact);
let total_words = capacity / w;
let start_pad = capacity % w;
let raw: Vec<bool> = layout
.data_positions()
.iter()
.map(|&(x, y)| m.get(x, y))
.collect();
let message = &raw[start_pad..];
let mut words: Vec<u16> = message
.chunks(w)
.map(|c| c.iter().fold(0u16, |a, &b| (a << 1) | b as u16))
.collect();
if words.len() != total_words || data_words >= total_words {
return Err(Error::undecodable("inconsistent Aztec codeword count"));
}
let gf = Gf::for_word_size(w).ok_or(Error::Unsupported {
what: "Aztec symbols above 22 layers",
})?;
let ec = total_words - data_words;
if !rs_decode(&gf, &mut words, ec) {
return Err(Error::ErrorCorrectionFailed);
}
let mut stuffed = Vec::with_capacity(data_words * w);
for &wd in &words[..data_words] {
for k in (0..w).rev() {
stuffed.push((wd >> k) & 1 != 0);
}
}
let hl = unstuff_bits(&stuffed, w);
let payload = highlevel::decode(&hl);
let meta = AztecMeta {
compact,
layers: layers as u8,
};
Ok(Symbol::new(
Symbology::Aztec,
vec![Segment::byte(payload)],
SymbolMeta::Aztec(meta),
))
}
}
impl Decode for AztecDecoder {
fn decode(&self, encoding: &Encoding) -> Result<Symbol> {
match encoding {
Encoding::Matrix(m) => self.decode_matrix(m),
Encoding::Linear(_) => Err(Error::Unsupported {
what: "Aztec decode of a linear pattern",
}),
}
}
}
fn detect_size(size: usize, m: &BitMatrix) -> Result<(bool, usize)> {
let compact_layers = (size >= 15 && (size - 11).is_multiple_of(4) && (size - 11) / 4 <= 4)
.then(|| (size - 11) / 4)
.filter(|&l| l >= 1);
let full_layers = (1..=22).find(|&l| full_size(l) == size);
let center = size / 2;
match (compact_layers, full_layers) {
(Some(cl), Some(_)) => {
if m.get(center - 5, center - 5) {
Ok((true, cl))
} else {
Ok((false, full_layers.unwrap()))
}
}
(Some(cl), None) => Ok((true, cl)),
(None, Some(fl)) => Ok((false, fl)),
(None, None) => Err(Error::undecodable("grid size is not a valid Aztec symbol")),
}
}
pub fn read_mode_message(bits: &[bool], compact: bool) -> Result<(usize, usize)> {
let expected = if compact { 28 } else { 40 };
if bits.len() != expected {
return Err(Error::undecodable("wrong Aztec mode-message length"));
}
let gf = Gf::gf16();
let total_words = if compact { 7 } else { 10 };
let ec = if compact { 5 } else { 6 };
let mut words: Vec<u16> = bits
.chunks(4)
.map(|c| c.iter().fold(0u16, |a, &b| (a << 1) | b as u16))
.collect();
debug_assert_eq!(words.len(), total_words);
if !rs_decode(&gf, &mut words, ec) {
return Err(Error::ErrorCorrectionFailed);
}
let data_words = total_words - ec;
let mut value: u32 = 0;
for &wd in &words[..data_words] {
value = (value << 4) | wd as u32;
}
if compact {
let layers = ((value >> 6) & 0b11) as usize + 1;
let dwords = (value & 0b11_1111) as usize + 1;
Ok((layers, dwords))
} else {
let layers = ((value >> 11) & 0b1_1111) as usize + 1;
let dwords = (value & 0b111_1111_1111) as usize + 1;
Ok((layers, dwords))
}
}