Skip to main content

qrcode_core/
traits.rs

1//! Core extension traits for encoding, rendering, and module-grid storage.
2//!
3//! These traits are intentionally small so the facade crate and future split
4//! crates can share the same abstraction layer without pulling renderer or image
5//! dependencies into `qrcode-core`.
6
7use crate::types::{Color, EcLevel, Version};
8
9/// Borrowed row-major view over a read-only QR module grid.
10///
11/// `ModuleView` is useful for adapters and tests that already have a module
12/// slice and need to pass it through the shared [`ModuleSource`] abstraction
13/// without allocating or implementing a bespoke wrapper type.
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub struct ModuleView<'a> {
16    modules: &'a [Color],
17    width: usize,
18    height: usize,
19}
20
21impl<'a> ModuleView<'a> {
22    /// Creates a square module view from a row-major module slice.
23    ///
24    /// Returns `None` when `width == 0` or `modules.len() != width * width`.
25    #[must_use]
26    pub const fn new(modules: &'a [Color], width: usize) -> Option<Self> {
27        Self::new_rect(modules, width, width)
28    }
29
30    /// Creates a rectangular module view from a row-major module slice.
31    ///
32    /// This keeps the same zero-copy storage contract as [`ModuleView::new`],
33    /// but allows callers to borrow a contiguous range of full rows.
34    ///
35    /// Returns `None` when either dimension is zero or when
36    /// `modules.len() != width * height`.
37    #[must_use]
38    pub const fn new_rect(modules: &'a [Color], width: usize, height: usize) -> Option<Self> {
39        if !has_valid_module_geometry(modules.len(), width, height) {
40            return None;
41        }
42        Some(Self { modules, width, height })
43    }
44
45    /// Returns a zero-copy view over a contiguous range of full rows.
46    ///
47    /// The returned view keeps the same width and borrows a sub-slice of the
48    /// original row-major module slice.
49    #[must_use]
50    pub fn row_range(&self, start: usize, end: usize) -> Option<Self> {
51        if start >= end || end > self.height {
52            return None;
53        }
54        let height = end - start;
55        let start = start.checked_mul(self.width)?;
56        let end = end.checked_mul(self.width)?;
57        Some(Self { modules: self.modules.get(start..end)?, width: self.width, height })
58    }
59}
60
61const fn has_valid_module_geometry(len: usize, width: usize, height: usize) -> bool {
62    if width == 0 || height == 0 {
63        return false;
64    }
65    match width.checked_mul(height) {
66        Some(expected) => len == expected,
67        None => false,
68    }
69}
70
71impl ModuleSource for ModuleView<'_> {
72    fn get(&self, x: usize, y: usize) -> Color {
73        self.modules[y * self.width + x]
74    }
75
76    fn width(&self) -> usize {
77        self.width
78    }
79
80    fn height(&self) -> usize {
81        self.height
82    }
83
84    fn modules(&self) -> &[Color] {
85        self.modules
86    }
87}
88
89/// Borrowed QR symbol with module-grid data and QR metadata.
90///
91/// This is the zero-copy counterpart to an owned QR code. It carries the same
92/// metadata expected by [`QrSymbol`] while borrowing the row-major module slice
93/// from an existing symbol.
94#[derive(Clone, Copy, Debug, PartialEq, Eq)]
95pub struct QrCodeRef<'a> {
96    modules: &'a [Color],
97    version: Version,
98    ec_level: EcLevel,
99    width: usize,
100}
101
102impl<'a> QrCodeRef<'a> {
103    /// Creates a borrowed QR symbol from row-major module data.
104    ///
105    /// Returns `None` when `width == 0` or `modules.len() != width * width`.
106    #[must_use]
107    pub const fn new(modules: &'a [Color], width: usize, version: Version, ec_level: EcLevel) -> Option<Self> {
108        if !has_valid_module_geometry(modules.len(), width, width) {
109            return None;
110        }
111        Some(Self { modules, version, ec_level, width })
112    }
113
114    /// Returns a read-only module view over the same borrowed modules.
115    #[must_use]
116    pub const fn module_view(self) -> ModuleView<'a> {
117        ModuleView { modules: self.modules, width: self.width, height: self.width }
118    }
119}
120
121impl ModuleSource for QrCodeRef<'_> {
122    fn get(&self, x: usize, y: usize) -> Color {
123        self.modules[y * self.width + x]
124    }
125
126    fn width(&self) -> usize {
127        self.width
128    }
129
130    fn height(&self) -> usize {
131        self.width
132    }
133
134    fn modules(&self) -> &[Color] {
135        self.modules
136    }
137}
138
139impl QrSymbol for QrCodeRef<'_> {
140    fn version(&self) -> Version {
141        self.version
142    }
143
144    fn error_correction_level(&self) -> EcLevel {
145        self.ec_level
146    }
147}
148
149/// Encodes raw input bytes into a concrete output type.
150///
151/// Implementations can produce a full QR code, an intermediate bit stream, or a
152/// third-party symbol type. The input is borrowed to keep the trait usable in
153/// `no_std + alloc` environments without requiring an owned buffer.
154pub trait Encoder {
155    /// The successful encoding output.
156    type Output;
157
158    /// The encoding error type.
159    type Error;
160
161    /// Encodes `input`.
162    ///
163    /// # Errors
164    ///
165    /// Returns [`Self::Error`] when the implementation cannot encode the input.
166    fn encode(&self, input: &[u8]) -> Result<Self::Output, Self::Error>;
167}
168
169/// Builds a configured value into its final output.
170///
171/// This small trait gives encoders, renderers, and future plugin factories a
172/// shared builder contract without forcing them into one concrete builder type.
173pub trait Builder {
174    /// The successfully built value.
175    type Output;
176
177    /// The build error type.
178    type Error;
179
180    /// Consumes the builder and returns its output.
181    ///
182    /// # Errors
183    ///
184    /// Returns [`Self::Error`] when the configured value cannot be built.
185    fn build(self) -> Result<Self::Output, Self::Error>;
186}
187
188/// Renders a module-grid source into a concrete output type.
189///
190/// The `Code` parameter is usually a type implementing [`ModuleSource`], such
191/// as the facade crate's `QrCode`, but may also be a third-party borrowed view.
192pub trait Renderer<Code: ModuleSource + ?Sized> {
193    /// The rendered output.
194    type Output;
195
196    /// The rendering error type.
197    type Error;
198
199    /// Renders `code`.
200    ///
201    /// # Errors
202    ///
203    /// Returns [`Self::Error`] when rendering fails.
204    fn render(&self, code: &Code) -> Result<Self::Output, Self::Error>;
205}
206
207/// Read-only access to a QR module grid.
208///
209/// Coordinates are zero-based and exclude any quiet zone. Implementations should
210/// store modules in row-major order when exposing [`modules`](Self::modules).
211pub trait ModuleSource {
212    /// Returns the color at `(x, y)`.
213    ///
214    /// # Panics
215    ///
216    /// Implementations may panic when `x >= width()` or `y >= height()`.
217    fn get(&self, x: usize, y: usize) -> Color;
218
219    /// Returns the number of modules per row.
220    fn width(&self) -> usize;
221
222    /// Returns the number of module rows.
223    fn height(&self) -> usize;
224
225    /// Returns all modules in row-major order.
226    fn modules(&self) -> &[Color];
227
228    /// Returns row `y` as a contiguous row-major slice.
229    ///
230    /// # Panics
231    ///
232    /// Panics when `y >= height()` or when [`modules`](Self::modules) does not
233    /// contain a complete row-major grid.
234    fn row(&self, y: usize) -> &[Color] {
235        let width = self.width();
236        let start = y * width;
237        &self.modules()[start..start + width]
238    }
239
240    /// Returns whether this storage has no modules.
241    fn is_empty(&self) -> bool {
242        self.width() == 0 || self.height() == 0
243    }
244}
245
246/// Read-only QR symbol metadata plus module-grid access.
247///
248/// `QrSymbol` is the higher-level counterpart to [`ModuleSource`]: renderers
249/// and adapters can use it when they need both the module grid and QR-specific
250/// metadata such as [`Version`] and [`EcLevel`].
251pub trait QrSymbol: ModuleSource {
252    /// Returns the encoded QR or Micro QR version.
253    fn version(&self) -> Version;
254
255    /// Returns the encoded error-correction level.
256    fn error_correction_level(&self) -> EcLevel;
257
258    /// Returns the default quiet-zone width in modules for this symbol.
259    ///
260    /// Normal QR symbols use four modules. Micro QR symbols use two modules.
261    fn quiet_zone(&self) -> u32 {
262        if self.version().is_micro() { 2 } else { 4 }
263    }
264}
265
266/// Read/write access to a QR module grid.
267///
268/// Rendering and inspection APIs should prefer [`ModuleSource`] when they only
269/// need read access. This trait remains available for in-place mutation and
270/// testing utilities.
271pub trait ModuleStorage {
272    /// Returns the color at `(x, y)`.
273    ///
274    /// # Panics
275    ///
276    /// Implementations may panic when `x >= width()` or `y >= height()`.
277    fn get(&self, x: usize, y: usize) -> Color;
278
279    /// Sets the color at `(x, y)`.
280    ///
281    /// # Panics
282    ///
283    /// Implementations may panic when `x >= width()` or `y >= height()`.
284    fn set(&mut self, x: usize, y: usize, color: Color);
285
286    /// Returns the number of modules per row.
287    fn width(&self) -> usize;
288
289    /// Returns the number of module rows.
290    fn height(&self) -> usize;
291
292    /// Returns all modules in row-major order.
293    fn modules(&self) -> &[Color];
294
295    /// Returns whether this storage has no modules.
296    fn is_empty(&self) -> bool {
297        self.width() == 0 || self.height() == 0
298    }
299}
300
301impl<T: ModuleStorage + ?Sized> ModuleSource for T {
302    fn get(&self, x: usize, y: usize) -> Color {
303        ModuleStorage::get(self, x, y)
304    }
305
306    fn width(&self) -> usize {
307        ModuleStorage::width(self)
308    }
309
310    fn height(&self) -> usize {
311        ModuleStorage::height(self)
312    }
313
314    fn modules(&self) -> &[Color] {
315        ModuleStorage::modules(self)
316    }
317
318    fn is_empty(&self) -> bool {
319        ModuleStorage::is_empty(self)
320    }
321}
322
323#[cfg(test)]
324mod tests {
325    use super::{Builder, Encoder, ModuleSource, ModuleStorage, ModuleView, QrCodeRef, QrSymbol, Renderer};
326    use crate::{Color, EcLevel, Version};
327    use core::convert::Infallible;
328
329    struct DummySymbol {
330        version: Version,
331        modules: [Color; 1],
332    }
333
334    impl ModuleSource for DummySymbol {
335        fn get(&self, _x: usize, _y: usize) -> Color {
336            self.modules[0]
337        }
338
339        fn width(&self) -> usize {
340            1
341        }
342
343        fn height(&self) -> usize {
344            1
345        }
346
347        fn modules(&self) -> &[Color] {
348            &self.modules
349        }
350    }
351
352    impl QrSymbol for DummySymbol {
353        fn version(&self) -> Version {
354            self.version
355        }
356
357        fn error_correction_level(&self) -> EcLevel {
358            EcLevel::M
359        }
360    }
361
362    struct DummyBuilder {
363        value: u8,
364    }
365
366    impl Builder for DummyBuilder {
367        type Output = u8;
368        type Error = ();
369
370        fn build(self) -> Result<Self::Output, Self::Error> {
371            Ok(self.value)
372        }
373    }
374
375    struct DummyEncoder;
376
377    impl Encoder for DummyEncoder {
378        type Output = usize;
379        type Error = Infallible;
380
381        fn encode(&self, input: &[u8]) -> Result<Self::Output, Self::Error> {
382            Ok(input.len())
383        }
384    }
385
386    struct DummyRenderer {
387        dark: char,
388        light: char,
389    }
390
391    impl<C: ModuleSource + ?Sized> Renderer<C> for DummyRenderer {
392        type Output = String;
393        type Error = Infallible;
394
395        fn render(&self, code: &C) -> Result<Self::Output, Self::Error> {
396            let mut out = String::new();
397            for y in 0..code.height() {
398                for x in 0..code.width() {
399                    out.push(match code.get(x, y) {
400                        Color::Dark => self.dark,
401                        Color::Light => self.light,
402                    });
403                }
404            }
405            Ok(out)
406        }
407    }
408
409    struct DummyStorage {
410        modules: [Color; 4],
411        width: usize,
412    }
413
414    impl ModuleStorage for DummyStorage {
415        fn get(&self, x: usize, y: usize) -> Color {
416            self.modules[y * self.width + x]
417        }
418
419        fn set(&mut self, x: usize, y: usize, color: Color) {
420            self.modules[y * self.width + x] = color;
421        }
422
423        fn width(&self) -> usize {
424            self.width
425        }
426
427        fn height(&self) -> usize {
428            self.modules.len() / self.width
429        }
430
431        fn modules(&self) -> &[Color] {
432            &self.modules
433        }
434    }
435
436    #[test]
437    fn module_view_reads_row_major_modules() {
438        let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
439        let view = ModuleView::new(&modules, 2).unwrap();
440
441        assert_eq!(view.width(), 2);
442        assert_eq!(view.height(), 2);
443        assert_eq!(view.modules(), modules);
444        assert_eq!(view.row(1), &[Color::Light, Color::Dark]);
445        assert_eq!(view.get(0, 0), Color::Dark);
446        assert_eq!(view.get(1, 1), Color::Dark);
447    }
448
449    #[test]
450    fn module_view_reads_rectangular_row_major_modules() {
451        let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark, Color::Dark, Color::Light];
452        let view = ModuleView::new_rect(&modules, 3, 2).unwrap();
453
454        assert_eq!(view.width(), 3);
455        assert_eq!(view.height(), 2);
456        assert_eq!(view.row(1), &[Color::Dark, Color::Dark, Color::Light]);
457    }
458
459    #[test]
460    fn module_view_row_range_borrows_contiguous_rows() {
461        let modules = [
462            Color::Dark,
463            Color::Light,
464            Color::Light,
465            Color::Dark,
466            Color::Dark,
467            Color::Light,
468            Color::Light,
469            Color::Dark,
470            Color::Dark,
471        ];
472        let view = ModuleView::new(&modules, 3).unwrap();
473        let rows = view.row_range(1, 3).unwrap();
474
475        assert_eq!(rows.width(), 3);
476        assert_eq!(rows.height(), 2);
477        assert_eq!(rows.modules(), &modules[3..9]);
478        assert!(view.row_range(2, 2).is_none());
479        assert!(view.row_range(2, 4).is_none());
480    }
481
482    #[test]
483    fn module_view_rejects_non_square_input() {
484        let modules = [Color::Dark, Color::Light, Color::Dark];
485
486        assert!(ModuleView::new(&modules, 2).is_none());
487        assert!(ModuleView::new(&modules, 0).is_none());
488    }
489
490    #[test]
491    fn module_view_rejects_invalid_rectangular_input() {
492        let modules = [Color::Dark, Color::Light, Color::Dark];
493
494        assert!(ModuleView::new_rect(&modules, 2, 2).is_none());
495        assert!(ModuleView::new_rect(&modules, 0, 2).is_none());
496        assert!(ModuleView::new_rect(&modules, 2, 0).is_none());
497    }
498
499    #[test]
500    fn module_views_reject_overflowing_geometry() {
501        assert!(ModuleView::new_rect(&[], usize::MAX, 2).is_none());
502        assert!(QrCodeRef::new(&[], usize::MAX, Version::Normal(1), EcLevel::L).is_none());
503    }
504
505    #[test]
506    fn qr_code_ref_exposes_borrowed_symbol_metadata() {
507        let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
508        let symbol = QrCodeRef::new(&modules, 2, Version::Normal(3), EcLevel::Q).unwrap();
509
510        assert_eq!(symbol.width(), 2);
511        assert_eq!(symbol.height(), 2);
512        assert_eq!(symbol.modules(), modules);
513        assert_eq!(symbol.get(0, 0), Color::Dark);
514        assert_eq!(symbol.version(), Version::Normal(3));
515        assert_eq!(symbol.error_correction_level(), EcLevel::Q);
516        assert_eq!(symbol.quiet_zone(), 4);
517    }
518
519    #[test]
520    fn qr_code_ref_rejects_invalid_module_geometry() {
521        let modules = [Color::Dark, Color::Light, Color::Dark];
522
523        assert!(QrCodeRef::new(&modules, 2, Version::Normal(1), EcLevel::M).is_none());
524        assert!(QrCodeRef::new(&modules, 0, Version::Normal(1), EcLevel::M).is_none());
525    }
526
527    #[test]
528    fn qr_code_ref_module_view_reuses_borrowed_modules() {
529        let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
530        let symbol = QrCodeRef::new(&modules, 2, Version::Micro(1), EcLevel::L).unwrap();
531        let view = symbol.module_view();
532
533        assert_eq!(view.modules(), modules);
534        assert_eq!(view.get(1, 1), Color::Dark);
535        assert_eq!(symbol.quiet_zone(), 2);
536    }
537
538    #[test]
539    fn qr_symbol_default_quiet_zone_for_normal_qr_is_four_modules() {
540        let symbol = DummySymbol { version: Version::Normal(1), modules: [Color::Dark] };
541
542        assert_eq!(symbol.quiet_zone(), 4);
543    }
544
545    #[test]
546    fn qr_symbol_default_quiet_zone_for_micro_qr_is_two_modules() {
547        let symbol = DummySymbol { version: Version::Micro(1), modules: [Color::Dark] };
548
549        assert_eq!(symbol.quiet_zone(), 2);
550    }
551
552    #[test]
553    fn builder_trait_builds_configured_output() {
554        let result = DummyBuilder { value: 7 }.build();
555
556        assert_eq!(result, Ok(7));
557    }
558
559    #[test]
560    fn encoder_trait_accepts_third_party_implementations() {
561        let output = DummyEncoder.encode(b"hello").unwrap();
562
563        assert_eq!(output, 5);
564    }
565
566    #[test]
567    fn renderer_trait_accepts_third_party_implementations() {
568        let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
569        let view = ModuleView::new(&modules, 2).unwrap();
570        let renderer = DummyRenderer { dark: '#', light: '.' };
571
572        assert_eq!(renderer.render(&view).unwrap(), "#..#");
573    }
574
575    #[test]
576    fn module_storage_blanket_impl_provides_module_source() {
577        let mut storage = DummyStorage { modules: [Color::Light; 4], width: 2 };
578        storage.set(1, 0, Color::Dark);
579        storage.set(0, 1, Color::Dark);
580
581        assert_eq!(ModuleSource::width(&storage), 2);
582        assert_eq!(ModuleSource::height(&storage), 2);
583        assert_eq!(ModuleSource::get(&storage, 1, 0), Color::Dark);
584        assert_eq!(<DummyStorage as ModuleSource>::row(&storage, 1), &[Color::Dark, Color::Light]);
585        assert_eq!(ModuleSource::modules(&storage), &[Color::Light, Color::Dark, Color::Dark, Color::Light]);
586    }
587}