smart-package-tracker 0.4.0

Generate package tracking IDs, render them as Code 128 barcodes (PNG and SVG), and read them back out of images
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
//! Turning payloads into bit patterns.
//!
//! A symbology is anything that maps a string to a grid of dark and light
//! modules. Linear symbologies such as Code 128 produce a single row; matrix
//! symbologies such as QR produce a square. Both are represented as a
//! [`BitMatrix`] inside a [`Symbol`], which is what the renderers consume.
//!
//! Keeping the renderers on this side of the boundary is what makes new
//! symbologies cheap: adding QR means adding a [`Symbology`] implementation,
//! not touching the PNG or SVG code.

#[cfg(feature = "code128")]
pub mod code128;
#[cfg(feature = "qr")]
pub mod qr;

#[cfg(feature = "code128")]
pub use code128::Code128;
#[cfg(feature = "qr")]
pub use qr::{Ecc, Qr, QrVersion};

use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use core::fmt;

use crate::error::Result;

/// Which symbology produced a [`Symbol`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum SymbologyKind {
    /// Code 128, per ISO/IEC 15417.
    Code128,
    /// QR Code, per ISO/IEC 18004.
    ///
    /// QR Code is a registered trademark of Denso Wave Incorporated.
    Qr,
}

impl SymbologyKind {
    /// Short human-readable name, used in error messages.
    pub fn name(self) -> &'static str {
        match self {
            Self::Code128 => "Code 128",
            Self::Qr => "QR Code",
        }
    }

    /// Whether the symbology encodes data along one axis only.
    ///
    /// Linear symbologies take their height from
    /// [`RenderOptions`](crate::RenderOptions); matrix symbologies derive it
    /// from the module grid.
    pub fn is_linear(self) -> bool {
        match self {
            Self::Code128 => true,
            Self::Qr => false,
        }
    }

    /// How this symbology groups elements into fixed-width characters, if it
    /// is linear.
    ///
    /// Returns `None` for matrix symbologies, which have no scan-line
    /// structure to describe. [`is_linear`](Self::is_linear) and this method
    /// always agree.
    pub fn linear_character(self) -> Option<LinearCharacter> {
        match self {
            // ISO/IEC 15417: every Code 128 character is three bars and three
            // spaces totalling 11 modules; the stop pattern adds a fourth bar
            // and two extra modules.
            Self::Code128 => Some(LinearCharacter {
                elements: 6,
                modules: 11,
                stop_elements: 7,
                stop_modules: 13,
            }),
            Self::Qr => None,
        }
    }

    /// Quiet zone the specification requires, in modules per side.
    ///
    /// Code 128 requires 10 modules; QR requires 4 on all four sides. Getting
    /// this wrong is the single most common cause of barcodes that "look fine
    /// but will not scan".
    pub fn required_quiet_zone(self) -> u32 {
        match self {
            Self::Code128 => 10,
            Self::Qr => 4,
        }
    }
}

impl fmt::Display for SymbologyKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.name())
    }
}

/// How a linear symbology groups bars and spaces into fixed-width characters.
///
/// Every character of a linear symbology occupies a known number of modules
/// spread over a known number of elements — bars and spaces — and the symbol
/// finishes with a wider terminating pattern. A scanner uses this to convert
/// pixel measurements back into modules one character at a time, instead of
/// assuming a single module width holds across the whole symbol. That is what
/// keeps decoding accurate when a label is printed a little off-scale or fed
/// through a scanner at a slight skew.
///
/// This is metadata for the same reason
/// [`required_quiet_zone`](SymbologyKind::required_quiet_zone) is: it lets
/// [`Scanner`](crate::scan::Scanner) stay ignorant of which symbology it is
/// reading, exactly as the renderers are.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct LinearCharacter {
    /// Bars and spaces in one character.
    pub elements: u32,
    /// Modules in one character.
    pub modules: u32,
    /// Bars and spaces in the terminating pattern.
    pub stop_elements: u32,
    /// Modules in the terminating pattern.
    pub stop_modules: u32,
}

