1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
|
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<ScreenDimensions> {
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<u8> {
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<MetadataColour>) -> Option<Colour> {
let c = colour?;
Some(Colour::from_rgb(c.red, c.green, c.blue))
}
pub fn parse_small_icon(bytes: Option<Vec<u8>>, bg: Colour, fg: Colour) -> Option<Icon> {
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<Vec<u8>>, bg: Colour, fg: Colour) -> Option<Icon> {
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<u8> {
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;
}
|