use crate::types::{Color, EcLevel, Version};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ModuleView<'a> {
modules: &'a [Color],
width: usize,
height: usize,
}
impl<'a> ModuleView<'a> {
#[must_use]
pub const fn new(modules: &'a [Color], width: usize) -> Option<Self> {
Self::new_rect(modules, width, width)
}
#[must_use]
pub const fn new_rect(modules: &'a [Color], width: usize, height: usize) -> Option<Self> {
if !has_valid_module_geometry(modules.len(), width, height) {
return None;
}
Some(Self { modules, width, height })
}
#[must_use]
pub fn row_range(&self, start: usize, end: usize) -> Option<Self> {
if start >= end || end > self.height {
return None;
}
let height = end - start;
let start = start.checked_mul(self.width)?;
let end = end.checked_mul(self.width)?;
Some(Self { modules: self.modules.get(start..end)?, width: self.width, height })
}
}
const fn has_valid_module_geometry(len: usize, width: usize, height: usize) -> bool {
if width == 0 || height == 0 {
return false;
}
match width.checked_mul(height) {
Some(expected) => len == expected,
None => false,
}
}
impl ModuleSource for ModuleView<'_> {
fn get(&self, x: usize, y: usize) -> Color {
self.modules[y * self.width + x]
}
fn width(&self) -> usize {
self.width
}
fn height(&self) -> usize {
self.height
}
fn modules(&self) -> &[Color] {
self.modules
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct QrCodeRef<'a> {
modules: &'a [Color],
version: Version,
ec_level: EcLevel,
width: usize,
}
impl<'a> QrCodeRef<'a> {
#[must_use]
pub const fn new(modules: &'a [Color], width: usize, version: Version, ec_level: EcLevel) -> Option<Self> {
if !has_valid_module_geometry(modules.len(), width, width) {
return None;
}
Some(Self { modules, version, ec_level, width })
}
#[must_use]
pub const fn module_view(self) -> ModuleView<'a> {
ModuleView { modules: self.modules, width: self.width, height: self.width }
}
}
impl ModuleSource for QrCodeRef<'_> {
fn get(&self, x: usize, y: usize) -> Color {
self.modules[y * self.width + x]
}
fn width(&self) -> usize {
self.width
}
fn height(&self) -> usize {
self.width
}
fn modules(&self) -> &[Color] {
self.modules
}
}
impl QrSymbol for QrCodeRef<'_> {
fn version(&self) -> Version {
self.version
}
fn error_correction_level(&self) -> EcLevel {
self.ec_level
}
}
pub trait Encoder {
type Output;
type Error;
fn encode(&self, input: &[u8]) -> Result<Self::Output, Self::Error>;
}
pub trait Builder {
type Output;
type Error;
fn build(self) -> Result<Self::Output, Self::Error>;
}
pub trait Renderer<Code: ModuleSource + ?Sized> {
type Output;
type Error;
fn render(&self, code: &Code) -> Result<Self::Output, Self::Error>;
}
pub trait ModuleSource {
fn get(&self, x: usize, y: usize) -> Color;
fn width(&self) -> usize;
fn height(&self) -> usize;
fn modules(&self) -> &[Color];
fn row(&self, y: usize) -> &[Color] {
let width = self.width();
let start = y * width;
&self.modules()[start..start + width]
}
fn is_empty(&self) -> bool {
self.width() == 0 || self.height() == 0
}
}
pub trait QrSymbol: ModuleSource {
fn version(&self) -> Version;
fn error_correction_level(&self) -> EcLevel;
fn quiet_zone(&self) -> u32 {
if self.version().is_micro() { 2 } else { 4 }
}
}
pub trait ModuleStorage {
fn get(&self, x: usize, y: usize) -> Color;
fn set(&mut self, x: usize, y: usize, color: Color);
fn width(&self) -> usize;
fn height(&self) -> usize;
fn modules(&self) -> &[Color];
fn is_empty(&self) -> bool {
self.width() == 0 || self.height() == 0
}
}
impl<T: ModuleStorage + ?Sized> ModuleSource for T {
fn get(&self, x: usize, y: usize) -> Color {
ModuleStorage::get(self, x, y)
}
fn width(&self) -> usize {
ModuleStorage::width(self)
}
fn height(&self) -> usize {
ModuleStorage::height(self)
}
fn modules(&self) -> &[Color] {
ModuleStorage::modules(self)
}
fn is_empty(&self) -> bool {
ModuleStorage::is_empty(self)
}
}
#[cfg(test)]
mod tests {
use super::{Builder, Encoder, ModuleSource, ModuleStorage, ModuleView, QrCodeRef, QrSymbol, Renderer};
use crate::{Color, EcLevel, Version};
use core::convert::Infallible;
struct DummySymbol {
version: Version,
modules: [Color; 1],
}
impl ModuleSource for DummySymbol {
fn get(&self, _x: usize, _y: usize) -> Color {
self.modules[0]
}
fn width(&self) -> usize {
1
}
fn height(&self) -> usize {
1
}
fn modules(&self) -> &[Color] {
&self.modules
}
}
impl QrSymbol for DummySymbol {
fn version(&self) -> Version {
self.version
}
fn error_correction_level(&self) -> EcLevel {
EcLevel::M
}
}
struct DummyBuilder {
value: u8,
}
impl Builder for DummyBuilder {
type Output = u8;
type Error = ();
fn build(self) -> Result<Self::Output, Self::Error> {
Ok(self.value)
}
}
struct DummyEncoder;
impl Encoder for DummyEncoder {
type Output = usize;
type Error = Infallible;
fn encode(&self, input: &[u8]) -> Result<Self::Output, Self::Error> {
Ok(input.len())
}
}
struct DummyRenderer {
dark: char,
light: char,
}
impl<C: ModuleSource + ?Sized> Renderer<C> for DummyRenderer {
type Output = String;
type Error = Infallible;
fn render(&self, code: &C) -> Result<Self::Output, Self::Error> {
let mut out = String::new();
for y in 0..code.height() {
for x in 0..code.width() {
out.push(match code.get(x, y) {
Color::Dark => self.dark,
Color::Light => self.light,
});
}
}
Ok(out)
}
}
struct DummyStorage {
modules: [Color; 4],
width: usize,
}
impl ModuleStorage for DummyStorage {
fn get(&self, x: usize, y: usize) -> Color {
self.modules[y * self.width + x]
}
fn set(&mut self, x: usize, y: usize, color: Color) {
self.modules[y * self.width + x] = color;
}
fn width(&self) -> usize {
self.width
}
fn height(&self) -> usize {
self.modules.len() / self.width
}
fn modules(&self) -> &[Color] {
&self.modules
}
}
#[test]
fn module_view_reads_row_major_modules() {
let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
let view = ModuleView::new(&modules, 2).unwrap();
assert_eq!(view.width(), 2);
assert_eq!(view.height(), 2);
assert_eq!(view.modules(), modules);
assert_eq!(view.row(1), &[Color::Light, Color::Dark]);
assert_eq!(view.get(0, 0), Color::Dark);
assert_eq!(view.get(1, 1), Color::Dark);
}
#[test]
fn module_view_reads_rectangular_row_major_modules() {
let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark, Color::Dark, Color::Light];
let view = ModuleView::new_rect(&modules, 3, 2).unwrap();
assert_eq!(view.width(), 3);
assert_eq!(view.height(), 2);
assert_eq!(view.row(1), &[Color::Dark, Color::Dark, Color::Light]);
}
#[test]
fn module_view_row_range_borrows_contiguous_rows() {
let modules = [
Color::Dark,
Color::Light,
Color::Light,
Color::Dark,
Color::Dark,
Color::Light,
Color::Light,
Color::Dark,
Color::Dark,
];
let view = ModuleView::new(&modules, 3).unwrap();
let rows = view.row_range(1, 3).unwrap();
assert_eq!(rows.width(), 3);
assert_eq!(rows.height(), 2);
assert_eq!(rows.modules(), &modules[3..9]);
assert!(view.row_range(2, 2).is_none());
assert!(view.row_range(2, 4).is_none());
}
#[test]
fn module_view_rejects_non_square_input() {
let modules = [Color::Dark, Color::Light, Color::Dark];
assert!(ModuleView::new(&modules, 2).is_none());
assert!(ModuleView::new(&modules, 0).is_none());
}
#[test]
fn module_view_rejects_invalid_rectangular_input() {
let modules = [Color::Dark, Color::Light, Color::Dark];
assert!(ModuleView::new_rect(&modules, 2, 2).is_none());
assert!(ModuleView::new_rect(&modules, 0, 2).is_none());
assert!(ModuleView::new_rect(&modules, 2, 0).is_none());
}
#[test]
fn module_views_reject_overflowing_geometry() {
assert!(ModuleView::new_rect(&[], usize::MAX, 2).is_none());
assert!(QrCodeRef::new(&[], usize::MAX, Version::Normal(1), EcLevel::L).is_none());
}
#[test]
fn qr_code_ref_exposes_borrowed_symbol_metadata() {
let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
let symbol = QrCodeRef::new(&modules, 2, Version::Normal(3), EcLevel::Q).unwrap();
assert_eq!(symbol.width(), 2);
assert_eq!(symbol.height(), 2);
assert_eq!(symbol.modules(), modules);
assert_eq!(symbol.get(0, 0), Color::Dark);
assert_eq!(symbol.version(), Version::Normal(3));
assert_eq!(symbol.error_correction_level(), EcLevel::Q);
assert_eq!(symbol.quiet_zone(), 4);
}
#[test]
fn qr_code_ref_rejects_invalid_module_geometry() {
let modules = [Color::Dark, Color::Light, Color::Dark];
assert!(QrCodeRef::new(&modules, 2, Version::Normal(1), EcLevel::M).is_none());
assert!(QrCodeRef::new(&modules, 0, Version::Normal(1), EcLevel::M).is_none());
}
#[test]
fn qr_code_ref_module_view_reuses_borrowed_modules() {
let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
let symbol = QrCodeRef::new(&modules, 2, Version::Micro(1), EcLevel::L).unwrap();
let view = symbol.module_view();
assert_eq!(view.modules(), modules);
assert_eq!(view.get(1, 1), Color::Dark);
assert_eq!(symbol.quiet_zone(), 2);
}
#[test]
fn qr_symbol_default_quiet_zone_for_normal_qr_is_four_modules() {
let symbol = DummySymbol { version: Version::Normal(1), modules: [Color::Dark] };
assert_eq!(symbol.quiet_zone(), 4);
}
#[test]
fn qr_symbol_default_quiet_zone_for_micro_qr_is_two_modules() {
let symbol = DummySymbol { version: Version::Micro(1), modules: [Color::Dark] };
assert_eq!(symbol.quiet_zone(), 2);
}
#[test]
fn builder_trait_builds_configured_output() {
let result = DummyBuilder { value: 7 }.build();
assert_eq!(result, Ok(7));
}
#[test]
fn encoder_trait_accepts_third_party_implementations() {
let output = DummyEncoder.encode(b"hello").unwrap();
assert_eq!(output, 5);
}
#[test]
fn renderer_trait_accepts_third_party_implementations() {
let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
let view = ModuleView::new(&modules, 2).unwrap();
let renderer = DummyRenderer { dark: '#', light: '.' };
assert_eq!(renderer.render(&view).unwrap(), "#..#");
}
#[test]
fn module_storage_blanket_impl_provides_module_source() {
let mut storage = DummyStorage { modules: [Color::Light; 4], width: 2 };
storage.set(1, 0, Color::Dark);
storage.set(0, 1, Color::Dark);
assert_eq!(ModuleSource::width(&storage), 2);
assert_eq!(ModuleSource::height(&storage), 2);
assert_eq!(ModuleSource::get(&storage, 1, 0), Color::Dark);
assert_eq!(<DummyStorage as ModuleSource>::row(&storage, 1), &[Color::Dark, Color::Light]);
assert_eq!(ModuleSource::modules(&storage), &[Color::Light, Color::Dark, Color::Dark, Color::Light]);
}
}