/// A rectangular grid of dark (`true`) and light (`false`) modules.
///
/// The `Debug` implementation renders ASCII art, which makes failing tests
/// readable at a glance.
#[derive(Clone, PartialEq, Eq)]
pub struct BitMatrix {
    width: u32,
    height: u32,
    bits: Vec<bool>,
}

impl BitMatrix {
    /// Create an all-light matrix.
    ///
    /// # Panics
    ///
    /// Panics if `width` or `height` is zero, which no symbology should ever
    /// produce.
    pub fn new(width: u32, height: u32) -> Self {
        assert!(
            width > 0 && height > 0,
            "a symbol must have a positive size"
        );
        Self {
            width,
            height,
            bits: vec![false; (width as usize) * (height as usize)],
        }
    }

    /// Build a single-row matrix from a run of modules.
    ///
    /// # Panics
    ///
    /// Panics if `row` is empty, which no symbology should ever produce. Use
    /// [`from_vec`](Self::from_vec) to get an [`Option`] instead.
    pub fn from_row(row: Vec<bool>) -> Self {
        assert!(!row.is_empty(), "a symbol must have a positive size");
        Self {
            width: row.len() as u32,
            height: 1,
            bits: row,
        }
    }

    /// Build a matrix from row-major module data.
    ///
    /// Returns `None` if `bits.len()` is not exactly `width * height`, or if
    /// either dimension is zero.
    pub fn from_vec(width: u32, height: u32, bits: Vec<bool>) -> Option<Self> {
        if width == 0 || height == 0 {
            return None;
        }
        if bits.len() != (width as usize).checked_mul(height as usize)? {
            return None;
        }
        Some(Self {
            width,
            height,
            bits,
        })
    }

    /// Width in modules.
    pub fn width(&self) -> u32 {
        self.width
    }

    /// Height in modules.
    pub fn height(&self) -> u32 {
        self.height
    }

    /// Whether the module at `(x, y)` is dark. Out-of-bounds reads as light.
    pub fn get(&self, x: u32, y: u32) -> bool {
        if x >= self.width || y >= self.height {
            return false;
        }
        self.bits[(y as usize) * (self.width as usize) + (x as usize)]
    }

    /// Set the module at `(x, y)`. Out-of-bounds writes are ignored.
    pub fn set(&mut self, x: u32, y: u32, dark: bool) {
        if x >= self.width || y >= self.height {
            return;
        }
        let w = self.width as usize;
        self.bits[(y as usize) * w + (x as usize)] = dark;
    }

    /// One row of modules. An out-of-bounds row reads as empty, matching the
    /// way [`get`](Self::get) and [`set`](Self::set) tolerate out-of-bounds
    /// coordinates.
    pub fn row(&self, y: u32) -> &[bool] {
        if y >= self.height {
            return &[];
        }
        let w = self.width as usize;
        let start = (y as usize) * w;
        &self.bits[start..start + w]
    }
}

impl fmt::Debug for BitMatrix {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        writeln!(f, "BitMatrix {}x{}", self.width, self.height)?;
        for y in 0..self.height {
            for &dark in self.row(y) {
                f.write_str(if dark { "#" } else { "." })?;
            }
            writeln!(f)?;
        }
        Ok(())
    }
}

/// An encoded symbol: the module grid plus what it encodes.
///
/// The grid contains the symbol only. Quiet zones are a rendering concern and
/// are added by the renderers according to
/// [`RenderOptions`](crate::RenderOptions), so that the same `Symbol` can be
/// drawn with specification-conformant or deliberately tighter margins.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Symbol {
    kind: SymbologyKind,
    modules: BitMatrix,
    payload: String,
}

impl Symbol {
    /// Construct a symbol. Intended for [`Symbology`] implementations.
    pub fn new(kind: SymbologyKind, modules: BitMatrix, payload: String) -> Self {
        Self {
            kind,
            modules,
            payload,
        }
    }

    /// Which symbology produced this symbol.
    pub fn kind(&self) -> SymbologyKind {
        self.kind
    }

    /// The module grid, excluding quiet zones.
    pub fn modules(&self) -> &BitMatrix {
        &self.modules
    }

    /// The payload this symbol encodes.
    pub fn payload(&self) -> &str {
        &self.payload
    }

