1use colored::{Color, Colorize};
2
3pub struct BrailleCanvas {
4 pub width: usize,
5 pub height: usize,
6 grid: Vec<Vec<u8>>,
8 color_grid: Vec<Vec<Option<Color>>>,
10 text_layer: Vec<Vec<Option<char>>>,
12}
13
14impl BrailleCanvas {
15 pub fn new(width: usize, height: usize) -> Self {
16 Self {
17 width,
18 height,
19 grid: vec![vec![0u8; width]; height],
20 color_grid: vec![vec![None; width]; height],
22 text_layer: vec![vec![None; width]; height],
23 }
24 }
25
26 pub fn clear(&mut self) {
27 for row in self.grid.iter_mut() {
28 row.fill(0);
29 }
30 for row in self.color_grid.iter_mut() {
31 row.fill(None);
32 }
33 for row in self.text_layer.iter_mut() {
34 row.fill(None);
35 }
36 }
37
38 pub fn set_pixel(&mut self, px: usize, py: usize, color: Option<Color>) {
40 let pixel_width = self.width * 2;
41 let pixel_height = self.height * 4;
42
43 if px >= pixel_width || py >= pixel_height {
44 return;
45 }
46
47 let col_char = px / 2;
49 let row_char = (pixel_height - 1 - py) / 4;
51
52 if row_char >= self.height || col_char >= self.width {
53 return;
54 }
55
56 let dx = px % 2;
58 let dy = (pixel_height - 1 - py) % 4;
59
60 let mask = match (dx, dy) {
62 (0, 0) => 0x01,
63 (1, 0) => 0x08,
64 (0, 1) => 0x02,
65 (1, 1) => 0x10,
66 (0, 2) => 0x04,
67 (1, 2) => 0x20,
68 (0, 3) => 0x40,
69 (1, 3) => 0x80,
70 _ => 0,
71 };
72
73 self.grid[row_char][col_char] |= mask;
74
75 if let Some(c) = color {
77 self.color_grid[row_char][col_char] = Some(c);
78 }
79 }
80
81 pub fn line(&mut self, x0: isize, y0: isize, x1: isize, y1: isize, color: Option<Color>) {
83 let dx = (x1 - x0).abs();
84 let dy = -(y1 - y0).abs();
85 let sx = if x0 < x1 { 1 } else { -1 };
86 let sy = if y0 < y1 { 1 } else { -1 };
87 let mut err = dx + dy;
88 let mut x = x0;
89 let mut y = y0;
90
91 loop {
92 if x >= 0 && y >= 0 {
93 self.set_pixel(x as usize, y as usize, color);
94 }
95 if x == x1 && y == y1 {
96 break;
97 }
98 let e2 = 2 * err;
99 if e2 >= dy {
100 err += dy;
101 x += sx;
102 }
103 if e2 <= dx {
104 err += dx;
105 y += sy;
106 }
107 }
108 }
109
110 pub fn circle(&mut self, xc: isize, yc: isize, r: isize, color: Option<Color>) {
112 let mut x = 0;
113 let mut y = r;
114 let mut d = 3 - 2 * r;
115
116 let mut draw_octants = |cx: isize, cy: isize, x: isize, y: isize| {
118 let points = [
119 (cx + x, cy + y),
120 (cx - x, cy + y),
121 (cx + x, cy - y),
122 (cx - x, cy - y),
123 (cx + y, cy + x),
124 (cx - y, cy + x),
125 (cx + y, cy - x),
126 (cx - y, cy - x),
127 ];
128 for (px, py) in points {
129 if px >= 0 && py >= 0 {
130 self.set_pixel(px as usize, py as usize, color);
131 }
132 }
133 };
134
135 draw_octants(xc, yc, x, y);
136 while y >= x {
137 x += 1;
138 if d > 0 {
139 y -= 1;
140 d = d + 4 * (x - y) + 10;
141 } else {
142 d = d + 4 * x + 6;
143 }
144 draw_octants(xc, yc, x, y);
145 }
146 }
147
148 pub fn set_char(&mut self, char_x: usize, char_y: usize, c: char, color: Option<Color>) {
149 let row = self.height.saturating_sub(1).saturating_sub(char_y);
151
152 if row < self.height && char_x < self.width {
153 self.text_layer[row][char_x] = Some(c);
154 if let Some(col) = color {
155 self.color_grid[row][char_x] = Some(col);
156 }
157 }
158 }
159
160 pub fn set_char_vertical(
161 &mut self,
162 char_x: usize,
163 char_y_start: usize,
164 text: &str,
165 color: Option<Color>,
166 ) {
167 for (i, ch) in text.chars().enumerate() {
168 let y = char_y_start.saturating_sub(i);
169 self.set_char(char_x, y, ch, color); }
171 }
172 pub fn render(&self) -> String {
173 self.render_with_options(true, None)
174 }
175
176 pub fn render_no_color(&self) -> String {
178 let mut output = String::new();
179 for row_masks in &self.grid {
180 for &mask in row_masks {
181 let ch = std::char::from_u32(0x2800 + mask as u32).unwrap_or(' ');
182 output.push(ch);
183 }
184 output.push('\n');
185 }
186 output
187 }
188
189 pub fn render_with_options(&self, show_border: bool, title: Option<&str>) -> String {
191 let mut output = String::new();
192
193 if let Some(t) = title {
195 output.push_str(&format!("{:^width$}\n", t, width = self.width + 2));
196 }
197
198 if show_border {
200 output.push_str(&format!("┌{}┐\n", "─".repeat(self.width)));
201 }
202
203 for (r_idx, row_masks) in self.grid.iter().enumerate() {
204 if show_border {
205 output.push('│');
206 }
207
208 for (c_idx, &mask) in row_masks.iter().enumerate() {
209 let char_to_print = if let Some(c) = self.text_layer[r_idx][c_idx] {
210 c.to_string()
211 } else {
212 std::char::from_u32(0x2800 + mask as u32)
213 .unwrap_or(' ')
214 .to_string()
215 };
216
217 if let Some(color) = self.color_grid[r_idx][c_idx] {
218 output.push_str(&char_to_print.color(color).to_string());
219 } else {
220 output.push_str(&char_to_print);
221 }
222 }
223
224 if show_border {
225 output.push('│');
226 }
227 output.push('\n');
228 }
229
230 if show_border {
232 output.push_str(&format!("└{}┘", "─".repeat(self.width)));
233 }
234
235 output
236 }
237}