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` as a QR Code with default settings: medium error
53 /// correction, and the smallest version that fits.
54 ///
55 /// Use [`Barcode::qr_with`] to choose the error correction level or pin the
56 /// version.
57 ///
58 /// # Errors
59 ///
60 /// Returns [`Error::EmptyPayload`](crate::Error::EmptyPayload) or
61 /// [`Error::Unencodable`](crate::Error::Unencodable).
62 #[cfg(feature = "qr")]
63 pub fn qr(data: impl AsRef<str>) -> Result<Self> {
64 Self::encode_with(&crate::symbology::Qr::new(), data.as_ref())
65 }
66
67 /// Encode `data` as a QR Code with an explicitly configured encoder.
68 ///
69 /// # Examples
70 ///
71 /// ```
72 /// use smart_package_tracker::{Barcode, symbology::{Ecc, Qr, QrVersion}};
73 ///
74 /// let barcode = Barcode::qr_with(
75 /// Qr::new().ecc(Ecc::High).version(QrVersion::Fixed(6)),
76 /// "PKG-9ED9285C",
77 /// )?;
78 /// assert_eq!(barcode.symbol().modules().width(), 41);
79 /// # Ok::<(), smart_package_tracker::Error>(())
80 /// ```
81 ///
82 /// # Errors
83 ///
84 /// Returns [`Error::EmptyPayload`](crate::Error::EmptyPayload) or
85 /// [`Error::Unencodable`](crate::Error::Unencodable).
86 #[cfg(feature = "qr")]
87 pub fn qr_with(encoder: crate::symbology::Qr, data: impl AsRef<str>) -> Result<Self> {
88 Self::encode_with(&encoder, data.as_ref())
89 }
90
91 /// Encode `data` with any symbology.
92 ///
93 /// # Errors
94 ///
95 /// Propagates whatever the symbology reports.
96 pub fn encode_with<S: Symbology + ?Sized>(symbology: &S, data: &str) -> Result<Self> {
97 Ok(Self {
98 symbol: symbology.encode(data)?,
99 })
100 }
101
102 /// Wrap an already-encoded symbol.
103 pub fn from_symbol(symbol: Symbol) -> Self {
104 Self { symbol }
105 }
106
107 /// The underlying symbol.
108 pub fn symbol(&self) -> &Symbol {
109 &self.symbol
110 }
111
112 /// The payload this barcode encodes.
113 pub fn payload(&self) -> &str {
114 self.symbol.payload()
115 }
116
117 /// Which symbology encoded it.
118 pub fn kind(&self) -> SymbologyKind {
119 self.symbol.kind()
120 }
121
122 /// Render with an explicit renderer.
123 ///
124 /// # Errors
125 ///
126 /// Propagates renderer failures.
127 pub fn render<R: Renderer>(&self, renderer: &R, options: &RenderOptions) -> Result<R::Output> {
128 renderer.render(&self.symbol, options)
129 }
130
131 /// Render to PNG bytes.
132 ///
133 /// # Errors
134 ///
135 /// Propagates renderer failures.
136 #[cfg(feature = "png")]
137 pub fn to_png(&self, options: &RenderOptions) -> Result<alloc::vec::Vec<u8>> {
138 self.render(&crate::render::Png, options)
139 }
140
141 /// Render to an SVG document.
142 ///
143 /// # Errors
144 ///
145 /// Propagates renderer failures.
146 #[cfg(feature = "svg")]
147 pub fn to_svg(&self, options: &RenderOptions) -> Result<alloc::string::String> {
148 self.render(&crate::render::Svg, options)
149 }
150
151 /// Render to PNG and write it to `path`.
152 ///
153 /// # Errors
154 ///
155 /// Propagates renderer failures and [`Error::Io`](crate::Error::Io).
156 #[cfg(all(feature = "png", feature = "std"))]
157 pub fn to_png_file(
158 &self,
159 path: impl AsRef<std::path::Path>,
160 options: &RenderOptions,
161 ) -> Result<()> {
162 std::fs::write(path, self.to_png(options)?)?;
163 Ok(())
164 }
165
166 /// Render to SVG and write it to `path`.
167 ///
168 /// # Errors
169 ///
170 /// Propagates renderer failures and [`Error::Io`](crate::Error::Io).
171 #[cfg(all(feature = "svg", feature = "std"))]
172 pub fn to_svg_file(
173 &self,
174 path: impl AsRef<std::path::Path>,
175 options: &RenderOptions,
176 ) -> Result<()> {
177 std::fs::write(path, self.to_svg(options)?)?;
178 Ok(())
179 }
180
181 /// Decode the barcode back into its payload from the module grid.
182 ///
183 /// This does not simply return the stored payload: it reconstructs bar and
184 /// space runs from the rendered module pattern and decodes those. A
185 /// successful round trip is therefore evidence that the encoded geometry
186 /// is correct.
187 ///
188 /// This reads the symbol in memory. To read one out of an image, see
189 /// [`scan`](crate::scan).
190 ///
191 /// # Errors
192 ///
193 /// Returns [`Error::Decode`](crate::Error::Decode) if this is not a
194 /// Code 128 barcode, or if the pattern is not valid Code 128. There is no
195 /// QR decoder in this crate.
196 #[cfg(feature = "code128")]
197 pub fn decode(&self) -> Result<alloc::string::String> {
198 crate::symbology::code128::decode(&self.symbol)
199 }
200}
201
202#[cfg(all(test, feature = "code128"))]
203mod tests {
204 use super::*;
205 use crate::TrackingId;
206
207 #[test]
208 fn encodes_a_tracking_id_by_reference_or_value() {
209 let id = TrackingId::parse("PKG-9ED9285C").unwrap();
210 let a = Barcode::code128(&id).unwrap();
211 let b = Barcode::code128("PKG-9ED9285C").unwrap();
212 let c = Barcode::code128(id.as_str()).unwrap();
213 assert_eq!(a, b);
214 assert_eq!(b, c);
215 assert_eq!(a.payload(), "PKG-9ED9285C");
216 assert_eq!(a.kind(), SymbologyKind::Code128);
217 }
218
219 #[test]
220 fn round_trips_through_the_module_grid() {
221 let barcode = Barcode::code128("PKG-9ED9285C").unwrap();
222 assert_eq!(barcode.decode().unwrap(), "PKG-9ED9285C");
223 }
224
225 #[test]
226 #[cfg(all(feature = "png", feature = "svg"))]
227 fn renders_both_formats_from_one_encode() {
228 let barcode = Barcode::code128("PKG-9ED9285C").unwrap();
229 let options = RenderOptions::default();
230 assert!(!barcode.to_png(&options).unwrap().is_empty());
231 assert!(barcode.to_svg(&options).unwrap().contains("<svg"));
232 }
233
234 #[test]
235 fn from_symbol_preserves_the_symbol() {
236 let symbol = crate::symbology::Code128.encode("PKG-9ED9285C").unwrap();
237 let barcode = Barcode::from_symbol(symbol.clone());
238 assert_eq!(barcode.symbol(), &symbol);
239 }
240
241 #[test]
242 fn rejects_an_empty_payload() {
243 assert!(Barcode::code128("").is_err());
244 }
245}