barcode_gen/
lib.rs

1use std::{collections::VecDeque, fmt::Display, sync::atomic::AtomicUsize};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub enum BarcodeType {
5    CodeA,
6    CodeB,
7    CodeC,
8}
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum BarcodeValue {
12    RegularCharacter(char),
13    Digit(u8),
14    FNC1,
15    FNC2,
16    FNC3,
17    FNC4,
18    StartA,
19    StartB,
20    StartC,
21    Stop,
22    ShiftA,
23    ShiftB,
24    CodeA,
25    CodeB,
26    CodeC,
27}
28
29impl From<char> for BarcodeValue {
30    fn from(value: char) -> Self {
31        BarcodeValue::RegularCharacter(value)
32    }
33}
34
35impl From<u8> for BarcodeValue {
36    fn from(value: u8) -> Self {
37        BarcodeValue::Digit(value)
38    }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub struct TableEntry {
43    value: u8,
44    a: BarcodeValue,
45    b: BarcodeValue,
46    c: BarcodeValue,
47    latin: char,
48}
49
50impl TableEntry {
51    pub fn new(
52        value: u8,
53        a: impl Into<BarcodeValue>,
54        b: impl Into<BarcodeValue>,
55        c: impl Into<BarcodeValue>,
56        latin: char,
57    ) -> Self {
58        Self::new_barcode_value(value, a.into(), b.into(), c.into(), latin)
59    }
60
61    const fn new_barcode_value(
62        value: u8,
63        a: BarcodeValue,
64        b: BarcodeValue,
65        c: BarcodeValue,
66        latin: char,
67    ) -> Self {
68        TableEntry {
69            value,
70            a,
71            b,
72            c,
73            latin,
74        }
75    }
76
77    const fn new_barcode_chars(value: u8, a: char, b: char, c: u8, latin: char) -> Self {
78        Self::new_barcode_value(
79            value,
80            BarcodeValue::RegularCharacter(a),
81            BarcodeValue::RegularCharacter(b),
82            BarcodeValue::Digit(c),
83            latin,
84        )
85    }
86
87    pub fn value(&self) -> u8 {
88        self.value
89    }
90
91    pub fn latin(&self) -> char {
92        self.latin
93    }
94}
95
96#[derive(Debug)]
97pub struct Table {
98    entries: [TableEntry; 107],
99}
100
101impl Table {
102    pub const fn new() -> Self {
103        Table {
104            entries: [
105                TableEntry::new_barcode_chars(0, ' ', ' ', 0, ' '),
106                TableEntry::new_barcode_chars(1, '!', '!', 1, '!'),
107                TableEntry::new_barcode_chars(2, '"', '"', 2, '"'),
108                TableEntry::new_barcode_chars(3, '#', '#', 3, '#'),
109                TableEntry::new_barcode_chars(4, '$', '$', 4, '$'),
110                TableEntry::new_barcode_chars(5, '%', '%', 5, '%'),
111                TableEntry::new_barcode_chars(6, '&', '&', 6, '&'),
112                TableEntry::new_barcode_chars(7, '\'', '\'', 7, '\''),
113                TableEntry::new_barcode_chars(8, '(', '(', 8, '('),
114                TableEntry::new_barcode_chars(9, ')', ')', 9, ')'),
115                TableEntry::new_barcode_chars(10, '*', '*', 10, '*'),
116                TableEntry::new_barcode_chars(11, '+', '+', 11, '+'),
117                TableEntry::new_barcode_chars(12, ',', ',', 12, ','),
118                TableEntry::new_barcode_chars(13, '-', '-', 13, '-'),
119                TableEntry::new_barcode_chars(14, '.', '.', 14, '.'),
120                TableEntry::new_barcode_chars(15, '/', '/', 15, '/'),
121                TableEntry::new_barcode_chars(16, '0', '0', 16, '0'),
122                TableEntry::new_barcode_chars(17, '1', '1', 17, '1'),
123                TableEntry::new_barcode_chars(18, '2', '2', 18, '2'),
124                TableEntry::new_barcode_chars(19, '3', '3', 19, '3'),
125                TableEntry::new_barcode_chars(20, '4', '4', 20, '4'),
126                TableEntry::new_barcode_chars(21, '5', '5', 21, '5'),
127                TableEntry::new_barcode_chars(22, '6', '6', 22, '6'),
128                TableEntry::new_barcode_chars(23, '7', '7', 23, '7'),
129                TableEntry::new_barcode_chars(24, '8', '8', 24, '8'),
130                TableEntry::new_barcode_chars(25, '9', '9', 25, '9'),
131                TableEntry::new_barcode_chars(26, ':', ':', 26, ':'),
132                TableEntry::new_barcode_chars(27, ';', ';', 27, ';'),
133                TableEntry::new_barcode_chars(28, '<', '<', 28, '<'),
134                TableEntry::new_barcode_chars(29, '=', '=', 29, '='),
135                TableEntry::new_barcode_chars(30, '>', '>', 30, '>'),
136                TableEntry::new_barcode_chars(31, '?', '?', 31, '?'),
137                TableEntry::new_barcode_chars(32, '@', '@', 32, '@'),
138                TableEntry::new_barcode_chars(33, 'A', 'A', 33, 'A'),
139                TableEntry::new_barcode_chars(34, 'B', 'B', 34, 'B'),
140                TableEntry::new_barcode_chars(35, 'C', 'C', 35, 'C'),
141                TableEntry::new_barcode_chars(36, 'D', 'D', 36, 'D'),
142                TableEntry::new_barcode_chars(37, 'E', 'E', 37, 'E'),
143                TableEntry::new_barcode_chars(38, 'F', 'F', 38, 'F'),
144                TableEntry::new_barcode_chars(39, 'G', 'G', 39, 'G'),
145                TableEntry::new_barcode_chars(40, 'H', 'H', 40, 'H'),
146                TableEntry::new_barcode_chars(41, 'I', 'I', 41, 'I'),
147                TableEntry::new_barcode_chars(42, 'J', 'J', 42, 'J'),
148                TableEntry::new_barcode_chars(43, 'K', 'K', 43, 'K'),
149                TableEntry::new_barcode_chars(44, 'L', 'L', 44, 'L'),
150                TableEntry::new_barcode_chars(45, 'M', 'M', 45, 'M'),
151                TableEntry::new_barcode_chars(46, 'N', 'N', 46, 'N'),
152                TableEntry::new_barcode_chars(47, 'O', 'O', 47, 'O'),
153                TableEntry::new_barcode_chars(48, 'P', 'P', 48, 'P'),
154                TableEntry::new_barcode_chars(49, 'Q', 'Q', 49, 'Q'),
155                TableEntry::new_barcode_chars(50, 'R', 'R', 50, 'R'),
156                TableEntry::new_barcode_chars(51, 'S', 'S', 51, 'S'),
157                TableEntry::new_barcode_chars(52, 'T', 'T', 52, 'T'),
158                TableEntry::new_barcode_chars(53, 'U', 'U', 53, 'U'),
159                TableEntry::new_barcode_chars(54, 'V', 'V', 54, 'V'),
160                TableEntry::new_barcode_chars(55, 'W', 'W', 55, 'W'),
161                TableEntry::new_barcode_chars(56, 'X', 'X', 56, 'X'),
162                TableEntry::new_barcode_chars(57, 'Y', 'Y', 57, 'Y'),
163                TableEntry::new_barcode_chars(58, 'Z', 'Z', 58, 'Z'),
164                TableEntry::new_barcode_chars(59, '[', '[', 59, '['),
165                TableEntry::new_barcode_chars(60, '\\', '\\', 60, '\\'),
166                TableEntry::new_barcode_chars(61, ']', ']', 61, ']'),
167                TableEntry::new_barcode_chars(62, '^', '^', 62, '^'),
168                TableEntry::new_barcode_chars(63, '_', '_', 63, '_'),
169                TableEntry::new_barcode_chars(64, '\x00', '`', 64, '`'),
170                TableEntry::new_barcode_chars(65, '\x01', 'a', 65, 'a'),
171                TableEntry::new_barcode_chars(66, '\x02', 'b', 66, 'b'),
172                TableEntry::new_barcode_chars(67, '\x03', 'c', 67, 'c'),
173                TableEntry::new_barcode_chars(68, '\x04', 'd', 68, 'd'),
174                TableEntry::new_barcode_chars(69, '\x05', 'e', 69, 'e'),
175                TableEntry::new_barcode_chars(70, '\x06', 'f', 70, 'f'),
176                TableEntry::new_barcode_chars(71, '\x07', 'g', 71, 'g'),
177                TableEntry::new_barcode_chars(72, '\x08', 'h', 72, 'h'),
178                TableEntry::new_barcode_chars(73, '\x09', 'i', 73, 'i'),
179                TableEntry::new_barcode_chars(74, '\x0A', 'j', 74, 'j'),
180                TableEntry::new_barcode_chars(75, '\x0B', 'k', 75, 'k'),
181                TableEntry::new_barcode_chars(76, '\x0C', 'l', 76, 'l'),
182                TableEntry::new_barcode_chars(77, '\x0D', 'm', 77, 'm'),
183                TableEntry::new_barcode_chars(78, '\x0E', 'n', 78, 'n'),
184                TableEntry::new_barcode_chars(79, '\x0F', 'o', 79, 'o'),
185                TableEntry::new_barcode_chars(80, '\x10', 'p', 80, 'p'),
186                TableEntry::new_barcode_chars(81, '\x11', 'q', 81, 'q'),
187                TableEntry::new_barcode_chars(82, '\x12', 'r', 82, 'r'),
188                TableEntry::new_barcode_chars(83, '\x13', 's', 83, 's'),
189                TableEntry::new_barcode_chars(84, '\x14', 't', 84, 't'),
190                TableEntry::new_barcode_chars(85, '\x15', 'u', 85, 'u'),
191                TableEntry::new_barcode_chars(86, '\x16', 'v', 86, 'v'),
192                TableEntry::new_barcode_chars(87, '\x17', 'w', 87, 'w'),
193                TableEntry::new_barcode_chars(88, '\x18', 'x', 88, 'x'),
194                TableEntry::new_barcode_chars(89, '\x19', 'y', 89, 'y'),
195                TableEntry::new_barcode_chars(90, '\x1A', 'z', 90, 'z'),
196                TableEntry::new_barcode_chars(91, '\x1B', '{', 91, '{'),
197                TableEntry::new_barcode_chars(92, '\x1C', '|', 92, '|'),
198                TableEntry::new_barcode_chars(93, '\x1D', '}', 93, '}'),
199                TableEntry::new_barcode_chars(94, '\x1E', '~', 94, '~'),
200                TableEntry::new_barcode_chars(95, '\x1F', '\x7F', 95, 'Ã'),
201                TableEntry::new_barcode_value(
202                    96,
203                    BarcodeValue::FNC3,
204                    BarcodeValue::FNC3,
205                    BarcodeValue::Digit(96),
206                    'Ä',
207                ),
208                TableEntry::new_barcode_value(
209                    97,
210                    BarcodeValue::FNC2,
211                    BarcodeValue::FNC2,
212                    BarcodeValue::Digit(97),
213                    'Å',
214                ),
215                TableEntry::new_barcode_value(
216                    98,
217                    BarcodeValue::ShiftB,
218                    BarcodeValue::ShiftA,
219                    BarcodeValue::Digit(98),
220                    'Æ',
221                ),
222                TableEntry::new_barcode_value(
223                    99,
224                    BarcodeValue::CodeC,
225                    BarcodeValue::CodeC,
226                    BarcodeValue::Digit(99),
227                    'Ç',
228                ),
229                TableEntry::new_barcode_value(
230                    100,
231                    BarcodeValue::CodeB,
232                    BarcodeValue::FNC4,
233                    BarcodeValue::CodeB,
234                    'È',
235                ),
236                TableEntry::new_barcode_value(
237                    101,
238                    BarcodeValue::FNC4,
239                    BarcodeValue::CodeA,
240                    BarcodeValue::CodeA,
241                    'É',
242                ),
243                TableEntry::new_barcode_value(
244                    102,
245                    BarcodeValue::FNC1,
246                    BarcodeValue::FNC1,
247                    BarcodeValue::FNC1,
248                    'Ê',
249                ),
250                TableEntry::new_barcode_value(
251                    103,
252                    BarcodeValue::StartA,
253                    BarcodeValue::StartA,
254                    BarcodeValue::StartA,
255                    'Ë',
256                ),
257                TableEntry::new_barcode_value(
258                    104,
259                    BarcodeValue::StartB,
260                    BarcodeValue::StartB,
261                    BarcodeValue::StartB,
262                    'Ì',
263                ),
264                TableEntry::new_barcode_value(
265                    105,
266                    BarcodeValue::StartC,
267                    BarcodeValue::StartC,
268                    BarcodeValue::StartC,
269                    'Í',
270                ),
271                TableEntry::new_barcode_value(
272                    106,
273                    BarcodeValue::Stop,
274                    BarcodeValue::Stop,
275                    BarcodeValue::Stop,
276                    'Î',
277                ),
278            ],
279        }
280    }
281
282    pub fn entry_in_set(
283        &self,
284        value: impl Into<BarcodeValue>,
285        set: BarcodeType,
286    ) -> Option<&TableEntry> {
287        let value = value.into();
288        self.entries.iter().find(|entry| match set {
289            BarcodeType::CodeA => entry.a == value,
290            BarcodeType::CodeB => entry.b == value,
291            BarcodeType::CodeC => entry.c == value,
292        })
293    }
294}
295
296#[derive(Debug, PartialEq, Eq)]
297pub struct Barcode<'table> {
298    code: Vec<&'table TableEntry>,
299}
300
301impl PartialOrd for Barcode<'_> {
302    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
303        Some(self.cmp(other))
304    }
305}
306
307impl Ord for Barcode<'_> {
308    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
309        self.code.len().cmp(&other.code.len())
310    }
311}
312
313impl Display for Barcode<'_> {
314    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
315        let mut output = String::new();
316        for entry in &self.code {
317            output.push(entry.latin());
318        }
319        write!(f, "{output}")
320    }
321}
322static TABLE: Table = Table::new();
323
324pub fn make_barcode(text: &str) -> Option<Barcode<'_>> {
325    #[cfg(feature = "parallel")]
326    let res = make_barcode_custom_table_par(&TABLE, text);
327    #[cfg(not(feature = "parallel"))]
328    let res = make_barcode_custom_table(&TABLE, text);
329    res
330}
331
332pub fn make_barcode_custom_table<'table>(
333    table: &'table Table,
334    text: &str,
335) -> Option<Barcode<'table>> {
336    let mut stack = VecDeque::new();
337    stack.push_back(BarcodeBuilder::new(table, text));
338    let mut done = vec![];
339
340    let shortest = AtomicUsize::new(usize::MAX);
341
342    while let Some(partial_code) = stack.pop_front() {
343        let options = partial_code.create_child_options();
344
345        for option in options {
346            if option.remaining.is_empty() {
347                let code = option.build();
348                shortest.fetch_min(code.code.len(), std::sync::atomic::Ordering::SeqCst);
349                done.push(code);
350            } else {
351                if option.code.len() <= shortest.load(std::sync::atomic::Ordering::SeqCst) {
352                    stack.push_back(option);
353                }
354            }
355        }
356    }
357
358    done.sort_unstable_by_key(|k| k.code.len());
359
360    done.into_iter().next()
361}
362
363#[cfg(feature = "parallel")]
364pub fn make_barcode_custom_table_par<'table>(
365    table: &'table Table,
366    text: &str,
367) -> Option<Barcode<'table>> {
368    use rayon::iter::ParallelIterator;
369    let walker = rayon::iter::walk_tree(BarcodeBuilder::new(table, text), |t| {
370        t.create_child_options()
371    });
372    let res = walker.min_by_key(|v| {
373        if v.remaining.is_empty() {
374            v.code.len()
375        } else {
376            usize::MAX
377        }
378    });
379    res.map(|r| r.build())
380}
381
382#[derive(Debug, Clone)]
383struct BarcodeBuilder<'table, 's> {
384    table: &'table Table,
385    code: Vec<&'table TableEntry>,
386    remaining: &'s str,
387    set: BarcodeType,
388}
389
390impl<'table, 's> BarcodeBuilder<'table, 's> {
391    fn new(table: &'table Table, code: &'s str) -> Self {
392        Self {
393            table,
394            code: Vec::new(),
395            remaining: code,
396            set: BarcodeType::CodeA,
397        }
398    }
399
400    fn create_child_options(&self) -> Vec<Self> {
401        if self.code.is_empty() {
402            return vec![
403                Self {
404                    code: vec![
405                        self.table
406                            .entry_in_set(BarcodeValue::StartA, BarcodeType::CodeA)
407                            .unwrap(),
408                    ],
409                    ..self.clone()
410                },
411                Self {
412                    code: vec![
413                        self.table
414                            .entry_in_set(BarcodeValue::StartB, BarcodeType::CodeB)
415                            .unwrap(),
416                    ],
417                    set: BarcodeType::CodeB,
418                    ..self.clone()
419                },
420                Self {
421                    code: vec![
422                        self.table
423                            .entry_in_set(BarcodeValue::StartC, BarcodeType::CodeC)
424                            .unwrap(),
425                    ],
426                    set: BarcodeType::CodeC,
427                    ..self.clone()
428                },
429            ];
430        }
431        let mut options = Vec::new();
432        let old_set = self.set;
433
434        // try to see if set C works, always two numbers
435
436        if self.remaining.len() >= 2 {
437            let first_two_characters = &self.remaining[..2];
438            if first_two_characters.chars().all(|c| c.is_ascii_digit()) {
439                if let Ok(v) = first_two_characters.parse::<u8>() {
440                    if let Some(s) = self.try_push(|mut s| {
441                        if !matches!(old_set, BarcodeType::CodeC) {
442                            s.code.push(s.in_set(BarcodeValue::CodeC)?);
443                            s.set = BarcodeType::CodeC;
444                        }
445                        s.code.push(s.in_set(v)?);
446                        s.remaining = &s.remaining[2..];
447                        Some(s)
448                    }) {
449                        options.push(s)
450                    };
451                }
452            }
453        }
454
455        // single character, so set A or B
456        if let Some(fc) = self.remaining.chars().next() {
457            // does it exist in the current set?
458            if let Some(s) = self.try_push(|mut s| {
459                s.code.push(s.in_set(fc)?);
460                s.remaining = &s.remaining[fc.len_utf8()..];
461                Some(s)
462            }) {
463                options.push(s);
464            }
465            // shift A or B?
466            for new_set in [BarcodeType::CodeA, BarcodeType::CodeB] {
467                let shift_to = match new_set {
468                    BarcodeType::CodeA => BarcodeValue::ShiftA,
469                    BarcodeType::CodeB => BarcodeValue::ShiftB,
470                    BarcodeType::CodeC => unreachable!(),
471                };
472                if let Some(s) = self.try_push(|mut s| {
473                    s.code.push(s.in_set(shift_to)?);
474                    s.set = new_set;
475                    s.code.push(s.in_set(fc)?);
476                    s.set = old_set;
477                    s.remaining = &s.remaining[fc.len_utf8()..];
478                    Some(s)
479                }) {
480                    options.push(s);
481                }
482
483                let force_to = match new_set {
484                    BarcodeType::CodeA => BarcodeValue::CodeA,
485                    BarcodeType::CodeB => BarcodeValue::CodeB,
486                    BarcodeType::CodeC => unreachable!(),
487                };
488
489                if let Some(s) = self.try_push(|mut s| {
490                    s.code.push(s.in_set(force_to)?);
491                    s.set = new_set;
492                    s.code.push(s.in_set(fc)?);
493                    s.remaining = &s.remaining[fc.len_utf8()..];
494                    Some(s)
495                }) {
496                    options.push(s);
497                }
498            }
499        }
500
501        options
502    }
503
504    fn try_push(&self, to_try: impl FnOnce(Self) -> Option<Self>) -> Option<Self> {
505        to_try(self.clone())
506    }
507
508    fn in_set(&self, v: impl Into<BarcodeValue>) -> Option<&'table TableEntry> {
509        self.table.entry_in_set(v, self.set)
510    }
511
512    fn build(mut self) -> Barcode<'table> {
513        let mut total = 0;
514        for (mut idx, character) in self.code.iter().enumerate() {
515            if idx == 0 {
516                idx = 1;
517            }
518            total += character.value() as usize * idx
519        }
520        total %= 103;
521        self.code.push(
522            self.table
523                .entries
524                .iter()
525                .find(|v| v.value() as usize == total)
526                .unwrap(),
527        );
528        self.code.push(
529            self.table
530                .entry_in_set(BarcodeValue::Stop, self.set)
531                .unwrap(),
532        );
533        Barcode { code: self.code }
534    }
535}
536
537