Created
July 22, 2021 15:25
-
-
Save hinzundcode/0480c5c8aa220cd43cc8da634119a3c0 to your computer and use it in GitHub Desktop.
psf font parser in rust
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::fs; | |
use std::mem; | |
// https://www.win.tue.nl/~aeb/linux/kbd/font-formats-1.html | |
const PSF1_MAGIC: [u8;2] = [0x36, 0x04]; | |
const PSF2_MAGIC: [u8;4] = [0x72, 0xb5, 0x4a, 0x86]; | |
#[derive(Debug)] | |
enum Error { | |
OutOfBounds, | |
InvalidMagic, | |
} | |
#[repr(C, packed)] | |
struct PSF1Header { | |
magic: [u8;2], | |
mode: u8, | |
char_size: u8, | |
} | |
#[repr(C, packed)] | |
struct PSF2Header { | |
magic: [u8;4], | |
version: u32, | |
header_size: u32, | |
flags: u32, | |
length: u32, | |
char_size: u32, | |
height: u32, | |
width: u32, | |
} | |
enum PSFFont<'a> { | |
PSF1Font(PSF1Font<'a>), | |
PSF2Font(PSF2Font<'a>), | |
} | |
impl<'a> PSFFont<'a> { | |
fn parse(data: &'a [u8]) -> Result<Self, Error> { | |
if data.len() < mem::size_of::<PSF1Header>() { | |
return Err(Error::OutOfBounds); | |
} | |
let header = unsafe { | |
let ref header = *(data.as_ptr() as *const PSF1Header); | |
header | |
}; | |
if header.magic == PSF1_MAGIC { | |
return Ok(PSFFont::PSF1Font(PSF1Font::parse(data)?)); | |
} | |
if data.len() < mem::size_of::<PSF2Header>() { | |
return Err(Error::OutOfBounds); | |
} | |
let header = unsafe { | |
let ref header = *(data.as_ptr() as *const PSF2Header); | |
header | |
}; | |
if header.magic == PSF2_MAGIC { | |
return Ok(PSFFont::PSF2Font(PSF2Font::parse(data)?)); | |
} | |
return Err(Error::InvalidMagic); | |
} | |
fn glyph_size(&self) -> (u32, u32) { | |
match self { | |
PSFFont::PSF1Font(font) => font.glyph_size(), | |
PSFFont::PSF2Font(font) => font.glyph_size(), | |
} | |
} | |
fn glyph_count(&self) -> u32 { | |
match self { | |
PSFFont::PSF1Font(font) => font.glyph_count(), | |
PSFFont::PSF2Font(font) => font.glyph_count(), | |
} | |
} | |
fn glyph(&self, index: u32) -> Option<&[u8]> { | |
match self { | |
PSFFont::PSF1Font(font) => font.glyph(index), | |
PSFFont::PSF2Font(font) => font.glyph(index), | |
} | |
} | |
} | |
struct PSF1Font<'a> { | |
data: &'a [u8], | |
header: &'a PSF1Header, | |
} | |
impl<'a> PSF1Font<'a> { | |
fn parse(data: &'a [u8]) -> Result<Self, Error> { | |
if data.len() < mem::size_of::<PSF1Header>() { | |
return Err(Error::OutOfBounds); | |
} | |
let header = unsafe { | |
let ref header = *(data.as_ptr() as *const PSF1Header); | |
header | |
}; | |
if header.magic != PSF1_MAGIC { | |
return Err(Error::InvalidMagic); | |
} | |
let last_glyph_pos = mem::size_of::<PSF1Header>() + header.char_size as usize * 256; | |
if data.len() < last_glyph_pos { | |
return Err(Error::OutOfBounds); | |
} | |
Ok(PSF1Font { | |
data, | |
header, | |
}) | |
} | |
fn glyph_size(&self) -> (u32, u32) { | |
(8, self.header.char_size as u32) | |
} | |
fn glyph_count(&self) -> u32 { | |
256 | |
} | |
fn glyph(&self, index: u32) -> Option<&[u8]> { | |
if index >= 256 { | |
return None | |
} | |
let length = self.header.char_size as usize; | |
let offset = mem::size_of::<PSF1Header>() + index as usize * length; | |
Some(&self.data[offset..(offset+length)]) | |
} | |
} | |
struct PSF2Font<'a> { | |
data: &'a [u8], | |
header: &'a PSF2Header, | |
} | |
impl<'a> PSF2Font<'a> { | |
fn parse(data: &'a [u8]) -> Result<Self, Error> { | |
if data.len() < mem::size_of::<PSF2Header>() { | |
return Err(Error::OutOfBounds); | |
} | |
let header = unsafe { | |
let ref header = *(data.as_ptr() as *const PSF2Header); | |
header | |
}; | |
if header.magic != PSF2_MAGIC { | |
return Err(Error::InvalidMagic); | |
} | |
let last_glyph_pos = header.header_size + header.char_size * (header.length - 1); | |
if data.len() < last_glyph_pos as usize { | |
return Err(Error::OutOfBounds); | |
} | |
Ok(PSF2Font { | |
data, | |
header, | |
}) | |
} | |
fn glyph_size(&self) -> (u32, u32) { | |
(self.header.width, self.header.height) | |
} | |
fn glyph_count(&self) -> u32 { | |
self.header.length | |
} | |
fn glyph(&self, index: u32) -> Option<&[u8]> { | |
if index >= self.header.length { | |
return None | |
} | |
let length = self.header.char_size as usize; | |
let offset = self.header.header_size as usize + index as usize * length; | |
Some(&self.data[offset..(offset+length)]) | |
} | |
} | |
fn print_bitmap(bitmap: &[u8], size: (u32, u32)) { | |
let stride = (size.0 + 7) / 8; | |
println!("stride {}", stride); | |
for y in 0..size.1 { | |
for x in 0..size.0 { | |
let byte = y * stride + (x / 8); | |
let bit = 7 - (x % 8); | |
if (bitmap[byte as usize] & (1 << bit)) == 0 { | |
print!("_"); | |
} else { | |
print!("#"); | |
} | |
} | |
println!(); | |
} | |
} | |
fn main() -> Result<(), std::io::Error> { | |
let buffer = fs::read("Lat7-Terminus28x14.psf")?; | |
//let buffer = fs::read("Lat7-Terminus16.psf")?; | |
let font = PSFFont::parse(&buffer).unwrap(); | |
println!("glyph count: {}", font.glyph_count()); | |
println!("glyph size: {:?}", font.glyph_size()); | |
for glyph_index in 0..256 { | |
let glyph = font.glyph(glyph_index).unwrap(); | |
println!("glyph {}: {:?}", glyph_index, glyph); | |
print_bitmap(glyph, font.glyph_size()); | |
} | |
Ok(()) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment