text rendering

This commit is contained in:
Vinzenz Schroeter 2024-05-10 18:24:26 +02:00
parent 05c7b4cb71
commit ae12c82c2e
9 changed files with 447 additions and 166 deletions

211
Cargo.lock generated
View file

@ -534,6 +534,18 @@ dependencies = [
"libc", "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]] [[package]]
name = "crc32fast" name = "crc32fast"
version = "1.4.0" version = "1.4.0"
@ -574,6 +586,16 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" 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]] [[package]]
name = "cursor-icon" name = "cursor-icon"
version = "1.1.0" version = "1.1.0"
@ -591,6 +613,27 @@ dependencies = [
"winapi", "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]] [[package]]
name = "dispatch" name = "dispatch"
version = "0.2.0" version = "0.2.0"
@ -618,6 +661,18 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" 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]] [[package]]
name = "either" name = "either"
version = "1.11.0" version = "1.11.0"
@ -698,6 +753,12 @@ dependencies = [
"miniz_oxide", "miniz_oxide",
] ]
[[package]]
name = "float-ord"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d"
[[package]] [[package]]
name = "flume" name = "flume"
version = "0.11.0" version = "0.11.0"
@ -707,6 +768,31 @@ dependencies = [
"spin", "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]] [[package]]
name = "foreign-types" name = "foreign-types"
version = "0.3.2" version = "0.3.2"
@ -749,6 +835,27 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" 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]] [[package]]
name = "gethostname" name = "gethostname"
version = "0.4.3" version = "0.4.3"
@ -1057,6 +1164,12 @@ dependencies = [
"pkg-config", "pkg-config",
] ]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "lebe" name = "lebe"
version = "0.5.2" version = "0.5.2"
@ -1111,6 +1224,16 @@ dependencies = [
"redox_syscall 0.4.1", "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]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.4.13" version = "0.4.13"
@ -1480,7 +1603,7 @@ version = "0.3.47"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52f0d54bde9774d3a51dcf281a5def240c71996bc6ca05d2c847ec8b2b216166" checksum = "52f0d54bde9774d3a51dcf281a5def240c71996bc6ca05d2c847ec8b2b216166"
dependencies = [ dependencies = [
"libredox", "libredox 0.0.2",
] ]
[[package]] [[package]]
@ -1521,6 +1644,25 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 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]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.3.1" version = "2.3.1"
@ -1559,14 +1701,18 @@ version = "0.1.0"
dependencies = [ dependencies = [
"clap", "clap",
"env_logger", "env_logger",
"font-kit",
"image", "image",
"log", "log",
"num", "num",
"num-derive", "num-derive",
"num-traits", "num-traits",
"pathfinder_geometry",
"pixels", "pixels",
"raw-window-handle 0.6.1", "raw-window-handle 0.6.1",
"servicepoint2",
"winit", "winit",
"yeslogic-fontconfig-sys",
] ]
[[package]] [[package]]
@ -1631,12 +1777,11 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]] [[package]]
name = "proc-macro-crate" name = "proc-macro-crate"
version = "1.3.1" version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284"
dependencies = [ dependencies = [
"once_cell", "toml_edit 0.21.1",
"toml_edit 0.19.15",
] ]
[[package]] [[package]]
@ -1836,6 +1981,17 @@ dependencies = [
"bitflags 2.5.0", "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]] [[package]]
name = "regex" name = "regex"
version = "1.10.4" version = "1.10.4"
@ -1892,6 +2048,15 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 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]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.34" version = "0.38.34"
@ -1948,6 +2113,12 @@ dependencies = [
"tiny-skia", "tiny-skia",
] ]
[[package]]
name = "semver"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.201" version = "1.0.201"
@ -1977,6 +2148,11 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "servicepoint2"
version = "0.1.0"
source = "git+https://github.com/kaesaecracker/servicepoint.git#a23ca55f607b64324eda02f27ae347998d227deb"
[[package]] [[package]]
name = "simd-adler32" name = "simd-adler32"
version = "0.3.7" version = "0.3.7"
@ -2205,9 +2381,9 @@ dependencies = [
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
version = "0.19.15" version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
dependencies = [ dependencies = [
"indexmap 2.2.6", "indexmap 2.2.6",
"toml_datetime", "toml_datetime",
@ -2949,6 +3125,15 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "wio"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "x11-dl" name = "x11-dl"
version = "2.21.0" version = "2.21.0"
@ -3006,6 +3191,18 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "054a8e68b76250b253f671d1268cb7f1ae089ec35e195b2efb2a4e9a836d0621" 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]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.7.34" version = "0.7.34"

View file

@ -15,3 +15,7 @@ env_logger = "0.11"
num = "0.4" num = "0.4"
num-derive = "0.4" num-derive = "0.4"
num-traits = "0.2" 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" }

View file

@ -8,3 +8,9 @@ Use cases:
- getting error messages for invalid packages - getting error messages for invalid packages
- test your project when outside of CCCB - test your project when outside of CCCB
- test your project while other people are using the display - 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.

BIN
Web437_IBM_BIOS.woff Normal file

Binary file not shown.

56
src/font.rs Normal file
View file

@ -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]
}
}

View file

@ -1,8 +1,8 @@
use crate::protocol::{PIXEL_HEIGHT, PIXEL_WIDTH};
use crate::DISPLAY; use crate::DISPLAY;
use log::{trace, warn}; use log::{trace, warn};
use pixels::wgpu::TextureFormat; use pixels::wgpu::TextureFormat;
use pixels::{Pixels, PixelsBuilder, SurfaceTexture}; use pixels::{Pixels, PixelsBuilder, SurfaceTexture};
use servicepoint2::{PIXEL_HEIGHT, PIXEL_WIDTH};
use winit::application::ApplicationHandler; use winit::application::ApplicationHandler;
use winit::dpi::{LogicalSize, Size}; use winit::dpi::{LogicalSize, Size};
use winit::event::WindowEvent; use winit::event::WindowEvent;

View file

@ -1,17 +1,17 @@
#![deny(clippy::all)] #![deny(clippy::all)]
mod font;
mod gui; mod gui;
mod upd_loop;
mod protocol; mod protocol;
mod upd_loop;
use std::default::Default; use std::default::Default;
use std::sync::mpsc;
use crate::gui::App; use crate::gui::App;
use crate::upd_loop::start_udp_thread; use crate::upd_loop::UdpThread;
use clap::Parser; use clap::Parser;
use log::info; use log::info;
use protocol::PIXEL_COUNT; use servicepoint2::PIXEL_COUNT;
use winit::event_loop::{ControlFlow, EventLoop}; use winit::event_loop::{ControlFlow, EventLoop};
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
@ -28,8 +28,7 @@ fn main() {
let cli = Cli::parse(); let cli = Cli::parse();
info!("starting with args: {:?}", &cli); info!("starting with args: {:?}", &cli);
let (stop_udp_tx, stop_udp_rx) = mpsc::channel(); let thread = UdpThread::start_new(cli.bind);
let thread = start_udp_thread(cli.bind, stop_udp_rx);
let event_loop = EventLoop::new().expect("could not create event loop"); let event_loop = EventLoop::new().expect("could not create event loop");
event_loop.set_control_flow(ControlFlow::Poll); event_loop.set_control_flow(ControlFlow::Poll);
@ -39,6 +38,5 @@ fn main() {
.run_app(&mut app) .run_app(&mut app)
.expect("could not run event loop"); .expect("could not run event loop");
stop_udp_tx.send(()).expect("could not cancel thread"); thread.stop_and_wait();
thread.join().expect("could not join threads");
} }

View file

