mirror of
https://github.com/cccb/servicepoint.git
synced 2025-01-18 10:00:14 +01:00
first CMD_UTF8_DATA implementation
UTF8 now works
This commit is contained in:
parent
38316169e9
commit
efaa52faa1
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -4,3 +4,4 @@ out
|
||||||
.direnv
|
.direnv
|
||||||
.envrc
|
.envrc
|
||||||
result
|
result
|
||||||
|
mutants.*
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
use servicepoint::{CharGrid, Command, Connection, Cp437Grid, Origin};
|
use servicepoint::{CharGrid, Command, Connection, Origin, TILE_WIDTH};
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
struct Cli {
|
struct Cli {
|
||||||
|
@ -41,11 +41,21 @@ fn main() {
|
||||||
.expect("sending clear failed");
|
.expect("sending clear failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
let text = cli.text.join("\n");
|
let text = cli
|
||||||
let grid = CharGrid::from(text);
|
.text
|
||||||
let grid = Cp437Grid::from(grid);
|
.iter()
|
||||||
|
.flat_map(move |x| {
|
||||||
|
x.chars()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.chunks(TILE_WIDTH)
|
||||||
|
.map(|c| String::from_iter(c))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
let grid = CharGrid::from(text);
|
||||||
connection
|
connection
|
||||||
.send(Command::Cp437Data(Origin::ZERO, grid))
|
.send(Command::Utf8Data(Origin::ZERO, grid))
|
||||||
.expect("sending text failed");
|
.expect("sending text failed");
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,11 +31,8 @@ fn main() {
|
||||||
let mut filled_grid = Bitmap::max_sized();
|
let mut filled_grid = Bitmap::max_sized();
|
||||||
filled_grid.fill(true);
|
filled_grid.fill(true);
|
||||||
|
|
||||||
let command = BitmapLinearWin(
|
let command =
|
||||||
Origin::ZERO,
|
BitmapLinearWin(Origin::ZERO, filled_grid, CompressionCode::Lzma);
|
||||||
filled_grid,
|
|
||||||
CompressionCode::Lzma,
|
|
||||||
);
|
|
||||||
connection.send(command).expect("send failed");
|
connection.send(command).expect("send failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,11 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
connection
|
connection
|
||||||
.send(Command::BitmapLinearWin(Origin::ZERO, enabled_pixels.clone(), CompressionCode::Lzma))
|
.send(Command::BitmapLinearWin(
|
||||||
|
Origin::ZERO,
|
||||||
|
enabled_pixels.clone(),
|
||||||
|
CompressionCode::Lzma,
|
||||||
|
))
|
||||||
.expect("could not send command to display");
|
.expect("could not send command to display");
|
||||||
thread::sleep(sleep_duration);
|
thread::sleep(sleep_duration);
|
||||||
}
|
}
|
||||||
|
|
|
@ -203,7 +203,7 @@ impl<'t> Iterator for IterRows<'t> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{Bitmap, DataRef, Grid};
|
use crate::{BitVec, Bitmap, DataRef, Grid};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fill() {
|
fn fill() {
|
||||||
|
@ -295,4 +295,12 @@ mod tests {
|
||||||
data[1] = 0x0F;
|
data[1] = 0x0F;
|
||||||
assert!(grid.get(7, 1));
|
assert!(grid.get(7, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn to_bitvec() {
|
||||||
|
let mut grid = Bitmap::new(8, 2);
|
||||||
|
grid.set(0, 0, true);
|
||||||
|
let bitvec: BitVec = grid.into();
|
||||||
|
assert_eq!(bitvec.as_raw_slice(), [0x80, 0x00]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{Grid, PrimitiveGrid};
|
use crate::primitive_grid::PrimitiveGrid;
|
||||||
|
use crate::{ByteGrid, Grid};
|
||||||
#[cfg(feature = "rand")]
|
#[cfg(feature = "rand")]
|
||||||
use rand::{
|
use rand::{
|
||||||
distributions::{Distribution, Standard},
|
distributions::{Distribution, Standard},
|
||||||
|
@ -40,7 +40,8 @@ pub type BrightnessGrid = PrimitiveGrid<Brightness>;
|
||||||
impl BrightnessGrid {
|
impl BrightnessGrid {
|
||||||
/// Like [Self::load], but ignoring any out-of-range brightness values
|
/// Like [Self::load], but ignoring any out-of-range brightness values
|
||||||
pub fn saturating_load(width: usize, height: usize, data: &[u8]) -> Self {
|
pub fn saturating_load(width: usize, height: usize, data: &[u8]) -> Self {
|
||||||
PrimitiveGrid::load(width, height, data).map(Brightness::saturating_from)
|
PrimitiveGrid::load(width, height, data)
|
||||||
|
.map(Brightness::saturating_from)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +102,7 @@ impl From<BrightnessGrid> for Vec<u8> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&BrightnessGrid> for PrimitiveGrid<u8> {
|
impl From<&BrightnessGrid> for ByteGrid {
|
||||||
fn from(value: &PrimitiveGrid<Brightness>) -> Self {
|
fn from(value: &PrimitiveGrid<Brightness>) -> Self {
|
||||||
let u8s = value
|
let u8s = value
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -111,10 +112,10 @@ impl From<&BrightnessGrid> for PrimitiveGrid<u8> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<PrimitiveGrid<u8>> for BrightnessGrid {
|
impl TryFrom<ByteGrid> for BrightnessGrid {
|
||||||
type Error = u8;
|
type Error = u8;
|
||||||
|
|
||||||
fn try_from(value: PrimitiveGrid<u8>) -> Result<Self, Self::Error> {
|
fn try_from(value: ByteGrid) -> Result<Self, Self::Error> {
|
||||||
let brightnesses = value
|
let brightnesses = value
|
||||||
.iter()
|
.iter()
|
||||||
.map(|b| Brightness::try_from(*b))
|
.map(|b| Brightness::try_from(*b))
|
||||||
|
@ -171,7 +172,18 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn saturating_load() {
|
fn saturating_load() {
|
||||||
assert_eq!(BrightnessGrid::load(2,2, &[Brightness::MAX, Brightness::MAX, Brightness::MIN, Brightness::MAX]),
|
assert_eq!(
|
||||||
BrightnessGrid::saturating_load(2,2, &[255u8, 23, 0, 42]));
|
BrightnessGrid::load(
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
&[
|
||||||
|
Brightness::MAX,
|
||||||
|
Brightness::MAX,
|
||||||
|
Brightness::MIN,
|
||||||
|
Brightness::MAX
|
||||||
|
]
|
||||||
|
),
|
||||||
|
BrightnessGrid::saturating_load(2, 2, &[255u8, 23, 0, 42])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
use crate::primitive_grid::SeriesError;
|
use crate::primitive_grid::{
|
||||||
use crate::{Grid, PrimitiveGrid};
|
PrimitiveGrid, SeriesError, TryLoadPrimitiveGridError,
|
||||||
|
};
|
||||||
|
use crate::Grid;
|
||||||
|
use std::string::FromUtf8Error;
|
||||||
|
|
||||||
/// A grid containing UTF-8 characters.
|
/// A grid containing UTF-8 characters.
|
||||||
pub type CharGrid = PrimitiveGrid<char>;
|
pub type CharGrid = PrimitiveGrid<char>;
|
||||||
|
@ -40,17 +43,39 @@ impl CharGrid {
|
||||||
) -> Result<(), SeriesError> {
|
) -> Result<(), SeriesError> {
|
||||||
self.set_col(x, value.chars().collect::<Vec<_>>().as_ref())
|
self.set_col(x, value.chars().collect::<Vec<_>>().as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Loads a [CharGrid] with the specified dimensions from the provided UTF-8 bytes.
|
||||||
|
///
|
||||||
|
/// returns: [CharGrid] that contains the provided data, or [FromUtf8Error] if the data is invalid.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// - when the dimensions and data size do not match exactly.
|
||||||
|
pub fn load_utf8(
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
bytes: Vec<u8>,
|
||||||
|
) -> Result<CharGrid, LoadUtf8Error> {
|
||||||
|
let s: Vec<char> = String::from_utf8(bytes)?.chars().collect();
|
||||||
|
Ok(CharGrid::try_load(width, height, s)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum LoadUtf8Error {
|
||||||
|
#[error(transparent)]
|
||||||
|
FromUtf8Error(#[from] FromUtf8Error),
|
||||||
|
#[error(transparent)]
|
||||||
|
TryLoadError(#[from] TryLoadPrimitiveGridError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&str> for CharGrid {
|
impl From<&str> for CharGrid {
|
||||||
fn from(value: &str) -> Self {
|
fn from(value: &str) -> Self {
|
||||||
let value = value.replace("\r\n", "\n");
|
let value = value.replace("\r\n", "\n");
|
||||||
let mut lines = value
|
let mut lines = value.split('\n').collect::<Vec<_>>();
|
||||||
.split('\n')
|
let width = lines
|
||||||
.map(move |line| line.trim_end())
|
.iter()
|
||||||
.collect::<Vec<_>>();
|
.fold(0, move |a, x| std::cmp::max(a, x.chars().count()));
|
||||||
let width =
|
|
||||||
lines.iter().fold(0, move |a, x| std::cmp::max(a, x.len()));
|
|
||||||
|
|
||||||
while lines.last().is_some_and(move |line| line.is_empty()) {
|
while lines.last().is_some_and(move |line| line.is_empty()) {
|
||||||
_ = lines.pop();
|
_ = lines.pop();
|
||||||
|
@ -73,22 +98,34 @@ impl From<String> for CharGrid {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<CharGrid> for String {
|
||||||
|
fn from(grid: CharGrid) -> Self {
|
||||||
|
String::from(&grid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<&CharGrid> for String {
|
impl From<&CharGrid> for String {
|
||||||
fn from(value: &CharGrid) -> Self {
|
fn from(value: &CharGrid) -> Self {
|
||||||
value
|
value
|
||||||
.iter_rows()
|
.iter_rows()
|
||||||
.map(move |chars| {
|
.map(String::from_iter)
|
||||||
chars
|
.collect::<Vec<String>>()
|
||||||
.collect::<String>()
|
|
||||||
.replace('\0', " ")
|
|
||||||
.trim_end()
|
|
||||||
.to_string()
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join("\n")
|
.join("\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<&CharGrid> for Vec<u8> {
|
||||||
|
fn from(value: &CharGrid) -> Self {
|
||||||
|
String::from_iter(value.iter()).into_bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CharGrid> for Vec<u8> {
|
||||||
|
fn from(value: CharGrid) -> Self {
|
||||||
|
Self::from(&value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -120,10 +157,28 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn str_to_char_grid() {
|
fn str_to_char_grid() {
|
||||||
let original = "Hello\r\nWorld!\n...\n";
|
// conversion with .to_string() covers one more line
|
||||||
|
let original = "Hello\r\nWorld!\n...\n".to_string();
|
||||||
|
|
||||||
let grid = CharGrid::from(original);
|
let grid = CharGrid::from(original);
|
||||||
assert_eq!(3, grid.height());
|
assert_eq!(3, grid.height());
|
||||||
let actual = String::from(&grid);
|
assert_eq!("Hello\0\nWorld!\n...\0\0\0", String::from(grid));
|
||||||
assert_eq!("Hello\nWorld!\n...", actual);
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn round_trip_bytes() {
|
||||||
|
let grid = CharGrid::from("Hello\0\nWorld!\n...\0\0\0");
|
||||||
|
let bytes: Vec<u8> = grid.clone().into();
|
||||||
|
let copy =
|
||||||
|
CharGrid::load_utf8(grid.width(), grid.height(), bytes).unwrap();
|
||||||
|
assert_eq!(grid, copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn round_trip_string() {
|
||||||
|
let grid = CharGrid::from("Hello\0\nWorld!\n...\0\0\0");
|
||||||
|
let str: String = grid.clone().into();
|
||||||
|
let copy = CharGrid::from(str);
|
||||||
|
assert_eq!(grid, copy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
|
use crate::primitive_grid::PrimitiveGrid;
|
||||||
use crate::{
|
use crate::{
|
||||||
command_code::CommandCode,
|
command_code::CommandCode,
|
||||||
compression::into_decompressed,
|
compression::into_decompressed,
|
||||||
packet::{Header, Packet},
|
packet::{Header, Packet},
|
||||||
Bitmap, Brightness, BrightnessGrid, CompressionCode, Cp437Grid, Origin,
|
BitVec, Bitmap, Brightness, BrightnessGrid, CharGrid, CompressionCode,
|
||||||
Pixels, PrimitiveGrid, BitVec, Tiles, TILE_SIZE,
|
Cp437Grid, Origin, Pixels, Tiles, TILE_SIZE,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Type alias for documenting the meaning of the u16 in enum values
|
/// Type alias for documenting the meaning of the u16 in enum values
|
||||||
|
@ -72,10 +73,27 @@ pub enum Command {
|
||||||
/// ```
|
/// ```
|
||||||
Clear,
|
Clear,
|
||||||
|
|
||||||
|
/// Show text on the screen.
|
||||||
|
///
|
||||||
|
/// The text is sent in the form of a 2D grid of UTF-8 encoded characters (the default encoding in rust).
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use servicepoint::{Command, Connection, Origin};
|
||||||
|
/// # let connection = Connection::Fake;
|
||||||
|
/// use servicepoint::{CharGrid};
|
||||||
|
/// let grid = CharGrid::from("Hello,\nWorld!");
|
||||||
|
/// connection.send(Command::Utf8Data(Origin::ZERO, grid)).expect("send failed");
|
||||||
|
/// ```
|
||||||
|
Utf8Data(Origin<Tiles>, CharGrid),
|
||||||
|
|
||||||
/// Show text on the screen.
|
/// Show text on the screen.
|
||||||
///
|
///
|
||||||
/// The text is sent in the form of a 2D grid of [CP-437] encoded characters.
|
/// The text is sent in the form of a 2D grid of [CP-437] encoded characters.
|
||||||
///
|
///
|
||||||
|
/// <div class="warning">You probably want to use [Command::Utf8Data] instead</div>
|
||||||
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
|
@ -234,6 +252,8 @@ pub enum TryFromPacketError {
|
||||||
/// The given brightness value is out of bounds
|
/// The given brightness value is out of bounds
|
||||||
#[error("The given brightness value {0} is out of bounds.")]
|
#[error("The given brightness value {0} is out of bounds.")]
|
||||||
InvalidBrightness(u8),
|
InvalidBrightness(u8),
|
||||||
|
#[error(transparent)]
|
||||||
|
InvalidUtf8(#[from] std::string::FromUtf8Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<Packet> for Command {
|
impl TryFrom<Packet> for Command {
|
||||||
|
@ -269,6 +289,7 @@ impl TryFrom<Packet> for Command {
|
||||||
CommandCode::CharBrightness => {
|
CommandCode::CharBrightness => {
|
||||||
Self::packet_into_char_brightness(&packet)
|
Self::packet_into_char_brightness(&packet)
|
||||||
}
|
}
|
||||||
|
CommandCode::Utf8Data => Self::packet_into_utf8(&packet),
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
CommandCode::BitmapLegacy => Ok(Command::BitmapLegacy),
|
CommandCode::BitmapLegacy => Ok(Command::BitmapLegacy),
|
||||||
CommandCode::BitmapLinear => {
|
CommandCode::BitmapLinear => {
|
||||||
|
@ -489,6 +510,28 @@ impl Command {
|
||||||
Cp437Grid::load(*c as usize, *d as usize, payload),
|
Cp437Grid::load(*c as usize, *d as usize, payload),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn packet_into_utf8(
|
||||||
|
packet: &Packet,
|
||||||
|
) -> Result<Command, TryFromPacketError> {
|
||||||
|
let Packet {
|
||||||
|
header:
|
||||||
|
Header {
|
||||||
|
command_code: _,
|
||||||
|
a,
|
||||||
|
b,
|
||||||
|
c,
|
||||||
|
d,
|
||||||
|
},
|
||||||
|
payload,
|
||||||
|
} = packet;
|
||||||
|
let payload: Vec<_> =
|
||||||
|
String::from_utf8(payload.clone())?.chars().collect();
|
||||||
|
Ok(Command::Utf8Data(
|
||||||
|
Origin::new(*a as usize, *b as usize),
|
||||||
|
CharGrid::load(*c as usize, *d as usize, &*payload),
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -499,8 +542,8 @@ mod tests {
|
||||||
command_code::CommandCode,
|
command_code::CommandCode,
|
||||||
origin::Pixels,
|
origin::Pixels,
|
||||||
packet::{Header, Packet},
|
packet::{Header, Packet},
|
||||||
Bitmap, Brightness, BrightnessGrid, Command, CompressionCode, Origin,
|
Bitmap, Brightness, BrightnessGrid, CharGrid, Command, CompressionCode,
|
||||||
PrimitiveGrid,
|
Cp437Grid, Origin,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn round_trip(original: Command) {
|
fn round_trip(original: Command) {
|
||||||
|
@ -556,16 +599,18 @@ mod tests {
|
||||||
fn round_trip_char_brightness() {
|
fn round_trip_char_brightness() {
|
||||||
round_trip(Command::CharBrightness(
|
round_trip(Command::CharBrightness(
|
||||||
Origin::new(5, 2),
|
Origin::new(5, 2),
|
||||||
PrimitiveGrid::new(7, 5),
|
BrightnessGrid::new(7, 5),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn round_trip_cp437_data() {
|
fn round_trip_cp437_data() {
|
||||||
round_trip(Command::Cp437Data(
|
round_trip(Command::Cp437Data(Origin::new(5, 2), Cp437Grid::new(7, 5)));
|
||||||
Origin::new(5, 2),
|
}
|
||||||
PrimitiveGrid::new(7, 5),
|
|
||||||
));
|
#[test]
|
||||||
|
fn round_trip_utf8_data() {
|
||||||
|
round_trip(Command::Utf8Data(Origin::new(5, 2), CharGrid::new(7, 5)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -21,6 +21,7 @@ pub(crate) enum CommandCode {
|
||||||
BitmapLinearWinBzip2 = 0x0018,
|
BitmapLinearWinBzip2 = 0x0018,
|
||||||
#[cfg(feature = "compression_lzma")]
|
#[cfg(feature = "compression_lzma")]
|
||||||
BitmapLinearWinLzma = 0x0019,
|
BitmapLinearWinLzma = 0x0019,
|
||||||
|
Utf8Data = 0x0020,
|
||||||
#[cfg(feature = "compression_zstd")]
|
#[cfg(feature = "compression_zstd")]
|
||||||
BitmapLinearWinZstd = 0x001A,
|
BitmapLinearWinZstd = 0x001A,
|
||||||
}
|
}
|
||||||
|
@ -93,6 +94,9 @@ impl TryFrom<u16> for CommandCode {
|
||||||
value if value == CommandCode::BitmapLinearWinBzip2 as u16 => {
|
value if value == CommandCode::BitmapLinearWinBzip2 as u16 => {
|
||||||
Ok(CommandCode::BitmapLinearWinBzip2)
|
Ok(CommandCode::BitmapLinearWinBzip2)
|
||||||
}
|
}
|
||||||
|
value if value == CommandCode::Utf8Data as u16 => {
|
||||||
|
Ok(CommandCode::Utf8Data)
|
||||||
|
}
|
||||||
_ => Err(()),
|
_ => Err(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,9 +107,7 @@ impl Connection {
|
||||||
|
|
||||||
let request = ClientRequestBuilder::new(uri).into_client_request()?;
|
let request = ClientRequestBuilder::new(uri).into_client_request()?;
|
||||||
let (sock, _) = connect(request)?;
|
let (sock, _) = connect(request)?;
|
||||||
Ok(Self::WebSocket(std::sync::Mutex::new(
|
Ok(Self::WebSocket(std::sync::Mutex::new(sock)))
|
||||||
sock,
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send something packet-like to the display. Usually this is in the form of a Command.
|
/// Send something packet-like to the display. Usually this is in the form of a Command.
|
||||||
|
@ -159,9 +157,7 @@ impl Drop for Connection {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
#[cfg(feature = "protocol_websocket")]
|
#[cfg(feature = "protocol_websocket")]
|
||||||
if let Connection::WebSocket(sock) = self {
|
if let Connection::WebSocket(sock) = self {
|
||||||
_ = sock
|
_ = sock.try_lock().map(move |mut sock| sock.close(None));
|
||||||
.try_lock()
|
|
||||||
.map(move |mut sock| sock.close(None));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
//!
|
//!
|
||||||
//! Most of the functionality is only available with feature "cp437" enabled.
|
//! Most of the functionality is only available with feature "cp437" enabled.
|
||||||
|
|
||||||
use crate::{Grid, PrimitiveGrid};
|
use crate::{Grid, primitive_grid::PrimitiveGrid};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
/// A grid containing codepage 437 characters.
|
/// A grid containing codepage 437 characters.
|
||||||
|
@ -12,7 +12,9 @@ pub type Cp437Grid = PrimitiveGrid<u8>;
|
||||||
|
|
||||||
/// The error occurring when loading an invalid character
|
/// The error occurring when loading an invalid character
|
||||||
#[derive(Debug, PartialEq, thiserror::Error)]
|
#[derive(Debug, PartialEq, thiserror::Error)]
|
||||||
#[error("The character {char:?} at position {index} is not a valid CP437 character")]
|
#[error(
|
||||||
|
"The character {char:?} at position {index} is not a valid CP437 character"
|
||||||
|
)]
|
||||||
pub struct InvalidCharError {
|
pub struct InvalidCharError {
|
||||||
/// invalid character is at this position in input
|
/// invalid character is at this position in input
|
||||||
index: usize,
|
index: usize,
|
||||||
|
|
|
@ -49,11 +49,13 @@ pub use crate::cp437::Cp437Grid;
|
||||||
pub use crate::data_ref::DataRef;
|
pub use crate::data_ref::DataRef;
|
||||||
pub use crate::grid::Grid;
|
pub use crate::grid::Grid;
|
||||||
pub use crate::origin::{Origin, Pixels, Tiles};
|
pub use crate::origin::{Origin, Pixels, Tiles};
|
||||||
pub use crate::primitive_grid::{PrimitiveGrid, SeriesError};
|
|
||||||
|
|
||||||
/// An alias for the specific type of [bitvec::prelude::BitVec] used.
|
/// An alias for the specific type of [bitvec::prelude::BitVec] used.
|
||||||
pub type BitVec = bitvec::prelude::BitVec<u8, bitvec::prelude::Msb0>;
|
pub type BitVec = bitvec::prelude::BitVec<u8, bitvec::prelude::Msb0>;
|
||||||
|
|
||||||
|
/// A simple grid of bytes - see [primitive_grid::PrimitiveGrid].
|
||||||
|
pub type ByteGrid = primitive_grid::PrimitiveGrid<u8>;
|
||||||
|
|
||||||
mod bitmap;
|
mod bitmap;
|
||||||
mod brightness;
|
mod brightness;
|
||||||
mod char_grid;
|
mod char_grid;
|
||||||
|
@ -67,7 +69,7 @@ mod data_ref;
|
||||||
mod grid;
|
mod grid;
|
||||||
mod origin;
|
mod origin;
|
||||||
pub mod packet;
|
pub mod packet;
|
||||||
mod primitive_grid;
|
pub mod primitive_grid;
|
||||||
|
|
||||||
/// size of a single tile in one dimension
|
/// size of a single tile in one dimension
|
||||||
pub const TILE_SIZE: usize = 8;
|
pub const TILE_SIZE: usize = 8;
|
||||||
|
|
|
@ -209,6 +209,9 @@ impl From<Command> for Packet {
|
||||||
grid,
|
grid,
|
||||||
CommandCode::Cp437Data,
|
CommandCode::Cp437Data,
|
||||||
),
|
),
|
||||||
|
Command::Utf8Data(origin, grid) => {
|
||||||
|
Self::origin_grid_to_packet(origin, grid, CommandCode::Utf8Data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
|
//! This module contains the implementation of the [PrimitiveGrid].
|
||||||
|
|
||||||
|
use std::fmt::Debug;
|
||||||
use std::slice::{Iter, IterMut};
|
use std::slice::{Iter, IterMut};
|
||||||
|
|
||||||
use crate::{DataRef, Grid};
|
use crate::{DataRef, Grid};
|
||||||
|
|
||||||
pub trait PrimitiveGridType: Sized + Default + Copy + Clone {}
|
/// A type that can be stored in a [PrimitiveGrid], e.g. [char], [u8].
|
||||||
impl<T: Sized + Default + Copy + Clone> PrimitiveGridType for T {}
|
pub trait PrimitiveGridType: Sized + Default + Copy + Clone + Debug {}
|
||||||
|
impl<T: Sized + Default + Copy + Clone + Debug> PrimitiveGridType for T {}
|
||||||
|
|
||||||
/// A 2D grid of bytes
|
/// A 2D grid of bytes
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
@ -60,7 +64,11 @@ impl<T: PrimitiveGridType> PrimitiveGrid<T> {
|
||||||
/// - when the dimensions and data size do not match exactly.
|
/// - when the dimensions and data size do not match exactly.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn load(width: usize, height: usize, data: &[T]) -> Self {
|
pub fn load(width: usize, height: usize, data: &[T]) -> Self {
|
||||||
assert_eq!(width * height, data.len());
|
assert_eq!(
|
||||||
|
width * height,
|
||||||
|
data.len(),
|
||||||
|
"dimension mismatch for data {data:?}"
|
||||||
|
);
|
||||||
Self {
|
Self {
|
||||||
data: Vec::from(data),
|
data: Vec::from(data),
|
||||||
width,
|
width,
|
||||||
|
@ -68,12 +76,31 @@ impl<T: PrimitiveGridType> PrimitiveGrid<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Loads a [PrimitiveGrid] with the specified dimensions from the provided data.
|
||||||
|
///
|
||||||
|
/// returns: [PrimitiveGrid] that contains a copy of the provided data or [TryLoadPrimitiveGridError].
|
||||||
|
pub fn try_load(
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
data: Vec<T>,
|
||||||
|
) -> Result<Self, TryLoadPrimitiveGridError> {
|
||||||
|
if width * height != data.len() {
|
||||||
|
return Err(TryLoadPrimitiveGridError::InvalidDimensions);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
data,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Iterate over all cells in [PrimitiveGrid].
|
/// Iterate over all cells in [PrimitiveGrid].
|
||||||
///
|
///
|
||||||
/// Order is equivalent to the following loop:
|
/// Order is equivalent to the following loop:
|
||||||
/// ```
|
/// ```
|
||||||
/// # use servicepoint::{PrimitiveGrid, Grid};
|
/// # use servicepoint::{ByteGrid, Grid};
|
||||||
/// # let grid = PrimitiveGrid::<u8>::new(2,2);
|
/// # let grid = ByteGrid::new(2,2);
|
||||||
/// for y in 0..grid.height() {
|
/// for y in 0..grid.height() {
|
||||||
/// for x in 0..grid.width() {
|
/// for x in 0..grid.width() {
|
||||||
/// grid.get(x, y);
|
/// grid.get(x, y);
|
||||||
|
@ -140,9 +167,9 @@ impl<T: PrimitiveGridType> PrimitiveGrid<T> {
|
||||||
///
|
///
|
||||||
/// Use logic written for u8s and then convert to [Brightness] values for sending in a [Command].
|
/// Use logic written for u8s and then convert to [Brightness] values for sending in a [Command].
|
||||||
/// ```
|
/// ```
|
||||||
/// # fn foo(grid: &mut PrimitiveGrid<u8>) {}
|
/// # fn foo(grid: &mut ByteGrid) {}
|
||||||
/// # use servicepoint::{Brightness, BrightnessGrid, Command, Origin, PrimitiveGrid, TILE_HEIGHT, TILE_WIDTH};
|
/// # use servicepoint::{Brightness, BrightnessGrid, ByteGrid, Command, Origin, TILE_HEIGHT, TILE_WIDTH};
|
||||||
/// let mut grid: PrimitiveGrid<u8> = PrimitiveGrid::new(TILE_WIDTH, TILE_HEIGHT);
|
/// let mut grid: ByteGrid = ByteGrid::new(TILE_WIDTH, TILE_HEIGHT);
|
||||||
/// foo(&mut grid);
|
/// foo(&mut grid);
|
||||||
/// let grid: BrightnessGrid = grid.map(Brightness::saturating_from);
|
/// let grid: BrightnessGrid = grid.map(Brightness::saturating_from);
|
||||||
/// let command = Command::CharBrightness(Origin::ZERO, grid);
|
/// let command = Command::CharBrightness(Origin::ZERO, grid);
|
||||||
|
@ -238,6 +265,12 @@ impl<T: PrimitiveGridType> PrimitiveGrid<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum TryLoadPrimitiveGridError {
|
||||||
|
#[error("The provided dimensions do not match with the data size")]
|
||||||
|
InvalidDimensions,
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: PrimitiveGridType> Grid<T> for PrimitiveGrid<T> {
|
impl<T: PrimitiveGridType> Grid<T> for PrimitiveGrid<T> {
|
||||||
/// Sets the value of the cell at the specified position in the `PrimitiveGrid.
|
/// Sets the value of the cell at the specified position in the `PrimitiveGrid.
|
||||||
///
|
///
|
||||||
|
@ -300,6 +333,7 @@ impl<T: PrimitiveGridType> From<PrimitiveGrid<T>> for Vec<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An iterator iver the rows in a [PrimitiveGrid]
|
||||||
pub struct IterRows<'t, T: PrimitiveGridType> {
|
pub struct IterRows<'t, T: PrimitiveGridType> {
|
||||||
byte_grid: &'t PrimitiveGrid<T>,
|
byte_grid: &'t PrimitiveGrid<T>,
|
||||||
row: usize,
|
row: usize,
|
||||||
|
@ -323,7 +357,8 @@ impl<'t, T: PrimitiveGridType> Iterator for IterRows<'t, T> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{DataRef, Grid, PrimitiveGrid, SeriesError};
|
use crate::primitive_grid::{PrimitiveGrid, SeriesError};
|
||||||
|
use crate::{DataRef, Grid};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fill() {
|
fn fill() {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
//! prefix `sp_brightness_grid_`
|
//! prefix `sp_brightness_grid_`
|
||||||
|
|
||||||
use crate::SPByteSlice;
|
use crate::SPByteSlice;
|
||||||
use servicepoint::{Brightness, DataRef, Grid, PrimitiveGrid};
|
use servicepoint::{Brightness, ByteGrid, DataRef, Grid};
|
||||||
use std::convert::Into;
|
use std::convert::Into;
|
||||||
use std::intrinsics::transmute;
|
use std::intrinsics::transmute;
|
||||||
use std::ptr::NonNull;
|
use std::ptr::NonNull;
|
||||||
|
@ -80,7 +80,7 @@ pub unsafe extern "C" fn sp_brightness_grid_load(
|
||||||
) -> NonNull<SPBrightnessGrid> {
|
) -> NonNull<SPBrightnessGrid> {
|
||||||
assert!(!data.is_null());
|
assert!(!data.is_null());
|
||||||
let data = std::slice::from_raw_parts(data, data_length);
|
let data = std::slice::from_raw_parts(data, data_length);
|
||||||
let grid = PrimitiveGrid::load(width, height, data);
|
let grid = ByteGrid::load(width, height, data);
|
||||||
let grid = servicepoint::BrightnessGrid::try_from(grid)
|
let grid = servicepoint::BrightnessGrid::try_from(grid)
|
||||||
.expect("invalid brightness value");
|
.expect("invalid brightness value");
|
||||||
let result = Box::new(SPBrightnessGrid(grid));
|
let result = Box::new(SPBrightnessGrid(grid));
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use servicepoint::{Grid, SeriesError};
|
use crate::cp437_grid::Cp437Grid;
|
||||||
|
use servicepoint::{Grid, primitive_grid::SeriesError};
|
||||||
use std::convert::Into;
|
use std::convert::Into;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
use crate::cp437_grid::Cp437Grid;
|
|
||||||
|
|
||||||
#[derive(uniffi::Object)]
|
#[derive(uniffi::Object)]
|
||||||
pub struct CharGrid {
|
pub struct CharGrid {
|
||||||
|
|
Loading…
Reference in a new issue