anyd/codes/pharmacode/
mod.rs1use crate::error::{Error, Result};
30use crate::output::{Encoding, LinearPattern};
31use crate::segment::Segment;
32use crate::symbol::{Symbol, SymbolMeta};
33use crate::symbology::Symbology;
34use crate::traits::{Decode, Encode};
35
36const WIDE: u32 = 3;
38const QUIET_ZONE: usize = 10;
40
41const ONE_TRACK_RANGE: std::ops::RangeInclusive<u32> = 3..=131070;
43const TWO_TRACK_RANGE: std::ops::RangeInclusive<u32> = 4..=64570080;
45
46#[derive(Debug, Clone, PartialEq, Eq, Default)]
50pub struct PharmacodeMeta;
51
52fn parse_value(payload: &[u8]) -> Result<u32> {
54 let text = std::str::from_utf8(payload)
55 .map_err(|_| Error::invalid_data("Pharmacode payload not UTF-8"))?;
56 text.parse::<u32>()
57 .map_err(|_| Error::invalid_data("Pharmacode payload is not an integer"))
58}
59
60fn push_run(modules: &mut Vec<bool>, bar: bool, width: u32) {
62 modules.extend(std::iter::repeat_n(bar, width as usize));
63}
64
65fn render_bars(bar_widths: &[u32]) -> Vec<bool> {
67 let mut modules = Vec::new();
68 for (i, &w) in bar_widths.iter().enumerate() {
69 if i > 0 {
70 push_run(&mut modules, false, 1);
71 }
72 push_run(&mut modules, true, w);
73 }
74 modules
75}
76
77fn bar_widths(modules: &[bool]) -> Result<Vec<u32>> {
80 if modules.is_empty() || !modules[0] {
81 return Err(Error::undecodable("linear pattern must start with a bar"));
82 }
83 let mut runs = Vec::new();
84 let mut cur = modules[0];
85 let mut len = 0u32;
86 for &m in modules {
87 if m == cur {
88 len += 1;
89 } else {
90 runs.push(len);
91 cur = m;
92 len = 1;
93 }
94 }
95 runs.push(len);
96 if runs.len() % 2 == 0 {
98 return Err(Error::undecodable("Pharmacode must end with a bar"));
99 }
100 for (i, &w) in runs.iter().enumerate() {
101 if i % 2 == 1 && w != 1 {
102 return Err(Error::undecodable("Pharmacode space not narrow"));
103 }
104 }
105 Ok(runs.iter().step_by(2).copied().collect())
106}
107
108fn one_track_bars(mut n: u32) -> Vec<u32> {
112 let mut bars = Vec::new(); while n > 0 {
114 if n % 2 == 1 {
115 bars.push(1); n = (n - 1) / 2;
117 } else {
118 bars.push(WIDE); n = (n - 2) / 2;
120 }
121 }
122 bars.reverse(); bars
124}
125
126fn one_track_value(bars: &[u32]) -> Result<u32> {
128 let mut value: u32 = 0;
129 for &w in bars {
130 let digit = match w {
131 1 => 1,
132 _ if w > 1 => 2,
133 _ => return Err(Error::undecodable("invalid Pharmacode bar")),
134 };
135 value = value
136 .checked_mul(2)
137 .and_then(|v| v.checked_add(digit))
138 .ok_or_else(|| Error::undecodable("Pharmacode value overflow"))?;
139 }
140 Ok(value)
141}
142
143fn two_track_bars(mut n: u32) -> Vec<u32> {
147 let mut digits = Vec::new();
148 while n > 0 {
149 let r = n % 3;
150 if r == 0 {
151 digits.push(3);
152 n = n / 3 - 1;
153 } else {
154 digits.push(r);
155 n /= 3;
156 }
157 }
158 digits.reverse();
159 digits
160}
161
162fn two_track_value(bars: &[u32]) -> Result<u32> {
164 let mut value: u32 = 0;
165 for &w in bars {
166 if !(1..=3).contains(&w) {
167 return Err(Error::undecodable("invalid two-track bar width"));
168 }
169 value = value
170 .checked_mul(3)
171 .and_then(|v| v.checked_add(w))
172 .ok_or_else(|| Error::undecodable("two-track value overflow"))?;
173 }
174 Ok(value)
175}
176
177#[derive(Debug, Default, Clone, Copy)]
179pub struct PharmacodeEncoder;
180
181impl PharmacodeEncoder {
182 pub fn new() -> Self {
184 Self
185 }
186
187 pub fn build(&self, value: u32) -> Result<Symbol> {
189 if !ONE_TRACK_RANGE.contains(&value) {
190 return Err(Error::capacity(
191 "one-track Pharmacode value out of range 3..=131070",
192 ));
193 }
194 Ok(Symbol::new(
195 Symbology::Pharmacode,
196 vec![Segment::numeric(value.to_string().into_bytes())],
197 SymbolMeta::Pharmacode(PharmacodeMeta),
198 ))
199 }
200
201 pub fn build_two_track(&self, value: u32) -> Result<Symbol> {
203 if !TWO_TRACK_RANGE.contains(&value) {
204 return Err(Error::capacity(
205 "two-track Pharmacode value out of range 4..=64570080",
206 ));
207 }
208 Ok(Symbol::new(
209 Symbology::PharmacodeTwoTrack,
210 vec![Segment::numeric(value.to_string().into_bytes())],
211 SymbolMeta::Pharmacode(PharmacodeMeta),
212 ))
213 }
214}
215
216impl Encode for PharmacodeEncoder {
217 fn encode(&self, symbol: &Symbol) -> Result<Encoding> {
218 let value = parse_value(&symbol.payload_bytes())?;
219 let bars = match symbol.symbology {
220 Symbology::Pharmacode => {
221 if !ONE_TRACK_RANGE.contains(&value) {
222 return Err(Error::capacity("one-track Pharmacode value out of range"));
223 }
224 one_track_bars(value)
225 }
226 Symbology::PharmacodeTwoTrack => {
227 if !TWO_TRACK_RANGE.contains(&value) {
228 return Err(Error::capacity("two-track Pharmacode value out of range"));
229 }
230 two_track_bars(value)
231 }
232 _ => return Err(Error::invalid_parameter("not a Pharmacode symbology")),
233 };
234 if !matches!(symbol.meta, SymbolMeta::Pharmacode(_)) {
235 return Err(Error::invalid_parameter(
236 "Pharmacode symbol missing PharmacodeMeta",
237 ));
238 }
239 Ok(Encoding::Linear(LinearPattern {
240 modules: render_bars(&bars),
241 quiet_zone: QUIET_ZONE,
242 }))
243 }
244}
245
246#[derive(Debug, Default, Clone, Copy)]
248pub struct PharmacodeDecoder {
249 two_track: bool,
250}
251
252impl PharmacodeDecoder {
253 pub fn new() -> Self {
255 Self { two_track: false }
256 }
257
258 pub fn two_track() -> Self {
260 Self { two_track: true }
261 }
262}
263
264impl Decode for PharmacodeDecoder {
265 fn decode(&self, encoding: &Encoding) -> Result<Symbol> {
266 let pattern = match encoding {
267 Encoding::Linear(p) => p,
268 Encoding::Matrix(_) => {
269 return Err(Error::invalid_parameter(
270 "Pharmacode expects a linear pattern",
271 ));
272 }
273 };
274 let bars = bar_widths(&pattern.modules)?;
275 let (value, symbology, range) = if self.two_track {
276 (
277 two_track_value(&bars)?,
278 Symbology::PharmacodeTwoTrack,
279 &TWO_TRACK_RANGE,
280 )
281 } else {
282 (
283 one_track_value(&bars)?,
284 Symbology::Pharmacode,
285 &ONE_TRACK_RANGE,
286 )
287 };
288 if !range.contains(&value) {
289 return Err(Error::undecodable("Pharmacode value out of range"));
290 }
291 Ok(Symbol::new(
292 symbology,
293 vec![Segment::numeric(value.to_string().into_bytes())],
294 SymbolMeta::Pharmacode(PharmacodeMeta),
295 ))
296 }
297}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302
303 #[test]
304 fn one_track_bounds() {
305 assert_eq!(one_track_bars(3), vec![1, 1]);
307 assert_eq!(one_track_bars(131070), vec![WIDE; 16]);
308 }
309
310 #[test]
311 fn two_track_bounds() {
312 assert_eq!(two_track_bars(4), vec![1, 1]);
313 assert_eq!(two_track_bars(64570080), vec![3; 16]);
314 }
315
316 #[test]
317 fn one_track_roundtrip() {
318 let enc = PharmacodeEncoder::new();
319 for value in [3u32, 4, 42, 1234, 65535, 131070] {
320 let sym = enc.build(value).unwrap();
321 let encoding = enc.encode(&sym).unwrap();
322 let back = PharmacodeDecoder::new().decode(&encoding).unwrap();
323 assert_eq!(back.segments, sym.segments);
324 assert_eq!(enc.encode(&back).unwrap(), encoding);
325 }
326 }
327
328 #[test]
329 fn two_track_roundtrip() {
330 let enc = PharmacodeEncoder::new();
331 for value in [4u32, 5, 100, 117480, 64570080] {
332 let sym = enc.build_two_track(value).unwrap();
333 let encoding = enc.encode(&sym).unwrap();
334 let back = PharmacodeDecoder::two_track().decode(&encoding).unwrap();
335 assert_eq!(back.symbology, Symbology::PharmacodeTwoTrack);
336 assert_eq!(back.segments, sym.segments);
337 assert_eq!(enc.encode(&back).unwrap(), encoding);
338 }
339 }
340
341 #[test]
342 fn range_checks() {
343 let enc = PharmacodeEncoder::new();
344 assert!(enc.build(2).is_err());
345 assert!(enc.build(131071).is_err());
346 assert!(enc.build_two_track(3).is_err());
347 assert!(enc.build_two_track(64570081).is_err());
348 }
349}