1use colored::Color;
2use std::fmt::{self, Write};
3
4#[derive(Clone, Copy, PartialEq, Eq)]
5pub enum ColorBlend {
6 Overwrite,
8 KeepFirst,
10}
11
12pub struct BrailleCanvas {
13 pub width: usize,
14 pub height: usize,
15 pub blend_mode: ColorBlend,
16 buffer: Vec<u8>,
17 colors: Vec<Option<Color>>,
18 text_layer: Vec<Option<char>>,
19}
20
21impl BrailleCanvas {
22 pub fn new(width: usize, height: usize) -> Self {
23 let size = width * height;
24 Self {
25 width,
26 height,
27 blend_mode: ColorBlend::Overwrite,
28 buffer: vec![0u8; size],
29 colors: vec![None; size],
30 text_layer: vec![None; size],
31 }
32 }
33
34 #[inline]
35 pub fn pixel_width(&self) -> usize {
36 self.width * 2
37 }
38
39 #[inline]
40 pub fn pixel_height(&self) -> usize {
41 self.height * 4
42 }
43
44 pub fn clear(&mut self) {
45 self.buffer.fill(0);
46 self.colors.fill(None);
47 self.text_layer.fill(None);
48 }
49
50 #[inline]
53 fn idx(&self, col: usize, row: usize) -> usize {
54 row * self.width + col
55 }
56
57 #[inline]
58 fn get_mask(sub_x: usize, sub_y: usize) -> u8 {
59 match (sub_x, sub_y) {
60 (0, 0) => 0x01, (1, 0) => 0x08,
61 (0, 1) => 0x02, (1, 1) => 0x10,
62 (0, 2) => 0x04, (1, 2) => 0x20,
63 (0, 3) => 0x40, (1, 3) => 0x80,
64 _ => 0,
65 }
66 }
67
68 fn set_pixel_impl(&mut self, px: usize, py: usize, color: Option<Color>) {
69 if px >= self.pixel_width() || py >= self.pixel_height() {
70 return;
71 }
72
73 let index = self.idx(px / 2, py / 4);
74 self.buffer[index] |= Self::get_mask(px % 2, py % 4);
75
76 if let Some(c) = color {
77 match self.blend_mode {
78 ColorBlend::Overwrite => self.colors[index] = Some(c),
79 ColorBlend::KeepFirst => {
80 if self.colors[index].is_none() {
81 self.colors[index] = Some(c);
82 }
83 }
84 }
85 }
86 }
87
88 fn unset_pixel_impl(&mut self, px: usize, py: usize) {
89 if px >= self.pixel_width() || py >= self.pixel_height() {
90 return;
91 }
92 let index = self.idx(px / 2, py / 4);
93 self.buffer[index] &= !Self::get_mask(px % 2, py % 4);
94 if self.buffer[index] == 0 {
95 self.colors[index] = None;
96 }
97 }
98
99 pub fn set_pixel(&mut self, x: usize, y: usize, color: Option<Color>) {
102 let inverted_y = self.pixel_height().saturating_sub(1).saturating_sub(y);
103 self.set_pixel_impl(x, inverted_y, color);
104 }
105
106 pub fn set_pixel_screen(&mut self, x: usize, y: usize, color: Option<Color>) {
107 self.set_pixel_impl(x, y, color);
108 }
109
110 pub fn unset_pixel(&mut self, x: usize, y: usize) {
111 let inverted_y = self.pixel_height().saturating_sub(1).saturating_sub(y);
112 self.unset_pixel_impl(x, inverted_y);
113 }
114
115 pub fn unset_pixel_screen(&mut self, x: usize, y: usize) {
116 self.unset_pixel_impl(x, y);
117 }
118
119 pub fn toggle_pixel_screen(&mut self, x: usize, y: usize, color: Option<Color>) {
120 if x >= self.pixel_width() || y >= self.pixel_height() { return; }
121 let index = self.idx(x / 2, y / 4);
122 let mask = Self::get_mask(x % 2, y % 4);
123
124 if (self.buffer[index] & mask) != 0 {
125 self.unset_pixel_impl(x, y);
126 } else {
127 self.set_pixel_impl(x, y, color);
128 }
129 }
130
131 fn compute_outcode(&self, x: isize, y: isize) -> u8 {
134 let mut code = 0;
135 let w = self.pixel_width() as isize;
136 let h = self.pixel_height() as isize;
137
138 if x < 0 { code |= 1; } else if x >= w { code |= 2; }
139 if y < 0 { code |= 4; } else if y >= h { code |= 8; }
140 code
141 }
142
143 fn bresenham(&mut self, mut x0: isize, mut y0: isize, mut x1: isize, mut y1: isize, color: Option<Color>, cartesian: bool) {
144 let w = self.pixel_width() as isize;
145 let h = self.pixel_height() as isize;
146
147 let mut outcode0 = self.compute_outcode(x0, y0);
149 let mut outcode1 = self.compute_outcode(x1, y1);
150 let mut accept = false;
151
152 loop {
153 if (outcode0 | outcode1) == 0 {
154 accept = true; break;
155 } else if (outcode0 & outcode1) != 0 {
156 break;
157 } else {
158 let outcode_out = if outcode0 != 0 { outcode0 } else { outcode1 };
159 let mut x = 0;
160 let mut y = 0;
161
162 if outcode_out & 8 != 0 {
163 x = x0 + (x1 - x0) * (h - 1 - y0) / (y1 - y0);
164 y = h - 1;
165 } else if outcode_out & 4 != 0 {
166 x = x0 + (x1 - x0) * (0 - y0) / (y1 - y0);
167 y = 0;
168 } else if outcode_out & 2 != 0 {
169 y = y0 + (y1 - y0) * (w - 1 - x0) / (x1 - x0);
170 x = w - 1;
171 } else if outcode_out & 1 != 0 {
172 y = y0 + (y1 - y0) * (0 - x0) / (x1 - x0);
173 x = 0;
174 }
175
176 if outcode_out == outcode0 {
177 x0 = x; y0 = y;
178 outcode0 = self.compute_outcode(x0, y0);
179 } else {
180 x1 = x; y1 = y;
181 outcode1 = self.compute_outcode(x1, y1);
182 }
183 }
184 }
185
186 if !accept { return; }
187
188 let dx = (x1 - x0).abs();
189 let dy = -(y1 - y0).abs();
190 let sx = if x0 < x1 { 1 } else { -1 };
191 let sy = if y0 < y1 { 1 } else { -1 };
192 let mut err = dx + dy;
193
194 let mut x = x0;
195 let mut y = y0;
196
197 loop {
198 if cartesian {
199 self.set_pixel(x as usize, y as usize, color);
200 } else {
201 self.set_pixel_screen(x as usize, y as usize, color);
202 }
203 if x == x1 && y == y1 { break; }
204 let e2 = 2 * err;
205 if e2 >= dy { err += dy; x += sx; }
206 if e2 <= dx { err += dx; y += sy; }
207 }
208 }
209
210 pub fn line(&mut self, x0: isize, y0: isize, x1: isize, y1: isize, color: Option<Color>) {
211 self.bresenham(x0, y0, x1, y1, color, true);
212 }
213
214 pub fn line_screen(&mut self, x0: isize, y0: isize, x1: isize, y1: isize, color: Option<Color>) {
215 self.bresenham(x0, y0, x1, y1, color, false);
216 }
217
218 pub fn rect(&mut self, x: isize, y: isize, w: usize, h: usize, color: Option<Color>) {
221 let x1 = x + w as isize - 1;
222 let y1 = y + h as isize - 1;
223 self.line_screen(x, y, x1, y, color);
224 self.line_screen(x1, y, x1, y1, color);
225 self.line_screen(x1, y1, x, y1, color);
226 self.line_screen(x, y1, x, y, color);
227 }
228
229 pub fn rect_filled(&mut self, x: isize, y: isize, w: usize, h: usize, color: Option<Color>) {
230 let max_y = y + h as isize;
231 for cy in y..max_y {
232 self.line_screen(x, cy, x + w as isize - 1, cy, color);
233 }
234 }
235
236 pub fn circle(&mut self, xc: isize, yc: isize, r: isize, color: Option<Color>) {
237 let mut x = 0;
238 let mut y = r;
239 let mut d = 3 - 2 * r;
240
241 let mut draw_octants = |cx: isize, cy: isize, x: isize, y: isize| {
242 let points = [
243 (cx + x, cy + y), (cx - x, cy + y), (cx + x, cy - y), (cx - x, cy - y),
244 (cx + y, cy + x), (cx - y, cy + x), (cx + y, cy - x), (cx - y, cy - x),
245 ];
246 for (px, py) in points {
247 if px >= 0 && py >= 0 {
248 self.set_pixel(px as usize, py as usize, color);
249 }
250 }
251 };
252
253 draw_octants(xc, yc, x, y);
254 while y >= x {
255 x += 1;
256 if d > 0 {
257 y -= 1;
258 d = d + 4 * (x - y) + 10;
259 } else {
260 d = d + 4 * x + 6;
261 }
262 draw_octants(xc, yc, x, y);
263 }
264 }
265
266 pub fn circle_filled(&mut self, xc: isize, yc: isize, r: isize, color: Option<Color>) {
267 let mut x = 0;
268 let mut y = r;
269 let mut d = 3 - 2 * r;
270
271 let mut draw_lines = |cx: isize, cy: isize, x: isize, y: isize| {
272 self.line(cx - x, cy + y, cx + x, cy + y, color);
273 self.line(cx - x, cy - y, cx + x, cy - y, color);
274 self.line(cx - y, cy + x, cx + y, cy + x, color);
275 self.line(cx - y, cy - x, cx + y, cy - x, color);
276 };
277
278 draw_lines(xc, yc, x, y);
279 while y >= x {
280 x += 1;
281 if d > 0 {
282 y -= 1;
283 d = d + 4 * (x - y) + 10;
284 } else {
285 d = d + 4 * x + 6;
286 }
287 draw_lines(xc, yc, x, y);
288 }
289 }
290
291 pub fn set_char(&mut self, col: usize, row: usize, c: char, color: Option<Color>) {
292 let inverted_row = self.height.saturating_sub(1).saturating_sub(row);
293 if col < self.width && inverted_row < self.height {
294 let idx = self.idx(col, inverted_row);
295 self.text_layer[idx] = Some(c);
296 if let Some(col_val) = color {
297 self.colors[idx] = Some(col_val);
298 }
299 }
300 }
301
302 fn write_ansi_color<W: Write>(w: &mut W, color: Color) -> fmt::Result {
306 match color {
307 Color::Black => w.write_str("\x1b[30m"),
308 Color::Red => w.write_str("\x1b[31m"),
309 Color::Green => w.write_str("\x1b[32m"),
310 Color::Yellow => w.write_str("\x1b[33m"),
311 Color::Blue => w.write_str("\x1b[34m"),
312 Color::Magenta => w.write_str("\x1b[35m"),
313 Color::Cyan => w.write_str("\x1b[36m"),
314 Color::White => w.write_str("\x1b[37m"),
315 Color::BrightBlack => w.write_str("\x1b[90m"),
316 Color::BrightRed => w.write_str("\x1b[91m"),
317 Color::BrightGreen => w.write_str("\x1b[92m"),
318 Color::BrightYellow => w.write_str("\x1b[93m"),
319 Color::BrightBlue => w.write_str("\x1b[94m"),
320 Color::BrightMagenta => w.write_str("\x1b[95m"),
321 Color::BrightCyan => w.write_str("\x1b[96m"),
322 Color::BrightWhite => w.write_str("\x1b[97m"),
323 Color::TrueColor { r, g, b } => write!(w, "\x1b[38;2;{};{};{}m", r, g, b),
324 }
325 }
326
327 pub fn render_to<W: Write>(&self, w: &mut W, show_border: bool, title: Option<&str>) -> fmt::Result {
328 if let Some(t) = title {
329 writeln!(w, "{:^width$}", t, width = self.width + 2)?;
330 }
331
332 if show_border {
333 w.write_char('┌')?;
334 for _ in 0..self.width { w.write_char('─')?; }
335 w.write_char('┐')?;
336 w.write_char('\n')?;
337 }
338
339 let mut last_color: Option<Color> = None;
340
341 for row in 0..self.height {
342 if show_border { w.write_char('│')?; }
343
344 for col in 0..self.width {
345 let idx = self.idx(col, row);
346
347 let char_to_print = if let Some(c) = self.text_layer[idx] {
348 c
349 } else {
350 let mask = self.buffer[idx];
351 std::char::from_u32(0x2800 + mask as u32).unwrap_or(' ')
352 };
353
354 let current_color = self.colors[idx];
355
356 if current_color != last_color {
357 match current_color {
358 Some(c) => Self::write_ansi_color(w, c)?,
359 None => w.write_str("\x1b[0m")?,
360 }
361 last_color = current_color;
362 }
363
364 w.write_char(char_to_print)?;
365 }
366
367 if last_color.is_some() {
368 w.write_str("\x1b[0m")?;
369 last_color = None;
370 }
371
372 if show_border { w.write_char('│')?; }
373 w.write_char('\n')?;
374 }
375
376 if show_border {
377 w.write_char('└')?;
378 for _ in 0..self.width { w.write_char('─')?; }
379 w.write_char('┘')?;
380 }
381
382 Ok(())
383 }
384
385 pub fn render_with_options(&self, show_border: bool, title: Option<&str>) -> String {
386 let mut out = String::with_capacity(self.width * self.height * 2 + 100);
387 let _ = self.render_to(&mut out, show_border, title);
388 out
389 }
390
391 pub fn render(&self) -> String {
392 self.render_with_options(true, None)
393 }
394
395 pub fn render_no_color(&self) -> String {
396 let mut out = String::with_capacity(self.width * self.height + self.height);
397 for row in 0..self.height {
398 for col in 0..self.width {
399 let mask = self.buffer[self.idx(col, row)];
400 out.push(std::char::from_u32(0x2800 + mask as u32).unwrap_or(' '));
401 }
402 out.push('\n');
403 }
404 out
405 }
406}