use std::borrow::Cow;
use std::fmt::{self, Display, Formatter};
use derive_more::{Display, LowerHex, UpperHex};
use thiserror::Error;
#[derive(Copy, Clone, Debug, Error)]
#[non_exhaustive]
pub enum PageError {
#[error(
"Wrong number of data bytes for a {}x{} page: Expected {}, got {}",
width,
height,
expected,
actual
)]
WrongPageLength {
width: u32,
height: u32,
expected: usize,
actual: usize,
},
}
const HEADER_LEN: usize = 4;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Page<'a> {
width: u32,
height: u32,
bytes: Cow<'a, [u8]>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Display, LowerHex, UpperHex)]
pub struct PageId(pub u8);
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
pub enum PageFlipStyle {
Automatic,
Manual,
}
impl<'a> Page<'a> {
pub fn new(id: PageId, width: u32, height: u32) -> Self {
let mut bytes = Vec::<u8>::with_capacity(Self::total_bytes(width, height));
bytes.extend_from_slice(&[id.0, 0x10, 0x00, 0x00]);
bytes.resize(Self::data_bytes(width, height), 0x00);
bytes.resize(Self::total_bytes(width, height), 0xFF);
Page {
width,
height,
bytes: bytes.into(),
}
}
pub fn from_bytes<T: Into<Cow<'a, [u8]>>>(width: u32, height: u32, bytes: T) -> Result<Self, PageError> {
let page = Page {
width,
height,
bytes: bytes.into(),
};
let expected_bytes = Self::total_bytes(width, height);
if page.bytes.len() != expected_bytes {
return Err(PageError::WrongPageLength {
width,
height,
expected: expected_bytes,
actual: page.bytes.len(),
});
}
Ok(page)
}
pub fn id(&self) -> PageId {
PageId(self.bytes[0])
}
pub fn width(&self) -> u32 {
self.width
}
pub fn height(&self) -> u32 {
self.height
}
pub fn get_pixel(&self, x: u32, y: u32) -> bool {
let (byte_index, bit_index) = self.byte_bit_indices(x, y);
let mask = 1 << bit_index;
let byte = &self.bytes[byte_index];
*byte & mask == mask
}
pub fn set_pixel(&mut self, x: u32, y: u32, value: bool) {
let (byte_index, bit_index) = self.byte_bit_indices(x, y);
let mask = 1 << bit_index;
let byte = &mut self.bytes.to_mut()[byte_index];
if value {
*byte |= mask;
} else {
*byte &= !mask;
}
}
pub fn set_all_pixels(&mut self, value: bool) {
let byte = if value { 0xFF } else { 0x00 };
self.bytes.to_mut()[HEADER_LEN..Self::data_bytes(self.width, self.height)].fill(byte);
}
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
}
fn bytes_per_column(height: u32) -> usize {
(height as usize + 7) / 8 }
fn data_bytes(width: u32, height: u32) -> usize {
HEADER_LEN + width as usize * Self::bytes_per_column(height)
}
fn total_bytes(width: u32, height: u32) -> usize {
(Self::data_bytes(width, height) + 15) / 16 * 16 }
fn byte_bit_indices(&self, x: u32, y: u32) -> (usize, u8) {
if x >= self.width || y >= self.height {
panic!(
"Coordinate ({}, {}) out of bounds for page of size {} x {}",
x, y, self.width, self.height
);
}
let byte_index = 4 + x as usize * Self::bytes_per_column(self.height) + y as usize / 8;
let bit_index = y % 8;
(byte_index, bit_index as u8)
}
}
impl Display for Page<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let border = str::repeat("-", self.width as usize);
writeln!(f, "+{}+", border)?;
for y in 0..self.height {
write!(f, "|")?;
for x in 0..self.width {
let dot = if self.get_pixel(x, y) { '@' } else { ' ' };
write!(f, "{}", dot)?;
}
writeln!(f, "|")?;
}
write!(f, "+{}+", border)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::error::Error;
use test_case::test_case;
#[test]
fn one_byte_per_column_empty() -> Result<(), Box<dyn Error>> {
let page = Page::new(PageId(3), 90, 7);
let bytes = page.as_bytes();
#[rustfmt::skip]
const EXPECTED: &[u8] = &[
0x03, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,
];
assert_eq!(bytes, EXPECTED);
let page2 = Page::from_bytes(90, 7, bytes)?;
assert_eq!(page, page2);
Ok(())
}
#[test]
fn two_bytes_per_column_empty() -> Result<(), Box<dyn Error>> {
let page = Page::new(PageId(1), 40, 12);
let bytes = page.as_bytes();
#[rustfmt::skip]
const EXPECTED: &[u8] = &[
0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
];
assert_eq!(bytes, EXPECTED);
let page2 = Page::from_bytes(40, 12, bytes)?;
assert_eq!(page, page2);
Ok(())
}
#[test]
fn one_byte_per_column_set_bits() -> Result<(), Box<dyn Error>> {
let mut page = Page::new(PageId(3), 90, 7);
page.set_pixel(0, 0, true);
page.set_pixel(89, 5, true);
page.set_pixel(89, 6, true);
page.set_pixel(4, 4, true);
page.set_pixel(4, 4, false);
let bytes = page.as_bytes();
#[rustfmt::skip]
const EXPECTED: &[u8] = &[
0x03, 0x10, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0xFF, 0xFF,
];
assert_eq!(bytes, EXPECTED);
let page2 = Page::from_bytes(90, 7, bytes)?;
assert_eq!(page, page2);
Ok(())
}
#[test]
fn two_bytes_per_column_set_bits() -> Result<(), Box<dyn Error>> {
let mut page = Page::new(PageId(1), 40, 12);
page.set_pixel(0, 0, true);
page.set_pixel(0, 11, true);
page.set_pixel(39, 5, true);
page.set_pixel(39, 6, true);
page.set_pixel(39, 8, true);
page.set_pixel(4, 4, true);
page.set_pixel(4, 4, false);
let bytes = page.as_bytes();
#[rustfmt::skip]
const EXPECTED: &[u8] = &[
0x01, 0x10, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x60, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
];
assert_eq!(bytes, EXPECTED);
let page2 = Page::from_bytes(40, 12, bytes)?;
assert_eq!(page, page2);
Ok(())
}
#[test]
fn wrong_size_rejected() {
let error = Page::from_bytes(90, 7, vec![0x01, 0x01, 0x03]).unwrap_err();
assert!(matches!(
error,
PageError::WrongPageLength {
expected: 96,
actual: 3,
..
}
));
}
#[test]
fn set_get_pixels() {
let mut page = Page::new(PageId(1), 16, 16);
page.set_pixel(0, 0, true);
assert_eq!(true, page.get_pixel(0, 0));
page.set_pixel(0, 0, false);
assert_eq!(false, page.get_pixel(0, 0));
page.set_pixel(13, 10, true);
assert_eq!(true, page.get_pixel(13, 10));
page.set_pixel(13, 10, false);
assert_eq!(false, page.get_pixel(13, 10));
}
#[test]
#[should_panic]
fn out_of_bounds_x() {
let mut page = Page::new(PageId(1), 8, 8);
page.set_pixel(9, 0, true);
}
#[test]
#[should_panic]
fn out_of_bounds_y() {
let mut page = Page::new(PageId(1), 8, 8);
page.set_pixel(0, 9, true);
}
#[test]
fn display() {
let mut page = Page::new(PageId(1), 2, 2);
page.set_pixel(0, 0, true);
page.set_pixel(1, 1, true);
let display = format!("{}", page);
let expected = "\
+--+\n\
|@ |\n\
| @|\n\
+--+";
assert_eq!(expected, display);
}
fn verify_all_pixels(page: &Page, value: bool) {
for x in 0..page.width() {
for y in 0..page.height() {
assert_eq!(value, page.get_pixel(x, y));
}
}
}
#[test_case(Page::new(PageId(3), 90, 7) ; "one byte per column")]
#[test_case(Page::new(PageId(1), 40, 12) ; "two bytes per column")]
fn set_all_pixels(mut page: Page) {
let bytes_before = page.as_bytes().to_vec();
verify_all_pixels(&page, false);
page.set_all_pixels(true);
verify_all_pixels(&page, true);
page.set_all_pixels(false);
verify_all_pixels(&page, false);
assert_eq!(bytes_before, page.as_bytes());
}
}