Skip to main content

forme/
barcode.rs

1//! # 1D Barcode Generation
2//!
3//! Converts data strings into barcode bar patterns for vector rendering in PDF.
4//! Uses the `barcoders` crate for encoding. Each barcode format produces a
5//! `Vec<u8>` of 0/1 values representing spaces and bars.
6
7use barcoders::sym::codabar::Codabar;
8use barcoders::sym::code128::Code128;
9use barcoders::sym::code39::Code39;
10use barcoders::sym::ean13::EAN13;
11use barcoders::sym::ean8::EAN8;
12
13/// Supported barcode formats.
14#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
15pub enum BarcodeFormat {
16    #[default]
17    Code128,
18    Code39,
19    #[serde(rename = "EAN13")]
20    Ean13,
21    #[serde(rename = "EAN8")]
22    Ean8,
23    Codabar,
24}
25
26/// A 1D barcode represented as a sequence of bar/space values.
27#[derive(Debug, Clone)]
28pub struct BarcodeData {
29    /// Each element is 0 (space) or 1 (bar).
30    pub bars: Vec<u8>,
31}
32
33/// Generate a barcode from the given data string and format.
34pub fn generate_barcode(data: &str, format: BarcodeFormat) -> Result<BarcodeData, String> {
35    let bars = match format {
36        BarcodeFormat::Code128 => {
37            // barcoders requires a start character: 'Ɓ' = Set B (standard ASCII).
38            // Auto-prepend if the user didn't provide one.
39            let input = if data.starts_with('À') || data.starts_with('Ɓ') || data.starts_with('Ć')
40            {
41                data.to_string()
42            } else {
43                format!("Ɓ{data}")
44            };
45            let barcode = Code128::new(&input).map_err(|e| format!("Code128 error: {e}"))?;
46            barcode.encode()
47        }
48        BarcodeFormat::Code39 => {
49            let barcode = Code39::new(data).map_err(|e| format!("Code39 error: {e}"))?;
50            barcode.encode()
51        }
52        BarcodeFormat::Ean13 => {
53            let barcode = EAN13::new(data).map_err(|e| format!("EAN13 error: {e}"))?;
54            barcode.encode()
55        }
56        BarcodeFormat::Ean8 => {
57            let barcode = EAN8::new(data).map_err(|e| format!("EAN8 error: {e}"))?;
58            barcode.encode()
59        }
60        BarcodeFormat::Codabar => {
61            let barcode = Codabar::new(data).map_err(|e| format!("Codabar error: {e}"))?;
62            barcode.encode()
63        }
64    };
65
66    Ok(BarcodeData { bars })
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn test_code128() {
75        let bc = generate_barcode("Hello", BarcodeFormat::Code128).unwrap();
76        assert!(!bc.bars.is_empty());
77        assert!(bc.bars.iter().all(|&b| b == 0 || b == 1));
78    }
79
80    #[test]
81    fn test_code39() {
82        let bc = generate_barcode("HELLO", BarcodeFormat::Code39).unwrap();
83        assert!(!bc.bars.is_empty());
84    }
85
86    #[test]
87    fn test_ean13() {
88        let bc = generate_barcode("5901234123457", BarcodeFormat::Ean13).unwrap();
89        assert!(!bc.bars.is_empty());
90    }
91
92    #[test]
93    fn test_ean8() {
94        let bc = generate_barcode("65833254", BarcodeFormat::Ean8).unwrap();
95        assert!(!bc.bars.is_empty());
96    }
97
98    #[test]
99    fn test_invalid_ean13() {
100        let result = generate_barcode("123", BarcodeFormat::Ean13);
101        assert!(result.is_err());
102    }
103
104    #[test]
105    fn test_bars_contain_dark() {
106        let bc = generate_barcode("Test", BarcodeFormat::Code128).unwrap();
107        assert!(bc.bars.iter().any(|&b| b == 1), "Should have dark bars");
108    }
109}