#![forbid(unsafe_code)]
extern crate alloc;
use alloc::{format, string::String, vec::Vec};
use super::ean13::{GUARD_NORMAL, L_CODE, check_digit};
use crate::common::{
errors::EncodeError,
traits::BarcodeEncoder,
types::{BarcodeOutput, LinearBarcode},
};
const G_CODE: [[bool; 7]; 10] = [
[false, true, false, false, true, true, true], [false, true, true, false, false, true, true], [false, false, true, true, false, true, true], [false, true, false, false, false, false, true], [false, false, true, true, true, false, true], [false, true, true, true, false, false, true], [false, false, false, false, true, false, true], [false, false, true, false, false, false, true], [false, false, false, true, false, false, true], [false, false, true, false, true, true, true], ];
const GUARD_END: [bool; 6] = [false, true, false, true, false, true];
const UPCE_PARITY: [[bool; 6]; 10] = [
[false, false, false, true, true, true], [false, false, true, false, true, true], [false, false, true, true, false, true], [false, false, true, true, true, false], [false, true, false, false, true, true], [false, true, true, false, false, true], [false, true, true, true, false, false], [false, true, false, true, false, true], [false, true, false, true, true, false], [false, true, true, false, true, false], ];
pub struct UpcE;
impl BarcodeEncoder for UpcE {
type Input = str;
type Error = EncodeError;
fn encode(input: &str) -> Result<BarcodeOutput, EncodeError> {
let (number_system, six_digits, check) = parse_and_validate(input)?;
let bars = encode_bars(number_system, &six_digits, check);
let text = format_text(number_system, &six_digits, check);
Ok(BarcodeOutput::Linear(LinearBarcode {
bars,
height: 69,
text: Some(text),
}))
}
fn symbology_name() -> &'static str {
"UPC-E"
}
}
fn parse_and_validate(input: &str) -> Result<(u8, [u8; 6], u8), EncodeError> {
let trimmed = input.trim();
if !trimmed.chars().all(|c| c.is_ascii_digit()) {
return Err(EncodeError::InvalidInput(
"UPC-E input must contain digits only".into(),
));
}
match trimmed.len() {
6 => {
let mut six = [0u8; 6];
for (i, c) in trimmed.chars().enumerate() {
six[i] = c as u8 - b'0';
}
let upca = expand_to_upca(0, &six);
let check = check_digit(&upca[..11]);
Ok((0, six, check))
}
7 => {
let mut buf = [0u8; 7];
for (i, c) in trimmed.chars().enumerate() {
buf[i] = c as u8 - b'0';
}
let six = [buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]];
let provided_check = buf[6];
let upca = expand_to_upca(0, &six);
let expected = check_digit(&upca[..11]);
if provided_check != expected {
return Err(EncodeError::InvalidInput(format!(
"check digit mismatch: got {provided_check}, expected {expected}"
)));
}
Ok((0, six, expected))
}
8 => {
let mut buf = [0u8; 8];
for (i, c) in trimmed.chars().enumerate() {
buf[i] = c as u8 - b'0';
}
let ns = buf[0];
if ns > 1 {
return Err(EncodeError::InvalidInput(
"UPC-E number system must be 0 or 1".into(),
));
}
let six = [buf[1], buf[2], buf[3], buf[4], buf[5], buf[6]];
let provided_check = buf[7];
let upca = expand_to_upca(ns, &six);
let expected = check_digit(&upca[..11]);
if provided_check != expected {
return Err(EncodeError::InvalidInput(format!(
"check digit mismatch: got {provided_check}, expected {expected}"
)));
}
Ok((ns, six, expected))
}
_ => Err(EncodeError::InvalidInput(
"UPC-E input must be 6, 7, or 8 digits".into(),
)),
}
}
pub fn expand_to_upca(number_system: u8, six: &[u8; 6]) -> [u8; 12] {
let mut upca = [0u8; 12];
upca[0] = number_system;
match six[5] {
0..=2 => {
upca[1] = six[0];
upca[2] = six[1];
upca[3] = six[5];
upca[4] = 0;
upca[5] = 0;
upca[6] = 0;
upca[7] = 0;
upca[8] = six[2];
upca[9] = six[3];
upca[10] = six[4];
}
3 => {
upca[1] = six[0];
upca[2] = six[1];
upca[3] = six[2];
upca[4] = 0;
upca[5] = 0;
upca[6] = 0;
upca[7] = 0;
upca[8] = 0;
upca[9] = six[3];
upca[10] = six[4];
}
4 => {
upca[1] = six[0];
upca[2] = six[1];
upca[3] = six[2];
upca[4] = six[3];
upca[5] = 0;
upca[6] = 0;
upca[7] = 0;
upca[8] = 0;
upca[9] = 0;
upca[10] = six[4];
}
d => {
upca[1] = six[0];
upca[2] = six[1];
upca[3] = six[2];
upca[4] = six[3];
upca[5] = six[4];
upca[6] = 0;
upca[7] = 0;
upca[8] = 0;
upca[9] = 0;
upca[10] = d;
}
}
upca
}
fn encode_bars(number_system: u8, six: &[u8; 6], check: u8) -> Vec<bool> {
let parity = UPCE_PARITY[check as usize];
let mut bars: Vec<bool> = Vec::with_capacity(51);
bars.extend_from_slice(&GUARD_NORMAL);
for (pos, &d) in six.iter().enumerate() {
let use_g = if number_system == 0 {
parity[pos]
} else {
!parity[pos]
};
let pattern = if use_g {
&G_CODE[d as usize]
} else {
&L_CODE[d as usize]
};
bars.extend_from_slice(pattern);
}
bars.extend_from_slice(&GUARD_END);
bars
}
fn format_text(number_system: u8, six: &[u8; 6], check: u8) -> String {
let mut s = String::with_capacity(8);
s.push((b'0' + number_system) as char);
for &d in six.iter() {
s.push((b'0' + d) as char);
}
s.push((b'0' + check) as char);
s
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encode_8_digits() {
let out = UpcE::encode("01234505").unwrap();
match out {
BarcodeOutput::Linear(lb) => {
assert_eq!(lb.bars.len(), 51);
}
_ => panic!("expected linear barcode"),
}
}
#[test]
fn test_encode_6_digits() {
let out6 = UpcE::encode("123450").unwrap();
assert!(matches!(out6, BarcodeOutput::Linear(_)));
}
#[test]
fn test_expand_to_upca_last0() {
let six = [1u8, 2, 3, 4, 5, 0];
let upca = expand_to_upca(0, &six);
assert_eq!(upca[0], 0); assert_eq!(upca[1], 1);
assert_eq!(upca[2], 2);
assert_eq!(upca[3], 0); assert_eq!(upca[4], 0);
assert_eq!(upca[5], 0);
assert_eq!(upca[6], 0);
assert_eq!(upca[7], 0);
assert_eq!(upca[8], 3);
assert_eq!(upca[9], 4);
assert_eq!(upca[10], 5);
}
#[test]
fn test_expand_to_upca_last3() {
let six = [1u8, 2, 3, 4, 5, 3];
let upca = expand_to_upca(0, &six);
assert_eq!(upca[1], 1);
assert_eq!(upca[2], 2);
assert_eq!(upca[3], 3);
assert_eq!(upca[4], 0);
assert_eq!(upca[5], 0);
assert_eq!(upca[9], 4);
assert_eq!(upca[10], 5);
}
#[test]
fn test_invalid_number_system() {
assert!(UpcE::encode("21234505").is_err());
}
#[test]
fn test_invalid_characters() {
assert!(UpcE::encode("0123450X").is_err());
}
#[test]
fn test_wrong_length() {
assert!(UpcE::encode("12345").is_err());
}
#[test]
fn test_symbology_name() {
assert_eq!(UpcE::symbology_name(), "UPC-E");
}
#[test]
fn test_svg_output() {
let svg = UpcE::encode("01234505").unwrap().to_svg_string();
assert!(svg.starts_with("<svg "));
}
}