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        assert!(x < self.width && y < self.height, "module coordinates out of bounds");
74        self.modules[y * self.width + x]
75    }
76
77    fn width(&self) -> usize {
78        self.width
79    }
80
81    fn height(&self) -> usize {
82        self.height
83    }
84
85    fn modules(&self) -> &[Color] {
86        self.modules
87    }
88}
89
90/// Borrowed QR symbol with module-grid data and QR metadata.
91///
92/// This is the zero-copy counterpart to an owned QR code. It carries the same
93/// metadata expected by [`QrSymbol`] while borrowing the row-major module slice
94/// from an existing symbol.
95#[derive(Clone, Copy, Debug, PartialEq, Eq)]
96pub struct QrCodeRef<'a> {
97    modules: &'a [Color],
98    version: Version,
99    ec_level: EcLevel,
100    width: usize,
101}
102
103impl<'a> QrCodeRef<'a> {
104    /// Creates a borrowed QR symbol from row-major module data.
105    ///
106    /// Returns `None` when `width == 0` or `modules.len() != width * width`.
107    #[must_use]
108    pub const fn new(modules: &'a [Color], width: usize, version: Version, ec_level: EcLevel) -> Option<Self> {
109        if !has_valid_module_geometry(modules.len(), width, width) {
110            return None;
111        }
112        Some(Self { modules, version, ec_level, width })
113    }
114
115    /// Returns a read-only module view over the same borrowed modules.
116    #[must_use]
117    pub const fn module_view(self) -> ModuleView<'a> {
118        ModuleView { modules: self.modules, width: self.width, height: self.width }
119    }
120}
121
122impl ModuleSource for QrCodeRef<'_> {
123    fn get(&self, x: usize, y: usize) -> Color {
124        assert!(x < self.width && y < self.width, "module coordinates out of bounds");
125        self.modules[y * self.width + x]
126    }
127
128    fn width(&self) -> usize {
129        self.width
130    }
131
132    fn height(&self) -> usize {
133        self.width
134    }
135
136    fn modules(&self) -> &[Color] {
137        self.modules
138    }
139}
140
141impl QrSymbol for QrCodeRef<'_> {
142    fn version(&self) -> Version {
143        self.version
144    }
145
146    fn error_correction_level(&self) -> EcLevel {
147        self.ec_level
148    }
149}
150
151/// Encodes raw input bytes into a concrete output type.
152///
153/// Implementations can produce a full QR code, an intermediate bit stream, or a
154/// third-party symbol type. The input is borrowed to keep the trait usable in
155/// `no_std + alloc` environments without requiring an owned buffer.
156pub trait Encoder {
157    /// The successful encoding output.
158    type Output;
159
160    /// The encoding error type.
161    type Error;
162
163    /// Encodes `input`.
164    ///
165    /// # Errors
166    ///
167    /// Returns [`Self::Error`] when the implementation cannot encode the input.
168    fn encode(&self, input: &[u8]) -> Result<Self::Output, Self::Error>;
169}
170
171/// Builds a configured value into its final output.
172///
173/// This small trait gives encoders, renderers, and future plugin factories a
174/// shared builder contract without forcing them into one concrete builder type.
175pub trait Builder {
176    /// The successfully built value.
177    type Output;
178
179    /// The build error type.
180    type Error;
181
182    /// Consumes the builder and returns its output.
183    ///
184    /// # Errors
185    ///
186    /// Returns [`Self::Error`] when the configured value cannot be built.
187    fn build(self) -> Result<Self::Output, Self::Error>;
188}
189
190/// Renders a module-grid source into a concrete output type.
191///
192/// The `Code` parameter is usually a type implementing [`ModuleSource`], such
193/// as the facade crate's `QrCode`, but may also be a third-party borrowed view.
194pub trait Renderer<Code: ModuleSource + ?Sized> {
195    /// The rendered output.
196    type Output;
197
198    /// The rendering error type.
199    type Error;
200
201    /// Renders `code`.
202    ///
203    /// # Errors
204    ///
205    /// Returns [`Self::Error`] when rendering fails.
206    fn render(&self, code: &Code) -> Result<Self::Output, Self::Error>;
207}
208
209/// Read-only access to a QR module grid.
210///
211/// Coordinates are zero-based and exclude any quiet zone. Implementations should
212/// store modules in row-major order when exposing [`modules`](Self::modules).
213pub trait ModuleSource {
214    /// Returns the color at `(x, y)`.
215    ///
216    /// # Panics
217    ///
218    /// Implementations may panic when `x >= width()` or `y >= height()`.
219    fn get(&self, x: usize, y: usize) -> Color;
220
221    /// Returns the number of modules per row.
222    fn width(&self) -> usize;
223
224    /// Returns the number of module rows.
225    fn height(&self) -> usize;
226
227    /// Returns all modules in row-major order.
228    fn modules(&self) -> &[Color];
229
230    /// Returns row `y` as a contiguous row-major slice.
231    ///
232    /// # Panics
233    ///
234    /// Panics when `y >= height()`, when row offset arithmetic overflows, or
235    /// when [`modules`](Self::modules) does not contain the requested row.
236    fn row(&self, y: usize) -> &[Color] {
237        let width = self.width();
238        assert!(y < self.height(), "module row out of bounds");
239        let start = y.checked_mul(width).expect("module row offset overflow");
240        let end = start.checked_add(width).expect("module row end overflow");
241        &self.modules()[start..end]
242    }
243
244    /// Returns whether this storage has no modules.
245    fn is_empty(&self) -> bool {
246        self.width() == 0 || self.height() == 0
247    }
248}
249
250/// Read-only QR symbol metadata plus module-grid access.
251///
252/// `QrSymbol` is the higher-level counterpart to [`ModuleSource`]: renderers
253/// and adapters can use it when they need both the module grid and QR-specific
254/// metadata such as [`Version`] and [`EcLevel`].
255pub trait QrSymbol: ModuleSource {
256    /// Returns the encoded QR or Micro QR version.
257    fn version(&self) -> Version;
258
259    /// Returns the encoded error-correction level.
260    fn error_correction_level(&self) -> EcLevel;
261
262    /// Returns the default quiet-zone width in modules for this symbol.
263    ///
264    /// Normal QR symbols use four modules. Micro QR symbols use two modules.
265    fn quiet_zone(&self) -> u32 {
266        if self.version().is_micro() { 2 } else { 4 }
267    }
268}
269
270/// Read/write access to a QR module grid.
271///
272/// Rendering and inspection APIs should prefer [`ModuleSource`] when they only
273/// need read access. This trait remains available for in-place mutation and
274/// testing utilities.
275pub trait ModuleStorage {
276    /// Returns the color at `(x, y)`.
277    ///
278    /// # Panics
279    ///
280    /// Implementations may panic when `x >= width()` or `y >= height()`.
281    fn get(&self, x: usize, y: usize) -> Color;
282
283    /// Sets the color at `(x, y)`.
284    ///
285    /// # Panics
286    ///
287    /// Implementations may panic when `x >= width()` or `y >= height()`.
288    fn set(&mut self, x: usize, y: usize, color: Color);
289
290    /// Returns the number of modules per row.
291    fn width(&self) -> usize;
292
293    /// Returns the number of module rows.
294    fn height(&self) -> usize;
295
296    /// Returns all modules in row-major order.
297    fn modules(&self) -> &[Color];
298
299    /// Returns whether this storage has no modules.
300    fn is_empty(&self) -> bool {
301        self.width() == 0 || self.height() == 0
302    }
303}
304
305impl<T: ModuleStorage + ?Sized> ModuleSource for T {
306    fn get(&self, x: usize, y: usize) -> Color {
307        ModuleStorage::get(self, x, y)
308    }
309
310    fn width(&self) -> usize {
311        ModuleStorage::width(self)
312    }
313
314    fn height(&self) -> usize {
315        ModuleStorage::height(self)
316    }
317
318    fn modules(&self) -> &[Color] {
319        ModuleStorage::modules(self)
320    }
321
322    fn is_empty(&self) -> bool {
323        ModuleStorage::is_empty(self)
324    }
325}
326
327#[cfg(test)]
328mod tests {
329    use super::{Builder, Encoder, ModuleSource, ModuleStorage, ModuleView, QrCodeRef, QrSymbol, Renderer};
330    use crate::{Color, EcLevel, Version};
331    use core::convert::Infallible;
332    use std::panic::catch_unwind;
333
334    struct RowSource<'a> {
335        modules: &'a [Color],
336        width: usize,
337        height: usize,
338    }
339
340    impl ModuleSource for RowSource<'_> {
341        fn get(&self, x: usize, y: usize) -> Color {
342            self.modules[y * self.width + x]
343        }
344
345        fn width(&self) -> usize {
346            self.width
347        }
348
349        fn height(&self) -> usize {
350            self.height
351        }
352
353        fn modules(&self) -> &[Color] {
354            self.modules
355        }
356    }
357
358    struct DummySymbol {
359        version: Version,
360        modules: [Color; 1],
361    }
362
363    impl ModuleSource for DummySymbol {
364        fn get(&self, _x: usize, _y: usize) -> Color {
365            self.modules[0]
366        }
367
368        fn width(&self) -> usize {
369            1
370        }
371
372        fn height(&self) -> usize {
373            1
374        }
375
376        fn modules(&self) -> &[Color] {
377            &self.modules
378        }
379    }
380
381    impl QrSymbol for DummySymbol {
382        fn version(&self) -> Version {
383            self.version
384        }
385
386        fn error_correction_level(&self) -> EcLevel {
387            EcLevel::M
388        }
389    }
390
391    struct DummyBuilder {
392        value: u8,
393    }
394
395    impl Builder for DummyBuilder {
396        type Output = u8;
397        type Error = ();
398
399        fn build(self) -> Result<Self::Output, Self::Error> {
400            Ok(self.value)
401        }
402    }
403
404    struct DummyEncoder;
405
406    impl Encoder for DummyEncoder {
407        type Output = usize;
408        type Error = Infallible;
409
410        fn encode(&self, input: &[u8]) -> Result<Self::Output, Self::Error> {
411            Ok(input.len())
412        }
413    }
414
415    struct DummyRenderer {
416        dark: char,
417        light: char,
418    }
419
420    impl<C: ModuleSource + ?Sized> Renderer<C> for DummyRenderer {
421        type Output = String;
422        type Error = Infallible;
423
424        fn render(&self, code: &C) -> Result<Self::Output, Self::Error> {
425            let mut out = String::new();
426            for y in 0..code.height() {
427                for x in 0..code.width() {
428                    out.push(match code.get(x, y) {
429                        Color::Dark => self.dark,
430                        Color::Light => self.light,
431                    });
432                }
433            }
434            Ok(out)
435        }
436    }
437
438    struct DummyStorage {
439        modules: [Color; 4],
440        width: usize,
441    }
442
443    impl ModuleStorage for DummyStorage {
444        fn get(&self, x: usize, y: usize) -> Color {
445            self.modules[y * self.width + x]
446        }
447
448        fn set(&mut self, x: usize, y: usize, color: Color) {
449            self.modules[y * self.width + x] = color;
450        }
451
452        fn width(&self) -> usize {
453            self.width
454        }
455
456        fn height(&self) -> usize {
457            self.modules.len() / self.width
458        }
459
460        fn modules(&self) -> &[Color] {
461            &self.modules
462        }
463    }
464
465    #[test]
466    fn module_view_reads_row_major_modules() {
467        let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
468        let view = ModuleView::new(&modules, 2).unwrap();
469
470        assert_eq!(view.width(), 2);
471        assert_eq!(view.height(), 2);
472        assert_eq!(view.modules(), modules);
473        assert_eq!(view.row(1), &[Color::Light, Color::Dark]);
474        assert_eq!(view.get(0, 0), Color::Dark);
475        assert_eq!(view.get(1, 1), Color::Dark);
476    }
477
478    #[test]
479    fn module_view_reads_rectangular_row_major_modules() {
480        let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark, Color::Dark, Color::Light];
481        let view = ModuleView::new_rect(&modules, 3, 2).unwrap();
482
483        assert_eq!(view.width(), 3);
484        assert_eq!(view.height(), 2);
485        assert_eq!(view.row(1), &[Color::Dark, Color::Dark, Color::Light]);
486    }
487
488    #[test]
489    fn module_view_coordinates_do_not_alias_another_row() {
490        for (width, height) in [(3_usize, 2_usize), (1, 3), (3, 1), (2, 2)] {
491            let modules = (0..width * height)
492                .map(|index| if index.is_multiple_of(2) { Color::Dark } else { Color::Light })
493                .collect::<Vec<_>>();
494            let view = ModuleView::new_rect(&modules, width, height).unwrap();
495            for y in 0..height {
496                for x in 0..width {
497                    assert_eq!(view.get(x, y), modules[y * width + x]);
498                }
499            }
500            for (x, y) in [(width, 0), (0, height), (usize::MAX, 0), (0, usize::MAX), (usize::MAX, usize::MAX)] {
501                assert!(catch_unwind(|| view.get(x, y)).is_err(), "{width}x{height}, ({x}, {y})");
502            }
503        }
504    }
505
506    #[test]
507    fn default_row_checks_reported_height_even_with_extra_backing_rows() {
508        let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
509        let source = RowSource { modules: &modules, width: 2, height: 1 };
510
511        assert_eq!(source.row(0), &modules[..2]);
512        assert!(catch_unwind(|| source.row(1)).is_err());
513        assert!(catch_unwind(|| source.row(usize::MAX)).is_err());
514    }
515
516    #[test]
517    fn default_row_rejects_offset_and_end_overflow() {
518        let modules = [Color::Dark];
519        let offset_overflow = RowSource { modules: &modules, width: 2, height: usize::MAX };
520        let end_overflow = RowSource { modules: &modules, width: usize::MAX, height: 2 };
521
522        assert!(catch_unwind(|| offset_overflow.row(usize::MAX / 2 + 1)).is_err());
523        assert!(catch_unwind(|| end_overflow.row(1)).is_err());
524    }
525
526    #[test]
527    fn default_row_only_requires_the_requested_row_to_exist() {
528        let modules = [Color::Dark, Color::Light, Color::Dark];
529        let source = RowSource { modules: &modules, width: 2, height: 2 };
530        let empty_rows = RowSource { modules: &[], width: 0, height: 3 };
531
532        assert_eq!(source.row(0), &modules[..2]);
533        assert!(catch_unwind(|| source.row(1)).is_err());
534        assert_eq!(empty_rows.row(2), &[]);
535        assert!(catch_unwind(|| empty_rows.row(3)).is_err());
536    }
537
538    #[test]
539    fn module_view_row_range_borrows_contiguous_rows() {
540        let modules = [
541            Color::Dark,
542            Color::Light,
543            Color::Light,
544            Color::Dark,
545            Color::Dark,
546            Color::Light,
547            Color::Light,
548            Color::Dark,
549            Color::Dark,
550        ];
551        let view = ModuleView::new(&modules, 3).unwrap();
552        let rows = view.row_range(1, 3).unwrap();
553
554        assert_eq!(rows.width(), 3);
555        assert_eq!(rows.height(), 2);
556        assert_eq!(rows.modules(), &modules[3..9]);
557        assert!(view.row_range(2, 2).is_none());
558        assert!(view.row_range(2, 4).is_none());
559    }
560
561    #[test]
562    fn module_view_rejects_non_square_input() {
563        let modules = [Color::Dark, Color::Light, Color::Dark];
564
565        assert!(ModuleView::new(&modules, 2).is_none());
566        assert!(ModuleView::new(&modules, 0).is_none());
567    }
568
569    #[test]
570    fn module_view_rejects_invalid_rectangular_input() {
571        let modules = [Color::Dark, Color::Light, Color::Dark];
572
573        assert!(ModuleView::new_rect(&modules, 2, 2).is_none());
574        assert!(ModuleView::new_rect(&modules, 0, 2).is_none());
575        assert!(ModuleView::new_rect(&modules, 2, 0).is_none());
576    }
577
578    #[test]
579    fn module_views_reject_overflowing_geometry() {
580        assert!(ModuleView::new_rect(&[], usize::MAX, 2).is_none());
581        assert!(QrCodeRef::new(&[], usize::MAX, Version::Normal(1), EcLevel::L).is_none());
582    }
583
584    #[test]
585    fn qr_code_ref_exposes_borrowed_symbol_metadata() {
586        let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
587        let symbol = QrCodeRef::new(&modules, 2, Version::Normal(3), EcLevel::Q).unwrap();
588
589        assert_eq!(symbol.width(), 2);
590        assert_eq!(symbol.height(), 2);
591        assert_eq!(symbol.modules(), modules);
592        assert_eq!(symbol.get(0, 0), Color::Dark);
593        assert_eq!(symbol.version(), Version::Normal(3));
594        assert_eq!(symbol.error_correction_level(), EcLevel::Q);
595        assert_eq!(symbol.quiet_zone(), 4);
596    }
597
598    #[test]
599    fn qr_code_ref_coordinates_do_not_alias_another_row() {
600        let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
601        let symbol = QrCodeRef::new(&modules, 2, Version::Normal(3), EcLevel::Q).unwrap();
602
603        for y in 0..2 {
604            for x in 0..2 {
605                assert_eq!(symbol.get(x, y), modules[y * 2 + x]);
606            }
607        }
608        for (x, y) in [(2, 0), (0, 2), (usize::MAX, 0), (0, usize::MAX), (usize::MAX, usize::MAX)] {
609            assert!(catch_unwind(|| symbol.get(x, y)).is_err());
610        }
611        assert_eq!(symbol.version(), Version::Normal(3));
612        assert_eq!(symbol.error_correction_level(), EcLevel::Q);
613    }
614
615    #[test]
616    fn qr_code_ref_rejects_invalid_module_geometry() {
617        let modules = [Color::Dark, Color::Light, Color::Dark];
618
619        assert!(QrCodeRef::new(&modules, 2, Version::Normal(1), EcLevel::M).is_none());
620        assert!(QrCodeRef::new(&modules, 0, Version::Normal(1), EcLevel::M).is_none());
621    }
622
623    #[test]
624    fn qr_code_ref_module_view_reuses_borrowed_modules() {
625        let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
626        let symbol = QrCodeRef::new(&modules, 2, Version::Micro(1), EcLevel::L).unwrap();
627        let view = symbol.module_view();
628
629        assert_eq!(view.modules(), modules);
630        assert_eq!(view.get(1, 1), Color::Dark);
631        assert_eq!(symbol.quiet_zone(), 2);
632    }
633
634    #[test]
635    fn qr_symbol_default_quiet_zone_for_normal_qr_is_four_modules() {
636        let symbol = DummySymbol { version: Version::Normal(1), modules: [Color::Dark] };
637
638        assert_eq!(symbol.quiet_zone(), 4);
639    }
640
641    #[test]
642    fn qr_symbol_default_quiet_zone_for_micro_qr_is_two_modules() {
643        let symbol = DummySymbol { version: Version::Micro(1), modules: [Color::Dark] };
644
645        assert_eq!(symbol.quiet_zone(), 2);
646    }
647
648    #[test]
649    fn builder_trait_builds_configured_output() {
650        let result = DummyBuilder { value: 7 }.build();
651
652        assert_eq!(result, Ok(7));
653    }
654
655    #[test]
656    fn encoder_trait_accepts_third_party_implementations() {
657        let output = DummyEncoder.encode(b"hello").unwrap();
658
659        assert_eq!(output, 5);
660    }
661
662    #[test]
663    fn renderer_trait_accepts_third_party_implementations() {
664        let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
665        let view = ModuleView::new(&modules, 2).unwrap();
666        let renderer = DummyRenderer { dark: '#', light: '.' };
667
668        assert_eq!(renderer.render(&view).unwrap(), "#..#");
669    }
670
671    #[test]
672    fn module_storage_blanket_impl_provides_module_source() {
673        let mut storage = DummyStorage { modules: [Color::Light; 4], width: 2 };
674        storage.set(1, 0, Color::Dark);
675        storage.set(0, 1, Color::Dark);
676
677        assert_eq!(ModuleSource::width(&storage), 2);
678        assert_eq!(ModuleSource::height(&storage), 2);
679        assert_eq!(ModuleSource::get(&storage, 1, 0), Color::Dark);
680        assert_eq!(<DummyStorage as ModuleSource>::row(&storage, 1), &[Color::Dark, Color::Light]);
681        assert_eq!(ModuleSource::modules(&storage), &[Color::Light, Color::Dark, Color::Dark, Color::Light]);
682    }
683}