use crate::config::QROptions;
use crate::error::{QRError, Result};
use qrcode::{QrCode, Version};
#[derive(Debug, Clone)]
pub struct QRMatrix {
modules: Vec<bool>,
size: usize,
}
impl QRMatrix {
pub fn new(data: &str, options: &QROptions) -> Result<Self> {
let ec_level = options.error_correction_level.to_qrcode_level();
let version = if options.type_number == 0 {
None } else {
Some(Version::Normal(options.type_number as i16))
};
let qr = if let Some(v) = version {
QrCode::with_version(data.as_bytes(), v, ec_level)
.map_err(|e| QRError::QRGenerationError(e.to_string()))?
} else {
QrCode::with_error_correction_level(data.as_bytes(), ec_level)
.map_err(|e| QRError::QRGenerationError(e.to_string()))?
};
let size = qr.width() as usize;
let mut modules = Vec::with_capacity(size * size);
for y in 0..size {
for x in 0..size {
let color = qr[(x, y)];
modules.push(color == qrcode::Color::Dark);
}
}
Ok(Self { modules, size })
}
#[inline]
pub fn size(&self) -> usize {
self.size
}
#[inline]
pub fn module_count(&self) -> usize {
self.size
}
#[inline]
pub fn is_dark(&self, row: usize, col: usize) -> bool {
if row >= self.size || col >= self.size {
return false;
}
self.modules[row * self.size + col]
}
#[inline]
pub fn is_dark_signed(&self, row: i32, col: i32) -> bool {
if row < 0 || col < 0 {
return false;
}
self.is_dark(row as usize, col as usize)
}
#[inline]
pub fn get_neighbor(&self, row: i32, col: i32, offset_x: i32, offset_y: i32) -> bool {
self.is_dark_signed(row + offset_y, col + offset_x)
}
pub fn is_finder_pattern(&self, row: usize, col: usize) -> bool {
let size = self.size;
if row < 7 && col < 7 {
return true;
}
if row < 7 && col >= size - 7 {
return true;
}
if row >= size - 7 && col < 7 {
return true;
}
false
}
pub fn is_finder_pattern_outer(&self, row: usize, col: usize) -> bool {
if !self.is_finder_pattern(row, col) {
return false;
}
let size = self.size;
let check_border = |r: usize, c: usize, start_r: usize, start_c: usize| -> bool {
let local_r = r - start_r;
let local_c = c - start_c;
local_r == 0 || local_r == 6 || local_c == 0 || local_c == 6
};
if row < 7 && col < 7 {
return check_border(row, col, 0, 0);
}
if row < 7 && col >= size - 7 {
return check_border(row, col, 0, size - 7);
}
if row >= size - 7 && col < 7 {
return check_border(row, col, size - 7, 0);
}
false
}
pub fn is_finder_pattern_inner(&self, row: usize, col: usize) -> bool {
if !self.is_finder_pattern(row, col) {
return false;
}
let size = self.size;
let check_inner = |r: usize, c: usize, start_r: usize, start_c: usize| -> bool {
let local_r = r - start_r;
let local_c = c - start_c;
local_r >= 2 && local_r <= 4 && local_c >= 2 && local_c <= 4
};
if row < 7 && col < 7 {
return check_inner(row, col, 0, 0);
}
if row < 7 && col >= size - 7 {
return check_inner(row, col, 0, size - 7);
}
if row >= size - 7 && col < 7 {
return check_inner(row, col, size - 7, 0);
}
false
}
}
#[allow(dead_code)]
pub const SQUARE_MASK: [[u8; 7]; 7] = [
[1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1],
];
#[allow(dead_code)]
pub const DOT_MASK: [[u8; 7]; 7] = [
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_qr_matrix_creation() {
let options = QROptions::default();
let matrix = QRMatrix::new("Hello", &options).unwrap();
assert!(matrix.size() >= 21); }
#[test]
fn test_is_dark() {
let options = QROptions::default();
let matrix = QRMatrix::new("Test", &options).unwrap();
assert!(matrix.is_dark(0, 0));
}
#[test]
fn test_neighbor_lookup() {
let options = QROptions::default();
let matrix = QRMatrix::new("Test", &options).unwrap();
let dark = matrix.is_dark(0, 0);
let neighbor = matrix.get_neighbor(0, 1, -1, 0);
assert_eq!(dark, neighbor);
}
#[test]
fn test_finder_pattern_detection() {
let options = QROptions::default();
let matrix = QRMatrix::new("Test", &options).unwrap();
assert!(matrix.is_finder_pattern(0, 0));
assert!(matrix.is_finder_pattern(3, 3));
assert!(matrix.is_finder_pattern(6, 6));
let mid = matrix.size() / 2;
assert!(!matrix.is_finder_pattern(mid, mid));
}
}