1use std::f64::consts::PI;
4
5use crate::figures::traits::{rotate_transform, svg_circle, svg_path, svg_rect};
6use crate::types::DotType;
7
8pub struct QRDot {
10 dot_type: DotType,
11}
12
13impl QRDot {
14 pub fn new(dot_type: DotType) -> Self {
16 Self { dot_type }
17 }
18
19 fn basic_dot(&self, x: f64, y: f64, size: f64, rotation: f64) -> String {
21 let transform = rotate_transform(x, y, size, rotation);
22 svg_circle(
23 x + size / 2.0,
24 y + size / 2.0,
25 size / 2.0,
26 transform.as_deref(),
27 )
28 }
29
30 fn basic_square(&self, x: f64, y: f64, size: f64, rotation: f64) -> String {
32 let transform = rotate_transform(x, y, size, rotation);
33 svg_rect(x, y, size, size, transform.as_deref())
34 }
35
36 fn basic_side_rounded(&self, x: f64, y: f64, size: f64, rotation: f64) -> String {
38 let transform = rotate_transform(x, y, size, rotation);
39 let half = size / 2.0;
40 let d = format!(
41 "M {} {} v {} h {} a {} {} 0 0 0 0 {}",
42 x, y, size, half, half, half, -size
43 );
44 svg_path(&d, None, transform.as_deref())
45 }
46
47 fn basic_corner_rounded(&self, x: f64, y: f64, size: f64, rotation: f64) -> String {
49 let transform = rotate_transform(x, y, size, rotation);
50 let half = size / 2.0;
51 let d = format!(
52 "M {} {} v {} h {} v {} a {} {} 0 0 0 {} {}",
53 x, y, size, size, -half, half, half, -half, -half
54 );
55 svg_path(&d, None, transform.as_deref())
56 }
57
58 fn basic_corner_extra_rounded(&self, x: f64, y: f64, size: f64, rotation: f64) -> String {
60 let transform = rotate_transform(x, y, size, rotation);
61 let d = format!(
62 "M {} {} v {} h {} a {} {} 0 0 0 {} {}",
63 x, y, size, size, size, size, -size, -size
64 );
65 svg_path(&d, None, transform.as_deref())
66 }
67
68 fn basic_corners_rounded(&self, x: f64, y: f64, size: f64, rotation: f64) -> String {
70 let transform = rotate_transform(x, y, size, rotation);
71 let half = size / 2.0;
72 let d = format!(
73 "M {} {} v {} a {} {} 0 0 0 {} {} h {} v {} a {} {} 0 0 0 {} {}",
74 x, y, half, half, half, half, half, half, -half, half, half, -half, -half
75 );
76 svg_path(&d, None, transform.as_deref())
77 }
78
79 pub fn draw<F>(&self, x: f64, y: f64, size: f64, get_neighbor: Option<F>) -> String
81 where
82 F: Fn(i32, i32) -> bool,
83 {
84 match self.dot_type {
85 DotType::Dots => self.basic_dot(x, y, size, 0.0),
86 DotType::Square => self.basic_square(x, y, size, 0.0),
87 DotType::Rounded => self.draw_rounded(x, y, size, get_neighbor),
88 DotType::ExtraRounded => self.draw_extra_rounded(x, y, size, get_neighbor),
89 DotType::Classy => self.draw_classy(x, y, size, get_neighbor),
90 DotType::ClassyRounded => self.draw_classy_rounded(x, y, size, get_neighbor),
91 }
92 }
93
94 fn draw_rounded<F>(&self, x: f64, y: f64, size: f64, get_neighbor: Option<F>) -> String
96 where
97 F: Fn(i32, i32) -> bool,
98 {
99 let (left, right, top, bottom) = self.get_neighbors(&get_neighbor);
100 let count = left + right + top + bottom;
101
102 if count == 0 {
103 return self.basic_dot(x, y, size, 0.0);
104 }
105
106 if count > 2 || (left == 1 && right == 1) || (top == 1 && bottom == 1) {
107 return self.basic_square(x, y, size, 0.0);
108 }
109
110 if count == 2 {
111 let rotation = if left == 1 && top == 1 {
112 PI / 2.0
113 } else if top == 1 && right == 1 {
114 PI
115 } else if right == 1 && bottom == 1 {
116 -PI / 2.0
117 } else {
118 0.0
119 };
120 return self.basic_corner_rounded(x, y, size, rotation);
121 }
122
123 let rotation = if top == 1 {
125 PI / 2.0
126 } else if right == 1 {
127 PI
128 } else if bottom == 1 {
129 -PI / 2.0
130 } else {
131 0.0
132 };
133 self.basic_side_rounded(x, y, size, rotation)
134 }
135
136 fn draw_extra_rounded<F>(&self, x: f64, y: f64, size: f64, get_neighbor: Option<F>) -> String
138 where
139 F: Fn(i32, i32) -> bool,
140 {
141 let (left, right, top, bottom) = self.get_neighbors(&get_neighbor);
142 let count = left + right + top + bottom;
143
144 if count == 0 {
145 return self.basic_dot(x, y, size, 0.0);
146 }
147
148 if count > 2 || (left == 1 && right == 1) || (top == 1 && bottom == 1) {
149 return self.basic_square(x, y, size, 0.0);
150 }
151
152 if count == 2 {
153 let rotation = if left == 1 && top == 1 {
154 PI / 2.0
155 } else if top == 1 && right == 1 {
156 PI
157 } else if right == 1 && bottom == 1 {
158 -PI / 2.0
159 } else {
160 0.0
161 };
162 return self.basic_corner_extra_rounded(x, y, size, rotation);
163 }
164
165 let rotation = if top == 1 {
167 PI / 2.0
168 } else if right == 1 {
169 PI
170 } else if bottom == 1 {
171 -PI / 2.0
172 } else {
173 0.0
174 };
175 self.basic_side_rounded(x, y, size, rotation)
176 }
177
178 fn draw_classy<F>(&self, x: f64, y: f64, size: f64, get_neighbor: Option<F>) -> String
180 where
181 F: Fn(i32, i32) -> bool,
182 {
183 let (left, right, top, bottom) = self.get_neighbors(&get_neighbor);
184 let count = left + right + top + bottom;
185
186 if count == 0 {
187 return self.basic_corners_rounded(x, y, size, PI / 2.0);
188 }
189
190 if left == 0 && top == 0 {
191 return self.basic_corner_rounded(x, y, size, -PI / 2.0);
192 }
193
194 if right == 0 && bottom == 0 {
195 return self.basic_corner_rounded(x, y, size, PI / 2.0);
196 }
197
198 self.basic_square(x, y, size, 0.0)
199 }
200
201 fn draw_classy_rounded<F>(&self, x: f64, y: f64, size: f64, get_neighbor: Option<F>) -> String
203 where
204 F: Fn(i32, i32) -> bool,
205 {
206 let (left, right, top, bottom) = self.get_neighbors(&get_neighbor);
207 let count = left + right + top + bottom;
208
209 if count == 0 {
210 return self.basic_corners_rounded(x, y, size, PI / 2.0);
211 }
212
213 if left == 0 && top == 0 {
214 return self.basic_corner_extra_rounded(x, y, size, -PI / 2.0);
215 }
216
217 if right == 0 && bottom == 0 {
218 return self.basic_corner_extra_rounded(x, y, size, PI / 2.0);
219 }
220
221 self.basic_square(x, y, size, 0.0)
222 }
223
224 fn get_neighbors<F>(&self, get_neighbor: &Option<F>) -> (u8, u8, u8, u8)
226 where
227 F: Fn(i32, i32) -> bool,
228 {
229 match get_neighbor {
230 Some(f) => (
231 if f(-1, 0) { 1 } else { 0 },
232 if f(1, 0) { 1 } else { 0 },
233 if f(0, -1) { 1 } else { 0 },
234 if f(0, 1) { 1 } else { 0 },
235 ),
236 None => (0, 0, 0, 0),
237 }
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244
245 #[test]
246 fn test_draw_square() {
247 let drawer = QRDot::new(DotType::Square);
248 let svg = drawer.draw(0.0, 0.0, 10.0, None::<fn(i32, i32) -> bool>);
249 assert!(svg.contains("rect"));
250 assert!(svg.contains("width=\"10\""));
251 }
252
253 #[test]
254 fn test_draw_dot() {
255 let drawer = QRDot::new(DotType::Dots);
256 let svg = drawer.draw(0.0, 0.0, 10.0, None::<fn(i32, i32) -> bool>);
257 assert!(svg.contains("circle"));
258 assert!(svg.contains("r=\"5\""));
259 }
260
261 #[test]
262 fn test_draw_rounded_no_neighbors() {
263 let drawer = QRDot::new(DotType::Rounded);
264 let svg = drawer.draw(0.0, 0.0, 10.0, None::<fn(i32, i32) -> bool>);
265 assert!(svg.contains("circle"));
267 }
268
269 #[test]
270 fn test_draw_rounded_with_neighbors() {
271 let drawer = QRDot::new(DotType::Rounded);
272 let neighbor_fn = |x: i32, _y: i32| x == 1; let svg = drawer.draw(0.0, 0.0, 10.0, Some(neighbor_fn));
274 assert!(svg.contains("path"));
276 }
277}