Merge branch 'console-font-support' into 'main'

Implement font support

See merge request redox-os/base!261
This commit is contained in:
Jeremy Soller
2026-05-15 17:21:18 -06:00
7 changed files with 199 additions and 27 deletions
Generated
+33
View File
@@ -56,6 +56,18 @@ dependencies = [
"thiserror 2.0.18",
]
[[package]]
name = "ahash"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [
"cfg-if 1.0.4",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "ahcid"
version = "0.1.0"
@@ -425,6 +437,7 @@ dependencies = [
"drm",
"graphics-ipc",
"orbclient",
"psf-rs",
"ransid",
]
@@ -751,11 +764,14 @@ dependencies = [
"libredox",
"log",
"orbclient",
"psf-rs",
"ransid",
"redox-scheme",
"redox_event",
"redox_syscall 0.7.5",
"scheme-utils",
"serde",
"toml",
]
[[package]]
@@ -1614,6 +1630,17 @@ dependencies = [
"redox_syscall 0.7.5",
]
[[package]]
name = "psf-rs"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "065311c47c07d1bd124117c2739388a5fd5788f8997fab6065e93e77874b7123"
dependencies = [
"ahash",
"hash32",
"heapless",
]
[[package]]
name = "ptyd"
version = "0.1.0"
@@ -2498,6 +2525,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "vesad"
version = "0.1.0"
+1
View File
@@ -87,6 +87,7 @@ libc = "0.2.181"
log = "0.4"
libredox = "0.1.16"
orbclient = "0.3.51"
psf-rs = "2.2.0"
parking_lot = "0.12"
pico-args = "0.5"
plain = "0.2.3"
+1
View File
@@ -8,6 +8,7 @@ edition = "2021"
drm.workspace = true
orbclient.workspace = true
ransid.workspace = true
psf-rs.workspace = true
graphics-ipc = { path = "../graphics-ipc" }
+110 -24
View File
@@ -1,5 +1,3 @@
extern crate ransid;
use std::collections::VecDeque;
use std::convert::TryFrom;
use std::{cmp, io, mem, ptr};
@@ -7,6 +5,7 @@ use std::{cmp, io, mem, ptr};
use drm::buffer::{Buffer, DrmFourcc};
use drm::control::{connector, crtc, framebuffer, ClipRect, Device, Mode};
use graphics_ipc::{CpuBackedBuffer, V2GraphicsHandle};
use orbclient::FONT;
#[derive(Debug, Copy, Clone)]
@@ -133,15 +132,68 @@ struct DisplayMap {
height: usize,
}
#[derive(Clone)]
pub struct ConsoleFont {
glyphs: Vec<u8>,
width: usize,
height: usize,
}
impl ConsoleFont {
pub fn new(glyphs: Vec<u8>, width: usize, height: usize) -> ConsoleFont {
ConsoleFont {
glyphs,
width,
height,
}
}
pub fn from_psf(data: &[u8]) -> ConsoleFont {
let font = psf_rs::Font::load(data);
let width = font.header.glyph_width as usize;
let height = font.header.glyph_height as usize;
let bytes_per_row = (width + 7) / 8;
let glyph_count = font.header.length as usize;
let mut glyphs = vec![0u8; glyph_count * height * bytes_per_row];
for i in 0..glyph_count {
if let Some(c) = char::from_u32(i as u32) {
let glyph_offset = i * height * bytes_per_row;
font.display_glyph(c, |bit, x, y| {
if bit != 0 {
let byte_offset =
glyph_offset + (y as usize) * bytes_per_row + ((x as usize) / 8);
let bit_offset = 7 - (x % 8);
if byte_offset < glyphs.len() {
glyphs[byte_offset] |= 1 << bit_offset;
}
}
});
}
}
Self {
glyphs,
width,
height,
}
}
}
pub struct TextScreen {
console: ransid::Console,
font: ConsoleFont,
}
impl TextScreen {
pub fn new() -> TextScreen {
pub fn new(font: Option<ConsoleFont>) -> TextScreen {
TextScreen {
// Width and height will be filled in on the next write to the console
console: ransid::Console::new(0, 0),
font: font.unwrap_or_else(|| ConsoleFont::new(FONT.to_vec(), 8, 16)),
}
}
@@ -210,18 +262,19 @@ impl TextScreen {
x: usize,
y: usize,
character: char,
font: &ConsoleFont,
color: u32,
_bold: bool,
_italic: bool,
) {
if x + 8 <= map.width && y + 16 <= map.height {
if x + font.width <= map.width && y + font.height <= map.height {
let mut dst = map.offscreen as *mut u8 as usize + (y * map.width + x) * 4;
let font_i = 16 * (character as usize);
if font_i + 16 <= FONT.len() {
for row in 0..16 {
let row_data = FONT[font_i + row];
for col in 0..8 {
let font_i = font.height * (character as usize);
if font_i + font.height <= font.glyphs.len() {
for row in 0..font.height {
let row_data = font.glyphs[font_i + row];
for col in 0..font.width {
if (row_data >> (7 - col)) & 1 == 1 {
unsafe {
*((dst + col * 4) as *mut u32) = color;
@@ -265,7 +318,8 @@ impl TextScreen {
}
};
self.console.resize(map.width / 8, map.height / 16);
self.console
.resize(map.width / self.font.width, map.height / self.font.height);
if self.console.state.x >= self.console.state.w {
self.console.state.x = self.console.state.w - 1;
}
@@ -279,7 +333,13 @@ impl TextScreen {
{
let x = self.console.state.x;
let y = self.console.state.y;
Self::invert(map, x * 8, y * 16, 8, 16);
Self::invert(
map,
x * self.font.width,
y * self.font.height,
self.font.width,
self.font.height,
);
col_changed(x);
line_changed(y);
}
@@ -293,13 +353,29 @@ impl TextScreen {
bold,
..
} => {
Self::char(map, x * 8, y * 16, c, color.as_rgb(), bold, false);
Self::char(
map,
x * self.font.width,
y * self.font.height,
c,
&self.font,
color.as_rgb(),
bold,
false,
);
col_changed(x);
line_changed(y);
}
ransid::Event::Input { data } => input.extend(data),
ransid::Event::Rect { x, y, w, h, color } => {
Self::rect(map, x * 8, y * 16, w * 8, h * 16, color.as_rgb());
Self::rect(
map,
x * self.font.width,
y * self.font.height,
w * self.font.width,
h * self.font.height,
color.as_rgb(),
);
for y2 in y..y + h {
line_changed(y2);
}
@@ -322,11 +398,13 @@ impl TextScreen {
for raw_y in 0..h {
let y = if from_y > to_y { raw_y } else { h - raw_y - 1 };
for pixel_y in 0..16 {
for pixel_y in 0..self.font.width {
{
let off_from = ((from_y + y) * 16 + pixel_y) * width + from_x * 8;
let off_to = ((to_y + y) * 16 + pixel_y) * width + to_x * 8;
let len = w * 8;
let off_from = ((from_y + y) * self.font.height + pixel_y) * width
+ from_x * self.font.width;
let off_to = ((to_y + y) * self.font.height + pixel_y) * width
+ to_x * self.font.width;
let len = w * self.font.width;
if off_from + len <= pixels.len() && off_to + len <= pixels.len() {
unsafe {
@@ -356,15 +434,23 @@ impl TextScreen {
{
let x = self.console.state.x;
let y = self.console.state.y;
Self::invert(map, x * 8, y * 16, 8, 16);
Self::invert(
map,
x * self.font.width,
y * self.font.height,
self.font.width,
self.font.height,
);
line_changed(y);
}
let damage = Damage {
x: u32::try_from(min_changed_x).unwrap() * 8,
y: u32::try_from(min_changed_y).unwrap() * 16,
width: u32::try_from(max_changed_x.saturating_sub(min_changed_x) + 1).unwrap() * 8,
height: u32::try_from(max_changed_y.saturating_sub(min_changed_y) + 1).unwrap() * 16,
x: u32::try_from(min_changed_x).unwrap() * self.font.width as u32,
y: u32::try_from(min_changed_y).unwrap() * self.font.height as u32,
width: u32::try_from(max_changed_x.saturating_sub(min_changed_x) + 1).unwrap()
* self.font.width as u32,
height: u32::try_from(max_changed_y.saturating_sub(min_changed_y) + 1).unwrap()
* self.font.height as u32,
};
damage
@@ -420,10 +506,10 @@ impl TextScreen {
} else {
let deleted_rows = (old_map.height - new_map.height).div_ceil(16);
for row in 0..new_map.height {
if row + (deleted_rows + 1) * 16 >= old_map.height {
if row + (deleted_rows + 1) * self.font.height >= old_map.height {
break;
}
copy_row(old_map, new_map, row + deleted_rows * 16, row);
copy_row(old_map, new_map, row + deleted_rows * self.font.height, row);
}
self.console.state.y = self.console.state.y.saturating_sub(deleted_rows);
}
+3
View File
@@ -20,6 +20,9 @@ daemon = { path = "../../../daemon" }
graphics-ipc = { path = "../graphics-ipc" }
inputd = { path = "../../inputd" }
libredox.workspace = true
psf-rs.workspace = true
serde.workspace = true
toml.workspace = true
[features]
default = []
+49 -1
View File
@@ -1,13 +1,18 @@
use std::collections::BTreeMap;
use std::fs;
use std::os::fd::AsRawFd;
use console_draw::ConsoleFont;
use event::{EventQueue, UserData};
use orbclient::FONT;
use redox_scheme::scheme::SchemeSync;
use redox_scheme::{CallerCtx, OpenResult};
use scheme_utils::{FpathWriter, HandleMap};
use syscall::schemev2::NewFdFlags;
use syscall::{Error, EventFlags, Result, EACCES, EAGAIN, EBADF, ENOENT, O_NONBLOCK};
use serde::Deserialize;
use crate::display::Display;
use crate::text::TextScreen;
@@ -49,6 +54,39 @@ impl FbconScheme {
pub fn new(vt_ids: &[usize], event_queue: &mut EventQueue<VtIndex>) -> FbconScheme {
let mut vts = BTreeMap::new();
let config = match fs::read_to_string("/etc/fbcond.toml") {
Ok(config) => config,
Err(err) => {
log::debug!("Failed to read config: {err}");
String::new()
}
};
let config = match toml::from_str::<FbconConfig>(&config) {
Ok(config) => config,
Err(err) => {
log::debug!("Failed to parse config: {err}");
log::debug!("Using fallback font");
FbconConfig {
font: FontConfig {
path: String::new(),
},
}
}
};
let font = if !&config.font.path.is_empty() {
match fs::read(&config.font.path) {
Ok(contents) => Some(ConsoleFont::from_psf(&contents)),
Err(err) => {
log::debug!("Failed to read font {}: {err}", &config.font.path);
log::debug!("Using fallback font");
None
}
}
} else {
None
};
for &vt_i in vt_ids {
let display = Display::open_new_vt().expect("Failed to open display for vt");
event_queue
@@ -58,7 +96,8 @@ impl FbconScheme {
event::EventFlags::READ,
)
.expect("Failed to subscribe to input events for vt");
vts.insert(VtIndex(vt_i), TextScreen::new(display));
vts.insert(VtIndex(vt_i), TextScreen::new(display, font.clone()));
}
FbconScheme {
@@ -191,3 +230,12 @@ impl SchemeSync for FbconScheme {
self.handles.remove(id);
}
}
#[derive(Deserialize)]
struct FbconConfig {
font: FontConfig,
}
#[derive(Deserialize)]
struct FontConfig {
path: String,
}
+2 -2
View File
@@ -13,10 +13,10 @@ pub struct TextScreen {
}
impl TextScreen {
pub fn new(display: Display) -> TextScreen {
pub fn new(display: Display, font: Option<console_draw::ConsoleFont>) -> TextScreen {
TextScreen {
display,
inner: console_draw::TextScreen::new(),
inner: console_draw::TextScreen::new(font),
ctrl: false,
input: VecDeque::new(),
}