From 538dfea78c894564532b1334bb54ef5acfa5b851 Mon Sep 17 00:00:00 2001 From: James Martin Date: Sat, 18 Jul 2020 00:24:03 -0700 Subject: [PATCH] Completely rewrite nearly everything having to do with fonts. --- src/graphics.rs | 7 +- src/graphics/display.rs | 39 ++++----- src/graphics/font.rs | 77 +++++----------- src/graphics/font/psf.rs | 146 ++++++++++++++++++++++--------- src/graphics/terminal/display.rs | 34 ++++--- 5 files changed, 171 insertions(+), 132 deletions(-) diff --git a/src/graphics.rs b/src/graphics.rs index 46901af..7504cc8 100644 --- a/src/graphics.rs +++ b/src/graphics.rs @@ -7,14 +7,17 @@ pub mod tty; use crate::graphics::color::{COLOR_BLACK, COLOR_WHITE}; use crate::graphics::display::gop::GopDisplay; -use crate::graphics::font::font; +use crate::graphics::font::psf::PSF; use crate::graphics::terminal::display::DisplayTerminal; use crate::graphics::tty::Tty; use crate::graphics::tty::terminal::TerminalTty; pub fn do_graphics(st: &uefi::prelude::SystemTable) { + let font_data = core::include_bytes!("graphics/font/cozette.psf"); + let font = PSF::parse(font_data); + let mut display = GopDisplay::init(st.boot_services()); - let mut terminal = DisplayTerminal::new(&mut display, font(), COLOR_BLACK, COLOR_WHITE); + let mut terminal = DisplayTerminal::new(&mut display, &font, COLOR_BLACK, COLOR_WHITE); let mut tty = TerminalTty::new(&mut terminal); for _ in 0..30 { diff --git a/src/graphics/display.rs b/src/graphics/display.rs index 6abe76a..2fc6dfb 100644 --- a/src/graphics/display.rs +++ b/src/graphics/display.rs @@ -1,5 +1,5 @@ use crate::graphics::color::RGB; -use crate::graphics::font::psf::PSFGlyph; +use crate::graphics::font::Glyph; pub mod gop; @@ -8,6 +8,7 @@ pub trait Display { fn width(&self) -> usize { self.resolution().0 } fn height(&self) -> usize { self.resolution().1 } + // HACK: This interface sucks. unsafe fn set_pixel(&mut self, color: RGB, x: usize, y: usize); fn set_pixel_ignore_oob(&mut self, color: RGB, x: usize, y: usize) { if x > self.width() || y > self.height() { @@ -21,34 +22,30 @@ pub trait Display { fn clear(&mut self, color: RGB); - unsafe fn draw_glyph(&mut self, color: RGB, x: usize, y: usize, glyph: PSFGlyph) { - // Glyphs may actually be larger than their nominal bounding box. - // In fact, the Cozette font is like this: the heart symbol is 7 pixels wide, - // despite nominally being a 6x13 font. - // However, despite not being an intended use of the format, that extra pixel - // can still be stored in the padding bits of the glyph (and is!). - // Therefore, we just continue writing those extra bits if they are present. - // Note that there is no similar trick for the height, - // because the height doesn't have padding. - for glyph_x in 0..glyph.width() { - for glyph_y in 0..glyph.height() { + unsafe fn draw_glyph(&mut self, bounding_box: (usize, usize), x: usize, y: usize, color: RGB, glyph: &dyn Glyph) { + // We only assume that space was left for pixels within the bounding box, + // and that pixels outside the bounding box may be out-of-bounds. + // We use `set_pixel` for the in-bounds pixels and `set_pixel_ignore_oob` + // for the out-of-bounds pixels. + + // HACK: I should be able to figure out whether a row or column will be out-of-bounds statically, and: + // 1. If it is going to be out-of-bounds and is inside the bounding box, panic, and + // 2. if it is outside of the bounding box, don't bother trying to draw that row. + + for glyph_x in 0..glyph.width().min(bounding_box.0) { + for glyph_y in 0..glyph.height().min(bounding_box.1) { if glyph.get(glyph_x, glyph_y) { - self.set_pixel(color, x + glyph_x as usize, y + glyph_y as usize); + self.set_pixel(color, x + glyph_x, y + glyph_y); } } } - // Sometimes, a font may actually have pixels outside its bounding box! - // For example, in Cozette, a 6x13 font, ♡ is actually 7 pixels wide. - // This data is still stored in the padding bits of the glyph. - // Note that there is no similar trick for height because height doesn't have padding. - // Futhermore, this only works on fonts whose width is not a multiple of eight. - for glyph_x in glyph.width()..num_integer::div_ceil(glyph.width(), 8) * 8 { - for glyph_y in 0..glyph.height() { + for glyph_x in glyph.width().min(bounding_box.0)..glyph.width() { + for glyph_y in 0..glyph.height().min(bounding_box.1) { if glyph.get(glyph_x, glyph_y) { // These pixels *nominally* aren't supposed to be there, // so we only force the pixels inside the bounding box. - self.set_pixel_ignore_oob(color, x + glyph_x as usize, y + glyph_y as usize); + self.set_pixel_ignore_oob(color, x + glyph_x, y + glyph_y); } } } diff --git a/src/graphics/font.rs b/src/graphics/font.rs index c3124ec..577039d 100644 --- a/src/graphics/font.rs +++ b/src/graphics/font.rs @@ -1,64 +1,31 @@ -use alloc::sync::Arc; -use alloc::vec::Vec; -use psf::*; - pub mod psf; -static mut FONT: Option> = None; +// Note that currently the Font and Glyph traits are fairly specialized to PSF. +// They will certainly have to be modified to support other types of fonts, +// but they *work* for now, and that's what's important/ -pub fn font() -> Arc { - unsafe { - FONT.clone().unwrap_or_else(|| { - let font = Arc::new(parse_font()); - FONT = Some(font.clone()); - font - }) - } +pub trait Font { + // Once Rust supports existential types, this needs to be an existential type. + type Glyph: Glyph; + + /// The size, in pixels, of the bounding box of each glyph in this font. + fn bounding_box(&self) -> (usize, usize); + + fn lookup<'a>(&'a self, ch: char) -> Option<&'a Self::Glyph>; } -fn parse_font() -> PSF { - use core::convert::TryInto; - let font = core::include_bytes!("font/cozette.psf"); - let length = u32::from_le_bytes(font[16..20].try_into().unwrap()); - let charsize = u32::from_le_bytes(font[20..24].try_into().unwrap()); - let height = u32::from_le_bytes(font[24..28].try_into().unwrap()); - let width = u32::from_le_bytes(font[28..32].try_into().unwrap()); +pub trait Glyph { + /// The width, in pixels, of this specific glyph. + /// This may be a different size than the font's bounding box. + fn width(&self) -> usize; - let glyphs_size = (length * charsize) as usize; - let mut glyphs = Vec::with_capacity(glyphs_size); - glyphs.extend_from_slice(&font[32..glyphs_size + 32]); + /// The height, in pixels, of this specific glyph. + /// This may be a different size than the font's bounding box. + fn height(&self) -> usize; - let mut unicode_map = Vec::new(); - let unicode_info = &font[glyphs_size + 32..]; - let mut glyph = 0; - let mut i = 0; - while i < unicode_info.len() { - let mut nc = unicode_info[i]; + // TODO: Support glyph offsets relative to the font bounding box. - while nc != 0xFE && nc != 0xFF { - let ch_bytes = nc.leading_ones().max(1) as usize; - let st = core::str::from_utf8(&unicode_info[i..i + ch_bytes as usize]).expect("Invalid character"); - let ch = st.chars().next().unwrap(); - unicode_map.push(UnicodeMap { c: ch, i: glyph }); - i += ch_bytes; - nc = unicode_info[i]; - } - - // Ignore multi-codepoint spellings of characters (for now). - while nc != 0xFF { - i += 1; - nc = unicode_info[i]; - } - - i += 1; - glyph += 1; - } - PSF { - width: width, - height: height, - length: length, - charsize: charsize, - glyphs: glyphs, - unicode: unicode_map, - } + /// Check whether an individual pixel of this glyph is set. + /// This function will panic if `x` and `y` are outside the width and height of this glyph. + fn get(&self, x: usize, y: usize) -> bool; } diff --git a/src/graphics/font/psf.rs b/src/graphics/font/psf.rs index b11e0d1..d0e6b37 100644 --- a/src/graphics/font/psf.rs +++ b/src/graphics/font/psf.rs @@ -1,38 +1,102 @@ use alloc::vec::Vec; - -pub struct UnicodeMap { - pub c: char, - pub i: usize, -} +use crate::graphics::font::{Font, Glyph}; pub struct PSF { - pub width: u32, - pub height: u32, - pub length: u32, - pub charsize: u32, - pub glyphs: Vec, - pub unicode: Vec, + width: usize, + height: usize, + glyphs: Vec, + // TODO: Replace this with a proper associative structure. + unicode: Vec, } -pub struct PSFGlyph<'a> { - width: u32, - height: u32, - bitmap: &'a [u8], +/// Associates a unicode character and a glyph. +struct UnicodeMap { + c: char, + // The index of the glyph. + i: usize, +} + +pub struct PSFGlyph { + bitmap: Vec, + line_size: usize, + width: usize, + height: usize, } impl PSF { - pub fn resolution(&self) -> (u32, u32) { - (self.width, self.height) - } - - pub fn width(&self) -> u32 { - self.width - } - - pub fn height(&self) -> u32 { - self.height + pub fn parse(font: &[u8]) -> PSF { + use core::convert::TryInto; + + // The number of glyphs in this font. + let length = u32::from_le_bytes(font[16..20].try_into().unwrap()) as usize; + // The size in bytes of a single glyph. + let charsize = u32::from_le_bytes(font[20..24].try_into().unwrap()) as usize; + // The height in pixels of this font's bounding box. + let height = u32::from_le_bytes(font[24..28].try_into().unwrap()) as usize; + // The width in pixels of this font's bounding box. + let width = u32::from_le_bytes(font[28..32].try_into().unwrap()) as usize; + // The size in bytes of a single row of pixels in a glyph. + let line_size = num_integer::div_ceil(width, 8); + + let glyphs_offset = 32; // the size of the header + let glyphs_size = length * charsize; + let unicode_offset = glyphs_offset + glyphs_size; + + let mut glyphs = Vec::with_capacity(length); + + for i in 0..length { + let mut bitmap = Vec::with_capacity(charsize); + let bitmap_begin = glyphs_offset + charsize * i; + let bitmap_end = bitmap_begin + charsize; + bitmap.extend_from_slice(&font[bitmap_begin..bitmap_end]); + + glyphs.push(PSFGlyph { + bitmap: bitmap, + line_size: line_size, + // Glyphs may overflow the font's nominal resolution in the padding bytes of the line! + // This trick only works for the width because there is no vertical padding. + // TODO: Pre-compute widths and bounding box offsets of individual glyphs. + width: line_size * 8, + height: height, + }); + } + + // HACK: This unicode map parser is still a mess. + let mut unicode_map = Vec::new(); + let unicode_info = &font[unicode_offset..]; + let mut glyph = 0; + let mut i = 0; + while i < unicode_info.len() { + let mut nc = unicode_info[i]; + + while nc != 0xFE && nc != 0xFF { + let ch_bytes = nc.leading_ones().max(1) as usize; + let st = core::str::from_utf8(&unicode_info[i..i + ch_bytes]).expect("Invalid character"); + let ch = st.chars().next().unwrap(); + unicode_map.push(UnicodeMap { c: ch, i: glyph }); + i += ch_bytes; + nc = unicode_info[i]; + } + + // TODO: Support multi-codepoint spellings of characters. + while nc != 0xFF { + i += 1; + nc = unicode_info[i]; + } + + i += 1; + glyph += 1; + } + + PSF { + width: width, + height: height, + glyphs: glyphs, + unicode: unicode_map, + } } + /// The index of the glyph associated with a particular unicde character. fn index_of(&self, c: char) -> Option { for entry in &self.unicode { if entry.c == c { @@ -41,31 +105,33 @@ impl PSF { } None } +} - fn get_bitmap<'a>(&'a self, index: usize) -> &'a [u8] { - let byte_index = self.charsize as usize * index; - &self.glyphs[byte_index..byte_index + self.charsize as usize] +impl Font for PSF { + type Glyph = PSFGlyph; + + fn bounding_box(&self) -> (usize, usize) { + (self.width, self.height) } - pub fn lookup<'a>(&'a self, c: char) -> Option> { - self.index_of(c).map(|i| PSFGlyph { - width: self.width, - height: self.height, - bitmap: self.get_bitmap(i) - }) + fn lookup<'a>(&'a self, c: char) -> Option<&'a PSFGlyph> { + self.index_of(c).map(|i| &self.glyphs[i]) } } -impl PSFGlyph<'_> { - pub fn width(&self) -> u32 { self.width } +impl Glyph for PSFGlyph { + fn width(&self) -> usize { self.width } + fn height(&self) -> usize { self.height } - pub fn height(&self) -> u32 { self.height } + fn get(&self, x: usize, y: usize) -> bool { + if x > self.width || y > self.height { + use crate::graphics::tty::Tty; + crate::panic!("Glyph pixel index out of bounds."); + } - pub fn get(&self, x: u32, y: u32) -> bool { - let line_size = num_integer::div_ceil(self.width, 8); let (line_byte_index, bit_index) = num_integer::div_rem(x, 8); let mask = 0b10000000 >> bit_index; - let byte = self.bitmap[(y * line_size + line_byte_index) as usize]; + let byte = self.bitmap[(y * self.line_size + line_byte_index) as usize]; byte & mask > 0 } } diff --git a/src/graphics/terminal/display.rs b/src/graphics/terminal/display.rs index aa7dcde..abb4916 100644 --- a/src/graphics/terminal/display.rs +++ b/src/graphics/terminal/display.rs @@ -1,36 +1,38 @@ -use alloc::sync::Arc; -use crate::graphics::color::RGB; +use crate::graphics::color::{Color, RGB}; use crate::graphics::display::Display; -use crate::graphics::font::psf::PSF; +use crate::graphics::font::{Font, Glyph}; use crate::graphics::terminal::Terminal; use crate::graphics::terminal::frame::TerminalFrame; -pub struct DisplayTerminal<'display> { - display: &'display mut dyn Display, +pub struct DisplayTerminal<'d, 'f, G: Glyph> { + display: &'d mut (dyn Display + 'd), + font: &'f (dyn Font + 'f), frame: TerminalFrame, - font: Arc, bg: RGB, fg: RGB, } -impl DisplayTerminal<'_> { - pub fn new<'display>(display: &'display mut dyn Display, font: Arc, bg: RGB, fg: RGB) -> DisplayTerminal<'display> { +impl DisplayTerminal<'_, '_, G> { + pub fn new<'d, 'f> + (display: &'d mut (dyn Display + 'd), font: &'f (dyn Font + 'f), + bg: impl Color, fg: impl Color) + -> DisplayTerminal<'d, 'f, G> { let (dp_width, dp_height) = display.resolution(); - let (ft_width, ft_height) = font.resolution(); + let (ft_width, ft_height) = font.bounding_box(); let ch_width = dp_width / ft_width as usize; let ch_height = dp_height / ft_height as usize; DisplayTerminal { display: display, - frame: TerminalFrame::new((ch_width, ch_height)), font: font, - bg: bg, - fg: fg, + frame: TerminalFrame::new((ch_width, ch_height)), + bg: bg.into_rgb(), + fg: fg.into_rgb(), } } } -impl Terminal for DisplayTerminal<'_> { +impl Terminal for DisplayTerminal<'_, '_, G> { fn get_frame<'a>(&'a self) -> &'a TerminalFrame { &self.frame } @@ -46,9 +48,13 @@ impl Terminal for DisplayTerminal<'_> { let c = self.frame.get(x, y); if c == '\u{0}' { continue; } + // FIXME: This code shouldn't throw errors. + // instead, it should display some kind of missing character. let glyph = self.font.lookup(c).expect("Character missing from font."); + let px_x = x * self.font.bounding_box().0; + let px_y = y * self.font.bounding_box().1; unsafe { - self.display.draw_glyph(self.fg, self.font.width() as usize * x, self.font.height() as usize * y, glyph); + self.display.draw_glyph(self.font.bounding_box(), px_x, px_y, self.fg, glyph); } } }