feat(display): DrawText with built-in Spleen 8x16 font (BSD-2)

This commit is contained in:
2026-10-27 16:33:26 +01:00
parent 8587661684
commit 64ec5df379
8 changed files with 290 additions and 2 deletions

View File

@@ -37,3 +37,16 @@ pub fn get_dimensions(ep: CapSlot) -> (Dimension, Dimension) {
(Dimension::new(0), Dimension::new(0)) (Dimension::new(0), Dimension::new(0))
} }
} }
/// Draws `s` (up to `TEXT_BYTES` per call, more in several) from `(x, y)`.
pub fn draw_text(ep: CapSlot, x: ScreenCoord, y: ScreenCoord, fg: Color, bg: Color, s: &[u8]) {
use types::display::text::{TEXT_BYTES, Text};
for (i, chunk) in s.chunks(TEXT_BYTES).enumerate() {
let x = ScreenCoord::new(x.raw().saturating_add((i * TEXT_BYTES * CELL_WIDTH) as u16));
let _ = syscall::request(ep, Text::new(x, y, fg, bg, chunk).words());
}
}
/// The display server's character cell, in pixels.
pub const CELL_WIDTH: usize = 8;
pub const CELL_HEIGHT: usize = 16;

View File

@@ -0,0 +1,24 @@
Copyright (c) 2018-2024, Frederic Cambus
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,114 @@
//! The display server's one font: Spleen 8x16 (BSD-2-Clause, Frederic
//! Cambus; see LICENSE-spleen), printable ASCII only. Generated from
//! spleen-8x16.bdf; each glyph is 16 rows, bit 7 the leftmost pixel.
pub const WIDTH: u16 = 8;
pub const HEIGHT: u16 = 16;
const FIRST: u8 = b' ';
const LAST: u8 = b'~';
/// The glyph for `c`; anything unprintable shows as `?`.
pub fn glyph(c: u8) -> &'static [u8; 16] {
let c = if (FIRST..=LAST).contains(&c) { c } else { b'?' };
&GLYPHS[usize::from(c - FIRST)]
}
#[rustfmt::skip]
static GLYPHS: [[u8; 16]; 95] = [
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // ' '
[0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00], // '!'
[0x00, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // '"'
[0x00, 0x00, 0x6c, 0x6c, 0xfe, 0x6c, 0x6c, 0x6c, 0x6c, 0xfe, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00], // '#'
[0x00, 0x10, 0x7e, 0xd0, 0xd0, 0xd0, 0x7c, 0x16, 0x16, 0x16, 0x16, 0xfc, 0x10, 0x00, 0x00, 0x00], // '$'
[0x00, 0x00, 0x06, 0x66, 0x6c, 0x0c, 0x18, 0x18, 0x30, 0x36, 0x66, 0x60, 0x00, 0x00, 0x00, 0x00], // '%'
[0x00, 0x00, 0x38, 0x6c, 0x6c, 0x6c, 0x38, 0x70, 0xda, 0xcc, 0xcc, 0x7a, 0x00, 0x00, 0x00, 0x00], // '&'
[0x00, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // "'"
[0x00, 0x0e, 0x18, 0x30, 0x30, 0x60, 0x60, 0x60, 0x60, 0x30, 0x30, 0x18, 0x0e, 0x00, 0x00, 0x00], // '('
[0x00, 0x70, 0x18, 0x0c, 0x0c, 0x06, 0x06, 0x06, 0x06, 0x0c, 0x0c, 0x18, 0x70, 0x00, 0x00, 0x00], // ')'
[0x00, 0x00, 0x00, 0x00, 0x66, 0x3c, 0x18, 0xff, 0x18, 0x3c, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00], // '*'
[0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x7e, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // '+'
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00], // ','
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // '-'
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00], // '.'
[0x00, 0x06, 0x06, 0x0c, 0x0c, 0x18, 0x18, 0x30, 0x30, 0x60, 0x60, 0xc0, 0xc0, 0x00, 0x00, 0x00], // '/'
[0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xce, 0xde, 0xf6, 0xe6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00], // '0'
[0x00, 0x00, 0x18, 0x38, 0x78, 0x58, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x00, 0x00, 0x00, 0x00], // '1'
[0x00, 0x00, 0x7c, 0xc6, 0x06, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc6, 0xfe, 0x00, 0x00, 0x00, 0x00], // '2'
[0x00, 0x00, 0x7c, 0xc6, 0x06, 0x06, 0x3c, 0x06, 0x06, 0x06, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00], // '3'
[0x00, 0x00, 0xc0, 0xc0, 0xcc, 0xcc, 0xcc, 0xcc, 0xfe, 0x0c, 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x00], // '4'
[0x00, 0x00, 0xfe, 0xc6, 0xc0, 0xc0, 0xfc, 0x06, 0x06, 0x06, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00], // '5'
[0x00, 0x00, 0x7c, 0xc6, 0xc0, 0xc0, 0xfc, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00], // '6'
[0x00, 0x00, 0xfe, 0xc6, 0x06, 0x06, 0x0c, 0x18, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00], // '7'
[0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00], // '8'
[0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0x7e, 0x06, 0x06, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00], // '9'
[0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00], // ':'
[0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00], // ';'
[0x00, 0x00, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x00, 0x00, 0x00, 0x00], // '<'
[0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // '='
[0x00, 0x00, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00], // '>'
[0x00, 0x00, 0x7c, 0xc6, 0x06, 0x0c, 0x18, 0x30, 0x30, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00], // '?'
[0x00, 0x00, 0x00, 0x7c, 0xc2, 0xda, 0xda, 0xda, 0xda, 0xde, 0xc0, 0x7c, 0x00, 0x00, 0x00, 0x00], // '@'
[0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00], // 'A'
[0x00, 0x00, 0xfc, 0xc6, 0xc6, 0xc6, 0xfc, 0xc6, 0xc6, 0xc6, 0xc6, 0xfc, 0x00, 0x00, 0x00, 0x00], // 'B'
[0x00, 0x00, 0x7e, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x7e, 0x00, 0x00, 0x00, 0x00], // 'C'
[0x00, 0x00, 0xfc, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xfc, 0x00, 0x00, 0x00, 0x00], // 'D'
[0x00, 0x00, 0x7e, 0xc0, 0xc0, 0xc0, 0xf8, 0xc0, 0xc0, 0xc0, 0xc0, 0x7e, 0x00, 0x00, 0x00, 0x00], // 'E'
[0x00, 0x00, 0x7e, 0xc0, 0xc0, 0xc0, 0xf8, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x00, 0x00, 0x00, 0x00], // 'F'
[0x00, 0x00, 0x7e, 0xc0, 0xc0, 0xc0, 0xde, 0xc6, 0xc6, 0xc6, 0xc6, 0x7e, 0x00, 0x00, 0x00, 0x00], // 'G'
[0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00], // 'H'
[0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x00, 0x00, 0x00, 0x00], // 'I'
[0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf0, 0x00, 0x00, 0x00, 0x00], // 'J'
[0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xcc, 0xf8, 0xcc, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00], // 'K'
[0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x7e, 0x00, 0x00, 0x00, 0x00], // 'L'
[0x00, 0x00, 0xc6, 0xee, 0xfe, 0xd6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00], // 'M'
[0x00, 0x00, 0xc6, 0xc6, 0xe6, 0xe6, 0xd6, 0xd6, 0xce, 0xce, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00], // 'N'
[0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00], // 'O'
[0x00, 0x00, 0xfc, 0xc6, 0xc6, 0xc6, 0xfc, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x00, 0x00, 0x00, 0x00], // 'P'
[0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xd6, 0xd6, 0x7c, 0x18, 0x0c, 0x00, 0x00], // 'Q'
[0x00, 0x00, 0xfc, 0xc6, 0xc6, 0xc6, 0xfc, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00], // 'R'
[0x00, 0x00, 0x7e, 0xc0, 0xc0, 0xc0, 0x7c, 0x06, 0x06, 0x06, 0x06, 0xfc, 0x00, 0x00, 0x00, 0x00], // 'S'
[0x00, 0x00, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00], // 'T'
[0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7e, 0x00, 0x00, 0x00, 0x00], // 'U'
[0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x6c, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00], // 'V'
[0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xd6, 0xfe, 0xee, 0xc6, 0x00, 0x00, 0x00, 0x00], // 'W'
[0x00, 0x00, 0xc6, 0xc6, 0xc6, 0x6c, 0x38, 0x6c, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00], // 'X'
[0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0x7e, 0x06, 0x06, 0x06, 0x06, 0xfc, 0x00, 0x00, 0x00, 0x00], // 'Y'
[0x00, 0x00, 0xfe, 0x06, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0xc0, 0xfe, 0x00, 0x00, 0x00, 0x00], // 'Z'
[0x00, 0x3e, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3e, 0x00, 0x00, 0x00], // '['
[0x00, 0xc0, 0xc0, 0x60, 0x60, 0x30, 0x30, 0x18, 0x18, 0x0c, 0x0c, 0x06, 0x06, 0x00, 0x00, 0x00], // '\\'
[0x00, 0x7c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x7c, 0x00, 0x00, 0x00], // ']'
[0x00, 0x10, 0x38, 0x6c, 0xc6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // '^'
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00], // '_'
[0x00, 0x30, 0x18, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // '`'
[0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x06, 0x7e, 0xc6, 0xc6, 0xc6, 0x7e, 0x00, 0x00, 0x00, 0x00], // 'a'
[0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xfc, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xfc, 0x00, 0x00, 0x00, 0x00], // 'b'
[0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x7e, 0x00, 0x00, 0x00, 0x00], // 'c'
[0x00, 0x00, 0x06, 0x06, 0x06, 0x7e, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7e, 0x00, 0x00, 0x00, 0x00], // 'd'
[0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0xc6, 0xc6, 0xfe, 0xc0, 0xc0, 0x7e, 0x00, 0x00, 0x00, 0x00], // 'e'
[0x00, 0x00, 0x1e, 0x30, 0x30, 0x30, 0x7c, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00], // 'f'
[0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x06, 0x06, 0xfc, 0x00], // 'g'
[0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xfc, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00], // 'h'
[0x00, 0x00, 0x18, 0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1c, 0x00, 0x00, 0x00, 0x00], // 'i'
[0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x70, 0x00], // 'j'
[0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xcc, 0xd8, 0xf0, 0xf0, 0xd8, 0xcc, 0xc6, 0x00, 0x00, 0x00, 0x00], // 'k'
[0x00, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x1c, 0x00, 0x00, 0x00, 0x00], // 'l'
[0x00, 0x00, 0x00, 0x00, 0x00, 0xec, 0xd6, 0xd6, 0xd6, 0xd6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00], // 'm'
[0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00], // 'n'
[0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00], // 'o'
[0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xfc, 0xc0, 0xc0, 0xc0, 0x00], // 'p'
[0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7e, 0x06, 0x06, 0x06, 0x00], // 'q'
[0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0xc6, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x00, 0x00, 0x00, 0x00], // 'r'
[0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0xc0, 0xc0, 0x7c, 0x06, 0x06, 0xfc, 0x00, 0x00, 0x00, 0x00], // 's'
[0x00, 0x00, 0x30, 0x30, 0x30, 0x7c, 0x30, 0x30, 0x30, 0x30, 0x30, 0x1e, 0x00, 0x00, 0x00, 0x00], // 't'
[0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7e, 0x00, 0x00, 0x00, 0x00], // 'u'
[0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0x6c, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00], // 'v'
[0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0xd6, 0xd6, 0xd6, 0xd6, 0x6e, 0x00, 0x00, 0x00, 0x00], // 'w'
[0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0x6c, 0x38, 0x38, 0x6c, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00], // 'x'
[0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7e, 0x06, 0x06, 0xfc, 0x00], // 'y'
[0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xfe, 0x00, 0x00, 0x00, 0x00], // 'z'
[0x00, 0x0e, 0x18, 0x18, 0x18, 0x18, 0x70, 0x70, 0x18, 0x18, 0x18, 0x18, 0x0e, 0x00, 0x00, 0x00], // '{'
[0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00], // '|'
[0x00, 0x70, 0x18, 0x18, 0x18, 0x18, 0x0e, 0x0e, 0x18, 0x18, 0x18, 0x18, 0x70, 0x00, 0x00, 0x00], // '}'
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x7e, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // '~'
];

View File

@@ -1,5 +1,6 @@
#![no_std] #![no_std]
pub mod font;
pub mod protocol; pub mod protocol;
pub mod server; pub mod server;

View File

@@ -1,4 +1,5 @@
use server_framework::{Decode, Encode}; use server_framework::{Decode, Encode};
use types::display::text::Text;
use types::display::{Color, Dimension, ScreenCoord}; use types::display::{Color, Dimension, ScreenCoord};
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@@ -7,6 +8,7 @@ pub enum DisplayRequest {
Clear { color: Color }, Clear { color: Color },
Flush, Flush,
GetDimensions, GetDimensions,
DrawText(Text),
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@@ -27,6 +29,7 @@ impl Encode for DisplayRequest {
DisplayRequest::Clear { color } => [1, color.raw() as usize, 0, 0], DisplayRequest::Clear { color } => [1, color.raw() as usize, 0, 0],
DisplayRequest::Flush => [2, 0, 0, 0], DisplayRequest::Flush => [2, 0, 0, 0],
DisplayRequest::GetDimensions => [3, 0, 0, 0], DisplayRequest::GetDimensions => [3, 0, 0, 0],
DisplayRequest::DrawText(t) => t.words(),
} }
} }
} }
@@ -45,7 +48,7 @@ impl Decode for DisplayRequest {
1 => Some(DisplayRequest::Clear { color: Color::from_raw(words[1] as u32) }), 1 => Some(DisplayRequest::Clear { color: Color::from_raw(words[1] as u32) }),
2 => Some(DisplayRequest::Flush), 2 => Some(DisplayRequest::Flush),
3 => Some(DisplayRequest::GetDimensions), 3 => Some(DisplayRequest::GetDimensions),
_ => None, _ => Text::from_words(words).map(DisplayRequest::DrawText),
} }
} }
} }

