use crate::base::randoms::Randoms;
use crate::utils::color::Color;
use crate::utils::font;
use base64::prelude::BASE64_STANDARD;
use base64::Engine;
use font_kit::font::Font;
use raqote::{DrawOptions, DrawTarget, PathBuilder, SolidSource, Source, StrokeStyle};
use std::fmt::Debug;
use std::io::Write;
use std::sync::Arc;
pub(crate) struct Captcha {
pub(crate) randoms: Randoms,
color: Vec<Color>,
font_names: [&'static str; 10],
font_name: String,
font_size: f32,
pub len: usize,
pub width: i32,
pub height: i32,
char_type: CaptchaType,
pub(crate) chars: Option<String>,
}
pub enum CaptchaType {
TypeDefault = 1,
TypeOnlyNumber,
TypeOnlyChar,
TypeOnlyUpper,
TypeOnlyLower,
TypeNumAndUpper,
}
pub enum CaptchaFont {
Font1,
Font2,
Font3,
Font4,
Font5,
Font6,
Font7,
Font8,
Font9,
Font10,
}
impl Captcha {
pub fn alphas(&mut self) -> Vec<char> {
let mut cs = vec!['\0'; self.len];
for i in 0..self.len {
match self.char_type {
CaptchaType::TypeDefault => cs[i] = self.randoms.alpha(),
CaptchaType::TypeOnlyNumber => {
cs[i] = self.randoms.alpha_under(self.randoms.num_max_index)
}
CaptchaType::TypeOnlyChar => {
cs[i] = self
.randoms
.alpha_between(self.randoms.char_min_index, self.randoms.char_max_index)
}
CaptchaType::TypeOnlyUpper => {
cs[i] = self
.randoms
.alpha_between(self.randoms.upper_min_index, self.randoms.upper_max_index)
}
CaptchaType::TypeOnlyLower => {
cs[i] = self
.randoms
.alpha_between(self.randoms.lower_min_index, self.randoms.lower_max_index)
}
CaptchaType::TypeNumAndUpper => {
cs[i] = self.randoms.alpha_under(self.randoms.upper_max_index)
}
}
}
self.chars = Some(cs.iter().collect());
cs
}
pub fn color_range(&mut self, fc: u8, bc: u8) -> Color {
let r = fc + self.randoms.num((bc - fc) as usize) as u8;
let g = fc + self.randoms.num((bc - fc) as usize) as u8;
let b = fc + self.randoms.num((bc - fc) as usize) as u8;
return (r, g, b).into();
}
pub fn color(&mut self) -> Color {
self.color[self.randoms.num(self.color.len())].clone()
}
pub fn text(&mut self) -> String {
self.check_alpha();
self.chars.clone().unwrap()
}
pub fn text_char(&mut self) -> Vec<char> {
self.check_alpha();
self.chars.clone().unwrap().chars().collect()
}
pub fn check_alpha(&mut self) {
if self.chars.is_none() {
self.alphas();
}
}
pub fn draw_line(&mut self, num: usize, g: &mut DrawTarget, color: Option<Color>) {
for _ in 0..num {
let color = color.clone().unwrap_or_else(|| self.color());
let color: raqote::Color = color.into();
let x1 = self.randoms.num_between(-10, self.width - 10);
let y1 = self.randoms.num_between(5, self.height - 5);
let x2 = self.randoms.num_between(10, self.width + 10);
let y2 = self.randoms.num_between(2, self.height - 2);
let mut pb = PathBuilder::new();
pb.move_to(x1 as f32, y1 as f32);
pb.line_to(x2 as f32, y2 as f32);
let path = pb.finish();
g.stroke(
&path,
&Source::Solid(SolidSource::from(color)),
&StrokeStyle {
width: 2.,
..Default::default()
},
&DrawOptions::new(),
);
}
}
pub fn draw_oval(&mut self, num: usize, g: &mut DrawTarget, color: Option<Color>) {
self.draw_oval_with_option(num, g, color, DrawOptions::new());
}
pub fn draw_oval_with_option(
&mut self,
num: usize,
g: &mut DrawTarget,
color: Option<Color>,
options: DrawOptions,
) {
for _ in 0..num {
let color = color.clone().unwrap_or_else(|| self.color());
let color: raqote::Color = color.into();
let w = 5 + self.randoms.num(10);
let x = self.randoms.num(self.width as usize - 25) + w;
let y = self.randoms.num(self.height as usize - 15) + w;
let mut pb = PathBuilder::new();
pb.arc(x as f32, y as f32, w as f32, 0., 2. * std::f32::consts::PI);
let path = pb.finish();
g.stroke(
&path,
&Source::Solid(SolidSource::from(color)),
&StrokeStyle {
width: 2.,
..Default::default()
},
&options,
);
}
}
pub fn draw_bessel_line(&mut self, num: usize, g: &mut DrawTarget, color: Option<Color>) {
self.draw_bessel_line_with_all_option(
num,
g,
color,
StrokeStyle {
width: 2.,
..Default::default()
},
DrawOptions::new(),
)
}
pub fn draw_bessel_line_with_all_option(
&mut self,
num: usize,
g: &mut DrawTarget,
color: Option<Color>,
stroke_style: StrokeStyle,
draw_options: DrawOptions,
) {
for _ in 0..num {
let color = color.clone().unwrap_or_else(|| self.color());
let color: raqote::Color = color.into();
let x1 = 5;
let mut y1 = self.randoms.num_between(5, self.height / 2);
let x2 = self.width - 5;
let mut y2 = self.randoms.num_between(self.height / 2, self.height - 5);
let cx = self.randoms.num_between(self.width / 4, self.width / 4 * 3);
let cy = self.randoms.num_between(5, self.height - 5);
if self.randoms.num(2) == 0 {
(y2, y1) = (y1, y2)
}
let mut pb = PathBuilder::new();
pb.move_to(x1 as f32, y1 as f32);
if self.randoms.num(2) == 0 {
pb.quad_to(cx as f32, cy as f32, x2 as f32, y2 as f32);
} else {
let cx1 = self.randoms.num_between(self.width / 4, self.width / 4 * 3);
let cy1 = self.randoms.num_between(5, self.height - 5);
pb.cubic_to(
cx as f32, cy as f32, cx1 as f32, cy1 as f32, x2 as f32, y2 as f32,
);
}
let path = pb.finish();
g.stroke(
&path,
&Source::Solid(SolidSource::from(color)),
&stroke_style,
&draw_options,
);
}
}
pub fn get_font(&mut self) -> Arc<Font> {
if let Some(font) = font::get_font(&self.font_name) {
font
} else {
font::get_font(self.font_names[0]).unwrap()
}
}
pub fn get_font_size(&mut self) -> f32 {
self.font_size
}
pub fn set_font_by_enum(&mut self, font: CaptchaFont, size: Option<f32>) {
let font_name = self.font_names[font as usize];
self.font_name = font_name.into();
self.font_size = size.unwrap_or(32.);
}
}
pub trait NewCaptcha
where
Self: Sized,
{
fn new() -> Self;
fn with_size(width: i32, height: i32) -> Self;
fn with_size_and_len(width: i32, height: i32, len: usize) -> Self;
fn with_all(width: i32, height: i32, len: usize, font: CaptchaFont, font_size: f32) -> Self;
}
impl NewCaptcha for Captcha {
fn new() -> Self {
let color = [
(0, 135, 255),
(51, 153, 51),
(255, 102, 102),
(255, 153, 0),
(153, 102, 0),
(153, 102, 153),
(51, 153, 153),
(102, 102, 255),
(0, 102, 204),
(204, 51, 51),
(0, 153, 204),
(0, 51, 102),
]
.iter()
.map(|v| (*v).into())
.collect();
let font_names = [
"actionj.ttf",
"epilog.ttf",
"fresnel.ttf",
"headache.ttf",
"lexo.ttf",
"prefix.ttf",
"progbot.ttf",
"ransom.ttf",
"robot.ttf",
"scandal.ttf",
];
let font_name = font_names[0].into();
let font_size = 32.;
let len = 5;
let width = 130;
let height = 48;
let char_type = CaptchaType::TypeDefault;
let chars = None;
Self {
randoms: Randoms::new(),
color,
font_names,
font_name,
font_size,
len,
width,
height,
char_type,
chars,
}
}
fn with_size(width: i32, height: i32) -> Self {
let mut _self = Self::new();
_self.width = width;
_self.height = height;
_self
}
fn with_size_and_len(width: i32, height: i32, len: usize) -> Self {
let mut _self = Self::new();
_self.width = width;
_self.height = height;
_self.len = len;
_self
}
fn with_all(width: i32, height: i32, len: usize, font: CaptchaFont, font_size: f32) -> Self {
let mut _self = Self::new();
_self.width = width;
_self.height = height;
_self.len = len;
_self.set_font_by_enum(font, None);
_self.font_size = font_size;
_self
}
}
pub trait AbstractCaptcha: NewCaptcha {
type Error: std::error::Error + Debug + Send + Sync + 'static;
fn out(&mut self, out: impl Write) -> Result<(), Self::Error>;
fn get_chars(&mut self) -> Vec<char>;
fn base64(&mut self) -> Result<String, Self::Error>;
fn get_content_type(&mut self) -> String;
fn base64_with_head(&mut self, head: &str) -> Result<String, Self::Error> {
let mut output_stream = Vec::new();
self.out(&mut output_stream)?;
Ok(String::from(head) + &BASE64_STANDARD.encode(&output_stream))
}
}