249 lines
7.4 KiB
Rust
249 lines
7.4 KiB
Rust
|
|
#[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<u8> {
|
||
|
|
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<Table>,
|
||
|
|
}
|
||
|
|
|
||
|
|
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<u8> {
|
||
|
|
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<Vec<u8>>,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug)]
|
||
|
|
pub struct FileDescOut {
|
||
|
|
pub char_width: usize,
|
||
|
|
pub char_height: usize,
|
||
|
|
pub encoding: Encoding,
|
||
|
|
pub tables: Vec<TableOut>,
|
||
|
|
}
|
||
|
|
|
||
|
|
fn push_u16(v: &mut Vec<u8>, value: u16) {
|
||
|
|
v.push((value & 0xff) as u8);
|
||
|
|
v.push(((value >> 8) & 0xff) as u8);
|
||
|
|
}
|
||
|
|
|
||
|
|
fn push_u32(v: &mut Vec<u8>, 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<u8> {
|
||
|
|
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
|
||
|
|
}
|
||
|
|
}
|