use super::tables::{
ADDON_LEAD, ADDON_SEP, CENTER_GUARD, EAN2_PARITY, EAN5_PARITY, EAN13_FIRST_PARITY,
NORMAL_GUARD, UPCE_END_GUARD, check_digit, ean5_checksum, l_code, left_code, r_code,
upce_expand, upce_parity,
};
use super::{AddOn, AddOnKind, EanMeta, EanVariant};
use crate::error::{Error, Result};
use crate::output::{Encoding, LinearPattern};
use crate::segment::{Mode, Segment};
use crate::symbol::{Symbol, SymbolMeta};
use crate::symbology::Symbology;
use crate::traits::Encode;
const QUIET_MAIN: usize = 9;
const QUIET_ADDON: usize = 7;
const ADDON_GAP: usize = 9;
#[derive(Debug, Default, Clone, Copy)]
pub struct EanEncoder;
impl EanEncoder {
pub fn new() -> Self {
EanEncoder
}
pub fn build_ean13(&self, digits: &str) -> Result<Symbol> {
let d = with_check(digits, 12)?;
Ok(main_symbol(Symbology::Ean13, EanVariant::Ean13, d))
}
pub fn build_ean8(&self, digits: &str) -> Result<Symbol> {
let d = with_check(digits, 7)?;
Ok(main_symbol(Symbology::Ean8, EanVariant::Ean8, d))
}
pub fn build_upca(&self, digits: &str) -> Result<Symbol> {
let d = with_check(digits, 11)?;
Ok(main_symbol(Symbology::UpcA, EanVariant::UpcA, d))
}
pub fn build_upce(&self, digits: &str) -> Result<Symbol> {
let raw = ascii_digits(digits)?;
let (ns, payload_ascii): (u8, &[u8]) = match raw.len() {
6 => (0, &raw[0..6]),
7 | 8 => (raw[0] - b'0', &raw[1..7]),
_ => return Err(Error::invalid_data("UPC-E expects 6, 7 or 8 digits")),
};
if ns > 1 {
return Err(Error::invalid_data("UPC-E number system must be 0 or 1"));
}
let payload = val6(&digit_values(payload_ascii)?);
let check = check_digit(&upce_expand(ns, &payload));
if raw.len() == 8 && raw[7] - b'0' != check {
return Err(Error::invalid_data("UPC-E check digit mismatch"));
}
let mut d = Vec::with_capacity(8);
d.push(b'0' + ns);
d.extend_from_slice(payload_ascii);
d.push(b'0' + check);
Ok(main_symbol(Symbology::UpcE, EanVariant::UpcE, d))
}
pub fn build_ean2(&self, digits: &str) -> Result<Symbol> {
let d = exact_digits(digits, 2)?;
Ok(main_symbol(Symbology::Ean2, EanVariant::Ean2, d))
}
pub fn build_ean5(&self, digits: &str) -> Result<Symbol> {
let d = exact_digits(digits, 5)?;
Ok(main_symbol(Symbology::Ean5, EanVariant::Ean5, d))
}
pub fn with_addon(&self, mut symbol: Symbol, addon: &str) -> Result<Symbol> {
let digits = ascii_digits(addon)?;
let kind = match digits.len() {
2 => AddOnKind::Two,
5 => AddOnKind::Five,
_ => return Err(Error::invalid_data("add-on must be 2 or 5 digits")),
};
match &mut symbol.meta {
SymbolMeta::Ean(meta) => meta.addon = Some(AddOn { kind, digits }),
_ => return Err(Error::invalid_parameter("not an EAN/UPC symbol")),
}
Ok(symbol)
}
}
impl Encode for EanEncoder {
fn encode(&self, symbol: &Symbol) -> Result<Encoding> {
let meta = match &symbol.meta {
SymbolMeta::Ean(m) => m,
_ => return Err(Error::invalid_parameter("EAN symbol missing EanMeta")),
};
let digits = segment_digits(symbol)?;
let (mut modules, quiet) = render_variant(meta.variant, &digits)?;
if let Some(addon) = &meta.addon {
if matches!(meta.variant, EanVariant::Ean2 | EanVariant::Ean5) {
return Err(Error::invalid_parameter(
"an add-on symbol cannot itself carry an add-on",
));
}
modules.extend(std::iter::repeat_n(false, ADDON_GAP));
modules.extend(render_addon(addon)?);
}
Ok(Encoding::Linear(LinearPattern {
modules,
quiet_zone: quiet,
}))
}
}
fn render_variant(variant: EanVariant, digits: &[u8]) -> Result<(Vec<bool>, usize)> {
let vals = digit_values(digits)?;
let modules = match variant {
EanVariant::Ean13 => {
expect_len(&vals, 13, "EAN-13")?;
verify_check(&vals, 12)?;
render_ean13(&vals)
}
EanVariant::UpcA => {
expect_len(&vals, 12, "UPC-A")?;
verify_check(&vals, 11)?;
let mut ean = Vec::with_capacity(13);
ean.push(0);
ean.extend_from_slice(&vals);
render_ean13(&ean)
}
EanVariant::Ean8 => {
expect_len(&vals, 8, "EAN-8")?;
verify_check(&vals, 7)?;
render_ean8(&vals)
}
EanVariant::UpcE => {
expect_len(&vals, 8, "UPC-E")?;
render_upce(&vals)?
}
EanVariant::Ean2 => {
expect_len(&vals, 2, "EAN-2")?;
return Ok((render_addon2(&vals), QUIET_ADDON));
}
EanVariant::Ean5 => {
expect_len(&vals, 5, "EAN-5")?;
return Ok((render_addon5(&vals), QUIET_ADDON));
}
};
Ok((modules, QUIET_MAIN))
}
fn render_ean13(vals: &[u8]) -> Vec<bool> {
let first = vals[0] as usize;
let parity = EAN13_FIRST_PARITY[first];
let mut m = Vec::with_capacity(95);
m.extend_from_slice(&NORMAL_GUARD);
for i in 0..6 {
m.extend_from_slice(&left_code(vals[1 + i], parity[i]));
}
m.extend_from_slice(&CENTER_GUARD);
for i in 0..6 {
m.extend_from_slice(&r_code(vals[7 + i]));
}
m.extend_from_slice(&NORMAL_GUARD);
m
}
fn render_ean8(vals: &[u8]) -> Vec<bool> {
let mut m = Vec::with_capacity(67);
m.extend_from_slice(&NORMAL_GUARD);
for &v in &vals[0..4] {
m.extend_from_slice(&l_code(v));
}
m.extend_from_slice(&CENTER_GUARD);
for &v in &vals[4..8] {
m.extend_from_slice(&r_code(v));
}
m.extend_from_slice(&NORMAL_GUARD);
m
}
fn render_upce(vals: &[u8]) -> Result<Vec<bool>> {
let ns = vals[0];
if ns > 1 {
return Err(Error::invalid_data("UPC-E number system must be 0 or 1"));
}
let payload: [u8; 6] = val6(&vals[1..7]);
let expanded = upce_expand(ns, &payload);
let check = check_digit(&expanded);
if vals[7] != check {
return Err(Error::invalid_data("UPC-E check digit mismatch"));
}
let parity = upce_parity(ns, check);
let mut m = Vec::with_capacity(51);
m.extend_from_slice(&NORMAL_GUARD);
for i in 0..6 {
m.extend_from_slice(&left_code(payload[i], parity[i]));
}
m.extend_from_slice(&UPCE_END_GUARD);
Ok(m)
}
fn render_addon(addon: &AddOn) -> Result<Vec<bool>> {
let vals = digit_values(&addon.digits)?;
Ok(match addon.kind {
AddOnKind::Two => {
expect_len(&vals, 2, "EAN-2 add-on")?;
render_addon2(&vals)
}
AddOnKind::Five => {
expect_len(&vals, 5, "EAN-5 add-on")?;
render_addon5(&vals)
}
})
}
fn render_addon2(vals: &[u8]) -> Vec<bool> {
let value = vals[0] * 10 + vals[1];
let parity = EAN2_PARITY[(value % 4) as usize];
let mut m = Vec::with_capacity(20);
m.extend_from_slice(&ADDON_LEAD);
m.extend_from_slice(&left_code(vals[0], parity[0]));
m.extend_from_slice(&ADDON_SEP);
m.extend_from_slice(&left_code(vals[1], parity[1]));
m
}
fn render_addon5(vals: &[u8]) -> Vec<bool> {
let checksum = ean5_checksum(vals);
let parity = EAN5_PARITY[checksum as usize];
let mut m = Vec::with_capacity(47);
m.extend_from_slice(&ADDON_LEAD);
for i in 0..5 {
if i > 0 {
m.extend_from_slice(&ADDON_SEP);
}
m.extend_from_slice(&left_code(vals[i], parity[i]));
}
m
}
fn main_symbol(symbology: Symbology, variant: EanVariant, digits: Vec<u8>) -> Symbol {
Symbol::new(
symbology,
vec![Segment::numeric(digits)],
SymbolMeta::Ean(EanMeta::new(variant)),
)
}
fn segment_digits(symbol: &Symbol) -> Result<Vec<u8>> {
let seg = symbol
.segments
.iter()
.find(|s| matches!(s.mode, Mode::Numeric))
.ok_or_else(|| Error::invalid_data("EAN symbol has no numeric segment"))?;
validate_ascii(&seg.data)?;
Ok(seg.data.clone())
}
fn ascii_digits(s: &str) -> Result<Vec<u8>> {
let bytes = s.as_bytes().to_vec();
validate_ascii(&bytes)?;
Ok(bytes)
}
fn validate_ascii(bytes: &[u8]) -> Result<()> {
if bytes.iter().all(u8::is_ascii_digit) {
Ok(())
} else {
Err(Error::invalid_data("EAN/UPC data must be ASCII digits"))
}
}
fn exact_digits(s: &str, len: usize) -> Result<Vec<u8>> {
let d = ascii_digits(s)?;
if d.len() != len {
return Err(Error::invalid_data(format!("expected {len} digits")));
}
Ok(d)
}
fn with_check(s: &str, data_len: usize) -> Result<Vec<u8>> {
let mut d = ascii_digits(s)?;
if d.len() == data_len {
let vals = digit_values(&d)?;
d.push(b'0' + check_digit(&vals));
} else if d.len() == data_len + 1 {
let vals = digit_values(&d)?;
verify_check(&vals, data_len)?;
} else {
return Err(Error::invalid_data(format!(
"expected {data_len} or {} digits",
data_len + 1
)));
}
Ok(d)
}
fn digit_values(digits: &[u8]) -> Result<Vec<u8>> {
validate_ascii(digits)?;
Ok(digits.iter().map(|&b| b - b'0').collect())
}
fn val6(vals: &[u8]) -> [u8; 6] {
let mut out = [0u8; 6];
out.copy_from_slice(&vals[0..6]);
out
}
fn expect_len(vals: &[u8], len: usize, what: &str) -> Result<()> {
if vals.len() == len {
Ok(())
} else {
Err(Error::invalid_data(format!(
"{what} expects {len} digits, got {}",
vals.len()
)))
}
}
fn verify_check(vals: &[u8], data_len: usize) -> Result<()> {
if check_digit(&vals[..data_len]) == vals[data_len] {
Ok(())
} else {
Err(Error::invalid_data("check digit mismatch"))
}
}