use std::fmt;
use crate::console::{Console, ConsoleOptions, Renderable};
use crate::measure::Measurement;
use crate::segment::Segment;
use crate::style::Style;
const PIXEL_MAP: [[u8; 2]; 4] = [
[0x01, 0x08], [0x02, 0x10], [0x04, 0x20], [0x40, 0x80], ];
const BRAILLE_BASE: u32 = 0x2800;
#[derive(Debug, Clone)]
pub struct Canvas {
width: usize,
height: usize,
pixels: Vec<Vec<u8>>,
style: Style,
}
impl Canvas {
pub fn new(width: usize, height: usize) -> Self {
Self {
width,
height,
pixels: vec![vec![0u8; width]; height],
style: Style::null(),
}
}
#[must_use]
pub fn with_style(mut self, style: Style) -> Self {
self.style = style;
self
}
pub fn pixel_width(&self) -> usize {
self.width * 2
}
pub fn pixel_height(&self) -> usize {
self.height * 4
}
pub fn set(&mut self, x: usize, y: usize) {
if x >= self.pixel_width() || y >= self.pixel_height() {
return;
}
let col = x / 2;
let row = y / 4;
let bit = PIXEL_MAP[y % 4][x % 2];
self.pixels[row][col] |= bit;
}
pub fn unset(&mut self, x: usize, y: usize) {
if x >= self.pixel_width() || y >= self.pixel_height() {
return;
}
let col = x / 2;
let row = y / 4;
let bit = PIXEL_MAP[y % 4][x % 2];
self.pixels[row][col] &= !bit;
}
pub fn toggle(&mut self, x: usize, y: usize) {
if x >= self.pixel_width() || y >= self.pixel_height() {
return;
}
let col = x / 2;
let row = y / 4;
let bit = PIXEL_MAP[y % 4][x % 2];
self.pixels[row][col] ^= bit;
}
pub fn get(&self, x: usize, y: usize) -> bool {
if x >= self.pixel_width() || y >= self.pixel_height() {
return false;
}
let col = x / 2;
let row = y / 4;
let bit = PIXEL_MAP[y % 4][x % 2];
self.pixels[row][col] & bit != 0
}
pub fn line(&mut self, x0: i32, y0: i32, x1: i32, y1: i32) {
let mut x0 = x0;
let mut y0 = y0;
let dx = (x1 - x0).abs();
let dy = -(y1 - y0).abs();
let sx: i32 = if x0 < x1 { 1 } else { -1 };
let sy: i32 = if y0 < y1 { 1 } else { -1 };
let mut err = dx + dy;
loop {
if x0 >= 0 && y0 >= 0 {
self.set(x0 as usize, y0 as usize);
}
if x0 == x1 && y0 == y1 {
break;
}
let e2 = 2 * err;
if e2 >= dy {
if x0 == x1 {
break;
}
err += dy;
x0 += sx;
}
if e2 <= dx {
if y0 == y1 {
break;
}
err += dx;
y0 += sy;
}
}
}
pub fn rect(&mut self, x: usize, y: usize, w: usize, h: usize) {
if w == 0 || h == 0 {
return;
}
let x1 = x + w - 1;
let y1 = y + h - 1;
self.line(x as i32, y as i32, x1 as i32, y as i32); self.line(x as i32, y1 as i32, x1 as i32, y1 as i32); self.line(x as i32, y as i32, x as i32, y1 as i32); self.line(x1 as i32, y as i32, x1 as i32, y1 as i32); }
pub fn fill_rect(&mut self, x: usize, y: usize, w: usize, h: usize) {
for dy in 0..h {
for dx in 0..w {
self.set(x + dx, y + dy);
}
}
}
pub fn circle(&mut self, cx: i32, cy: i32, r: i32) {
if r < 0 {
return;
}
let mut x = r;
let mut y: i32 = 0;
let mut err = 1 - r;
while x >= y {
self.set_signed(cx + x, cy + y);
self.set_signed(cx - x, cy + y);
self.set_signed(cx + x, cy - y);
self.set_signed(cx - x, cy - y);
self.set_signed(cx + y, cy + x);
self.set_signed(cx - y, cy + x);
self.set_signed(cx + y, cy - x);
self.set_signed(cx - y, cy - x);
y += 1;
if err <= 0 {
err += 2 * y + 1;
} else {
x -= 1;
err += 2 * (y - x) + 1;
}
}
}
fn set_signed(&mut self, x: i32, y: i32) {
if x >= 0 && y >= 0 {
self.set(x as usize, y as usize);
}
}
pub fn frame(&self) -> String {
let mut lines: Vec<String> = Vec::with_capacity(self.height);
for row in &self.pixels {
let line: String = row
.iter()
.map(|&bits| {
char::from_u32(BRAILLE_BASE + bits as u32).unwrap_or(' ')
})
.collect();
lines.push(line);
}
lines.join("\n")
}
pub fn clear(&mut self) {
for row in &mut self.pixels {
for cell in row.iter_mut() {
*cell = 0;
}
}
}
}
impl fmt::Display for Canvas {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.frame())
}
}
impl Renderable for Canvas {
fn gilt_console(&self, _console: &Console, _options: &ConsoleOptions) -> Vec<Segment> {
let mut segments = Vec::new();
for (i, row) in self.pixels.iter().enumerate() {
let line: String = row
.iter()
.map(|&bits| char::from_u32(BRAILLE_BASE + bits as u32).unwrap_or(' '))
.collect();
segments.push(Segment::new(&line, Some(self.style.clone()), None));
if i < self.height - 1 {
segments.push(Segment::line());
}
}
segments.push(Segment::line());
segments
}
}
impl Canvas {
pub fn measure(&self, _console: &Console, _options: &ConsoleOptions) -> Measurement {
Measurement::new(self.width, self.width)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::console::{Console, ConsoleDimensions, ConsoleOptions};
fn make_options(max_width: usize) -> ConsoleOptions {
ConsoleOptions {
size: ConsoleDimensions {
width: max_width,
height: 25,
},
legacy_windows: false,
min_width: 1,
max_width,
is_terminal: false,
encoding: "utf-8".to_string(),
max_height: 25,
justify: None,
overflow: None,
no_wrap: false,
highlight: None,
markup: None,
height: None,
}
}
#[test]
fn test_empty_canvas() {
let c = Canvas::new(3, 2);
let frame = c.frame();
let empty_braille = '\u{2800}';
let expected = format!(
"{}{}{}",
std::iter::repeat_n(empty_braille, 3).collect::<String>(),
"\n",
std::iter::repeat_n(empty_braille, 3).collect::<String>(),
);
assert_eq!(frame, expected);
}
#[test]
fn test_single_pixel_set() {
let mut c = Canvas::new(1, 1);
c.set(0, 0);
assert!(c.get(0, 0));
let expected_char = char::from_u32(BRAILLE_BASE + 0x01).unwrap();
assert_eq!(c.frame(), expected_char.to_string());
}
#[test]
fn test_pixel_unset() {
let mut c = Canvas::new(1, 1);
c.set(0, 0);
assert!(c.get(0, 0));
c.unset(0, 0);
assert!(!c.get(0, 0));
}
#[test]
fn test_pixel_toggle() {
let mut c = Canvas::new(1, 1);
assert!(!c.get(0, 0));
c.toggle(0, 0);
assert!(c.get(0, 0));
c.toggle(0, 0);
assert!(!c.get(0, 0));
}
#[test]
fn test_pixel_get_unset() {
let c = Canvas::new(2, 2);
assert!(!c.get(0, 0));
assert!(!c.get(3, 7));
}
#[test]
fn test_braille_mapping() {
let dots: [(usize, usize, u8); 8] = [
(0, 0, 0x01), (0, 1, 0x02), (0, 2, 0x04), (0, 3, 0x40), (1, 0, 0x08), (1, 1, 0x10), (1, 2, 0x20), (1, 3, 0x80), ];
for (px, py, expected_bit) in dots {
let mut c = Canvas::new(1, 1);
c.set(px, py);
assert_eq!(
c.pixels[0][0], expected_bit,
"pixel ({px},{py}) should set bit 0x{expected_bit:02x}"
);
}
}
#[test]
fn test_line_horizontal() {
let mut c = Canvas::new(5, 1);
c.line(0, 0, 9, 0); for x in 0..10 {
assert!(c.get(x, 0), "pixel ({x}, 0) should be set");
}
}
#[test]
fn test_line_vertical() {
let mut c = Canvas::new(1, 2);
c.line(0, 0, 0, 7); for y in 0..8 {
assert!(c.get(0, y), "pixel (0, {y}) should be set");
}
}
#[test]
fn test_line_diagonal() {
let mut c = Canvas::new(4, 2);
c.line(0, 0, 7, 7);
assert!(c.get(0, 0));
assert!(c.get(7, 7));
}
#[test]
fn test_rect_outline() {
let mut c = Canvas::new(4, 2);
c.rect(0, 0, 8, 8);
assert!(c.get(0, 0));
assert!(c.get(7, 0));
assert!(c.get(0, 7));
assert!(c.get(7, 7));
assert!(!c.get(3, 3));
}
#[test]
fn test_fill_rect() {
let mut c = Canvas::new(2, 1);
c.fill_rect(0, 0, 4, 4);
for y in 0..4 {
for x in 0..4 {
assert!(c.get(x, y), "pixel ({x},{y}) should be set");
}
}
}
#[test]
fn test_circle() {
let mut c = Canvas::new(10, 5);
c.circle(10, 10, 8);
assert!(c.get(18, 10));
assert!(c.get(10, 18));
}
#[test]
fn test_out_of_bounds() {
let mut c = Canvas::new(2, 2);
c.set(100, 100);
c.unset(100, 100);
c.toggle(100, 100);
assert!(!c.get(100, 100));
}
#[test]
fn test_clear() {
let mut c = Canvas::new(3, 2);
c.fill_rect(0, 0, 6, 8);
c.clear();
for row in &c.pixels {
for &cell in row {
assert_eq!(cell, 0);
}
}
}
#[test]
fn test_frame_multiline() {
let c = Canvas::new(2, 3);
let frame = c.frame();
let lines: Vec<&str> = frame.split('\n').collect();
assert_eq!(lines.len(), 3);
for line in lines {
assert_eq!(line.chars().count(), 2);
}
}
#[test]
fn test_display_trait() {
let c = Canvas::new(2, 2);
let displayed = format!("{c}");
assert_eq!(displayed, c.frame());
}
#[test]
fn test_renderable() {
let c = Canvas::new(3, 2);
let console = Console::builder().width(80).build();
let opts = make_options(80);
let segments = c.gilt_console(&console, &opts);
assert!(!segments.is_empty());
assert_eq!(segments.last().unwrap().text.as_str(), "\n");
}
#[test]
fn test_renderable_style() {
let style = Style::parse("bold green").unwrap();
let c = Canvas::new(2, 1).with_style(style.clone());
let console = Console::builder().width(80).build();
let opts = make_options(80);
let segments = c.gilt_console(&console, &opts);
assert_eq!(segments[0].style.as_ref(), Some(&style));
}
#[test]
fn test_measure() {
let c = Canvas::new(20, 10);
let console = Console::builder().width(80).build();
let opts = make_options(80);
let m = c.measure(&console, &opts);
assert_eq!(m, Measurement::new(20, 20));
}
#[test]
fn test_pixel_dimensions() {
let c = Canvas::new(10, 5);
assert_eq!(c.pixel_width(), 20);
assert_eq!(c.pixel_height(), 20);
}
#[test]
fn test_multiple_pixels_same_cell() {
let mut c = Canvas::new(1, 1);
c.set(0, 0); c.set(1, 0); assert_eq!(c.pixels[0][0], 0x01 | 0x08);
assert!(c.get(0, 0));
assert!(c.get(1, 0));
}
#[test]
fn test_circle_radius_zero() {
let mut c = Canvas::new(2, 2);
c.circle(2, 2, 0);
assert!(c.get(2, 2));
}
#[test]
fn test_circle_negative_radius() {
let mut c = Canvas::new(2, 2);
c.circle(2, 2, -5);
for y in 0..c.pixel_height() {
for x in 0..c.pixel_width() {
assert!(!c.get(x, y));
}
}
}
#[test]
fn test_rect_zero() {
let mut c = Canvas::new(2, 2);
c.rect(0, 0, 0, 0);
for y in 0..c.pixel_height() {
for x in 0..c.pixel_width() {
assert!(!c.get(x, y));
}
}
}
#[test]
fn test_full_braille_char() {
let mut c = Canvas::new(1, 1);
for y in 0..4 {
for x in 0..2 {
c.set(x, y);
}
}
assert_eq!(c.pixels[0][0], 0xFF);
let ch = char::from_u32(BRAILLE_BASE + 0xFF).unwrap();
assert_eq!(c.frame(), ch.to_string());
}
}