use std::f64::consts::PI;
use crate::figures::traits::{rotate_transform, svg_circle, svg_path, svg_rect};
use crate::types::DotType;
pub struct QRDot {
dot_type: DotType,
}
impl QRDot {
pub fn new(dot_type: DotType) -> Self {
Self { dot_type }
}
fn basic_dot(&self, x: f64, y: f64, size: f64, rotation: f64) -> String {
let transform = rotate_transform(x, y, size, rotation);
svg_circle(
x + size / 2.0,
y + size / 2.0,
size / 2.0,
transform.as_deref(),
)
}
fn basic_square(&self, x: f64, y: f64, size: f64, rotation: f64) -> String {
let transform = rotate_transform(x, y, size, rotation);
svg_rect(x, y, size, size, transform.as_deref())
}
fn basic_side_rounded(&self, x: f64, y: f64, size: f64, rotation: f64) -> String {
let transform = rotate_transform(x, y, size, rotation);
let half = size / 2.0;
let d = format!(
"M {} {} v {} h {} a {} {} 0 0 0 0 {}",
x, y, size, half, half, half, -size
);
svg_path(&d, None, transform.as_deref())
}
fn basic_corner_rounded(&self, x: f64, y: f64, size: f64, rotation: f64) -> String {
let transform = rotate_transform(x, y, size, rotation);
let half = size / 2.0;
let d = format!(
"M {} {} v {} h {} v {} a {} {} 0 0 0 {} {}",
x, y, size, size, -half, half, half, -half, -half
);
svg_path(&d, None, transform.as_deref())
}
fn basic_corner_extra_rounded(&self, x: f64, y: f64, size: f64, rotation: f64) -> String {
let transform = rotate_transform(x, y, size, rotation);
let d = format!(
"M {} {} v {} h {} a {} {} 0 0 0 {} {}",
x, y, size, size, size, size, -size, -size
);
svg_path(&d, None, transform.as_deref())
}
fn basic_corners_rounded(&self, x: f64, y: f64, size: f64, rotation: f64) -> String {
let transform = rotate_transform(x, y, size, rotation);
let half = size / 2.0;
let d = format!(
"M {} {} v {} a {} {} 0 0 0 {} {} h {} v {} a {} {} 0 0 0 {} {}",
x, y, half, half, half, half, half, half, -half, half, half, -half, -half
);
svg_path(&d, None, transform.as_deref())
}
pub fn draw<F>(&self, x: f64, y: f64, size: f64, get_neighbor: Option<F>) -> String
where
F: Fn(i32, i32) -> bool,
{
match self.dot_type {
DotType::Dots => self.basic_dot(x, y, size, 0.0),
DotType::Square => self.basic_square(x, y, size, 0.0),
DotType::Rounded => self.draw_rounded(x, y, size, get_neighbor),
DotType::ExtraRounded => self.draw_extra_rounded(x, y, size, get_neighbor),
DotType::Classy => self.draw_classy(x, y, size, get_neighbor),
DotType::ClassyRounded => self.draw_classy_rounded(x, y, size, get_neighbor),
}
}
fn draw_rounded<F>(&self, x: f64, y: f64, size: f64, get_neighbor: Option<F>) -> String
where
F: Fn(i32, i32) -> bool,
{
let (left, right, top, bottom) = self.get_neighbors(&get_neighbor);
let count = left + right + top + bottom;
if count == 0 {
return self.basic_dot(x, y, size, 0.0);
}
if count > 2 || (left == 1 && right == 1) || (top == 1 && bottom == 1) {
return self.basic_square(x, y, size, 0.0);
}
if count == 2 {
let rotation = if left == 1 && top == 1 {
PI / 2.0
} else if top == 1 && right == 1 {
PI
} else if right == 1 && bottom == 1 {
-PI / 2.0
} else {
0.0
};
return self.basic_corner_rounded(x, y, size, rotation);
}
let rotation = if top == 1 {
PI / 2.0
} else if right == 1 {
PI
} else if bottom == 1 {
-PI / 2.0
} else {
0.0
};
self.basic_side_rounded(x, y, size, rotation)
}
fn draw_extra_rounded<F>(&self, x: f64, y: f64, size: f64, get_neighbor: Option<F>) -> String
where
F: Fn(i32, i32) -> bool,
{
let (left, right, top, bottom) = self.get_neighbors(&get_neighbor);
let count = left + right + top + bottom;
if count == 0 {
return self.basic_dot(x, y, size, 0.0);
}
if count > 2 || (left == 1 && right == 1) || (top == 1 && bottom == 1) {
return self.basic_square(x, y, size, 0.0);
}
if count == 2 {
let rotation = if left == 1 && top == 1 {
PI / 2.0
} else if top == 1 && right == 1 {
PI
} else if right == 1 && bottom == 1 {
-PI / 2.0
} else {
0.0
};
return self.basic_corner_extra_rounded(x, y, size, rotation);
}
let rotation = if top == 1 {
PI / 2.0
} else if right == 1 {
PI
} else if bottom == 1 {
-PI / 2.0
} else {
0.0
};
self.basic_side_rounded(x, y, size, rotation)
}
fn draw_classy<F>(&self, x: f64, y: f64, size: f64, get_neighbor: Option<F>) -> String
where
F: Fn(i32, i32) -> bool,
{
let (left, right, top, bottom) = self.get_neighbors(&get_neighbor);
let count = left + right + top + bottom;
if count == 0 {
return self.basic_corners_rounded(x, y, size, PI / 2.0);
}
if left == 0 && top == 0 {
return self.basic_corner_rounded(x, y, size, -PI / 2.0);
}
if right == 0 && bottom == 0 {
return self.basic_corner_rounded(x, y, size, PI / 2.0);
}
self.basic_square(x, y, size, 0.0)
}
fn draw_classy_rounded<F>(&self, x: f64, y: f64, size: f64, get_neighbor: Option<F>) -> String
where
F: Fn(i32, i32) -> bool,
{
let (left, right, top, bottom) = self.get_neighbors(&get_neighbor);
let count = left + right + top + bottom;
if count == 0 {
return self.basic_corners_rounded(x, y, size, PI / 2.0);
}
if left == 0 && top == 0 {
return self.basic_corner_extra_rounded(x, y, size, -PI / 2.0);
}
if right == 0 && bottom == 0 {
return self.basic_corner_extra_rounded(x, y, size, PI / 2.0);
}
self.basic_square(x, y, size, 0.0)
}
fn get_neighbors<F>(&self, get_neighbor: &Option<F>) -> (u8, u8, u8, u8)
where
F: Fn(i32, i32) -> bool,
{
match get_neighbor {
Some(f) => (
if f(-1, 0) { 1 } else { 0 },
if f(1, 0) { 1 } else { 0 },
if f(0, -1) { 1 } else { 0 },
if f(0, 1) { 1 } else { 0 },
),
None => (0, 0, 0, 0),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_draw_square() {
let drawer = QRDot::new(DotType::Square);
let svg = drawer.draw(0.0, 0.0, 10.0, None::<fn(i32, i32) -> bool>);
assert!(svg.contains("rect"));
assert!(svg.contains("width=\"10\""));
}
#[test]
fn test_draw_dot() {
let drawer = QRDot::new(DotType::Dots);
let svg = drawer.draw(0.0, 0.0, 10.0, None::<fn(i32, i32) -> bool>);
assert!(svg.contains("circle"));
assert!(svg.contains("r=\"5\""));
}
#[test]
fn test_draw_rounded_no_neighbors() {
let drawer = QRDot::new(DotType::Rounded);
let svg = drawer.draw(0.0, 0.0, 10.0, None::<fn(i32, i32) -> bool>);
assert!(svg.contains("circle"));
}
#[test]
fn test_draw_rounded_with_neighbors() {
let drawer = QRDot::new(DotType::Rounded);
let neighbor_fn = |x: i32, _y: i32| x == 1; let svg = drawer.draw(0.0, 0.0, 10.0, Some(neighbor_fn));
assert!(svg.contains("path"));
}
}