use crate::*; #[derive(Copy, Clone, PartialEq)] pub enum Mode { Graphical, Headless, Dynamic, } impl Mode { pub fn from_str(string: &str) -> Self { match string { "g" | "graphical" => Self::Graphical, "h" | "headless" => Self::Headless, "d" | "dynamic" => Self::Dynamic, _ => fatal!("Invalid mode string '{string}'"), } } } pub fn parse_dimensions(string: &str) -> ScreenDimensions { fn parse_inner(string: &str) -> Option { let (w_str, h_str) = string.trim().split_once('x')?; Some( ScreenDimensions { width: w_str.parse().ok()?, height: h_str.parse().ok()?, } ) } let dimensions = parse_inner(string).unwrap_or_else(|| { fatal!("Invalid dimensions string '{string}'"); }); if dimensions.is_zero() { fatal!("Screen dimensions must be greater than zero"); } return dimensions; } pub fn parse_palette(string: &str) -> [Colour; 16] { fn decode_ascii_hex_digit(ascii: u8) -> Option { match ascii { b'0'..=b'9' => Some(ascii - b'0'), b'a'..=b'f' => Some(ascii - b'a' + 10), b'A'..=b'F' => Some(ascii - b'A' + 10), _ => { None } } } fn parse_inner(string: &str) -> Option<[Colour; 16]> { let mut c = Vec::new(); for token in string.split(',') { let mut bytes = token.bytes(); if bytes.len() != 3 { return None; } let r = decode_ascii_hex_digit(bytes.next().unwrap())?; let g = decode_ascii_hex_digit(bytes.next().unwrap())?; let b = decode_ascii_hex_digit(bytes.next().unwrap())?; c.push(Colour::from_rgb(r*17, g*17, b*17)); } Some(match c.len() { 2 => [ c[0], c[1], c[0], c[1], c[0], c[1], c[0], c[1], c[0], c[1], c[0], c[1], c[0], c[1], c[0], c[1] ], 3 => [ c[0], c[1], c[0], c[2], c[0], c[1], c[0], c[2], c[0], c[1], c[0], c[2], c[0], c[1], c[0], c[2] ], 4 => [ c[0], c[1], c[2], c[3], c[0], c[1], c[2], c[3], c[0], c[1], c[2], c[3], c[0], c[1], c[2], c[3] ], 8 => [ c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7] ], 16 => [ c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], c[8], c[9], c[10], c[11], c[12], c[13], c[14], c[15] ], _ => return None, }) } parse_inner(string).unwrap_or_else(|| { fatal!("Invalid palette string '{string}'"); }) } pub fn parse_metadata_colour(colour: Option) -> Option { let c = colour?; Some(Colour::from_rgb(c.red, c.green, c.blue)) } pub fn parse_small_icon(bytes: Option>, bg: Colour, fg: Colour) -> Option { let rgba = sprite_data_to_rgb(&bytes?, 3, bg, fg); match Icon::from_rgba(rgba, 24, 24) { Ok(icon) => Some(icon), Err(err) => unreachable!("Error while parsing small icon data: {err}"), } } pub fn parse_large_icon(bytes: Option>, bg: Colour, fg: Colour) -> Option { let rgba = sprite_data_to_rgb(&bytes?, 8, bg, fg); match Icon::from_rgba(rgba, 64, 64) { Ok(icon) => Some(icon), Err(err) => unreachable!("Error while parsing large icon data: {err}"), } } fn sprite_data_to_rgb(bytes: &[u8], size: usize, bg: Colour, fg: Colour) -> Vec { let sprites: Vec<&[u8]> = bytes.chunks_exact(8).collect(); let mut rgba = Vec::new(); for sprite_row in 0..size { for pixel_row in 0..8 { for sprite_column in 0..size { let sprite = &sprites[sprite_column + (sprite_row * size)]; let row = &sprite[pixel_row]; for bit in 0..8 { let state = row & (0x80 >> bit); let colour = match state != 0 { true => fg, false => bg, }; rgba.extend_from_slice(&colour.as_rgba_array()); } } } } return rgba; }