#![forbid(unsafe_code)]
extern crate alloc;
use alloc::{vec, vec::Vec};
use crate::common::{
errors::EncodeError,
traits::BarcodeEncoder,
types::{BarcodeOutput, MatrixBarcode},
};
fn gf64_mul(a: u8, b: u8) -> u8 {
let mut result = 0u8;
let mut aa = a & 0x3F;
let mut bb = b & 0x3F;
while bb > 0 {
if bb & 1 != 0 {
result ^= aa;
}
aa = (aa << 1) ^ if aa & 0x20 != 0 { 0x43 } else { 0 };
bb >>= 1;
}
result & 0x3F
}
fn rs_data(data: &[u8], ec_count: usize) -> Vec<u8> {
let mut remainder = vec![0u8; ec_count];
for &d in data {
let d = d & 0x3F;
let lead = d ^ remainder[0];
remainder.copy_within(1.., 0);
*remainder.last_mut().unwrap() = 0;
if lead != 0 {
for coef in remainder.iter_mut() {
*coef ^= gf64_mul(lead, *coef);
}
}
}
remainder
}
fn encode_text(input: &str) -> Vec<u8> {
let mut bits: Vec<bool> = Vec::new();
for &b in input.as_bytes() {
let code: Option<u8> = match b {
b' ' => Some(1),
b'A'..=b'Z' => Some(b - b'A' + 2),
b'a'..=b'z' => Some(b - b'a' + 2), b'.' => Some(28),
b',' => Some(29),
b':' => Some(30),
b'\r' => Some(31),
_ => None,
};
if let Some(c) = code {
for bit in (0..5).rev() {
bits.push((c >> bit) & 1 != 0);
}
} else {
for bit in (0..5).rev() {
bits.push((31u8 >> bit) & 1 != 0);
}
for bit in (0..8).rev() {
bits.push((b >> bit) & 1 != 0);
}
}
}
while !bits.len().is_multiple_of(6) {
bits.push(true); }
bits.chunks(6)
.map(|chunk| chunk.iter().fold(0u8, |acc, &b| (acc << 1) | b as u8))
.collect()
}
const COMPACT_FINDER_SIZE: usize = 11;
fn place_compact_finder(grid: &mut [Vec<i8>], center: usize) {
let _half = COMPACT_FINDER_SIZE / 2;
for ring in 0..=5i32 {
let dark = ring % 2 == 0; let val = if dark { 1i8 } else { 0i8 };
let r_start = (center as i32 - ring).max(0) as usize;
let r_end = (center as i32 + ring).min(grid.len() as i32 - 1) as usize;
#[allow(clippy::needless_range_loop)]
for r in r_start..=r_end {
for c in r_start..=r_end {
if r == r_start || r == r_end || c == r_start || c == r_end {
grid[r][c] = val;
}
}
}
}
if center + 1 < grid.len() && center + 1 < grid[0].len() {
grid[center + 1][center + 1] = 1;
}
}
fn place_compact_orientation(grid: &mut [Vec<i8>], center: usize) {
let c = center;
grid[c - 5][c - 5] = 1;
grid[c - 5][c - 4] = 1;
grid[c - 4][c - 5] = 1;
grid[c + 5][c + 5] = 0;
}
fn place_compact_layer(grid: &mut [Vec<i8>], size: usize, layer: usize, data_bits: &[bool]) {
let center = size / 2;
let start = center as i32 - 5 - layer as i32;
let end = center as i32 + 5 + layer as i32;
if start < 0 || end >= size as i32 {
return;
}
let mut bit_idx = 0;
let s = start as usize;
let e = end as usize;
#[allow(clippy::needless_range_loop)]
for c in s..=e {
if bit_idx < data_bits.len() && grid[s][c] < 0 {
grid[s][c] = data_bits[bit_idx] as i8;
bit_idx += 1;
}
}
#[allow(clippy::needless_range_loop)]
for r in s + 1..=e {
if bit_idx < data_bits.len() && grid[r][e] < 0 {
grid[r][e] = data_bits[bit_idx] as i8;
bit_idx += 1;
}
}
#[allow(clippy::needless_range_loop)]
for c in (s..e).rev() {
if bit_idx < data_bits.len() && grid[e][c] < 0 {
grid[e][c] = data_bits[bit_idx] as i8;
bit_idx += 1;
}
}
#[allow(clippy::needless_range_loop)]
for r in (s + 1..e).rev() {
if bit_idx < data_bits.len() && grid[r][s] < 0 {
grid[r][s] = data_bits[bit_idx] as i8;
bit_idx += 1;
}
}
}
pub struct Aztec;
impl BarcodeEncoder for Aztec {
type Input = str;
type Error = EncodeError;
fn encode(input: &str) -> Result<BarcodeOutput, EncodeError> {
if input.is_empty() {
return Err(EncodeError::InvalidInput(
"Aztec input must not be empty".into(),
));
}
let data_codewords = encode_text(input);
if data_codewords.is_empty() {
return Err(EncodeError::InvalidInput("no encodable data found".into()));
}
let layers = match data_codewords.len() {
0..=4 => 1,
5..=11 => 2,
12..=22 => 3,
23..=40 => 4,
_ => return Err(EncodeError::DataTooLong),
};
let size = 11 + layers * 4;
let mut grid: Vec<Vec<i8>> = vec![vec![-1i8; size]; size];
let center = size / 2;
place_compact_finder(&mut grid, center);
if center >= 5 {
place_compact_orientation(&mut grid, center);
}
let ec_count = (data_codewords.len() / 4).max(2);
let ec = rs_data(&data_codewords, ec_count);
let mut all_cw: Vec<u8> = Vec::new();
all_cw.extend_from_slice(&data_codewords);
all_cw.extend_from_slice(&ec);
let data_bits: Vec<bool> = all_cw
.iter()
.flat_map(|&cw| (0..6).rev().map(move |i| (cw >> i) & 1 != 0))
.collect();
for layer in 1..=layers {
let layer_bits_start = (layer - 1) * (data_bits.len() / layers);
let layer_bits_end = if layer == layers {
data_bits.len()
} else {
layer * (data_bits.len() / layers)
};
if layer_bits_start < data_bits.len() {
place_compact_layer(
&mut grid,
size,
layer,
&data_bits[layer_bits_start..layer_bits_end.min(data_bits.len())],
);
}
}
let modules: Vec<Vec<bool>> = grid
.into_iter()
.map(|row| row.into_iter().map(|v| v == 1).collect())
.collect();
Ok(BarcodeOutput::Matrix(MatrixBarcode {
width: size,
height: size,
modules,
}))
}
fn symbology_name() -> &'static str {
"Aztec Code"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encode_basic() {
let out = Aztec::encode("AZTEC").unwrap();
match out {
BarcodeOutput::Matrix(mb) => {
assert!(mb.width >= 15); assert_eq!(mb.width, mb.height);
}
_ => panic!("expected matrix barcode"),
}
}
#[test]
fn test_encode_short() {
let out = Aztec::encode("A").unwrap();
assert!(matches!(out, BarcodeOutput::Matrix(_)));
}
#[test]
fn test_finder_pattern_center_is_dark() {
let out = Aztec::encode("HI").unwrap();
match out {
BarcodeOutput::Matrix(mb) => {
let center = mb.width / 2;
assert!(mb.modules[center][center], "center must be dark");
}
_ => panic!("expected matrix"),
}
}
#[test]
fn test_empty_input() {
assert!(Aztec::encode("").is_err());
}
#[test]
fn test_symbology_name() {
assert_eq!(Aztec::symbology_name(), "Aztec Code");
}
#[test]
fn test_svg_output() {
let svg = Aztec::encode("Test").unwrap().to_svg_string();
assert!(svg.starts_with("<svg "));
}
#[test]
fn test_gf64_mul_zero() {
assert_eq!(gf64_mul(0, 5), 0);
assert_eq!(gf64_mul(5, 0), 0);
}
#[test]
fn test_gf64_mul_identity() {
assert_eq!(gf64_mul(1, 7), 7);
assert_eq!(gf64_mul(7, 1), 7);
}
}