From ae12c82c2e7283d72086d10f8803da6119f0c751 Mon Sep 17 00:00:00 2001 From: Vinzenz Schroeter Date: Fri, 10 May 2024 18:24:26 +0200 Subject: [PATCH] text rendering --- Cargo.lock | 211 ++++++++++++++++++++++++++++++++-- Cargo.toml | 4 + README.md | 6 + Web437_IBM_BIOS.woff | Bin 0 -> 8832 bytes src/font.rs | 56 +++++++++ src/gui.rs | 2 +- src/main.rs | 14 +-- src/protocol.rs | 57 +++++----- src/upd_loop.rs | 263 +++++++++++++++++++++++-------------------- 9 files changed, 447 insertions(+), 166 deletions(-) create mode 100644 Web437_IBM_BIOS.woff create mode 100644 src/font.rs diff --git a/Cargo.lock b/Cargo.lock index f5ae887..0488547 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -534,6 +534,18 @@ dependencies = [ "libc", ] +[[package]] +name = "core-text" +version = "20.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9d2790b5c08465d49f8dc05c8bcae9fea467855947db39b0f8145c091aaced5" +dependencies = [ + "core-foundation", + "core-graphics", + "foreign-types 0.5.0", + "libc", +] + [[package]] name = "crc32fast" version = "1.4.0" @@ -574,6 +586,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "cstr" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68523903c8ae5aacfa32a0d9ae60cadeb764e1da14ee0d26b1f3089f13a54636" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "cursor-icon" version = "1.1.0" @@ -591,6 +613,27 @@ dependencies = [ "winapi", ] +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -618,6 +661,18 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" +[[package]] +name = "dwrote" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b" +dependencies = [ + "lazy_static", + "libc", + "winapi", + "wio", +] + [[package]] name = "either" version = "1.11.0" @@ -698,6 +753,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "float-ord" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" + [[package]] name = "flume" version = "0.11.0" @@ -707,6 +768,31 @@ dependencies = [ "spin", ] +[[package]] +name = "font-kit" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d50ba02d3a19ab9012a00314ff4d105128cdc7ba223d69d48181f2d257244d51" +dependencies = [ + "bitflags 2.5.0", + "byteorder", + "core-foundation", + "core-graphics", + "core-text", + "dirs-next", + "dwrote", + "float-ord", + "freetype", + "lazy_static", + "libc", + "log", + "pathfinder_geometry", + "pathfinder_simd", + "walkdir", + "winapi", + "yeslogic-fontconfig-sys", +] + [[package]] name = "foreign-types" version = "0.3.2" @@ -749,6 +835,27 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" +[[package]] +name = "freetype" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a440748e063798e4893ceb877151e84acef9bea9a8c6800645cf3f1b3a7806e" +dependencies = [ + "freetype-sys", + "libc", +] + +[[package]] +name = "freetype-sys" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7edc5b9669349acfda99533e9e0bcf26a51862ab43b08ee7745c55d28eb134" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "gethostname" version = "0.4.3" @@ -1057,6 +1164,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "lebe" version = "0.5.2" @@ -1111,6 +1224,16 @@ dependencies = [ "redox_syscall 0.4.1", ] +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.5.0", + "libc", +] + [[package]] name = "linux-raw-sys" version = "0.4.13" @@ -1480,7 +1603,7 @@ version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52f0d54bde9774d3a51dcf281a5def240c71996bc6ca05d2c847ec8b2b216166" dependencies = [ - "libredox", + "libredox 0.0.2", ] [[package]] @@ -1521,6 +1644,25 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pathfinder_geometry" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" +dependencies = [ + "log", + "pathfinder_simd", +] + +[[package]] +name = "pathfinder_simd" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebf45976c56919841273f2a0fc684c28437e2f304e264557d9c72be5d5a718be" +dependencies = [ + "rustc_version", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1559,14 +1701,18 @@ version = "0.1.0" dependencies = [ "clap", "env_logger", + "font-kit", "image", "log", "num", "num-derive", "num-traits", + "pathfinder_geometry", "pixels", "raw-window-handle 0.6.1", + "servicepoint2", "winit", + "yeslogic-fontconfig-sys", ] [[package]] @@ -1631,12 +1777,11 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro-crate" -version = "1.3.1" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" dependencies = [ - "once_cell", - "toml_edit 0.19.15", + "toml_edit 0.21.1", ] [[package]] @@ -1836,6 +1981,17 @@ dependencies = [ "bitflags 2.5.0", ] +[[package]] +name = "redox_users" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +dependencies = [ + "getrandom", + "libredox 0.1.3", + "thiserror", +] + [[package]] name = "regex" version = "1.10.4" @@ -1892,6 +2048,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.34" @@ -1948,6 +2113,12 @@ dependencies = [ "tiny-skia", ] +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + [[package]] name = "serde" version = "1.0.201" @@ -1977,6 +2148,11 @@ dependencies = [ "serde", ] +[[package]] +name = "servicepoint2" +version = "0.1.0" +source = "git+https://github.com/kaesaecracker/servicepoint.git#a23ca55f607b64324eda02f27ae347998d227deb" + [[package]] name = "simd-adler32" version = "0.3.7" @@ -2205,9 +2381,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.19.15" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ "indexmap 2.2.6", "toml_datetime", @@ -2949,6 +3125,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "wio" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" +dependencies = [ + "winapi", +] + [[package]] name = "x11-dl" version = "2.21.0" @@ -3006,6 +3191,18 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "054a8e68b76250b253f671d1268cb7f1ae089ec35e195b2efb2a4e9a836d0621" +[[package]] +name = "yeslogic-fontconfig-sys" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb6b23999a8b1a997bf47c7bb4d19ad4029c3327bb3386ebe0a5ff584b33c7a" +dependencies = [ + "cstr", + "dlib", + "once_cell", + "pkg-config", +] + [[package]] name = "zerocopy" version = "0.7.34" diff --git a/Cargo.toml b/Cargo.toml index 583d07c..16e30f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,3 +15,7 @@ env_logger = "0.11" num = "0.4" num-derive = "0.4" num-traits = "0.2" +font-kit = { version = "0.13.0", features = ["loader-freetype-default", "loader-freetype"], default-features = false } +pathfinder_geometry = "0.5.1" +yeslogic-fontconfig-sys = { version = "5.0", features = ["dlopen"] } +servicepoint2 = { git = "https://github.com/kaesaecracker/servicepoint.git" } diff --git a/README.md b/README.md index b730160..b66c004 100644 --- a/README.md +++ b/README.md @@ -8,3 +8,9 @@ Use cases: - getting error messages for invalid packages - test your project when outside of CCCB - test your project while other people are using the display + +## Legal stuff + +The included font is https://int10h.org/oldschool-pc-fonts/fontlist/font?ibm_bios (included in the download from https://int10h.org/oldschool-pc-fonts/download/). The font is CC BY-SA 4.0. + +For everything else see the LICENSE file. diff --git a/Web437_IBM_BIOS.woff b/Web437_IBM_BIOS.woff new file mode 100644 index 0000000000000000000000000000000000000000..9daecb32fd2a72690825701ec583edaf73f3a246 GIT binary patch literal 8832 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 B1Cjs$ literal 0 HcmV?d00001 diff --git a/src/font.rs b/src/font.rs new file mode 100644 index 0000000..2c3cdbf --- /dev/null +++ b/src/font.rs @@ -0,0 +1,56 @@ +use font_kit::canvas::*; +use font_kit::hinting::HintingOptions; +use pathfinder_geometry::transform2d::Transform2F; +use pathfinder_geometry::vector::{vec2f, vec2i}; +use servicepoint2::{PixelGrid, TILE_SIZE}; + +pub struct BitmapFont { + bitmaps: [PixelGrid; u8::MAX as usize], +} + +impl BitmapFont { + pub fn load_file(file: &str) -> BitmapFont { + let font = font_kit::font::Font::from_path(file, 0).expect("could not load font"); + + let mut bitmaps = + core::array::from_fn(|_| PixelGrid::new(TILE_SIZE as usize, TILE_SIZE as usize)); + + for char_code in u8::MIN..u8::MAX { + let char = char_code as char; + let glyph_id = match font.glyph_for_char(char) { + None => continue, + Some(val) => val, + }; + + let size = 8f32; + let transform = Transform2F::default(); + let mut canvas = Canvas::new(vec2i(size as i32, size as i32), Format::A8); + font.rasterize_glyph( + &mut canvas, + glyph_id, + size, + Transform2F::from_translation(vec2f(0f32, size)) * transform, + HintingOptions::None, + RasterizationOptions::GrayscaleAa, + ) + .unwrap(); + + assert_eq!(canvas.pixels.len(), 64); + assert_eq!(canvas.stride, 8); + + for y in 0..TILE_SIZE as usize { + for x in 0..TILE_SIZE as usize { + let index = x + y * TILE_SIZE as usize; + let canvas_val = canvas.pixels[index] != 0; + bitmaps[char_code as usize].set(x, y, canvas_val); + } + } + } + + return BitmapFont { bitmaps }; + } + + pub fn get_bitmap(&self, char_code: u8) -> &PixelGrid { + &self.bitmaps[char_code as usize] + } +} diff --git a/src/gui.rs b/src/gui.rs index 953bb23..850c794 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -1,8 +1,8 @@ -use crate::protocol::{PIXEL_HEIGHT, PIXEL_WIDTH}; use crate::DISPLAY; use log::{trace, warn}; use pixels::wgpu::TextureFormat; use pixels::{Pixels, PixelsBuilder, SurfaceTexture}; +use servicepoint2::{PIXEL_HEIGHT, PIXEL_WIDTH}; use winit::application::ApplicationHandler; use winit::dpi::{LogicalSize, Size}; use winit::event::WindowEvent; diff --git a/src/main.rs b/src/main.rs index dff120f..d0d7710 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,17 @@ #![deny(clippy::all)] +mod font; mod gui; -mod upd_loop; mod protocol; +mod upd_loop; use std::default::Default; -use std::sync::mpsc; use crate::gui::App; -use crate::upd_loop::start_udp_thread; +use crate::upd_loop::UdpThread; use clap::Parser; use log::info; -use protocol::PIXEL_COUNT; +use servicepoint2::PIXEL_COUNT; use winit::event_loop::{ControlFlow, EventLoop}; #[derive(Parser, Debug)] @@ -28,8 +28,7 @@ fn main() { let cli = Cli::parse(); info!("starting with args: {:?}", &cli); - let (stop_udp_tx, stop_udp_rx) = mpsc::channel(); - let thread = start_udp_thread(cli.bind, stop_udp_rx); + let thread = UdpThread::start_new(cli.bind); let event_loop = EventLoop::new().expect("could not create event loop"); event_loop.set_control_flow(ControlFlow::Poll); @@ -39,6 +38,5 @@ fn main() { .run_app(&mut app) .expect("could not run event loop"); - stop_udp_tx.send(()).expect("could not cancel thread"); - thread.join().expect("could not join threads"); + thread.stop_and_wait(); } diff --git a/src/protocol.rs b/src/protocol.rs index 4cae660..39e7f57 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -1,27 +1,38 @@ -use num_derive::{FromPrimitive, ToPrimitive}; use std::mem::size_of; +use num_derive::{FromPrimitive, ToPrimitive}; +#[repr(u16)] #[derive(Debug, FromPrimitive, ToPrimitive, Default)] -pub enum DisplayCommand { +pub enum DisplayCommandCode { #[default] - CmdClear = 0x0002, - CmdCp437data = 0x0003, - CmdCharBrightness = 0x0005, - CmdBrightness = 0x0007, - CmdHardReset = 0x000b, - CmdFadeOut = 0x000d, - CmdBitmapLegacy = 0x0010, - CmdBitmapLinear = 0x0012, - CmdBitmapLinearWin = 0x0013, - CmdBitmapLinearAnd = 0x0014, - CmdBitmapLinearOr = 0x0015, - CmdBitmapLinearXor = 0x0016, + Clear = 0x0002, + Cp437data = 0x0003, + CharBrightness = 0x0005, + Brightness = 0x0007, + HardReset = 0x000b, + FadeOut = 0x000d, + BitmapLegacy = 0x0010, + BitmapLinear = 0x0012, + BitmapLinearWin = 0x0013, + BitmapLinearAnd = 0x0014, + BitmapLinearOr = 0x0015, + BitmapLinearXor = 0x0016, +} + +impl DisplayCommandCode { + pub fn from_primitive(value: u16) -> Option { + num::FromPrimitive::from_u16(value) + } + + pub fn to_primitive(&self) -> u16 { + num::ToPrimitive::to_u16(self).unwrap() + } } #[repr(C)] #[derive(Debug, Default)] pub struct HdrWindow { - pub command: DisplayCommand, + pub command: DisplayCommandCode, pub x: u16, pub y: u16, pub w: u16, @@ -49,27 +60,22 @@ pub enum DisplaySubcommand { SubCmdBitmapCompressZs = 0x7a73, } -pub const TILE_SIZE: u16 = 8; -pub const TILE_WIDTH: u16 = 56; -pub const TILE_HEIGHT: u16 = 20; -pub const PIXEL_WIDTH: u16 = TILE_WIDTH * TILE_SIZE; -pub const PIXEL_HEIGHT: u16 = TILE_HEIGHT * TILE_SIZE; -pub const PIXEL_COUNT: usize = PIXEL_WIDTH as usize * PIXEL_HEIGHT as usize; - #[derive(Debug)] pub enum ReadHeaderError { BufferTooSmall, - WrongCommandEndianness(u16, DisplayCommand), + WrongCommandEndianness(u16, DisplayCommandCode), InvalidCommand(u16), } pub fn read_header(buffer: &[u8]) -> Result { + assert_eq!(size_of::(), 10, "invalid struct size"); + if buffer.len() < size_of::() { return Err(ReadHeaderError::BufferTooSmall); } let command_u16 = read_beu16(&buffer[0..=1]); - return match num::FromPrimitive::from_u16(command_u16) { + return match DisplayCommandCode::from_primitive(command_u16) { Some(command) => Ok(HdrWindow { command, x: read_beu16(&buffer[2..=3]), @@ -78,8 +84,7 @@ pub fn read_header(buffer: &[u8]) -> Result { h: read_beu16(&buffer[8..=9]), }), None => { - let maybe_command: Option = - num::FromPrimitive::from_u16(u16::swap_bytes(command_u16)); + let maybe_command = DisplayCommandCode::from_primitive(u16::swap_bytes(command_u16)); return match maybe_command { None => Err(ReadHeaderError::InvalidCommand(command_u16)), Some(command) => Err(ReadHeaderError::WrongCommandEndianness( diff --git a/src/upd_loop.rs b/src/upd_loop.rs index 103c713..f4b6a8c 100644 --- a/src/upd_loop.rs +++ b/src/upd_loop.rs @@ -1,164 +1,179 @@ -use crate::protocol::{ - read_header, DisplayCommand, HdrWindow, ReadHeaderError, PIXEL_WIDTH, TILE_SIZE, -}; +use crate::font::BitmapFont; +use crate::protocol::{read_header, DisplayCommandCode, HdrWindow, ReadHeaderError}; use crate::DISPLAY; -use log::{error, info, warn}; +use log::{debug, error, info, warn}; +use servicepoint2::{PixelGrid, PIXEL_WIDTH, TILE_SIZE}; use std::io::ErrorKind; -use std::mem::size_of; -use std::net::UdpSocket; -use std::sync::mpsc::Receiver; +use std::net::{ToSocketAddrs, UdpSocket}; +use std::sync::mpsc; +use std::sync::mpsc::Sender; use std::thread; use std::thread::JoinHandle; use std::time::Duration; -pub fn start_udp_thread(bind: String, stop_receiver: Receiver<()>) -> JoinHandle<()> { - assert_eq!(size_of::(), 10, "invalid struct size"); +pub struct UdpThread { + thread: JoinHandle<()>, + stop_tx: Sender<()>, +} + +impl UdpThread { + pub fn start_new(bind: impl ToSocketAddrs) -> Self { + let (stop_tx, stop_rx) = mpsc::channel(); - return thread::spawn(move || { let socket = UdpSocket::bind(bind).expect("could not bind socket"); socket .set_nonblocking(true) .expect("could not enter non blocking mode"); - let mut buf = [0; 8985]; + let font = BitmapFont::load_file("Web437_IBM_BIOS.woff"); - while stop_receiver.try_recv().is_err() { - let (amount, _) = match socket.recv_from(&mut buf) { - Err(err) if err.kind() == ErrorKind::WouldBlock => { - thread::sleep(Duration::from_millis(1)); - continue; + let thread = thread::spawn(move || { + let mut buf = [0; 8985]; + + while stop_rx.try_recv().is_err() { + let (amount, _) = match socket.recv_from(&mut buf) { + Err(err) if err.kind() == ErrorKind::WouldBlock => { + thread::sleep(Duration::from_millis(1)); + continue; + } + Ok(result) => result, + other => other.unwrap(), + }; + + if amount == buf.len() { + warn!( + "the received package may have been truncated to a length of {}", + amount + ); } - Ok(result) => result, - other => other.unwrap(), - }; - if amount == buf.len() { - warn!( - "the received package may have been truncated to a length of {}", - amount - ); + Self::handle_package(&mut buf[..amount], &font); } + }); - handle_package(&mut buf[..amount]); - } - }); -} + return Self { stop_tx, thread }; + } -fn handle_package(received: &mut [u8]) { - let header = match read_header(&received[..10]) { - Err(ReadHeaderError::BufferTooSmall) => { - error!("received a packet that is too small"); - return; - } - Err(ReadHeaderError::InvalidCommand(command_u16)) => { - error!("received invalid command {}", command_u16); - return; - } - Err(ReadHeaderError::WrongCommandEndianness(command_u16, command_swapped)) => { - error!( + pub fn stop_and_wait(self) { + self.stop_tx.send(()).expect("could not send stop packet"); + self.thread.join().expect("could not wait on udp thread"); + } + + fn handle_package(received: &mut [u8], font: &BitmapFont) { + let header = match read_header(&received[..10]) { + Err(ReadHeaderError::BufferTooSmall) => { + error!("received a packet that is too small"); + return; + } + Err(ReadHeaderError::InvalidCommand(command_u16)) => { + error!("received invalid command {}", command_u16); + return; + } + Err(ReadHeaderError::WrongCommandEndianness(command_u16, command_swapped)) => { + error!( "The reversed byte order of {} matches command {:?}, you are probably sending the wrong endianness", command_u16, command_swapped ); - return; - } - Ok(value) => value, - }; + return; + } + Ok(value) => value, + }; - let payload = &received[10..]; + let payload = &received[10..]; - info!( - "received from {:?} (and {} bytes of payload)", - header, - payload.len() - ); + info!( + "received from {:?} (and {} bytes of payload)", + header, + payload.len() + ); - match header.command { - DisplayCommand::CmdClear => { - info!("clearing display"); - for v in unsafe { DISPLAY.iter_mut() } { - *v = false; + match header.command { + DisplayCommandCode::Clear => { + info!("clearing display"); + for v in unsafe { DISPLAY.iter_mut() } { + *v = false; + } + } + DisplayCommandCode::HardReset => { + warn!("display shutting down"); + return; + } + DisplayCommandCode::BitmapLinearWin => { + Self::print_bitmap_linear_win(&header, payload); + } + DisplayCommandCode::Cp437data => { + Self::print_cp437_data(&header, payload, font); + } + _ => { + error!("command {:?} not implemented yet", header.command); } } - DisplayCommand::CmdHardReset => { - warn!("display shutting down"); + } + + fn check_payload_size(buf: &[u8], expected: usize) -> bool { + let actual = buf.len(); + if actual == expected { + return true; + } + + error!( + "expected a payload length of {} but got {}", + expected, actual + ); + return false; + } + + fn print_bitmap_linear_win(header: &HdrWindow, payload: &[u8]) { + if !Self::check_payload_size(payload, header.w as usize * header.h as usize) { return; } - DisplayCommand::CmdBitmapLinearWin => { - print_bitmap_linear_win(&header, payload); - } - DisplayCommand::CmdCp437data => { - print_cp437_data(&header, payload); - } - _ => { - error!("command {:?} not implemented yet", header.command); - } - } -} -fn check_payload_size(buf: &[u8], expected: usize) -> bool { - let actual = buf.len(); - if actual == expected { - return true; + let pixel_grid = PixelGrid::load( + header.w as usize * TILE_SIZE as usize, + header.h as usize, + payload, + ); + + Self::print_pixel_grid( + header.x as usize * TILE_SIZE as usize, + header.y as usize, + &pixel_grid, + ); } - error!( - "expected a payload length of {} but got {}", - expected, actual - ); - return false; -} + fn print_cp437_data(header: &HdrWindow, payload: &[u8], font: &BitmapFont) { + if !UdpThread::check_payload_size(payload, (header.w * header.h) as usize) { + return; + } -fn print_bitmap_linear_win(header: &HdrWindow, payload: &[u8]) { - if !check_payload_size(payload, header.w as usize * header.h as usize) { - return; + for char_y in 0usize..header.h as usize { + for char_x in 0usize..header.w as usize { + let char_code = payload[char_y * header.w as usize + char_x]; + + let tile_x = char_x + header.x as usize; + let tile_y = char_y + header.y as usize; + + let bitmap = font.get_bitmap(char_code); + Self::print_pixel_grid( + tile_x * TILE_SIZE as usize, + tile_y * TILE_SIZE as usize, + bitmap, + ); + } + } } - info!( - "top left is offset {} tiles in x-direction and {} pixels in y-direction", - header.x, header.y - ); - - for y in 0..header.h { - for byte_x in 0..header.w { - let byte_index = (y * header.w + byte_x) as usize; - let byte = payload[byte_index]; - - for pixel_x in 0u8..8u8 { - let bit_index = 7 - pixel_x; - let bitmask = 1 << bit_index; - let is_set = byte & bitmask != 0; - - let x = byte_x * TILE_SIZE + pixel_x as u16; - - let translated_x = (x + header.x) as usize; - let translated_y = (y + header.y) as usize; - let index = translated_y * PIXEL_WIDTH as usize + translated_x; - + fn print_pixel_grid(offset_x: usize, offset_y: usize, pixels: &PixelGrid) { + debug!("printing {}x{} grid at {offset_x} {offset_y}", pixels.width, pixels.height); + for inner_y in 0..pixels.height { + for inner_x in 0..pixels.width { + let is_set = pixels.get(inner_x, inner_y); + let display_index = + (offset_x + inner_x) + ((offset_y + inner_y) * PIXEL_WIDTH as usize); unsafe { - DISPLAY[index] = is_set; + DISPLAY[display_index] = is_set; } } } } } - -// TODO: actually convert from CP437 -fn print_cp437_data(header: &HdrWindow, payload: &[u8]) { - if !check_payload_size(payload, (header.w * header.h) as usize) { - return; - } - - info!("top left is offset by ({} | {}) tiles", header.x, header.y); - - let mut str = String::new(); - for y in 0..header.h { - for x in 0..header.w { - let byte_index = (y * header.w + x) as usize; - str.push(payload[byte_index] as char); - } - - str.push('\n'); - } - - info!("{}", str); -}