diff --git a/crates/servicepoint/src/command.rs b/crates/servicepoint/src/command.rs index 6b821e9..6095bb3 100644 --- a/crates/servicepoint/src/command.rs +++ b/crates/servicepoint/src/command.rs @@ -4,18 +4,13 @@ use crate::{ command_code::CommandCode, compression::into_decompressed, packet::{Header, Packet}, - Brightness, BrightnessGrid, CompressionCode, Origin, PixelGrid, Pixels, - PrimitiveGrid, SpBitVec, Tiles, TILE_SIZE, + Brightness, BrightnessGrid, CompressionCode, Cp437Grid, Origin, PixelGrid, + Pixels, PrimitiveGrid, SpBitVec, Tiles, TILE_SIZE, }; /// Type alias for documenting the meaning of the u16 in enum values pub type Offset = usize; -/// A grid containing codepage 437 characters. -/// -/// The encoding is currently not enforced. -pub type Cp437Grid = PrimitiveGrid; - /// A low-level display command. /// /// This struct and associated functions implement the UDP protocol for the display. @@ -92,9 +87,8 @@ pub enum Command { /// /// ```rust /// # use servicepoint::{Command, Connection, Cp437Grid, Origin}; - /// # let connection = Connection::open("127.0.0.1:2342").unwrap(); - /// let chars = ['H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd'].map(move |c| c as u8); - /// let grid = Cp437Grid::load(5, 2, &chars); + /// # let connection = Connection::Fake; + /// let grid = Cp437Grid::load_ascii("Hello\nWorld", 5, false).unwrap(); /// connection.send(Command::Cp437Data(Origin::new(2, 2), grid)).unwrap(); /// ``` Cp437Data(Origin, Cp437Grid), diff --git a/crates/servicepoint/src/cp437.rs b/crates/servicepoint/src/cp437.rs new file mode 100644 index 0000000..b0fd991 --- /dev/null +++ b/crates/servicepoint/src/cp437.rs @@ -0,0 +1,100 @@ +use crate::cp437::Cp437LoadError::InvalidChar; +use crate::{Grid, PrimitiveGrid}; + +/// A grid containing codepage 437 characters. +/// +/// The encoding is currently not enforced. +pub type Cp437Grid = PrimitiveGrid; + +#[derive(Debug)] +pub enum Cp437LoadError { + InvalidChar { index: usize, char: char }, +} + +impl Cp437Grid { + /// Load an ASCII-only [&str] into a [Cp437Grid] of specified width. + /// + /// # Panics + /// + /// - for width == 0 + /// - on empty strings + pub fn load_ascii( + value: &str, + width: usize, + wrap: bool, + ) -> Result { + assert!(width > 0); + assert!(!value.is_empty()); + + let mut chars = { + let mut x = 0; + let mut y = 0; + + for (index, char) in value.chars().enumerate() { + if !char.is_ascii() { + return Err(InvalidChar { index, char }); + } + + let is_lf = char == '\n'; + if is_lf || (wrap && x == width) { + y += 1; + x = 0; + if is_lf { + continue; + } + } + + x += 1; + } + + Cp437Grid::new(width, y + 1) + }; + + let mut x = 0; + let mut y = 0; + for char in value.chars().map(move |c| c as u8) { + let is_lf = char == b'\n'; + if is_lf || (wrap && x == width) { + y += 1; + x = 0; + if is_lf { + continue; + } + } + + if wrap || x < width { + chars.set(x, y, char); + } + x += 1; + } + + Ok(chars) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn load_ascii_nowrap() { + let chars = ['H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd'] + .map(move |c| c as u8); + let expected = Cp437Grid::load(5, 2, &chars); + + let actual = Cp437Grid::load_ascii("Hello,\nWorld!", 5, false).unwrap(); + // comma will be removed because line is too long and wrap is off + assert_eq!(actual, expected); + } + + #[test] + fn load_ascii_wrap() { + let chars = ['H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd'] + .map(move |c| c as u8); + let expected = Cp437Grid::load(5, 2, &chars); + + let actual = Cp437Grid::load_ascii("HelloWorld", 5, true).unwrap(); + // line break will be added + assert_eq!(actual, expected); + } +} diff --git a/crates/servicepoint/src/lib.rs b/crates/servicepoint/src/lib.rs index 4cceea8..4ed440f 100644 --- a/crates/servicepoint/src/lib.rs +++ b/crates/servicepoint/src/lib.rs @@ -41,9 +41,10 @@ pub use bitvec; use bitvec::prelude::{BitVec, Msb0}; pub use crate::brightness::{Brightness, BrightnessGrid}; -pub use crate::command::{Command, Cp437Grid, Offset}; +pub use crate::command::{Command, Offset}; pub use crate::compression_code::CompressionCode; pub use crate::connection::Connection; +pub use crate::cp437::Cp437Grid; pub use crate::data_ref::DataRef; pub use crate::grid::Grid; pub use crate::origin::{Origin, Pixels, Tiles}; @@ -58,6 +59,7 @@ mod command_code; mod compression; mod compression_code; mod connection; +mod cp437; mod data_ref; mod grid; mod origin; diff --git a/crates/servicepoint_binding_c/src/cp437_grid.rs b/crates/servicepoint_binding_c/src/cp437_grid.rs index a8d4684..781c345 100644 --- a/crates/servicepoint_binding_c/src/cp437_grid.rs +++ b/crates/servicepoint_binding_c/src/cp437_grid.rs @@ -40,7 +40,9 @@ pub unsafe extern "C" fn sp_cp437_grid_new( width: usize, height: usize, ) -> *mut SPCp437Grid { - Box::into_raw(Box::new(SPCp437Grid(servicepoint::Cp437Grid::new(width, height)))) + Box::into_raw(Box::new(SPCp437Grid(servicepoint::Cp437Grid::new( + width, height, + )))) } /// Loads a `SPCp437Grid` with the specified dimensions from the provided data. @@ -67,7 +69,9 @@ pub unsafe extern "C" fn sp_cp437_grid_load( data_length: usize, ) -> *mut SPCp437Grid { let data = std::slice::from_raw_parts(data, data_length); - Box::into_raw(Box::new(SPCp437Grid(servicepoint::Cp437Grid::load(width, height, data)))) + Box::into_raw(Box::new(SPCp437Grid(servicepoint::Cp437Grid::load( + width, height, data, + )))) } /// Clones a `SPCp437Grid`.