1use crate::types::{Color, EcLevel, Version};
8
9#[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 #[must_use]
26 pub const fn new(modules: &'a [Color], width: usize) -> Option<Self> {
27 Self::new_rect(modules, width, width)
28 }
29
30 #[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 #[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#[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 #[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 #[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
151pub trait Encoder {
157 type Output;
159
160 type Error;
162
163 fn encode(&self, input: &[u8]) -> Result<Self::Output, Self::Error>;
169}
170
171pub trait Builder {
176 type Output;
178
179 type Error;
181
182 fn build(self) -> Result<Self::Output, Self::Error>;
188}
189
190pub trait Renderer<Code: ModuleSource + ?Sized> {
195 type Output;
197
198 type Error;
200
201 fn render(&self, code: &Code) -> Result<Self::Output, Self::Error>;
207}
208
209pub trait ModuleSource {
214 fn get(&self, x: usize, y: usize) -> Color;
220
221 fn width(&self) -> usize;
223
224 fn height(&self) -> usize;
226
227 fn modules(&self) -> &[Color];
229
230 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 fn is_empty(&self) -> bool {
246 self.width() == 0 || self.height() == 0
247 }
248}
249
250pub trait QrSymbol: ModuleSource {
256 fn version(&self) -> Version;
258
259 fn error_correction_level(&self) -> EcLevel;
261
262 fn quiet_zone(&self) -> u32 {
266 if self.version().is_micro() { 2 } else { 4 }
267 }
268}
269
270pub trait ModuleStorage {
276 fn get(&self, x: usize, y: usize) -> Color;
282
283 fn set(&mut self, x: usize, y: usize, color: Color);
289
290 fn width(&self) -> usize;
292
293 fn height(&self) -> usize;
295
296 fn modules(&self) -> &[Color];
298
299 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}