#![forbid(unsafe_code)]
extern crate alloc;
use alloc::{format, string::String, vec::Vec};
use crate::common::{
errors::EncodeError,
traits::BarcodeEncoder,
types::{BarcodeOutput, LinearBarcode},
};
const RM4SCC_TABLE: &[(char, [u8; 4])] = &[
('0', [3, 2, 1, 0]), ('1', [3, 0, 2, 1]),
('2', [3, 0, 1, 2]),
('3', [3, 0, 0, 3]), ('4', [2, 3, 1, 0]),
('5', [2, 1, 3, 0]),
('6', [2, 1, 0, 3]),
('7', [2, 0, 3, 1]),
('8', [2, 0, 1, 3]),
('9', [0, 3, 2, 1]),
('A', [3, 1, 2, 0]),
('B', [3, 1, 0, 2]),
('C', [3, 0, 3, 0]),
('D', [1, 3, 2, 0]),
('E', [1, 3, 0, 2]),
('F', [1, 2, 3, 0]),
('G', [3, 2, 0, 1]),
('H', [1, 0, 3, 2]),
('I', [0, 3, 1, 2]),
('J', [1, 2, 0, 3]),
('K', [1, 0, 2, 3]),
('L', [0, 3, 0, 3]),
('M', [0, 1, 3, 2]),
('N', [0, 1, 2, 3]),
('O', [0, 0, 3, 3]),
('P', [2, 3, 0, 1]),
('Q', [0, 2, 3, 1]),
('R', [2, 0, 3, 1]), ('S', [0, 2, 1, 3]),
('T', [2, 0, 1, 3]),
('U', [1, 3, 1, 1]),
('V', [1, 1, 3, 1]),
('W', [1, 1, 1, 3]),
('X', [0, 3, 3, 0]),
('Y', [0, 1, 0, 3]), ('Z', [3, 0, 0, 3]),
];
const START_BAR: u8 = 3; const STOP_BAR: u8 = 3;
fn state_to_bars(state: u8) -> (bool, bool) {
match state {
3 => (true, true), 1 => (true, false), 2 => (false, true), _ => (false, false), }
}
fn states_to_modules(states: &[u8]) -> Vec<bool> {
let mut modules: Vec<bool> = Vec::new();
for (i, &state) in states.iter().enumerate() {
let dark = state != 0; modules.push(dark);
if i + 1 < states.len() {
modules.push(false); }
}
modules
}
fn compute_check(chars: &[char]) -> Result<u8, EncodeError> {
let mut row_sum: i32 = 0;
let mut col_sum: i32 = 0;
for &ch in chars {
let entry = RM4SCC_TABLE
.iter()
.find(|(c, _)| *c == ch)
.ok_or_else(|| EncodeError::InvalidInput(format!("invalid character '{ch}'")))?;
let (a0, _d0) = state_to_bars(entry.1[0]);
let (a1, _d1) = state_to_bars(entry.1[1]);
let (_a2, d2) = state_to_bars(entry.1[2]);
let (_a3, d3) = state_to_bars(entry.1[3]);
row_sum += a0 as i32 + a1 as i32;
col_sum += d2 as i32 + d3 as i32;
}
let check_row = (row_sum % 6) as u8;
let check_col = (col_sum % 6) as u8;
Ok(check_row * 6 + check_col)
}
pub struct Rm4scc;
impl BarcodeEncoder for Rm4scc {
type Input = str;
type Error = EncodeError;
fn encode(input: &str) -> Result<BarcodeOutput, EncodeError> {
let normalized: String = input
.chars()
.filter(|c| !c.is_whitespace())
.map(|c| c.to_ascii_uppercase())
.collect();
if normalized.is_empty() {
return Err(EncodeError::InvalidInput(
"RM4SCC input must not be empty".into(),
));
}
for ch in normalized.chars() {
if !ch.is_ascii_alphanumeric() {
return Err(EncodeError::InvalidInput(format!(
"character '{ch}' is not valid in RM4SCC"
)));
}
if RM4SCC_TABLE.iter().find(|(c, _)| *c == ch).is_none() {
return Err(EncodeError::InvalidInput(format!(
"character '{ch}' is not in RM4SCC table"
)));
}
}
let chars: Vec<char> = normalized.chars().collect();
let check_val = compute_check(&chars)?;
let mut states: Vec<u8> = Vec::new();
states.push(START_BAR);
for &ch in &chars {
let entry = RM4SCC_TABLE
.iter()
.find(|(c, _)| *c == ch)
.expect("already validated");
states.extend_from_slice(&entry.1);
}
let check_state = check_val % 4;
states.push(check_state.max(1));
states.push(STOP_BAR);
let modules = states_to_modules(&states);
Ok(BarcodeOutput::Linear(LinearBarcode {
bars: modules,
height: 20,
text: Some(input.trim().into()),
}))
}
fn symbology_name() -> &'static str {
"RM4SCC"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encode_postcode() {
let out = Rm4scc::encode("SN3 1SD").unwrap();
assert!(matches!(out, BarcodeOutput::Linear(_)));
}
#[test]
fn test_encode_alphanumeric() {
let out = Rm4scc::encode("EC1A1BB").unwrap();
assert!(matches!(out, BarcodeOutput::Linear(_)));
}
#[test]
fn test_normalize_spaces() {
let out1 = Rm4scc::encode("SN31SD").unwrap();
let out2 = Rm4scc::encode("SN3 1SD").unwrap();
match (out1, out2) {
(BarcodeOutput::Linear(a), BarcodeOutput::Linear(b)) => {
assert_eq!(a.bars, b.bars);
}
_ => panic!("expected linear"),
}
}
#[test]
fn test_invalid_char() {
assert!(Rm4scc::encode("SN3-1SD").is_err());
}
#[test]
fn test_empty_input() {
assert!(Rm4scc::encode("").is_err());
assert!(Rm4scc::encode(" ").is_err());
}
#[test]
fn test_symbology_name() {
assert_eq!(Rm4scc::symbology_name(), "RM4SCC");
}
#[test]
fn test_svg_output() {
let svg = Rm4scc::encode("EC1A1BB").unwrap().to_svg_string();
assert!(svg.starts_with("<svg "));
}
#[test]
fn test_bar_count() {
let out = Rm4scc::encode("SN31SD").unwrap();
match out {
BarcodeOutput::Linear(lb) => {
assert_eq!(lb.bars.len(), 53);
}
_ => panic!("expected linear"),
}
}
}