#![forbid(unsafe_code)]
use crate::common::{errors::EncodeError, traits::BarcodeEncoder, types::Encoded};
#[cfg(test)]
const MAX_SIZE: usize = 48;
#[cfg(test)]
const MAX_CELLS: usize = MAX_SIZE * MAX_SIZE;
const MAX_MAPPING: usize = 44;
const MAX_MAP_CELLS: usize = MAX_MAPPING * MAX_MAPPING;
const MAX_DATA_CW: usize = 174;
const MAX_EC: usize = 68;
const SYMBOL_PARAMS: &[(usize, usize, usize, usize, usize)] = &[
(10, 8, 1, 3, 5), (12, 10, 1, 5, 7), (14, 12, 1, 8, 10), (16, 14, 1, 12, 12), (18, 16, 1, 18, 14), (20, 18, 1, 22, 18), (22, 20, 1, 30, 20), (24, 22, 1, 36, 24), (26, 24, 1, 44, 28), (32, 14, 2, 62, 36), (36, 16, 2, 86, 42), (40, 18, 2, 114, 48), (44, 20, 2, 144, 56), (48, 22, 2, 174, 68), ];
const PRIM_POLY: u32 = 0x12D;
fn gf256_mul(a: u8, b: u8) -> u8 {
let mut result = 0u32;
let mut aa = a as u32;
let mut bb = b as u32;
while bb > 0 {
if bb & 1 != 0 {
result ^= aa;
}
aa <<= 1;
if aa & 0x100 != 0 {
aa ^= PRIM_POLY;
}
bb >>= 1;
}
result as u8
}
fn gf256_pow(base: u8, exp: usize) -> u8 {
let mut result = 1u8;
for _ in 0..exp {
result = gf256_mul(result, base);
}
result
}
fn rs_encode_dm(data: &[u8], ec_count: usize, out: &mut [u8]) {
let mut poly = [0u8; MAX_EC + 1];
poly[0] = 1;
for i in 0..ec_count {
let root = gf256_pow(2, i + 1);
let cur = i + 1; let mut new_poly = [0u8; MAX_EC + 1];
for j in 0..cur {
new_poly[j] ^= poly[j];
new_poly[j + 1] ^= gf256_mul(poly[j], root);
}
poly[..cur + 1].copy_from_slice(&new_poly[..cur + 1]);
}
let mut rem_buf = [0u8; MAX_EC];
let rem = &mut rem_buf[..ec_count];
for &d in data {
let lead = d ^ rem[0];
rem.copy_within(1.., 0);
rem[ec_count - 1] = 0;
if lead != 0 {
for i in 0..ec_count {
rem[i] ^= gf256_mul(lead, poly[i + 1]);
}
}
}
out[..ec_count].copy_from_slice(rem);
}
fn ascii_encode(input: &[u8], out: &mut [u8]) -> Result<usize, EncodeError> {
let mut n = 0;
let mut push = |v: u8| -> Result<(), EncodeError> {
*out.get_mut(n).ok_or(EncodeError::DataTooLong)? = v;
n += 1;
Ok(())
};
let mut i = 0;
while i < input.len() {
if i + 1 < input.len() && input[i].is_ascii_digit() && input[i + 1].is_ascii_digit() {
let val = (input[i] - b'0') * 10 + (input[i + 1] - b'0');
push(130 + val)?;
i += 2;
} else {
push(input[i] + 1)?;
i += 1;
}
}
Ok(n)
}
fn place_bit(a: &mut [u16], nr: isize, nc: isize, mut r: isize, mut c: isize, p: usize, b: u16) {
if r < 0 {
r += nr;
c += 4 - ((nr + 4) % 8);
}
if c < 0 {
c += nc;
r += 4 - ((nc + 4) % 8);
}
a[(r * nc + c) as usize] = ((p as u16) << 3) | b;
}
fn place_block(a: &mut [u16], nr: isize, nc: isize, r: isize, c: isize, p: usize) {
place_bit(a, nr, nc, r - 2, c - 2, p, 7);
place_bit(a, nr, nc, r - 2, c - 1, p, 6);
place_bit(a, nr, nc, r - 1, c - 2, p, 5);
place_bit(a, nr, nc, r - 1, c - 1, p, 4);
place_bit(a, nr, nc, r - 1, c, p, 3);
place_bit(a, nr, nc, r, c - 2, p, 2);
place_bit(a, nr, nc, r, c - 1, p, 1);
place_bit(a, nr, nc, r, c, p, 0);
}
fn corner_a(a: &mut [u16], nr: isize, nc: isize, p: usize) {
place_bit(a, nr, nc, nr - 1, 0, p, 7);
place_bit(a, nr, nc, nr - 1, 1, p, 6);
place_bit(a, nr, nc, nr - 1, 2, p, 5);
place_bit(a, nr, nc, 0, nc - 2, p, 4);
place_bit(a, nr, nc, 0, nc - 1, p, 3);
place_bit(a, nr, nc, 1, nc - 1, p, 2);
place_bit(a, nr, nc, 2, nc - 1, p, 1);
place_bit(a, nr, nc, 3, nc - 1, p, 0);
}
fn corner_b(a: &mut [u16], nr: isize, nc: isize, p: usize) {
place_bit(a, nr, nc, nr - 3, 0, p, 7);
place_bit(a, nr, nc, nr - 2, 0, p, 6);
place_bit(a, nr, nc, nr - 1, 0, p, 5);
place_bit(a, nr, nc, 0, nc - 4, p, 4);
place_bit(a, nr, nc, 0, nc - 3, p, 3);
place_bit(a, nr, nc, 0, nc - 2, p, 2);
place_bit(a, nr, nc, 0, nc - 1, p, 1);
place_bit(a, nr, nc, 1, nc - 1, p, 0);
}
fn corner_c(a: &mut [u16], nr: isize, nc: isize, p: usize) {
place_bit(a, nr, nc, nr - 3, 0, p, 7);
place_bit(a, nr, nc, nr - 2, 0, p, 6);
place_bit(a, nr, nc, nr - 1, 0, p, 5);
place_bit(a, nr, nc, 0, nc - 2, p, 4);
place_bit(a, nr, nc, 0, nc - 1, p, 3);
place_bit(a, nr, nc, 1, nc - 1, p, 2);
place_bit(a, nr, nc, 2, nc - 1, p, 1);
place_bit(a, nr, nc, 3, nc - 1, p, 0);
}
fn corner_d(a: &mut [u16], nr: isize, nc: isize, p: usize) {
place_bit(a, nr, nc, nr - 1, 0, p, 7);
place_bit(a, nr, nc, nr - 1, nc - 1, p, 6);
place_bit(a, nr, nc, 0, nc - 3, p, 5);
place_bit(a, nr, nc, 0, nc - 2, p, 4);
place_bit(a, nr, nc, 0, nc - 1, p, 3);
place_bit(a, nr, nc, 1, nc - 3, p, 2);
place_bit(a, nr, nc, 1, nc - 2, p, 1);
place_bit(a, nr, nc, 1, nc - 1, p, 0);
}
fn ecc200_placement(nr: usize, nc: usize, a: &mut [u16]) {
for x in a[..nr * nc].iter_mut() {
*x = 0;
}
let (nri, nci) = (nr as isize, nc as isize);
let idx = |r: isize, c: isize| (r * nci + c) as usize;
let mut p = 1usize;
let mut r: isize = 4;
let mut c: isize = 0;
loop {
if r == nri && c == 0 {
corner_a(a, nri, nci, p);
p += 1;
}
if r == nri - 2 && c == 0 && (nci % 4) != 0 {
corner_b(a, nri, nci, p);
p += 1;
}
if r == nri - 2 && c == 0 && (nci % 8) == 4 {
corner_c(a, nri, nci, p);
p += 1;
}
if r == nri + 4 && c == 2 && (nci % 8) == 0 {
corner_d(a, nri, nci, p);
p += 1;
}
loop {
if r < nri && c >= 0 && a[idx(r, c)] == 0 {
place_block(a, nri, nci, r, c, p);
p += 1;
}
r -= 2;
c += 2;
if !(r >= 0 && c < nci) {
break;
}
}
r += 1;
c += 3;
loop {
if r >= 0 && c < nci && a[idx(r, c)] == 0 {
place_block(a, nri, nci, r, c, p);
p += 1;
}
r += 2;
c -= 2;
if !(r < nri && c >= 0) {
break;
}
}
r += 3;
c += 1;
if !(r < nri || c < nci) {
break;
}
}
let last = nr * nc - 1;
if a[last] == 0 {
a[last] = 1;
a[nr * nc - nc - 2] = 1;
}
}
fn build_grid(
size: usize,
data_region: usize,
regions: usize,
data_codewords: &[u8],
ec_codewords: &[u8],
buf: &mut [bool],
) -> Result<(), EncodeError> {
let cells = size * size;
if buf.len() < cells {
return Err(EncodeError::BufferTooSmall);
}
for x in buf[..cells].iter_mut() {
*x = false;
}
let block = data_region + 2;
for br in 0..regions {
for bc in 0..regions {
let r0 = br * block;
let c0 = bc * block;
for i in 0..block {
buf[(r0 + block - 1) * size + c0 + i] = true; buf[(r0 + i) * size + c0] = true; }
let mut i = 0;
while i < block {
buf[r0 * size + c0 + i] = true; i += 2;
}
let mut i = 1;
while i < block {
buf[(r0 + i) * size + c0 + block - 1] = true; i += 2;
}
}
}
let mapping = regions * data_region;
let mut places = [0u16; MAX_MAP_CELLS];
ecc200_placement(mapping, mapping, &mut places);
let data_len = data_codewords.len();
for mr in 0..mapping {
for mc in 0..mapping {
let v = places[mr * mapping + mc];
let dark = match v {
0 => false,
1 => true,
_ => {
let cw_idx = (v >> 3) as usize - 1;
let cw = if cw_idx < data_len {
data_codewords[cw_idx]
} else {
ec_codewords[cw_idx - data_len]
};
(cw >> (v & 7)) & 1 == 1
}
};
let pr = (mr / data_region) * block + 1 + (mr % data_region);
let pc = (mc / data_region) * block + 1 + (mc % data_region);
buf[pr * size + pc] = dark;
}
}
Ok(())
}
pub struct DataMatrix;
impl BarcodeEncoder for DataMatrix {
type Input = str;
fn encode_into(input: &str, buf: &mut [bool]) -> Result<Encoded, EncodeError> {
if input.is_empty() {
return Err(EncodeError::InvalidInput(
"Data Matrix input must not be empty",
));
}
let mut data_cw = [0u8; MAX_DATA_CW + 1];
let n = ascii_encode(input.as_bytes(), &mut data_cw)?;
let params = SYMBOL_PARAMS
.iter()
.find(|&&(_, _, _, data_cap, _)| n <= data_cap)
.ok_or(EncodeError::DataTooLong)?;
let (size, data_region, regions, data_cap, ec_count) = *params;
let mut padded = [0u8; MAX_DATA_CW];
padded[..n].copy_from_slice(&data_cw[..n]);
if n < data_cap {
padded[n] = 129;
let mut i = n + 1;
while i < data_cap {
let pos = i + 1; let r = ((149 * pos) % 253) + 1;
let v = 129 + r;
padded[i] = if v > 254 { (v - 254) as u8 } else { v as u8 };
i += 1;
}
}
let data = &padded[..data_cap];
let mut ec = [0u8; MAX_EC];
rs_encode_dm(data, ec_count, &mut ec);
build_grid(size, data_region, regions, data, &ec[..ec_count], buf)?;
Ok(Encoded::Matrix {
width: size,
height: size,
})
}
fn symbology_name() -> &'static str {
"Data Matrix"
}
}
#[cfg(test)]
mod tests {
use super::*;
fn encode(input: &str, buf: &mut [bool]) -> (usize, usize) {
match DataMatrix::encode_into(input, buf).unwrap() {
Encoded::Matrix { width, height } => (width, height),
_ => panic!("expected matrix"),
}
}
fn recover(
buf: &[bool],
size: usize,
data_region: usize,
regions: usize,
out: &mut [u8],
) -> usize {
let block = data_region + 2;
let mapping = regions * data_region;
let mut places = [0u16; MAX_MAP_CELLS];
ecc200_placement(mapping, mapping, &mut places);
let capacity = mapping * mapping / 8;
for x in out[..capacity].iter_mut() {
*x = 0;
}
for mr in 0..mapping {
for mc in 0..mapping {
let v = places[mr * mapping + mc];
if v > 1 {
let pr = (mr / data_region) * block + 1 + (mr % data_region);
let pc = (mc / data_region) * block + 1 + (mc % data_region);
if buf[pr * size + pc] {
out[(v >> 3) as usize - 1] |= 1 << (v & 7);
}
}
}
}
capacity
}
#[test]
fn test_encode_basic() {
let mut buf = [false; MAX_CELLS];
let (w, h) = encode("Hello", &mut buf);
assert!(w >= 10);
assert_eq!(w, h);
}
#[test]
fn test_encode_digits() {
let mut buf = [false; MAX_CELLS];
let (w, _) = encode("12345", &mut buf);
assert!(w >= 10);
}
#[test]
fn test_finder_timing() {
let mut buf = [false; MAX_CELLS];
let (size, _) = encode("Hi", &mut buf);
let n = size - 1;
assert!(buf[(size - 1) * size], "bottom-left dark");
assert!(buf[n * size + n], "bottom-right dark");
assert!(buf[(size - 1) * size..size * size].iter().all(|&b| b));
for r in 0..size {
assert!(buf[r * size], "left column dark");
}
assert!(buf[2], "top timing even col dark");
assert!(!buf[1], "top timing odd col light");
assert!(buf[size + n], "right timing odd row dark");
assert!(!buf[n], "right timing even row light");
}
#[test]
fn test_symbol_size_10x10_for_small_input() {
let mut buf = [false; MAX_CELLS];
assert_eq!(encode("Hi", &mut buf), (10, 10));
}
#[test]
fn test_empty_input() {
let mut buf = [false; MAX_CELLS];
assert!(DataMatrix::encode_into("", &mut buf).is_err());
}
#[test]
fn test_buffer_too_small() {
let mut buf = [false; 16];
assert_eq!(
DataMatrix::encode_into("Hi", &mut buf),
Err(EncodeError::BufferTooSmall)
);
}
#[test]
fn test_symbology_name() {
assert_eq!(DataMatrix::symbology_name(), "Data Matrix");
}
#[test]
fn test_gf256_mul() {
assert_eq!(gf256_mul(0, 1), 0);
assert_eq!(gf256_mul(1, 1), 1);
assert_eq!(gf256_mul(2, 2), 4);
}
#[test]
fn test_rs_iso_reference_vector() {
let mut data = [0u8; MAX_DATA_CW + 1];
let n = ascii_encode(b"123456", &mut data).unwrap();
assert_eq!(&data[..n], &[142, 164, 186]);
let mut ec = [0u8; MAX_EC];
rs_encode_dm(&data[..n], 5, &mut ec);
assert_eq!(&ec[..5], &[114, 25, 5, 88, 102]);
}
#[test]
fn test_round_trip_all_sizes() {
let a62 = [b'A'; 62];
let inputs: &[&str] = &[
"A",
"Hi",
"12345",
"HELLO WORLD",
"f3411c82-1c70-4207-977e-99f5580e7e3b",
core::str::from_utf8(&a62).unwrap(), ];
for &input in inputs {
let mut buf = [false; MAX_CELLS];
let (size, _) = encode(input, &mut buf);
let (_, dr, regions, data_cap, ec_count) =
*SYMBOL_PARAMS.iter().find(|p| p.0 == size).unwrap();
let mut cw = [0u8; MAX_DATA_CW + MAX_EC];
recover(&buf, size, dr, regions, &mut cw);
let (data, rest) = cw.split_at(data_cap);
let ec = &rest[..ec_count];
let mut ec_check = [0u8; MAX_EC];
rs_encode_dm(data, ec_count, &mut ec_check);
assert_eq!(&ec_check[..ec_count], ec, "RS mismatch ({size}x{size})");
let mut dec = [0u8; MAX_DATA_CW * 2];
let mut dn = 0;
for &c in data {
match c {
129 => break,
1..=128 => {
dec[dn] = c - 1;
dn += 1;
}
130..=229 => {
let vv = c - 130;
dec[dn] = b'0' + vv / 10;
dec[dn + 1] = b'0' + vv % 10;
dn += 2;
}
_ => {}
}
}
assert_eq!(&dec[..dn], input.as_bytes(), "round-trip ({size}x{size})");
}
}
#[test]
fn test_padding_253_state() {
let a50 = [b'A'; 50];
let mut buf = [false; MAX_CELLS];
let (size, _) = encode(core::str::from_utf8(&a50).unwrap(), &mut buf);
assert_eq!(size, 32);
let mut cw = [0u8; MAX_DATA_CW + MAX_EC];
recover(&buf, size, 14, 2, &mut cw);
assert_eq!(
&cw[50..62],
&[129, 34, 184, 79, 229, 124, 20, 170, 65, 215, 110, 6]
);
}
#[test]
fn test_large_capacity() {
let a174 = [b'A'; 174];
let mut buf = [false; MAX_CELLS];
assert_eq!(
encode(core::str::from_utf8(&a174).unwrap(), &mut buf),
(48, 48)
);
let a200 = [b'A'; 200];
let mut buf2 = [false; MAX_CELLS];
assert_eq!(
DataMatrix::encode_into(core::str::from_utf8(&a200).unwrap(), &mut buf2),
Err(EncodeError::DataTooLong)
);
}
#[cfg(feature = "alloc")]
#[test]
fn test_svg_output() {
let svg = DataMatrix::encode("Test").unwrap().to_svg_string();
assert!(svg.starts_with("<svg "));
}
}