1use anyhow::{Result, bail};
9
10pub const EXTENSIONS: &[&str] = &["fnt", "f08", "f14", "f16", "psf", "psfu"];
12
13pub fn parse(data: &[u8]) -> Result<Vec<u8>> {
15 let (height, glyphs) = if data.starts_with(&[0x36, 0x04]) && data.len() >= 4 {
16 let h = data[3] as usize;
18 (h, &data[4..])
19 } else if data.starts_with(&[0x72, 0xB5, 0x4A, 0x86]) && data.len() >= 32 {
20 let word = |i: usize| u32::from_le_bytes([data[i], data[i + 1], data[i + 2], data[i + 3]]) as usize;
21 let (header, count, size, h, w) = (word(8), word(16), word(20), word(24), word(28));
22 if w > 8 {
23 bail!("the font is {w} pixels wide; BadTerm's cells are 8");
24 }
25 if size != h || count < 256 {
26 bail!("not a font of 256 or more glyphs of one byte a row");
27 }
28 (h, data.get(header..).unwrap_or_default())
29 } else if !data.is_empty() && data.len().is_multiple_of(256) {
30 (data.len() / 256, data)
31 } else {
32 bail!("not a font file BadTerm reads (.fnt, .f08, .f14, .f16 or .psf)");
33 };
34 if !(4..=32).contains(&height) || glyphs.len() < 256 * height {
35 bail!("not a font file BadTerm reads (glyphs {height} rows high)");
36 }
37 let to = match height {
38 ..=10 => 8,
39 11..=14 => 14,
40 _ => 16,
41 };
42 Ok((0..256).flat_map(|c| (0..to).map(move |y| glyphs[c * height + y * height / to])).collect())
43}
44
45#[cfg(test)]
46mod tests {
47 use super::*;
48
49 #[test]
50 fn raw_and_psf() {
51 let raw: Vec<u8> = (0..256 * 16).map(|i| (i % 251) as u8).collect();
52 assert_eq!(parse(&raw).unwrap(), raw);
53 let mut psf1 = vec![0x36, 0x04, 0, 14];
54 psf1.extend(std::iter::repeat_n(0x18, 256 * 14));
55 assert_eq!(parse(&psf1).unwrap().len(), 256 * 14);
56 let mut psf2 = vec![0x72, 0xB5, 0x4A, 0x86];
58 for v in [0u32, 32, 0, 512, 12, 12, 8] {
59 psf2.extend(v.to_le_bytes());
60 }
61 psf2.extend((0..512 * 12).map(|i| (i / 12) as u8));
62 let f = parse(&psf2).unwrap();
63 assert_eq!((f.len(), f[14 * 65]), (256 * 14, 65));
64 psf2[28] = 9;
66 assert!(parse(&psf2).is_err());
67 assert!(parse(b"hello").is_err());
68 }
69}