diff --git a/crates/servicepoint/examples/announce.rs b/crates/servicepoint/examples/announce.rs index 4159384..05d2b19 100644 --- a/crates/servicepoint/examples/announce.rs +++ b/crates/servicepoint/examples/announce.rs @@ -40,20 +40,8 @@ fn main() { .expect("sending clear failed"); } - let text = cli - .text - .iter() - .flat_map(move |x| { - x.chars() - .collect::>() - .chunks(TILE_WIDTH) - .map(|c| String::from_iter(c)) - .collect::>() - }) - .collect::>() - .join("\n"); - - let grid = CharGrid::from(text); + let text = cli.text.join("\n"); + let grid = CharGrid::wrap_str(TILE_WIDTH, &text); connection .send(Command::Utf8Data(Origin::ZERO, grid)) .expect("sending text failed"); diff --git a/crates/servicepoint/src/bitmap.rs b/crates/servicepoint/src/bitmap.rs index 807e2ad..079410f 100644 --- a/crates/servicepoint/src/bitmap.rs +++ b/crates/servicepoint/src/bitmap.rs @@ -9,12 +9,13 @@ use ::bitvec::slice::IterMut; /// /// The values are stored in packed bytes (8 values per byte) in the same order as used by the display for storing pixels. /// This means that no conversion is necessary for sending the data to the display. +/// The downside is that the width has to be a multiple of 8. /// /// # Examples /// /// ```rust /// use servicepoint::Bitmap; -/// let mut bitmap = Bitmap::new(4, 2); +/// let mut bitmap = Bitmap::new(8, 2); /// /// ``` #[derive(Debug, Clone, PartialEq, Eq)] @@ -192,12 +193,18 @@ impl From for Vec { } impl From for BitVec { + /// Turns a [Bitmap] into the underlying [BitVec]. fn from(value: Bitmap) -> Self { value.bit_vec } } impl From<&ValueGrid> for Bitmap { + /// Converts a grid of [bool]s into a [Bitmap]. + /// + /// # Panics + /// + /// - when the width of `value` is not dividable by 8 fn from(value: &ValueGrid) -> Self { let mut result = Self::new(value.width(), value.height()); for (mut to, from) in result.iter_mut().zip(value.iter()) { @@ -208,6 +215,7 @@ impl From<&ValueGrid> for Bitmap { } impl From<&Bitmap> for ValueGrid { + /// Converts a [Bitmap] into a grid of [bool]s. fn from(value: &Bitmap) -> Self { let mut result = Self::new(value.width(), value.height()); for (to, from) in result.iter_mut().zip(value.iter()) { diff --git a/crates/servicepoint/src/char_grid.rs b/crates/servicepoint/src/char_grid.rs index 717e70b..d1a3fd7 100644 --- a/crates/servicepoint/src/char_grid.rs +++ b/crates/servicepoint/src/char_grid.rs @@ -3,9 +3,9 @@ use std::string::FromUtf8Error; /// A grid containing UTF-8 characters. /// -/// To send a CharGrid to the display, use [crate::Command::Utf8Data]. +/// To send a CharGrid to the display, use [Command::Utf8Data](crate::Command::Utf8Data). /// -/// Also see [crate::ValueGrid] for the non-specialized operations and examples. +/// Also see [ValueGrid] for the non-specialized operations and examples. /// /// # Examples /// @@ -20,9 +20,52 @@ use std::string::FromUtf8Error; pub type CharGrid = ValueGrid; impl CharGrid { + /// Loads a [CharGrid] with the specified width from the provided text, wrapping to as many rows as needed. + /// + /// The passed rows are extended with '\0' if needed. + /// + /// returns: [CharGrid] that contains a copy of the provided data. + /// + /// # Examples + /// + /// ``` + /// # use servicepoint::CharGrid; + /// let grid = CharGrid::wrap_str(2, "abc\ndef"); + /// ``` + pub fn wrap_str(width: usize, text: &str) -> Self { + let lines = text + .split('\n') + .flat_map(move |x| { + x.chars() + .collect::>() + .chunks(width) + .map(|c| { + let mut s = String::from_iter(c); + s.push_str(&"\0".repeat(width - s.chars().count())); + s + }) + .collect::>() + }) + .collect::>(); + let height = lines.len(); + let mut result = Self::new(width, height); + for (row, text_line) in lines.iter().enumerate() { + result.set_row_str(row, text_line).unwrap() + } + result + } + /// Copies a column from the grid as a String. /// /// Returns [None] if x is out of bounds. + /// + /// # Examples + /// + /// ``` + /// # use servicepoint::CharGrid; + /// let grid = CharGrid::from("ab\ncd"); + /// let col = grid.get_col_str(0).unwrap(); // "ac" + /// ``` pub fn get_col_str(&self, x: usize) -> Option { Some(String::from_iter(self.get_col(x)?)) } @@ -30,6 +73,14 @@ impl CharGrid { /// Copies a row from the grid as a String. /// /// Returns [None] if y is out of bounds. + /// + /// # Examples + /// + /// ``` + /// # use servicepoint::CharGrid; + /// let grid = CharGrid::from("ab\ncd"); + /// let row = grid.get_row_str(0).unwrap(); // "ab" + /// ``` pub fn get_row_str(&self, y: usize) -> Option { Some(String::from_iter(self.get_row(y)?)) } @@ -37,6 +88,14 @@ impl CharGrid { /// Overwrites a row in the grid with a str. /// /// Returns [SetValueSeriesError] if y is out of bounds or `row` is not of the correct size. + /// + /// # Examples + /// + /// ``` + /// # use servicepoint::CharGrid; + /// let mut grid = CharGrid::from("ab\ncd"); + /// grid.set_row_str(0, "ef").unwrap(); + /// ``` pub fn set_row_str( &mut self, y: usize, @@ -48,6 +107,14 @@ impl CharGrid { /// Overwrites a column in the grid with a str. /// /// Returns [SetValueSeriesError] if y is out of bounds or `row` is not of the correct size. + /// + /// # Examples + /// + /// ``` + /// # use servicepoint::CharGrid; + /// let mut grid = CharGrid::from("ab\ncd"); + /// grid.set_col_str(0, "ef").unwrap(); + /// ``` pub fn set_col_str( &mut self, x: usize, @@ -60,9 +127,12 @@ impl CharGrid { /// /// returns: [CharGrid] that contains the provided data, or [FromUtf8Error] if the data is invalid. /// - /// # Panics + /// # Examples /// - /// - when the dimensions and data size do not match exactly. + /// ``` + /// # use servicepoint::CharGrid; + /// let grid = CharGrid::load_utf8(2, 2, [97u8, 98, 99, 100].to_vec()); + /// ``` pub fn load_utf8( width: usize, height: usize, @@ -117,6 +187,18 @@ impl From for String { } impl From<&CharGrid> for String { + /// Converts a [CharGrid] into a [String]. + /// + /// Rows are separated by '\n'. + /// + /// # Examples + /// + /// ```rust + /// # use servicepoint::CharGrid; + /// let grid = CharGrid::from("ab\ncd"); + /// let string = String::from(grid); + /// let grid = CharGrid::from(string); + /// ``` fn from(value: &CharGrid) -> Self { value .iter_rows() @@ -127,12 +209,26 @@ impl From<&CharGrid> for String { } impl From<&CharGrid> for Vec { + /// Converts a [CharGrid] into a [`Vec`]. + /// + /// Rows are not separated. + /// + /// # Examples + /// + /// ```rust + /// # use servicepoint::{CharGrid, Grid}; + /// let grid = CharGrid::from("ab\ncd"); + /// let height = grid.height(); + /// let width = grid.width(); + /// let grid = CharGrid::load_utf8(width, height, grid.into()); + /// ``` fn from(value: &CharGrid) -> Self { String::from_iter(value.iter()).into_bytes() } } impl From for Vec { + /// See [`From<&CharGrid>::from`]. fn from(value: CharGrid) -> Self { Self::from(&value) } @@ -192,4 +288,11 @@ mod test { let copy = CharGrid::from(str); assert_eq!(grid, copy); } + + #[test] + fn wrap_str() { + let grid = CharGrid::wrap_str(2, "abc\ndef"); + assert_eq!(4, grid.height()); + assert_eq!("ab\nc\0\nde\nf\0", String::from(grid)); + } } diff --git a/crates/servicepoint/src/cp437_grid.rs b/crates/servicepoint/src/cp437_grid.rs index 22b3293..99506bc 100644 --- a/crates/servicepoint/src/cp437_grid.rs +++ b/crates/servicepoint/src/cp437_grid.rs @@ -90,6 +90,12 @@ mod feature_cp437 { value.map(Cp437Converter::cp437_to_char) } } + + impl From for CharGrid { + fn from(value: Cp437Grid) -> Self { + Self::from(&value) + } + } impl From<&CharGrid> for Cp437Grid { fn from(value: &CharGrid) -> Self { @@ -99,7 +105,7 @@ mod feature_cp437 { impl From for Cp437Grid { fn from(value: CharGrid) -> Self { - Cp437Grid::from(&value) + Self::from(&value) } } } @@ -150,8 +156,8 @@ mod tests_feature_cp437 { #[test] fn round_trip_cp437() { let utf8 = CharGrid::load(2, 2, &['Ä', 'x', '\n', '$']); - let cp437 = Cp437Grid::from(&utf8); - let actual = CharGrid::from(&cp437); + let cp437 = Cp437Grid::from(utf8.clone()); + let actual = CharGrid::from(cp437); assert_eq!(actual, utf8); } } diff --git a/crates/servicepoint/src/value_grid.rs b/crates/servicepoint/src/value_grid.rs index e6c92e8..a3400ae 100644 --- a/crates/servicepoint/src/value_grid.rs +++ b/crates/servicepoint/src/value_grid.rs @@ -79,6 +79,27 @@ impl ValueGrid { } } + /// Loads a [ValueGrid] with the specified width from the provided data, wrapping to as many rows as needed. + /// + /// returns: [ValueGrid] that contains a copy of the provided data or [TryLoadValueGridError]. + /// + /// # Examples + /// + /// ``` + /// # use servicepoint::ValueGrid; + /// let grid = ValueGrid::wrap(2, &[0, 1, 2, 3, 4, 5]).unwrap(); + /// ``` + pub fn wrap( + width: usize, + data: &[T], + ) -> Result { + let len = data.len(); + if len % width != 0 { + return Err(TryLoadValueGridError::InvalidDimensions); + } + Ok(Self::load(width, len / width, data)) + } + /// Loads a [ValueGrid] with the specified dimensions from the provided data. /// /// returns: [ValueGrid] that contains a copy of the provided data or [TryLoadValueGridError]. @@ -277,7 +298,7 @@ impl ValueGrid { } /// Errors that can occur when loading a grid -#[derive(Debug, thiserror::Error)] +#[derive(Debug, thiserror::Error, PartialEq)] pub enum TryLoadValueGridError { #[error("The provided dimensions do not match with the data size")] /// The provided dimensions do not match with the data size @@ -537,4 +558,13 @@ mod tests { }) ); } + + #[test] + fn wrap() { + let grid = ValueGrid::wrap(2, &[0, 1, 2, 3, 4, 5]).unwrap(); + assert_eq!(grid.height(), 3); + + let grid = ValueGrid::wrap(4, &[0, 1, 2, 3, 4, 5]); + assert_eq!(grid.err(), Some(TryLoadValueGridError::InvalidDimensions)); + } } diff --git a/crates/servicepoint_binding_c/examples/lang_c/src/main.c b/crates/servicepoint_binding_c/examples/lang_c/src/main.c index e84e510..1454804 100644 --- a/crates/servicepoint_binding_c/examples/lang_c/src/main.c +++ b/crates/servicepoint_binding_c/examples/lang_c/src/main.c @@ -2,7 +2,7 @@ #include "servicepoint.h" int main(void) { - SPConnection *connection = sp_connection_open("172.23.42.29:2342"); + SPConnection *connection = sp_connection_open("localhost:2342"); if (connection == NULL) return 1; @@ -10,9 +10,8 @@ int main(void) { sp_bitmap_fill(pixels, true); SPCommand *command = sp_command_bitmap_linear_win(0, 0, pixels, SP_COMPRESSION_CODE_UNCOMPRESSED); - while (sp_connection_send_command(connection, sp_command_clone(command))); + sp_connection_send_command(connection, command); - sp_command_free(command); sp_connection_free(connection); return 0; }