#![forbid(unsafe_code)]
use crate::common::{errors::EncodeError, traits::BarcodeEncoder, types::Encoded};
const MAX_CHARS: usize = 50;
const MAX_STATES: usize = MAX_CHARS * 4 + 6;
const RM4KIX: [[u8; 4]; 36] = [
[3, 3, 0, 0],
[3, 2, 1, 0],
[3, 2, 0, 1],
[2, 3, 1, 0],
[2, 3, 0, 1],
[2, 2, 1, 1],
[3, 1, 2, 0],
[3, 0, 3, 0],
[3, 0, 2, 1],
[2, 1, 3, 0],
[2, 1, 2, 1],
[2, 0, 3, 1],
[3, 1, 0, 2],
[3, 0, 1, 2],
[3, 0, 0, 3],
[2, 1, 1, 2],
[2, 1, 0, 3],
[2, 0, 1, 3],
[1, 3, 2, 0],
[1, 2, 3, 0],
[1, 2, 2, 1],
[0, 3, 3, 0],
[0, 3, 2, 1],
[0, 2, 3, 1],
[1, 3, 0, 2],
[1, 2, 1, 2],
[1, 2, 0, 3],
[0, 3, 1, 2],
[0, 3, 0, 3],
[0, 2, 1, 3],
[1, 1, 2, 2],
[1, 0, 3, 2],
[1, 0, 2, 3],
[0, 1, 3, 2],
[0, 1, 2, 3],
[0, 0, 3, 3],
];
const CHECK_TOP_BOTTOM: [[u32; 2]; 36] = [
[1, 1],
[1, 2],
[1, 3],
[1, 4],
[1, 5],
[1, 0],
[2, 1],
[2, 2],
[2, 3],
[2, 4],
[2, 5],
[2, 0],
[3, 1],
[3, 2],
[3, 3],
[3, 4],
[3, 5],
[3, 0],
[4, 1],
[4, 2],
[4, 3],
[4, 4],
[4, 5],
[4, 0],
[5, 1],
[5, 2],
[5, 3],
[5, 4],
[5, 5],
[5, 0],
[0, 1],
[0, 2],
[0, 3],
[0, 4],
[0, 5],
[0, 0],
];
fn char_value(b: u8) -> Option<usize> {
match b {
b'0'..=b'9' => Some((b - b'0') as usize),
b'A'..=b'Z' => Some((b - b'A' + 10) as usize),
b'a'..=b'z' => Some((b - b'a' + 10) as usize),
_ => None,
}
}
pub struct Rm4scc;
impl BarcodeEncoder for Rm4scc {
type Input = str;
fn encode_into(input: &str, buf: &mut [bool]) -> Result<Encoded, EncodeError> {
let mut posns = [0usize; MAX_CHARS];
let mut n = 0;
for b in input.bytes() {
if b.is_ascii_whitespace() {
continue;
}
let v = char_value(b).ok_or(EncodeError::InvalidCharacter(b as char))?;
if n >= MAX_CHARS {
return Err(EncodeError::DataTooLong);
}
posns[n] = v;
n += 1;
}
if n == 0 {
return Err(EncodeError::InvalidInput("RM4SCC input must not be empty"));
}
let mut states = [0u8; MAX_STATES];
let mut s = 0;
states[s] = 1; s += 1;
let mut top = 0u32;
let mut bottom = 0u32;
for &p in &posns[..n] {
states[s..s + 4].copy_from_slice(&RM4KIX[p]);
s += 4;
top += CHECK_TOP_BOTTOM[p][0];
bottom += CHECK_TOP_BOTTOM[p][1];
}
let row = (top % 6).checked_sub(1).unwrap_or(5) as usize;
let column = (bottom % 6).checked_sub(1).unwrap_or(5) as usize;
let check = 6 * row + column;
states[s..s + 4].copy_from_slice(&RM4KIX[check]);
s += 4;
states[s] = 0; s += 1;
let width = s * 2 - 1;
let cells = 3 * width;
if buf.len() < cells {
return Err(EncodeError::BufferTooSmall);
}
for slot in buf[..cells].iter_mut() {
*slot = false;
}
for (i, &state) in states[..s].iter().enumerate() {
let col = i * 2;
if state == 0 || state == 1 {
buf[col] = true; }
buf[width + col] = true; if state == 0 || state == 2 {
buf[2 * width + col] = true; }
}
Ok(Encoded::Matrix { width, height: 3 })
}
fn symbology_name() -> &'static str {
"RM4SCC"
}
}
#[cfg(test)]
mod tests {
use super::*;
fn dims(input: &str) -> (usize, usize) {
let mut buf = [false; 3 * 512];
match Rm4scc::encode_into(input, &mut buf).unwrap() {
Encoded::Matrix { width, height } => (width, height),
_ => panic!("expected matrix"),
}
}
#[test]
fn test_encode_postcode() {
let (width, height) = dims("SN34RD1A");
assert_eq!(height, 3);
assert_eq!(width, 38 * 2 - 1);
}
#[test]
fn test_check_character_algorithm() {
let vals: [usize; 6] = ['S', 'N', '3', '5', 'T', 'L'].map(|c| char_value(c as u8).unwrap());
let mut top = 0u32;
let mut bottom = 0u32;
for &p in &vals {
top += CHECK_TOP_BOTTOM[p][0];
bottom += CHECK_TOP_BOTTOM[p][1];
}
let row = (top % 6).checked_sub(1).unwrap_or(5) as usize;
let column = (bottom % 6).checked_sub(1).unwrap_or(5) as usize;
assert!(6 * row + column < 36);
}
#[test]
fn test_normalize_spaces() {
let a = dims("SN34RD1A");
let b = dims("SN3 4RD1A");
assert_eq!(a, b);
}
#[test]
fn test_invalid_char() {
let mut buf = [false; 3 * 512];
assert!(Rm4scc::encode_into("SN3-4RD", &mut buf).is_err());
}
#[test]
fn test_empty_input() {
let mut buf = [false; 3 * 512];
assert!(Rm4scc::encode_into("", &mut buf).is_err());
}
#[test]
fn test_symbology_name() {
assert_eq!(Rm4scc::symbology_name(), "RM4SCC");
}
#[cfg(feature = "alloc")]
#[test]
fn test_svg_output() {
let svg = Rm4scc::encode("SN34RD1A").unwrap().to_svg_string();
assert!(svg.starts_with("<svg "));
}
}