karlos-font-helper/src/sff.rs

290 lines
8.9 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 => {
assert!(input.len() % 8 == 0); // TODO not strictly necessary
for c in 0..input.len() / 8 {
let mut byte = 0u8;
for i in 0..8 {
let piece = input[(c << 3) + i] >> 7;
byte |= piece << (7 - i);
}
out.push(byte);
}
}
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)]
pub struct FileDesc<'a> {
input: &'a [u8],
char_width: usize,
char_height: usize,
encoding: Encoding,
tables: Vec<Table>,
}
#[derive(Debug, Copy, Clone)]
pub struct GlyphOffset(u32);
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,
}
}
pub fn find_codepoint(&self, codepoint: u32) -> Option<GlyphOffset> {
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();
GlyphOffset(off as u32)
})
}
// returns the unscaled pixel value!
pub fn get_pixel_value_raw(&self, off: GlyphOffset, row: usize, col: usize) -> u8 {
assert!(row < self.char_height);
assert!(col < self.char_width);
let px_index = row * self.char_width + col;
let Access {
byte_index,
shift,
mask,
} = self.encoding.access_from_index(px_index);
(self.input[off.0 as usize + byte_index] >> shift) & mask
}
pub fn get_pixel_value_scaled(&self, off: GlyphOffset, row: usize, col: usize) -> u8 {
let raw = self.get_pixel_value_raw(off, row, col);
match self.encoding {
Encoding::BitPerPix1 => raw * 255,
Encoding::BitPerPix2 => (raw as usize * 255 / 3) as u8,
Encoding::BitPerPix4 => (raw as usize * 255 / 15) as u8,
Encoding::BitPerPix8 => raw,
}
}
pub fn print_symbol(&self, off: GlyphOffset) {
println!("warning: assuming dark mode console");
let p100 = '█';
let p75 = '▓';
let p50 = '▒';
let p25 = '░';
let p0 = ' ';
let chars = [p0, p25, p50, p75, p100];
for row in 0..self.char_height {
for col in 0..self.char_width {
let value = self.get_pixel_value_scaled(off, row, col);
// map 0..=255 to 0..=4
let c = chars[value as usize * 4 / 255];
print!("{}{}", c, c);
}
println!();
}
}
}
pub 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
}
}