1mod model;
29mod resource;
30use model::Command;
31use rand::{
32 rng,
33 seq::{IndexedRandom, SliceRandom},
34 Rng,
35};
36use resource::{FONT_PATHS, FONT_TABLE};
37
38#[derive(Debug, Clone, Default)]
40pub struct BiosvgBuilder {
41 length: usize,
42 difficulty: u16,
43 colors: Vec<String>,
44}
45
46impl BiosvgBuilder {
47 pub fn new() -> BiosvgBuilder {
49 BiosvgBuilder::default()
50 }
51
52 pub fn length(mut self, length: usize) -> BiosvgBuilder {
54 self.length = length;
55 self
56 }
57
58 pub fn difficulty(mut self, difficulty: u16) -> BiosvgBuilder {
61 self.difficulty = difficulty;
62 self
63 }
64
65 pub fn colors(mut self, colors: Vec<String>) -> BiosvgBuilder {
70 self.colors = colors;
71 self
72 }
73
74 pub fn build(self) -> Result<(String, String), model::PathError> {
76 let mut answer = String::new();
78 let mut rng = rng();
79 for _ in 0..self.length {
80 let index = rng.random_range(0..FONT_TABLE.len());
81 answer.push(String::from(FONT_TABLE).chars().nth(index).unwrap());
82 }
83
84 let mut char_colors = Vec::new();
86 let mut line_colors = Vec::new();
87
88 let mut colors = self.colors.clone();
91 let last_color = colors.pop().unwrap();
92 for color in colors {
93 if rng.random_bool(0.5) {
94 char_colors.push(color);
95 } else {
96 line_colors.push(color);
97 }
98 }
99 if char_colors.len() > line_colors.len() {
100 line_colors.push(last_color);
101 } else {
102 char_colors.push(last_color);
103 }
104
105 let mut font_paths = Vec::new();
106 for ch in answer.chars() {
107 if let Some(path) = FONT_PATHS.get(ch.to_string().as_str()) {
108 let random_angle = rng.random_range(-0.2..0.2 * std::f64::consts::PI);
109 let random_offset = rng.random_range(0.0..0.1 * path.width);
111 let random_color = char_colors.choose(&mut rng).unwrap();
112 let random_scale_x = rng.random_range(0.8..1.2);
113 let random_scale_y = rng.random_range(0.8..1.2);
114 let path = path
115 .with_color(random_color)
116 .scale(random_scale_x, random_scale_y)
117 .rotate(random_angle)
118 .offset(0.0, random_offset);
119
120 font_paths.push(path.clone())
121 }
122 }
123 let mut width = 0.0;
124 let mut height = 0.0;
125 for path in &font_paths {
126 width += path.width;
127 if path.height > height {
129 height = path.height;
130 }
131 }
132 width += 1.5 * height;
133 let mut start_point = height * 0.55;
134 let mut paths = Vec::new();
135 for path in font_paths {
136 let offset_x = start_point + path.width / 2.0;
137 let offset_y = (height * 1.5) / 2.0;
138 let mut random_splited_path = path.offset(offset_x, offset_y).random_split();
139 paths.append(random_splited_path.as_mut());
140 start_point += path.width + height * 0.4 / self.length as f64;
141 }
142 for _ in 1..self.difficulty {
143 let start_x = rng.random_range(0.0..width);
144 let end_x = rng.random_range(start_x..start_x + height);
145 let start_y = rng.random_range(0.0..height);
146 let end_y = rng.random_range(start_y..start_y + height);
147 let color = line_colors.choose(&mut rng).unwrap();
148 let start_command = Command {
149 x: start_x,
150 y: start_y,
151 command_type: model::CommandType::Move,
152 };
153 let end_command = Command {
154 x: end_x,
155 y: end_y,
156 command_type: model::CommandType::LineTo,
157 };
158 paths.push(model::Path {
159 commands: vec![start_command, end_command],
160 width,
161 height: height / 1.5,
162 color: color.clone(),
163 });
164 }
165 paths.shuffle(&mut rng);
166 let svg_content = paths
167 .iter()
168 .map(|path| path.to_string())
169 .collect::<Vec<String>>()
170 .join("");
171 Ok((
172 answer,
173 format!(
174 r#"<svg width="{}" height="{}" viewBox="0 0 {} {}" xmlns="http://www.w3.org/2000/svg" version="1.1">{}</svg>"#,
175 width,
176 height * 1.5,
177 width,
178 height * 1.5,
179 svg_content
180 ),
181 ))
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn it_works() {
191 let (answer, svg) = BiosvgBuilder::new()
192 .length(4)
193 .difficulty(6)
194 .colors(vec![
195 "#0078D6".to_string(),
196 "#aa3333".to_string(),
197 "#f08012".to_string(),
198 "#33aa00".to_string(),
199 "#aa33aa".to_string(),
200 ])
201 .build()
202 .unwrap();
203 println!("answer: {answer}");
204 println!("svg: {svg}");
205 }
206}