diff --git a/userspace/Cargo.lock b/userspace/Cargo.lock index 5e799b84..08cd4320 100644 --- a/userspace/Cargo.lock +++ b/userspace/Cargo.lock @@ -9,7 +9,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0" dependencies = [ "ab_glyph_rasterizer", - "owned_ttf_parser", + "owned_ttf_parser 0.25.0", ] [[package]] @@ -1818,13 +1818,22 @@ dependencies = [ "libredox", ] +[[package]] +name = "owned_ttf_parser" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e6affeb1632d6ff6a23d2cd40ffed138e82f1532571a26f527c8a284bb2fbb" +dependencies = [ + "ttf-parser 0.15.2", +] + [[package]] name = "owned_ttf_parser" version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4" dependencies = [ - "ttf-parser", + "ttf-parser 0.25.1", ] [[package]] @@ -2312,6 +2321,16 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "rusttype" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff8374aa04134254b7995b63ad3dc41c7f7236f69528b28553da7d72efaa967" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser 0.15.2", +] + [[package]] name = "rustversion" version = "1.0.19" @@ -2665,6 +2684,7 @@ dependencies = [ "logsink", "pci-ids", "rand 0.9.0-alpha.1", + "regex", "runtime", "serde", "serde_json", @@ -2697,6 +2717,7 @@ dependencies = [ "libpsf", "log", "logsink", + "rusttype", "thiserror", ] @@ -2834,6 +2855,12 @@ version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +[[package]] +name = "ttf-parser" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd" + [[package]] name = "ttf-parser" version = "0.25.1" diff --git a/userspace/etc/fonts/fixed-regular.ttf b/userspace/etc/fonts/fixed-regular.ttf index 5814fc91..1a39bc75 100644 Binary files a/userspace/etc/fonts/fixed-regular.ttf and b/userspace/etc/fonts/fixed-regular.ttf differ diff --git a/userspace/term/Cargo.toml b/userspace/term/Cargo.toml index fb940a98..6564d036 100644 --- a/userspace/term/Cargo.toml +++ b/userspace/term/Cargo.toml @@ -12,3 +12,5 @@ logsink.workspace = true log.workspace = true thiserror.workspace = true clap.workspace = true + +rusttype = "0.9.3" diff --git a/userspace/term/src/attr.rs b/userspace/term/src/attr.rs index 69a79c48..498c1c19 100644 --- a/userspace/term/src/attr.rs +++ b/userspace/term/src/attr.rs @@ -14,9 +14,9 @@ pub enum Color { #[derive(Clone, Copy, PartialEq, Eq, Debug)] #[repr(C)] pub struct DisplayColor { - r: u8, - g: u8, - b: u8, + pub r: u8, + pub g: u8, + pub b: u8, } #[derive(Clone, Copy, Debug)] diff --git a/userspace/term/src/error.rs b/userspace/term/src/error.rs index b7b2e54f..657b3c4a 100644 --- a/userspace/term/src/error.rs +++ b/userspace/term/src/error.rs @@ -4,4 +4,6 @@ pub enum Error { ApplicationError(#[from] libcolors::error::Error), #[error("I/O error: {0}")] IoError(#[from] std::io::Error), + #[error("Font load error")] + FontLoadError, } diff --git a/userspace/term/src/font.rs b/userspace/term/src/font.rs new file mode 100644 index 00000000..d120537d --- /dev/null +++ b/userspace/term/src/font.rs @@ -0,0 +1,70 @@ +use std::{fs, path::Path}; + +use crate::error::Error; + +pub trait Font { + fn layout(&self) -> &FontLayout; + fn map_glyph(&mut self, ch: char, mapper: F); +} + +pub struct TrueTypeFont<'a> { + inner: rusttype::Font<'a>, + scale: rusttype::Scale, + layout: FontLayout, + ascent: f32, +} + +impl TrueTypeFont<'static> { + pub fn load>(path: P, pixel_height: usize) -> Result { + let font_data = fs::read(path)?; + let inner = rusttype::Font::try_from_vec(font_data).ok_or(Error::FontLoadError)?; + let unscaled_v_metrics = inner.v_metrics(rusttype::Scale::uniform(1.0f32)); + let scale = + (pixel_height as f32) / (unscaled_v_metrics.ascent - unscaled_v_metrics.descent); + let scale = rusttype::Scale::uniform(scale); + let v_metrics = inner.v_metrics(scale); + let a_h_metrics = inner.glyph('a').scaled(scale).h_metrics(); + let layout = FontLayout { + width: a_h_metrics.advance_width.ceil() as usize, + height: (v_metrics.ascent - v_metrics.descent).ceil() as usize, + }; + let ascent = v_metrics.ascent; + Ok(Self { + inner, + scale, + layout, + ascent, + }) + } +} + +impl Font for TrueTypeFont<'_> { + fn layout(&self) -> &FontLayout { + &self.layout + } + + fn map_glyph(&mut self, ch: char, mut mapper: F) { + let glyph = self + .inner + .glyph(ch) + .scaled(self.scale) + .positioned(rusttype::Point { + x: 0.0, + y: self.ascent, + }); + + if let Some(bb) = glyph.pixel_bounding_box() { + glyph.draw(|x, y, v| { + let x = (x as i32 + bb.min.x).clamp(0, self.layout.width as i32); + let y = (y as i32 + bb.min.y).clamp(0, self.layout.height as i32); + mapper(x as usize, y as usize, v); + }); + } + } +} + +#[derive(Debug)] +pub struct FontLayout { + pub width: usize, + pub height: usize, +} diff --git a/userspace/term/src/main.rs b/userspace/term/src/main.rs index e7d8d334..f543bf0b 100644 --- a/userspace/term/src/main.rs +++ b/userspace/term/src/main.rs @@ -15,6 +15,7 @@ use std::{ rt::io::device, }, }, + path::PathBuf, process::{Child, Command, ExitCode, Stdio}, sync::{ atomic::{AtomicBool, Ordering}, @@ -22,29 +23,32 @@ use std::{ }, }; +use clap::Parser; use error::Error; -use libpsf::PcScreenFont; +use font::{Font, TrueTypeFont}; use libcolors::{ application::{ window::{EventOutcome, Window}, Application, }, - event::KeyModifiers, input::Key, + event::KeyModifiers, + input::Key, }; use state::{Cursor, State}; pub mod attr; pub mod error; +pub mod font; pub mod state; -struct DrawState { +struct DrawState { width: usize, force_redraw: bool, focus_changed: bool, focused: bool, old_cursor: Cursor, - font: PcScreenFont<'static>, + font: F, } pub struct Terminal<'a> { @@ -59,24 +63,24 @@ pub struct Terminal<'a> { shell: Child, } -impl DrawState { - pub fn new(font: PcScreenFont<'static>, width: usize) -> Self { +impl DrawState { + pub fn new(font: F, width: usize) -> Self { Self { width, + font, force_redraw: true, focus_changed: false, focused: true, old_cursor: Cursor { row: 0, col: 0 }, - - font, } } pub fn draw(&mut self, dt: &mut [u32], state: &mut State) { let default_fg = state.default_attributes.fg.to_display(false).to_u32(); let default_bg = state.default_attributes.bg.to_display(false).to_u32(); - let fw = self.font.width() as usize; - let fh = self.font.height() as usize; + let font_layout = self.font.layout(); + let fw = font_layout.width; + let fh = font_layout.height; let cursor_dirty = self.old_cursor != state.cursor; @@ -95,7 +99,7 @@ impl DrawState { for (j, cell) in row.cells().enumerate() { let bg = cell.attrs.bg.to_display(false).to_u32(); - let fg = cell.attrs.fg.to_display(cell.attrs.bright).to_u32(); + let fg = cell.attrs.fg.to_display(cell.attrs.bright); let cx = j * fw; @@ -109,9 +113,14 @@ impl DrawState { continue; } - let c = cell.char as u32; - self.font.map_glyph_pixels(c, |x, y, set| { - let color = if set { fg } else { bg }; + let c = cell.char as char; + self.font.map_glyph(c, |x, y, v| { + let v = (v * 2.0).min(1.0); + let r = fg.r as f32 * v; + let g = fg.g as f32 * v; + let b = fg.b as f32 * v; + let color = (b as u32) | ((g as u32) << 8) | ((r as u32) << 16) | 0xFF000000; + dt[(cy + y) * self.width + cx + x] = color; }); } @@ -145,7 +154,7 @@ impl DrawState { } impl Terminal<'_> { - pub fn new(font: PcScreenFont<'static>) -> Result { + pub fn new(font: F) -> Result { let mut app = Application::new()?; let mut window = Window::new(&app)?; let mut poll = PollChannel::new()?; @@ -153,8 +162,10 @@ impl Terminal<'_> { let width = window.width() as usize; let height = window.height() as usize; - let rows = height / font.height() as usize; - let columns = width / font.width() as usize; + let font_layout = font.layout(); + + let rows = height / font_layout.height; + let columns = width / font_layout.width; let termios = TerminalOptions::default(); let (pty_master, pty_slave) = create_pty(Some(termios), TerminalSize { rows, columns })?; @@ -175,8 +186,9 @@ impl Terminal<'_> { let width = width as usize; let height = height as usize; - let rows = height / ds.font.height() as usize; - let columns = width / ds.font.width() as usize; + let font_layout = ds.font.layout(); + let rows = height / font_layout.height; + let columns = width / font_layout.width; let term_size = TerminalSize { rows, columns }; let mut buffer = [0; 64]; @@ -187,10 +199,7 @@ impl Terminal<'_> { ) .unwrap(); - s.resize( - width / ds.font.width() as usize, - height / ds.font.height() as usize, - ); + s.resize(width / font_layout.width, height / font_layout.height); ds.width = width; ds.force_redraw = true; @@ -361,10 +370,36 @@ impl Terminal<'_> { static ABORT: AtomicBool = AtomicBool::new(false); -fn main() -> ExitCode { - logsink::setup_logging(false); - let font = PcScreenFont::default(); - let term = Terminal::new(font).unwrap(); - - term.run() +#[derive(Debug, Parser)] +struct Args { + #[clap( + long, + help = "TTF font to use", + default_value = "/etc/fonts/fixed-regular.ttf" + )] + regular_font: PathBuf, + #[clap( + long, + help = "Font height in pixels (only for TTF fonts)", + default_value_t = 16 + )] + font_size: usize, +} + +fn run(args: &Args) -> Result { + let font = TrueTypeFont::load(&args.regular_font, args.font_size)?; + let term = Terminal::new(font)?; + Ok(term.run()) +} + +fn main() -> ExitCode { + let args = Args::parse(); + logsink::setup_logging(false); + match run(&args) { + Ok(code) => code, + Err(error) => { + log::error!("{error}"); + ExitCode::FAILURE + } + } }