use std::fmt;
#[derive(Debug, PartialEq, Eq)]
pub struct Display {
width: usize,
height: usize,
cells: Vec<u8>,
}
impl Display {
pub fn with_dot_size(width: usize, height: usize) -> Self {
assert!(width % 2 == 0, "width must be a multiple of 2");
assert!(height % 4 == 0, "height must be a multiple of 4");
Self::with_output_size(width / 2, height / 4)
}
pub fn with_output_size(width: usize, height: usize) -> Self {
let cells = vec![0; width * height];
Self {
width,
height,
cells,
}
}
pub fn dot_width(&self) -> usize {
self.width * 2
}
pub fn dot_height(&self) -> usize {
self.height * 4
}
pub fn dot_size(&self) -> (usize, usize) {
(self.dot_width(), self.dot_height())
}
pub fn output_width(&self) -> usize {
self.width
}
pub fn output_height(&self) -> usize {
self.height
}
pub fn output_size(&self) -> (usize, usize) {
(self.width, self.height)
}
pub fn lines(&self) -> DisplayLines<'_> {
DisplayLines {
display: self,
index: 0,
}
}
pub fn is_set(&self, x: usize, y: usize) -> bool {
let ((cell_x, subcell_x), (cell_y, subcell_y)) = Self::parse_coord(x, y);
let i = self.coord_to_index(cell_x, cell_y);
braille_util::is_set(self.cells[i], subcell_x, subcell_y)
}
pub fn set(&mut self, x: usize, y: usize) {
let ((cell_x, subcell_x), (cell_y, subcell_y)) = Self::parse_coord(x, y);
let i = self.coord_to_index(cell_x, cell_y);
braille_util::set_coord(&mut self.cells[i], subcell_x, subcell_y);
}
pub fn unset(&mut self, x: usize, y: usize) {
let ((cell_x, subcell_x), (cell_y, subcell_y)) = Self::parse_coord(x, y);
let i = self.coord_to_index(cell_x, cell_y);
braille_util::unset_coord(&mut self.cells[i], subcell_x, subcell_y);
}
pub fn clear(&mut self) {
self.cells.fill(0);
}
fn parse_coord(x: usize, y: usize) -> ((usize, usize), (usize, usize)) {
((x / 2, x % 2), (y / 4, y % 4))
}
fn coord_to_index(&self, x: usize, y: usize) -> usize {
y * self.width + x
}
}
pub struct DisplayLines<'a> {
display: &'a Display,
index: usize,
}
impl Iterator for DisplayLines<'_> {
type Item = String;
fn next(&mut self) -> Option<Self::Item> {
if self.index >= self.display.height {
return None;
}
let start = self.display.coord_to_index(0, self.index);
let end = start + self.display.width;
let line = self.display.cells[start..end]
.iter()
.map(|&i| braille_util::get_char(i))
.collect();
self.index += 1;
Some(line)
}
}
impl From<&Display> for String {
fn from(value: &Display) -> Self {
value.lines().collect::<Vec<_>>().join("\n")
}
}
impl From<Display> for String {
fn from(value: Display) -> Self {
String::from(&value)
}
}
impl fmt::Display for Display {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", String::from(self))
}
}
mod braille_util {
const BRAILLE_UNICODE_OFFSET: u32 = 0x2800;
const TRANSFORMATION_MATRIX: [u32; 8] = [0x01, 0x02, 0x04, 0x40, 0x08, 0x10, 0x20, 0x80];
const fn gen_braille_table() -> [char; 256] {
let mut table = ['\0'; 256];
let mut i = 0;
while i < table.len() {
let mut u = BRAILLE_UNICODE_OFFSET;
let mut j = 0;
while j < 8 {
if i & (1 << j) != 0 {
u += TRANSFORMATION_MATRIX[j];
}
j += 1;
}
table[i] = char::from_u32(u).expect("logic error in lookup table generation");
i += 1;
}
table
}
const LOOKUP_TABLE: [char; 256] = gen_braille_table();
pub const fn get_char(i: u8) -> char {
LOOKUP_TABLE[i as usize]
}
pub fn is_set(i: u8, x: usize, y: usize) -> bool {
let mask = 1 << (4 * x + y);
i & mask != 0
}
pub fn set_mask(i: &mut u8, mask: u8) {
*i |= mask;
}
pub fn set_coord(i: &mut u8, x: usize, y: usize) {
let mask = 1 << (4 * x + y);
set_mask(i, mask);
}
pub fn unset_mask(i: &mut u8, mask: u8) {
*i &= mask;
}
pub fn unset_coord(i: &mut u8, x: usize, y: usize) {
let mask = !(1 << (4 * x + y));
unset_mask(i, mask);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn braille_lookup() {
assert_eq!(get_char(0b1110_1111), '⣷');
}
#[test]
fn set() {
let mut i: u8 = 0;
set_mask(&mut i, 0b1110_1111);
assert_eq!(get_char(i), '⣷');
i = 15;
assert_eq!(get_char(i), '⡇');
set_coord(&mut i, 1, 2);
assert_eq!(get_char(i), '⡧');
}
#[test]
fn unset() {
let mut i: u8 = 79;
assert_eq!(get_char(i), '⡧');
unset_coord(&mut i, 0, 1);
unset_coord(&mut i, 0, 2);
assert_eq!(get_char(i), '⡡');
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new() {
let display = Display::with_dot_size(4, 4);
assert_eq!(
display,
Display {
width: 2,
height: 1,
cells: vec![0, 0],
}
);
}
#[test]
fn display_lines() {
let mut display = Display::with_dot_size(4, 4);
display.cells[0] = 0b1001_1111;
display.cells[1] = 0b1111_1001;
let lines: Vec<_> = display.lines().collect();
assert_eq!(lines, vec!["⣏⣹"]);
}
#[test]
fn set() {
let mut display = Display::with_dot_size(8, 8);
for i in 0..8 {
display.set(i, i);
}
let lines: Vec<_> = display.lines().collect();
assert_eq!(
lines,
vec![
"⠑⢄⠀⠀", "⠀⠀⠑⢄", ]
)
}
}