use crate::error::{Error, Result};
use crate::output::{Encoding, LinearPattern};
use crate::segment::Segment;
use crate::symbol::{Symbol, SymbolMeta};
use crate::symbology::Symbology;
use crate::traits::{Decode, Encode};
const WIDE: u32 = 3;
const QUIET_ZONE: usize = 10;
const ONE_TRACK_RANGE: std::ops::RangeInclusive<u32> = 3..=131070;
const TWO_TRACK_RANGE: std::ops::RangeInclusive<u32> = 4..=64570080;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct PharmacodeMeta;
fn parse_value(payload: &[u8]) -> Result<u32> {
let text = std::str::from_utf8(payload)
.map_err(|_| Error::invalid_data("Pharmacode payload not UTF-8"))?;
text.parse::<u32>()
.map_err(|_| Error::invalid_data("Pharmacode payload is not an integer"))
}
fn push_run(modules: &mut Vec<bool>, bar: bool, width: u32) {
modules.extend(std::iter::repeat_n(bar, width as usize));
}
fn render_bars(bar_widths: &[u32]) -> Vec<bool> {
let mut modules = Vec::new();
for (i, &w) in bar_widths.iter().enumerate() {
if i > 0 {
push_run(&mut modules, false, 1);
}
push_run(&mut modules, true, w);
}
modules
}
fn bar_widths(modules: &[bool]) -> Result<Vec<u32>> {
if modules.is_empty() || !modules[0] {
return Err(Error::undecodable("linear pattern must start with a bar"));
}
let mut runs = Vec::new();
let mut cur = modules[0];
let mut len = 0u32;
for &m in modules {
if m == cur {
len += 1;
} else {
runs.push(len);
cur = m;
len = 1;
}
}
runs.push(len);
if runs.len() % 2 == 0 {
return Err(Error::undecodable("Pharmacode must end with a bar"));
}
for (i, &w) in runs.iter().enumerate() {
if i % 2 == 1 && w != 1 {
return Err(Error::undecodable("Pharmacode space not narrow"));
}
}
Ok(runs.iter().step_by(2).copied().collect())
}
fn one_track_bars(mut n: u32) -> Vec<u32> {
let mut bars = Vec::new(); while n > 0 {
if n % 2 == 1 {
bars.push(1); n = (n - 1) / 2;
} else {
bars.push(WIDE); n = (n - 2) / 2;
}
}
bars.reverse(); bars
}
fn one_track_value(bars: &[u32]) -> Result<u32> {
let mut value: u32 = 0;
for &w in bars {
let digit = match w {
1 => 1,
_ if w > 1 => 2,
_ => return Err(Error::undecodable("invalid Pharmacode bar")),
};
value = value
.checked_mul(2)
.and_then(|v| v.checked_add(digit))
.ok_or_else(|| Error::undecodable("Pharmacode value overflow"))?;
}
Ok(value)
}
fn two_track_bars(mut n: u32) -> Vec<u32> {
let mut digits = Vec::new();
while n > 0 {
let r = n % 3;
if r == 0 {
digits.push(3);
n = n / 3 - 1;
} else {
digits.push(r);
n /= 3;
}
}
digits.reverse();
digits
}
fn two_track_value(bars: &[u32]) -> Result<u32> {
let mut value: u32 = 0;
for &w in bars {
if !(1..=3).contains(&w) {
return Err(Error::undecodable("invalid two-track bar width"));
}
value = value
.checked_mul(3)
.and_then(|v| v.checked_add(w))
.ok_or_else(|| Error::undecodable("two-track value overflow"))?;
}
Ok(value)
}
#[derive(Debug, Default, Clone, Copy)]
pub struct PharmacodeEncoder;
impl PharmacodeEncoder {
pub fn new() -> Self {
Self
}
pub fn build(&self, value: u32) -> Result<Symbol> {
if !ONE_TRACK_RANGE.contains(&value) {
return Err(Error::capacity(
"one-track Pharmacode value out of range 3..=131070",
));
}
Ok(Symbol::new(
Symbology::Pharmacode,
vec![Segment::numeric(value.to_string().into_bytes())],
SymbolMeta::Pharmacode(PharmacodeMeta),
))
}
pub fn build_two_track(&self, value: u32) -> Result<Symbol> {
if !TWO_TRACK_RANGE.contains(&value) {
return Err(Error::capacity(
"two-track Pharmacode value out of range 4..=64570080",
));
}
Ok(Symbol::new(
Symbology::PharmacodeTwoTrack,
vec![Segment::numeric(value.to_string().into_bytes())],
SymbolMeta::Pharmacode(PharmacodeMeta),
))
}
}
impl Encode for PharmacodeEncoder {
fn encode(&self, symbol: &Symbol) -> Result<Encoding> {
let value = parse_value(&symbol.payload_bytes())?;
let bars = match symbol.symbology {
Symbology::Pharmacode => {
if !ONE_TRACK_RANGE.contains(&value) {
return Err(Error::capacity("one-track Pharmacode value out of range"));
}
one_track_bars(value)
}
Symbology::PharmacodeTwoTrack => {
if !TWO_TRACK_RANGE.contains(&value) {
return Err(Error::capacity("two-track Pharmacode value out of range"));
}
two_track_bars(value)
}
_ => return Err(Error::invalid_parameter("not a Pharmacode symbology")),
};
if !matches!(symbol.meta, SymbolMeta::Pharmacode(_)) {
return Err(Error::invalid_parameter(
"Pharmacode symbol missing PharmacodeMeta",
));
}
Ok(Encoding::Linear(LinearPattern {
modules: render_bars(&bars),
quiet_zone: QUIET_ZONE,
}))
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct PharmacodeDecoder {
two_track: bool,
}
impl PharmacodeDecoder {
pub fn new() -> Self {
Self { two_track: false }
}
pub fn two_track() -> Self {
Self { two_track: true }
}
}
impl Decode for PharmacodeDecoder {
fn decode(&self, encoding: &Encoding) -> Result<Symbol> {
let pattern = match encoding {
Encoding::Linear(p) => p,
Encoding::Matrix(_) => {
return Err(Error::invalid_parameter(
"Pharmacode expects a linear pattern",
));
}
};
let bars = bar_widths(&pattern.modules)?;
let (value, symbology, range) = if self.two_track {
(
two_track_value(&bars)?,
Symbology::PharmacodeTwoTrack,
&TWO_TRACK_RANGE,
)
} else {
(
one_track_value(&bars)?,
Symbology::Pharmacode,
&ONE_TRACK_RANGE,
)
};
if !range.contains(&value) {
return Err(Error::undecodable("Pharmacode value out of range"));
}
Ok(Symbol::new(
symbology,
vec![Segment::numeric(value.to_string().into_bytes())],
SymbolMeta::Pharmacode(PharmacodeMeta),
))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn one_track_bounds() {
assert_eq!(one_track_bars(3), vec![1, 1]);
assert_eq!(one_track_bars(131070), vec![WIDE; 16]);
}
#[test]
fn two_track_bounds() {
assert_eq!(two_track_bars(4), vec![1, 1]);
assert_eq!(two_track_bars(64570080), vec![3; 16]);
}
#[test]
fn one_track_roundtrip() {
let enc = PharmacodeEncoder::new();
for value in [3u32, 4, 42, 1234, 65535, 131070] {
let sym = enc.build(value).unwrap();
let encoding = enc.encode(&sym).unwrap();
let back = PharmacodeDecoder::new().decode(&encoding).unwrap();
assert_eq!(back.segments, sym.segments);
assert_eq!(enc.encode(&back).unwrap(), encoding);
}
}
#[test]
fn two_track_roundtrip() {
let enc = PharmacodeEncoder::new();
for value in [4u32, 5, 100, 117480, 64570080] {
let sym = enc.build_two_track(value).unwrap();
let encoding = enc.encode(&sym).unwrap();
let back = PharmacodeDecoder::two_track().decode(&encoding).unwrap();
assert_eq!(back.symbology, Symbology::PharmacodeTwoTrack);
assert_eq!(back.segments, sym.segments);
assert_eq!(enc.encode(&back).unwrap(), encoding);
}
}
#[test]
fn range_checks() {
let enc = PharmacodeEncoder::new();
assert!(enc.build(2).is_err());
assert!(enc.build(131071).is_err());
assert!(enc.build_two_track(3).is_err());
assert!(enc.build_two_track(64570081).is_err());
}
}