View File

@@ -2,7 +2,10 @@ use server_framework::Server;
use crate::protocol::{DisplayRequest, DisplayResponse}; use crate::protocol::{DisplayRequest, DisplayResponse};
use port_framebuffer::Framebuffer; use port_framebuffer::Framebuffer;
use types::display::Dimension; use types::display::text::Text;
use types::display::{Dimension, ScreenCoord};
use crate::font;
pub struct DisplayServer<F: Framebuffer> { pub struct DisplayServer<F: Framebuffer> {
fb: F, fb: F,
@@ -16,6 +19,28 @@ impl<F: Framebuffer> DisplayServer<F> {
pub fn framebuffer(&self) -> &F { pub fn framebuffer(&self) -> &F {
&self.fb &self.fb
} }
/// Draws each cell whole, background too, so text overwrites what was
/// there; cells past the screen edge are skipped.
fn draw_text(&mut self, t: &Text) {
let (fw, fh) = (self.fb.width().raw(), self.fb.height().raw());
let (x0, y0) = (t.x.raw(), t.y.raw());
if y0.saturating_add(font::HEIGHT) > fh {
return;
}
for (i, c) in t.chars().iter().enumerate() {
let Some(cx) = x0.checked_add(i as u16 * font::WIDTH).filter(|cx| cx + font::WIDTH <= fw) else {
return;
};
for (row, bits) in font::glyph(*c).iter().enumerate() {
for col in 0..font::WIDTH {
let on = bits & (0x80 >> col) != 0;
let (x, y) = (ScreenCoord::new(cx + col), ScreenCoord::new(y0 + row as u16));
self.fb.write_pixel(x, y, if on { t.fg } else { t.bg });
}
}
}
}
} }
impl<F: Framebuffer> Server for DisplayServer<F> { impl<F: Framebuffer> Server for DisplayServer<F> {
@@ -44,6 +69,10 @@ impl<F: Framebuffer> Server for DisplayServer<F> {
self.fb.flush(); self.fb.flush();
DisplayResponse::Ok DisplayResponse::Ok
} }
DisplayRequest::DrawText(t) => {
self.draw_text(&t);
DisplayResponse::Ok
}
DisplayRequest::GetDimensions => DisplayResponse::Dimensions { DisplayRequest::GetDimensions => DisplayResponse::Dimensions {
width: self.fb.width(), width: self.fb.width(),
height: self.fb.height(), height: self.fb.height(),

View File

@@ -138,3 +138,32 @@ fn an_off_screen_rect_draws_nothing() {
} }
assert_eq!(server.framebuffer().pixel_count(), 0); assert_eq!(server.framebuffer().pixel_count(), 0);
} }
#[test]
fn text_draws_whole_glyph_cells() {
use types::display::text::Text;
let mut server = test_server();
let (fg, bg) = (Color::rgb(255, 255, 255), Color::rgb(0, 0, 0));
let t = Text::new(ScreenCoord::new(8), ScreenCoord::new(16), fg, bg, b"A");
assert_eq!(server.handle(DisplayRequest::DrawText(t)), DisplayResponse::Ok);
// One 8x16 cell, written row by row.
assert_eq!(server.framebuffer().pixel_count(), 128);
// 'A' row 2 is 0x7c: column 0 off, column 1 on.
let at = |i: usize| server.framebuffer().pixel_at(i);
assert_eq!(at(16), (ScreenCoord::new(8), ScreenCoord::new(18), bg));
assert_eq!(at(17), (ScreenCoord::new(9), ScreenCoord::new(18), fg));
}
#[test]
fn text_past_the_edge_is_cut_not_wrapped() {
use types::display::text::Text;
let mut server = test_server();
let c = Color::rgb(1, 1, 1);
// 320 wide: from x=304 only two cells fit.
let t = Text::new(ScreenCoord::new(304), ScreenCoord::new(0), c, c, b"abc");
server.handle(DisplayRequest::DrawText(t));
assert_eq!(server.framebuffer().pixel_count(), 256);
let low = Text::new(ScreenCoord::new(0), ScreenCoord::new(230), c, c, b"a");
server.handle(DisplayRequest::DrawText(low));
assert_eq!(server.framebuffer().pixel_count(), 256);
}

View File

@@ -48,3 +48,78 @@ impl Dimension {
self.0 self.0
} }
} }
/// `DrawText`, shared by the display server and `libkos::display`: up to
/// `TEXT_BYTES` characters at a pixel position, each cell `fg` on `bg`.
/// Words: `[TEXT | len << 8 | fg << 16, x | y << 16 | bg << 32, bytes 0..8, 8..16]`.
pub mod text {
use super::{Color, ScreenCoord};
pub const TEXT: usize = 4;
pub const TEXT_BYTES: usize = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Text {
pub x: ScreenCoord,
pub y: ScreenCoord,
pub fg: Color,
pub bg: Color,
pub bytes: [u8; TEXT_BYTES],
pub len: usize,
}
impl Text {
/// `s` past `TEXT_BYTES` is cut off.
pub fn new(x: ScreenCoord, y: ScreenCoord, fg: Color, bg: Color, s: &[u8]) -> Self {
let len = s.len().min(TEXT_BYTES);
let mut bytes = [0u8; TEXT_BYTES];
bytes[..len].copy_from_slice(&s[..len]);
Self { x, y, fg, bg, bytes, len }
}
pub fn chars(&self) -> &[u8] {
&self.bytes[..self.len.min(TEXT_BYTES)]
}
pub fn words(&self) -> [usize; 4] {
let (lo, hi) = self.bytes.as_chunks::<8>().0.split_at(1);
[
TEXT | self.len << 8 | (self.fg.raw() as usize) << 16,
self.x.raw() as usize | (self.y.raw() as usize) << 16 | (self.bg.raw() as usize) << 32,
u64::from_le_bytes(lo[0]) as usize,
u64::from_le_bytes(hi[0]) as usize,
]
}
pub fn from_words(w: [usize; 4]) -> Option<Self> {
let len = (w[0] >> 8) & 0xFF;
if w[0] & 0xFF != TEXT || len > TEXT_BYTES {
return None;
}
let mut bytes = [0u8; TEXT_BYTES];
for (b, word) in bytes.as_chunks_mut::<8>().0.iter_mut().zip(&w[2..]) {
*b = (*word as u64).to_le_bytes();
}
Some(Self {
x: ScreenCoord::new((w[1] & 0xFFFF) as u16),
y: ScreenCoord::new(((w[1] >> 16) & 0xFFFF) as u16),
fg: Color::from_raw((w[0] >> 16) as u32),
bg: Color::from_raw((w[1] >> 32) as u32),
bytes,
len,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn text_round_trips() {
let t = Text::new(ScreenCoord::new(1272), ScreenCoord::new(784), Color::rgb(1, 2, 3), Color::rgb(4, 5, 6), b"$ ls -la /home/x");
assert_eq!(Text::from_words(t.words()), Some(t));
assert_eq!(Text::new(t.x, t.y, t.fg, t.bg, &[b'a'; 40]).len, TEXT_BYTES);
}
}
}