1use crate::config::QROptions;
4use crate::error::{QRError, Result};
5use qrcode::{QrCode, Version};
6
7#[derive(Debug, Clone)]
9pub struct QRMatrix {
10 modules: Vec<bool>,
12 size: usize,
14}
15
16impl QRMatrix {
17 pub fn new(data: &str, options: &QROptions) -> Result<Self> {
19 let ec_level = options.error_correction_level.to_qrcode_level();
20
21 let version = if options.type_number == 0 {
23 None } else {
25 Some(Version::Normal(options.type_number as i16))
26 };
27
28 let qr = if let Some(v) = version {
30 QrCode::with_version(data.as_bytes(), v, ec_level)
31 .map_err(|e| QRError::QRGenerationError(e.to_string()))?
32 } else {
33 QrCode::with_error_correction_level(data.as_bytes(), ec_level)
34 .map_err(|e| QRError::QRGenerationError(e.to_string()))?
35 };
36
37 let size = qr.width() as usize;
38 let mut modules = Vec::with_capacity(size * size);
39
40 for y in 0..size {
42 for x in 0..size {
43 let color = qr[(x, y)];
44 modules.push(color == qrcode::Color::Dark);
45 }
46 }
47
48 Ok(Self { modules, size })
49 }
50
51 #[inline]
53 pub fn size(&self) -> usize {
54 self.size
55 }
56
57 #[inline]
59 pub fn module_count(&self) -> usize {
60 self.size
61 }
62
63 #[inline]
65 pub fn is_dark(&self, row: usize, col: usize) -> bool {
66 if row >= self.size || col >= self.size {
67 return false;
68 }
69 self.modules[row * self.size + col]
70 }
71
72 #[inline]
75 pub fn is_dark_signed(&self, row: i32, col: i32) -> bool {
76 if row < 0 || col < 0 {
77 return false;
78 }
79 self.is_dark(row as usize, col as usize)
80 }
81
82 #[inline]
84 pub fn get_neighbor(&self, row: i32, col: i32, offset_x: i32, offset_y: i32) -> bool {
85 self.is_dark_signed(row + offset_y, col + offset_x)
86 }
87
88 pub fn is_finder_pattern(&self, row: usize, col: usize) -> bool {
94 let size = self.size;
95
96 if row < 7 && col < 7 {
98 return true;
99 }
100
101 if row < 7 && col >= size - 7 {
103 return true;
104 }
105
106 if row >= size - 7 && col < 7 {
108 return true;
109 }
110
111 false
112 }
113
114 pub fn is_finder_pattern_outer(&self, row: usize, col: usize) -> bool {
116 if !self.is_finder_pattern(row, col) {
117 return false;
118 }
119
120 let size = self.size;
121
122 let check_border = |r: usize, c: usize, start_r: usize, start_c: usize| -> bool {
124 let local_r = r - start_r;
125 let local_c = c - start_c;
126 local_r == 0 || local_r == 6 || local_c == 0 || local_c == 6
127 };
128
129 if row < 7 && col < 7 {
131 return check_border(row, col, 0, 0);
132 }
133
134 if row < 7 && col >= size - 7 {
136 return check_border(row, col, 0, size - 7);
137 }
138
139 if row >= size - 7 && col < 7 {
141 return check_border(row, col, size - 7, 0);
142 }
143
144 false
145 }
146
147 pub fn is_finder_pattern_inner(&self, row: usize, col: usize) -> bool {
149 if !self.is_finder_pattern(row, col) {
150 return false;
151 }
152
153 let size = self.size;
154
155 let check_inner = |r: usize, c: usize, start_r: usize, start_c: usize| -> bool {
156 let local_r = r - start_r;
157 let local_c = c - start_c;
158 local_r >= 2 && local_r <= 4 && local_c >= 2 && local_c <= 4
159 };
160
161 if row < 7 && col < 7 {
163 return check_inner(row, col, 0, 0);
164 }
165
166 if row < 7 && col >= size - 7 {
168 return check_inner(row, col, 0, size - 7);
169 }
170
171 if row >= size - 7 && col < 7 {
173 return check_inner(row, col, size - 7, 0);
174 }
175
176 false
177 }
178}
179
180#[allow(dead_code)]
183pub const SQUARE_MASK: [[u8; 7]; 7] = [
184 [1, 1, 1, 1, 1, 1, 1],
185 [1, 0, 0, 0, 0, 0, 1],
186 [1, 0, 0, 0, 0, 0, 1],
187 [1, 0, 0, 0, 0, 0, 1],
188 [1, 0, 0, 0, 0, 0, 1],
189 [1, 0, 0, 0, 0, 0, 1],
190 [1, 1, 1, 1, 1, 1, 1],
191];
192
193#[allow(dead_code)]
196pub const DOT_MASK: [[u8; 7]; 7] = [
197 [0, 0, 0, 0, 0, 0, 0],
198 [0, 0, 0, 0, 0, 0, 0],
199 [0, 0, 1, 1, 1, 0, 0],
200 [0, 0, 1, 1, 1, 0, 0],
201 [0, 0, 1, 1, 1, 0, 0],
202 [0, 0, 0, 0, 0, 0, 0],
203 [0, 0, 0, 0, 0, 0, 0],
204];
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
211 fn test_qr_matrix_creation() {
212 let options = QROptions::default();
213 let matrix = QRMatrix::new("Hello", &options).unwrap();
214 assert!(matrix.size() >= 21); }
216
217 #[test]
218 fn test_is_dark() {
219 let options = QROptions::default();
220 let matrix = QRMatrix::new("Test", &options).unwrap();
221
222 assert!(matrix.is_dark(0, 0));
224 }
225
226 #[test]
227 fn test_neighbor_lookup() {
228 let options = QROptions::default();
229 let matrix = QRMatrix::new("Test", &options).unwrap();
230
231 let dark = matrix.is_dark(0, 0);
233 let neighbor = matrix.get_neighbor(0, 1, -1, 0);
234 assert_eq!(dark, neighbor);
235 }
236
237 #[test]
238 fn test_finder_pattern_detection() {
239 let options = QROptions::default();
240 let matrix = QRMatrix::new("Test", &options).unwrap();
241
242 assert!(matrix.is_finder_pattern(0, 0));
244 assert!(matrix.is_finder_pattern(3, 3));
245 assert!(matrix.is_finder_pattern(6, 6));
246
247 let mid = matrix.size() / 2;
249 assert!(!matrix.is_finder_pattern(mid, mid));
250 }
251}