@ -1,27 +1,38 @@
use num_derive::{FromPrimitive, ToPrimitive};
use std::mem::size_of; use std::mem::size_of;
use num_derive::{FromPrimitive, ToPrimitive};
#[repr(u16)]
#[derive(Debug, FromPrimitive, ToPrimitive, Default)] #[derive(Debug, FromPrimitive, ToPrimitive, Default)]
pub enum DisplayCommand { pub enum DisplayCommandCode {
#[default] #[default]
CmdClear = 0x0002, Clear = 0x0002,
CmdCp437data = 0x0003, Cp437data = 0x0003,
CmdCharBrightness = 0x0005, CharBrightness = 0x0005,
CmdBrightness = 0x0007, Brightness = 0x0007,
CmdHardReset = 0x000b, HardReset = 0x000b,
CmdFadeOut = 0x000d, FadeOut = 0x000d,
CmdBitmapLegacy = 0x0010, BitmapLegacy = 0x0010,
CmdBitmapLinear = 0x0012, BitmapLinear = 0x0012,
CmdBitmapLinearWin = 0x0013, BitmapLinearWin = 0x0013,
CmdBitmapLinearAnd = 0x0014, BitmapLinearAnd = 0x0014,
CmdBitmapLinearOr = 0x0015, BitmapLinearOr = 0x0015,
CmdBitmapLinearXor = 0x0016, BitmapLinearXor = 0x0016,
}
impl DisplayCommandCode {
pub fn from_primitive(value: u16) -> Option<Self> {
num::FromPrimitive::from_u16(value)
}
pub fn to_primitive(&self) -> u16 {
num::ToPrimitive::to_u16(self).unwrap()
}
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct HdrWindow { pub struct HdrWindow {
pub command: DisplayCommand, pub command: DisplayCommandCode,
pub x: u16, pub x: u16,
pub y: u16, pub y: u16,
pub w: u16, pub w: u16,
@ -49,27 +60,22 @@ pub enum DisplaySubcommand {
SubCmdBitmapCompressZs = 0x7a73, 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)] #[derive(Debug)]
pub enum ReadHeaderError { pub enum ReadHeaderError {
BufferTooSmall, BufferTooSmall,
WrongCommandEndianness(u16, DisplayCommand), WrongCommandEndianness(u16, DisplayCommandCode),
InvalidCommand(u16), InvalidCommand(u16),
} }
pub fn read_header(buffer: &[u8]) -> Result<HdrWindow, ReadHeaderError> { pub fn read_header(buffer: &[u8]) -> Result<HdrWindow, ReadHeaderError> {
assert_eq!(size_of::<HdrWindow>(), 10, "invalid struct size");
if buffer.len() < size_of::<HdrWindow>() { if buffer.len() < size_of::<HdrWindow>() {
return Err(ReadHeaderError::BufferTooSmall); return Err(ReadHeaderError::BufferTooSmall);
} }
let command_u16 = read_beu16(&buffer[0..=1]); 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 { Some(command) => Ok(HdrWindow {
command, command,
x: read_beu16(&buffer[2..=3]), x: read_beu16(&buffer[2..=3]),
@ -78,8 +84,7 @@ pub fn read_header(buffer: &[u8]) -> Result<HdrWindow, ReadHeaderError> {
h: read_beu16(&buffer[8..=9]), h: read_beu16(&buffer[8..=9]),
}), }),
None => { None => {
let maybe_command: Option<DisplayCommand> = let maybe_command = DisplayCommandCode::from_primitive(u16::swap_bytes(command_u16));
num::FromPrimitive::from_u16(u16::swap_bytes(command_u16));
return match maybe_command { return match maybe_command {
None => Err(ReadHeaderError::InvalidCommand(command_u16)), None => Err(ReadHeaderError::InvalidCommand(command_u16)),
Some(command) => Err(ReadHeaderError::WrongCommandEndianness( Some(command) => Err(ReadHeaderError::WrongCommandEndianness(

View file

@ -1,28 +1,36 @@
use crate::protocol::{ use crate::font::BitmapFont;
read_header, DisplayCommand, HdrWindow, ReadHeaderError, PIXEL_WIDTH, TILE_SIZE, use crate::protocol::{read_header, DisplayCommandCode, HdrWindow, ReadHeaderError};
};
use crate::DISPLAY; 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::io::ErrorKind;
use std::mem::size_of; use std::net::{ToSocketAddrs, UdpSocket};
use std::net::UdpSocket; use std::sync::mpsc;
use std::sync::mpsc::Receiver; use std::sync::mpsc::Sender;
use std::thread; use std::thread;
use std::thread::JoinHandle; use std::thread::JoinHandle;
use std::time::Duration; use std::time::Duration;
pub fn start_udp_thread(bind: String, stop_receiver: Receiver<()>) -> JoinHandle<()> { pub struct UdpThread {
assert_eq!(size_of::<HdrWindow>(), 10, "invalid struct size"); 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"); let socket = UdpSocket::bind(bind).expect("could not bind socket");
socket socket
.set_nonblocking(true) .set_nonblocking(true)
.expect("could not enter non blocking mode"); .expect("could not enter non blocking mode");
let font = BitmapFont::load_file("Web437_IBM_BIOS.woff");
let thread = thread::spawn(move || {
let mut buf = [0; 8985]; let mut buf = [0; 8985];
while stop_receiver.try_recv().is_err() { while stop_rx.try_recv().is_err() {
let (amount, _) = match socket.recv_from(&mut buf) { let (amount, _) = match socket.recv_from(&mut buf) {
Err(err) if err.kind() == ErrorKind::WouldBlock => { Err(err) if err.kind() == ErrorKind::WouldBlock => {
thread::sleep(Duration::from_millis(1)); thread::sleep(Duration::from_millis(1));
@ -39,12 +47,19 @@ pub fn start_udp_thread(bind: String, stop_receiver: Receiver<()>) -> JoinHandle
); );
} }
handle_package(&mut buf[..amount]); Self::handle_package(&mut buf[..amount], &font);
} }
}); });
return Self { stop_tx, thread };
} }
fn handle_package(received: &mut [u8]) { 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]) { let header = match read_header(&received[..10]) {
Err(ReadHeaderError::BufferTooSmall) => { Err(ReadHeaderError::BufferTooSmall) => {
error!("received a packet that is too small"); error!("received a packet that is too small");
@ -73,21 +88,21 @@ fn handle_package(received: &mut [u8]) {
); );
match header.command { match header.command {
DisplayCommand::CmdClear => { DisplayCommandCode::Clear => {
info!("clearing display"); info!("clearing display");
for v in unsafe { DISPLAY.iter_mut() } { for v in unsafe { DISPLAY.iter_mut() } {
*v = false; *v = false;
} }
} }
DisplayCommand::CmdHardReset => { DisplayCommandCode::HardReset => {
warn!("display shutting down"); warn!("display shutting down");
return; return;
} }
DisplayCommand::CmdBitmapLinearWin => { DisplayCommandCode::BitmapLinearWin => {
print_bitmap_linear_win(&header, payload); Self::print_bitmap_linear_win(&header, payload);
} }
DisplayCommand::CmdCp437data => { DisplayCommandCode::Cp437data => {
print_cp437_data(&header, payload); Self::print_cp437_data(&header, payload, font);
} }
_ => { _ => {
error!("command {:?} not implemented yet", header.command); error!("command {:?} not implemented yet", header.command);
@ -109,56 +124,56 @@ fn check_payload_size(buf: &[u8], expected: usize) -> bool {
} }
fn print_bitmap_linear_win(header: &HdrWindow, payload: &[u8]) { fn print_bitmap_linear_win(header: &HdrWindow, payload: &[u8]) {
if !check_payload_size(payload, header.w as usize * header.h as usize) { if !Self::check_payload_size(payload, header.w as usize * header.h as usize) {
return; return;
} }
info!( let pixel_grid = PixelGrid::load(
"top left is offset {} tiles in x-direction and {} pixels in y-direction", header.w as usize * TILE_SIZE as usize,
header.x, header.y header.h as usize,
payload,
); );
for y in 0..header.h { Self::print_pixel_grid(
for byte_x in 0..header.w { header.x as usize * TILE_SIZE as usize,
let byte_index = (y * header.w + byte_x) as usize; header.y as usize,
let byte = payload[byte_index]; &pixel_grid,
);
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;
unsafe {
DISPLAY[index] = is_set;
}
}
}
}
} }
// TODO: actually convert from CP437 fn print_cp437_data(header: &HdrWindow, payload: &[u8], font: &BitmapFont) {
fn print_cp437_data(header: &HdrWindow, payload: &[u8]) { if !UdpThread::check_payload_size(payload, (header.w * header.h) as usize) {
if !check_payload_size(payload, (header.w * header.h) as usize) {
return; return;
} }
info!("top left is offset by ({} | {}) tiles", header.x, header.y); 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 mut str = String::new(); let tile_x = char_x + header.x as usize;
for y in 0..header.h { let tile_y = char_y + header.y as usize;
for x in 0..header.w {
let byte_index = (y * header.w + x) as usize; let bitmap = font.get_bitmap(char_code);
str.push(payload[byte_index] as char); Self::print_pixel_grid(
tile_x * TILE_SIZE as usize,
tile_y * TILE_SIZE as usize,
bitmap,
);
}
}
} }
str.push('\n'); 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[display_index] = is_set;
}
}
}
} }
info!("{}", str);
} }