Skip to main content

smart_package_tracker/
barcode.rs

1//! The high-level barcode facade.
2
3use crate::error::Result;
4use crate::render::{RenderOptions, Renderer};
5use crate::symbology::{Symbol, Symbology, SymbologyKind};
6
7/// An encoded barcode, ready to render.
8///
9/// This is the type most callers work with. It pairs an encoded [`Symbol`]
10/// with convenience methods for the built-in renderers, while
11/// [`Barcode::render`] stays open to any [`Renderer`] implementation.
12///
13/// # Examples
14///
15/// ```no_run
16/// # #[cfg(all(feature = "os-rng", feature = "code128", feature = "png", feature = "svg"))]
17/// # fn main() -> Result<(), smart_package_tracker::Error> {
18/// use smart_package_tracker::{Barcode, RenderOptions, TrackingId};
19///
20/// let id = TrackingId::generate()?;
21/// let barcode = Barcode::code128(&id)?;
22/// let options = RenderOptions::default();
23///
24/// barcode.to_png_file("label.png", &options)?;
25/// barcode.to_svg_file("label.svg", &options)?;
26/// # Ok(())
27/// # }
28/// # #[cfg(not(all(feature = "os-rng", feature = "code128", feature = "png", feature = "svg")))]
29/// # fn main() {}
30/// ```
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct Barcode {
33    symbol: Symbol,
34}
35
36impl Barcode {
37    /// Encode `data` as Code 128.
38    ///
39    /// Accepts anything that borrows as a string, including [`TrackingId`].
40    ///
41    /// [`TrackingId`]: crate::TrackingId
42    ///
43    /// # Errors
44    ///
45    /// Returns [`Error::EmptyPayload`](crate::Error::EmptyPayload) or
46    /// [`Error::Unencodable`](crate::Error::Unencodable).
47    #[cfg(feature = "code128")]
48    pub fn code128(data: impl AsRef<str>) -> Result<Self> {
49        Self::encode_with(&crate::symbology::Code128, data.as_ref())
50    }
51
52    /// Encode `data` with any symbology.
53    ///
54    /// # Errors
55    ///
56    /// Propagates whatever the symbology reports.
57    pub fn encode_with<S: Symbology + ?Sized>(symbology: &S, data: &str) -> Result<Self> {
58        Ok(Self {
59            symbol: symbology.encode(data)?,
60        })
61    }
62
63    /// Wrap an already-encoded symbol.
64    pub fn from_symbol(symbol: Symbol) -> Self {
65        Self { symbol }
66    }
67
68    /// The underlying symbol.
69    pub fn symbol(&self) -> &Symbol {
70        &self.symbol
71    }
72
73    /// The payload this barcode encodes.
74    pub fn payload(&self) -> &str {
75        self.symbol.payload()
76    }
77
78    /// Which symbology encoded it.
79    pub fn kind(&self) -> SymbologyKind {
80        self.symbol.kind()
81    }
82
83    /// Render with an explicit renderer.
84    ///
85    /// # Errors
86    ///
87    /// Propagates renderer failures.
88    pub fn render<R: Renderer>(&self, renderer: &R, options: &RenderOptions) -> Result<R::Output> {
89        renderer.render(&self.symbol, options)
90    }
91
92    /// Render to PNG bytes.
93    ///
94    /// # Errors
95    ///
96    /// Propagates renderer failures.
97    #[cfg(feature = "png")]
98    pub fn to_png(&self, options: &RenderOptions) -> Result<alloc::vec::Vec<u8>> {
99        self.render(&crate::render::Png, options)
100    }
101
102    /// Render to an SVG document.
103    ///
104    /// # Errors
105    ///
106    /// Propagates renderer failures.
107    #[cfg(feature = "svg")]
108    pub fn to_svg(&self, options: &RenderOptions) -> Result<alloc::string::String> {
109        self.render(&crate::render::Svg, options)
110    }
111
112    /// Render to PNG and write it to `path`.
113    ///
114    /// # Errors
115    ///
116    /// Propagates renderer failures and [`Error::Io`](crate::Error::Io).
117    #[cfg(all(feature = "png", feature = "std"))]
118    pub fn to_png_file(
119        &self,
120        path: impl AsRef<std::path::Path>,
121        options: &RenderOptions,
122    ) -> Result<()> {
123        std::fs::write(path, self.to_png(options)?)?;
124        Ok(())
125    }
126
127    /// Render to SVG and write it to `path`.
128    ///
129    /// # Errors
130    ///
131    /// Propagates renderer failures and [`Error::Io`](crate::Error::Io).
132    #[cfg(all(feature = "svg", feature = "std"))]
133    pub fn to_svg_file(
134        &self,
135        path: impl AsRef<std::path::Path>,
136        options: &RenderOptions,
137    ) -> Result<()> {
138        std::fs::write(path, self.to_svg(options)?)?;
139        Ok(())
140    }
141
142    /// Decode the barcode back into its payload from the module grid.
143    ///
144    /// This does not simply return the stored payload: it reconstructs bar and
145    /// space runs from the rendered module pattern and decodes those. A
146    /// successful round trip is therefore evidence that the encoded geometry
147    /// is correct.
148    ///
149    /// # Errors
150    ///
151    /// Returns [`Error::Decode`](crate::Error::Decode) if the pattern is not
152    /// valid Code 128.
153    #[cfg(feature = "code128")]
154    pub fn decode(&self) -> Result<alloc::string::String> {
155        crate::symbology::code128::decode(&self.symbol)
156    }
157}
158
159#[cfg(all(test, feature = "code128"))]
160mod tests {
161    use super::*;
162    use crate::TrackingId;
163
164    #[test]
165    fn encodes_a_tracking_id_by_reference_or_value() {
166        let id = TrackingId::parse("PKG-9ED9285C").unwrap();
167        let a = Barcode::code128(&id).unwrap();
168        let b = Barcode::code128("PKG-9ED9285C").unwrap();
169        let c = Barcode::code128(id.as_str()).unwrap();
170        assert_eq!(a, b);
171        assert_eq!(b, c);
172        assert_eq!(a.payload(), "PKG-9ED9285C");
173        assert_eq!(a.kind(), SymbologyKind::Code128);
174    }
175
176    #[test]
177    fn round_trips_through_the_module_grid() {
178        let barcode = Barcode::code128("PKG-9ED9285C").unwrap();
179        assert_eq!(barcode.decode().unwrap(), "PKG-9ED9285C");
180    }
181
182    #[test]
183    #[cfg(all(feature = "png", feature = "svg"))]
184    fn renders_both_formats_from_one_encode() {
185        let barcode = Barcode::code128("PKG-9ED9285C").unwrap();
186        let options = RenderOptions::default();
187        assert!(!barcode.to_png(&options).unwrap().is_empty());
188        assert!(barcode.to_svg(&options).unwrap().contains("<svg"));
189    }
190
191    #[test]
192    fn from_symbol_preserves_the_symbol() {
193        let symbol = crate::symbology::Code128.encode("PKG-9ED9285C").unwrap();
194        let barcode = Barcode::from_symbol(symbol.clone());
195        assert_eq!(barcode.symbol(), &symbol);
196    }
197
198    #[test]
199    fn rejects_an_empty_payload() {
200        assert!(Barcode::code128("").is_err());
201    }
202}