    /// Whether this symbol encodes data along one axis only.
    pub fn is_linear(&self) -> bool {
        self.kind.is_linear()
    }
}

/// Maps a payload to a [`Symbol`].
///
/// Implement this to add a symbology. Renderers work against `Symbol`, so an
/// implementation is all that a new barcode format requires.
pub trait Symbology {
    /// Which symbology this is.
    fn kind(&self) -> SymbologyKind;

    /// Encode `data`.
    ///
    /// # Errors
    ///
    /// Returns [`Error::EmptyPayload`](crate::Error::EmptyPayload) for an
    /// empty payload, or [`Error::Unencodable`](crate::Error::Unencodable) if
    /// the payload contains characters this symbology cannot represent.
    fn encode(&self, data: &str) -> Result<Symbol>;
}

/// Recovers a payload from a module grid.
///
/// The mirror of [`Symbology`]: an encoder turns a payload into a
/// [`BitMatrix`], a decoder turns one back.
/// [`Scanner`](crate::scan::Scanner) works against this trait rather than
/// against a concrete symbology, which is what keeps the scanning side of the
/// crate on the same seam as the rendering side.
pub trait Decoder {
    /// Which symbology this decodes.
    fn kind(&self) -> SymbologyKind;

    /// Decode `modules`, which must hold the symbol alone, with no quiet zone.
    ///
    /// # Errors
    ///
    /// Returns [`Error::Decode`](crate::Error::Decode) if the grid has the
    /// wrong shape for this symbology, or if the module pattern is not a valid
    /// symbol.
    fn decode(&self, modules: &BitMatrix) -> Result<String>;
}

#[cfg(test)]
mod tests {
    use super::*;
    use alloc::format;

    #[test]
    fn matrix_reads_and_writes() {
        let mut m = BitMatrix::new(3, 2);
        assert!(!m.get(0, 0));
        m.set(2, 1, true);
        assert!(m.get(2, 1));
        assert_eq!(m.row(1), &[false, false, true]);
    }

    #[test]
    fn matrix_ignores_out_of_bounds_access() {
        let mut m = BitMatrix::new(2, 2);
        m.set(9, 9, true); // must not panic
        assert!(!m.get(9, 9));
    }

    #[test]
    fn matrix_reads_out_of_bounds_rows_as_empty() {
        // `get` and `set` tolerate out-of-range coordinates, so `row` must too
        // rather than panicking on a slice range — this is public API, and the
        // crate promises not to panic on bad input.
        let m = BitMatrix::new(3, 2);
        assert_eq!(m.row(0).len(), 3);
        assert_eq!(m.row(1).len(), 3);
        assert!(m.row(2).is_empty());
        assert!(m.row(u32::MAX).is_empty());
    }

    #[test]
    fn debug_renders_ascii_art() {
        let m = BitMatrix::from_row(vec![true, false, true]);
        assert!(format!("{m:?}").contains("#.#"));
    }

    #[test]
    fn linearity_and_character_structure_agree() {
        // Two pieces of metadata that have to say the same thing. A scanner
        // asks for the character structure; a renderer asks whether the
        // symbology is linear. If they ever disagree, one of them silently
        // stops working.
        for kind in [SymbologyKind::Code128, SymbologyKind::Qr] {
            assert_eq!(
                kind.is_linear(),
                kind.linear_character().is_some(),
                "{kind} disagrees with itself about being linear"
            );
        }
    }

    #[test]
    fn a_linear_character_is_wider_than_its_element_count() {
        // Every element needs at least one module, or a scanner cannot
        // apportion a character's width without losing a bar.
        for kind in [SymbologyKind::Code128, SymbologyKind::Qr] {
            let Some(c) = kind.linear_character() else {
                continue;
            };
            assert!(c.modules >= c.elements, "{kind}: character too narrow");
            assert!(
                c.stop_modules >= c.stop_elements,
                "{kind}: stop pattern too narrow"
            );
        }
    }

    #[test]
    fn code128_requires_a_ten_module_quiet_zone() {
        assert_eq!(SymbologyKind::Code128.required_quiet_zone(), 10);
        assert!(SymbologyKind::Code128.is_linear());
    }
}