#[derive(Debug)] struct Table { first_codepoint: u32, num_codepoints: u32, offset: u32, } #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum Encoding { BitPerPix1, BitPerPix2, BitPerPix4, BitPerPix8, } struct Access { byte_index: usize, shift: u8, mask: u8, } impl Encoding { fn into_code(self) -> u16 { match self { Encoding::BitPerPix1 => 0, Encoding::BitPerPix2 => 1, Encoding::BitPerPix4 => 2, Encoding::BitPerPix8 => 3, } } fn access_from_index(self, px_index: usize) -> Access { match self { Encoding::BitPerPix1 => Access { byte_index: px_index / 8, shift: (7 - (px_index % 8)) as u8, mask: 0x01, }, Encoding::BitPerPix2 => Access { byte_index: px_index / 4, shift: ((3 - (px_index % 4)) << 1) as u8, mask: 0x03, }, Encoding::BitPerPix4 => Access { byte_index: px_index / 2, shift: ((1 - (px_index % 2)) << 2) as u8, mask: 0x0f, }, Encoding::BitPerPix8 => Access { byte_index: px_index, shift: 0, mask: 0xff, }, } } /// Compresses standard brightness data (0-255) into a suitable form for sff. fn compress(self, input: &[u8]) -> Vec { let mut out = Vec::new(); match self { Encoding::BitPerPix1 => todo!(), Encoding::BitPerPix2 => todo!(), Encoding::BitPerPix4 => { assert!(input.len() % 2 == 0); // TODO not strictly necessary for c in 0..input.len() / 2 { let one = input[2 * c] >> 4; let two = input[2 * c + 1] >> 4; out.push((one << 4) | two); } } Encoding::BitPerPix8 => Vec::extend_from_slice(&mut out, input), } out } } fn get_u16(input: &[u8], byte_offset: usize) -> u16 { assert!(byte_offset % 2 == 0); input[byte_offset] as u16 | (input[byte_offset + 1] as u16) << 8 } fn get_u32(input: &[u8], byte_offset: usize) -> u32 { assert!(byte_offset % 4 == 0); input[byte_offset] as u32 | (input[byte_offset + 1] as u32) << 8 | (input[byte_offset + 2] as u32) << 16 | (input[byte_offset + 3] as u32) << 24 } #[derive(Debug)] struct FileDesc<'a> { input: &'a [u8], char_width: usize, char_height: usize, encoding: Encoding, tables: Vec, } impl<'a> FileDesc<'a> { fn bytes_per_codepoint(&self) -> usize { let px_per_codepoint = self.char_width * self.char_height; match self.encoding { Encoding::BitPerPix1 => px_per_codepoint >> 3, Encoding::BitPerPix2 => px_per_codepoint >> 2, Encoding::BitPerPix4 => px_per_codepoint >> 1, Encoding::BitPerPix8 => px_per_codepoint, } } fn find_codepoint(&self, codepoint: u32) -> Option<&[u8]> { self.tables .iter() .find(|tab| { tab.first_codepoint <= codepoint && tab.first_codepoint + tab.num_codepoints > codepoint }) .map(|tab| { let off = tab.offset as usize + (codepoint - tab.first_codepoint) as usize * self.bytes_per_codepoint(); &self.input[off..off + self.bytes_per_codepoint()] }) } // input should be result of find_codepoint here fn get_pixel_value(&self, input: &[u8], row: u8, col: u8) -> Option { if row as usize >= self.char_height || col as usize >= self.char_width { return None; } let px_index = row as usize * self.char_width + col as usize; let Access { byte_index, shift, mask, } = self.encoding.access_from_index(px_index); Some((input[byte_index] >> shift) & mask) } } fn decode(input: &[u8]) -> FileDesc<'_> { if input[0] != b's' || input[1] != b'f' || input[2] != b'f' || input[3] != b'\0' { panic!("invalid header magic"); } if get_u32(input, 4) != 1 { panic!("invalid version"); } let char_width = get_u16(input, 0x8) as usize; let char_height = get_u16(input, 0xa) as usize; let encoding = match get_u16(input, 0xc) { 0 => Encoding::BitPerPix1, 1 => Encoding::BitPerPix2, 2 => Encoding::BitPerPix4, 3 => Encoding::BitPerPix8, _ => unreachable!(), }; let num_tables = get_u16(input, 0xe); let mut tables = Vec::new(); let mut coff = 0x10; for _ in 0..num_tables { let tab = Table { first_codepoint: get_u32(input, coff), num_codepoints: get_u32(input, coff + 4), offset: get_u32(input, coff + 8), }; tables.push(tab); coff += 3 * 4; } FileDesc { input, char_width, char_height, encoding, tables, } } /// data holds unencoded values, from 0 to 255. They are scaled down/rounded internally. #[derive(Debug)] pub struct TableOut { pub first_codepoint: u32, pub num_codepoints: u32, pub data: Vec>, } #[derive(Debug)] pub struct FileDescOut { pub char_width: usize, pub char_height: usize, pub encoding: Encoding, pub tables: Vec, } fn push_u16(v: &mut Vec, value: u16) { v.push((value & 0xff) as u8); v.push(((value >> 8) & 0xff) as u8); } fn push_u32(v: &mut Vec, value: u32) { v.push((value & 0xff) as u8); v.push(((value >> 8) & 0xff) as u8); v.push(((value >> 16) & 0xff) as u8); v.push(((value >> 24) & 0xff) as u8); } fn put_u16(output: &mut [u8], byte_offset: usize, value: u16) { assert!(byte_offset % 2 == 0); output[byte_offset] = (value & 0xff) as u8; output[byte_offset + 1] = ((value >> 8) & 0xff) as u8; } fn put_u32(output: &mut [u8], byte_offset: usize, value: u32) { assert!(byte_offset % 4 == 0); output[byte_offset] = (value & 0xff) as u8; output[byte_offset + 1] = ((value >> 8) & 0xff) as u8; output[byte_offset + 2] = ((value >> 16) & 0xff) as u8; output[byte_offset + 3] = ((value >> 24) & 0xff) as u8; } impl FileDescOut { pub fn encode(&self) -> Vec { let mut v = vec![b's', b'f', b'f', b'\0']; push_u32(&mut v, 1); assert!(self.char_width < 1 << 16); assert!(self.char_height < 1 << 16); push_u16(&mut v, self.char_width as u16); push_u16(&mut v, self.char_height as u16); push_u16(&mut v, self.encoding.into_code()); push_u16(&mut v, self.tables.len().try_into().unwrap()); for _ in 0..self.tables.len() { // first, reserve space for the table headers push_u32(&mut v, 0); push_u32(&mut v, 0); push_u32(&mut v, 0); } for (i, table) in self.tables.iter().enumerate() { let header_off = 0x10 + i * 3 * 4; put_u32(&mut v[..], header_off, table.first_codepoint); put_u32(&mut v[..], header_off + 4, table.num_codepoints); let curr_len = v.len() as u32; put_u32(&mut v[..], header_off + 8, curr_len); for glyph_data in table.data.iter() { // TODO ugly clone v.extend_from_slice(&self.encoding.compress(glyph_data)[..]); } } v } }