Improve text rendering capabilities

This commit is contained in:
Sofia 2026-05-16 19:42:02 +03:00
parent 325d707685
commit 5438f09820
3 changed files with 109 additions and 32 deletions

View File

@ -64,14 +64,14 @@ impl Mul<f32> for Rgb565 {
}
}
#[derive(Default, Clone, Copy)]
#[derive(Default, Clone, Copy, Debug)]
pub struct Position {
pub x: u16,
pub y: u16,
pub x: i16,
pub y: i16,
}
impl Position {
pub fn new(x: u16, y: u16) -> Position {
pub fn new(x: i16, y: i16) -> Position {
Position { x, y }
}
}
@ -334,8 +334,8 @@ impl<'d, DM: DriverMode, T: DelayNs> Display<'d, DM, T> {
}
pub fn set_window(&mut self, pos0: Position, pos1: Position) {
self.set_columns(pos0.x, pos1.x);
self.set_rows(pos0.y, pos1.y);
self.set_columns(pos0.x.max(0) as u16, pos1.x.max(0) as u16);
self.set_rows(pos0.y.min(240) as u16, pos1.y.min(240) as u16);
self.write(Writeable::Command(Command::RamWR));
}
@ -346,8 +346,8 @@ impl<'d, DM: DriverMode, T: DelayNs> Display<'d, DM, T> {
pub fn draw_rect(&mut self, pos0: Position, pos1: Position, color: Color) {
self.set_window(pos0, pos1);
let width = pos1.x - pos0.x;
let height = pos1.y - pos0.y;
let width = pos1.x as u16 - pos0.x as u16;
let height = pos1.y as u16 - pos0.y as u16;
let pixels = width * height;
let chunks = pixels / 256;
let mut full_buf = [0; 512];

View File

@ -1,6 +1,6 @@
use embedded_hal::delay::DelayNs;
use esp_hal::DriverMode;
use rusttype::{Font, Point, Scale};
use rusttype::{Font, Point, PositionedGlyph, Scale};
use alloc::string::String;
use alloc::vec;
@ -10,6 +10,18 @@ use crate::display::{self, Display, Position, Rgb565};
static OPEN_SANS: &'static [u8] = include_bytes!("./OpenSans_Condensed-Regular.ttf");
pub enum HorizontalAlignment {
RightToLeft,
Center,
LeftToRight,
}
pub enum VerticalAlignment {
TopToBottom,
Center,
BottomToTop,
}
pub struct FontRenderer<'a> {
font: Font<'a>,
height: u32,
@ -17,6 +29,11 @@ pub struct FontRenderer<'a> {
offset: Point<f32>,
}
pub struct RawRenderData<'a> {
glyphs: Vec<PositionedGlyph<'a>>,
pixel_width: usize,
}
impl<'a> FontRenderer<'a> {
pub fn create(size: u32) -> FontRenderer<'a> {
let font = Font::try_from_bytes(OPEN_SANS).unwrap();
@ -42,19 +59,21 @@ impl<'a> FontRenderer<'a> {
&self,
display: &mut Display<'d, DM, Delay>,
text: T,
pos: Position,
position: Position,
h_align: HorizontalAlignment,
v_align: VerticalAlignment,
) {
if pos.x > 240 || pos.y > 240 {
// Everything would be out-of-bounds
return;
let data = self.prepare(text.into());
self.raw_render(display, data, position);
}
pub fn prepare(&self, text: String) -> RawRenderData<'a> {
let glyphs = self
.font
.layout(&text.into(), self.scale, self.offset)
.layout(&text, self.scale, self.offset)
.collect::<Vec<_>>();
let text_width = libm::ceil(
let pixel_width = libm::ceil(
glyphs
.iter()
.rev()
@ -63,16 +82,41 @@ impl<'a> FontRenderer<'a> {
.unwrap_or(0.0) as f64,
) as usize;
let width = (text_width + pos.x as usize).min(240) - pos.x as usize;
let height = (self.height + pos.y as u32).min(240) - pos.y as u32;
RawRenderData {
glyphs,
pixel_width,
}
}
let mut pixel_data = vec![Rgb565::white(); height as usize * width];
pub fn raw_render<'d, DM: DriverMode, Delay: DelayNs>(
&self,
display: &mut Display<'d, DM, Delay>,
data: RawRenderData<'a>,
pos: Position,
) {
let start_x = pos.x.clamp(0, 240);
let start_y = pos.y.clamp(0, 240);
let end_x = (pos.x + data.pixel_width as i16).clamp(0, 240);
let end_y = (pos.y + self.height as i16).clamp(0, 240);
for g in glyphs {
if start_x == end_x || start_y == end_y {
// Nothing to draw
return;
}
let clip_left = if pos.x < 0 { -pos.x } else { 0 };
let clip_top = if pos.y < 0 { -pos.y } else { 0 };
let width = end_x - start_x;
let height = end_y - start_y;
let mut pixel_data = vec![Rgb565::white(); height as usize * width as usize];
for g in data.glyphs {
if let Some(bb) = g.pixel_bounding_box() {
g.draw(|x, y, cov| {
let x = bb.min.x + x as i32;
let y = bb.min.y + y as i32;
let x = bb.min.x + x as i32 - clip_left as i32;
let y = bb.min.y + y as i32 - clip_top as i32;
if x >= 0 && y >= 0 && x < width as i32 && y < height as i32 {
let col = 255 - (255. * cov) as u8;
pixel_data[(x + y * width as i32) as usize] = Rgb565(col, col, col);
@ -82,10 +126,13 @@ impl<'a> FontRenderer<'a> {
}
display.set_window(
pos,
Position {
x: pos.x + width as u16 - 1,
y: pos.y + height as u16 - 1,
x: start_x,
y: start_y,
},
Position {
x: end_x - 1,
y: end_y - 1,
},
);
display.write(display::Writeable::Data(

View File

@ -15,7 +15,7 @@ use esp_alloc::export::enumset::EnumSet;
use esp_hal::{
clock::CpuClock,
delay::Delay,
gpio::{Level, Output, OutputConfig},
gpio::{Input, InputConfig, Level, Output, OutputConfig},
main,
spi::master::{Config, Spi},
time::{Duration, Instant, Rate},
@ -27,7 +27,7 @@ use esp_backtrace as _;
use crate::{
at_commands::ATCommands,
display::{Display, Position, Rgb565, SetAddressMode},
font::FontRenderer,
font::{FontRenderer, HorizontalAlignment, VerticalAlignment},
};
extern crate alloc;
@ -111,7 +111,13 @@ fn main() -> ! {
let font_renderer = FontRenderer::create(30);
font_renderer.render(&mut display, "Hello World!", Position::new(70, 220));
font_renderer.render(
&mut display,
"Hello World!",
Position::new(0, 0),
HorizontalAlignment::LeftToRight,
VerticalAlignment::TopToBottom,
);
let sim_rst = Output::new(peripherals.GPIO15, Level::High, OutputConfig::default());
let sim_pwr_key = Output::new(peripherals.GPIO33, Level::High, OutputConfig::default());
@ -151,11 +157,35 @@ fn main() -> ! {
// )
// );
loop {
let delay_start = Instant::now();
let pull_down_cfg = InputConfig::default().with_pull(esp_hal::gpio::Pull::None);
let pull_up_cfg = InputConfig::default().with_pull(esp_hal::gpio::Pull::None);
// let mut input_1 = Output::new(peripherals.GPIO4, Level::High, OutputConfig::default());
// let mut input_2 = Output::new(peripherals.GPIO36, Level::High, OutputConfig::default());
// let mut input_3 = Output::new(peripherals.GPIO22, Level::High, OutputConfig::default());
// let mut input_4 = Output::new(peripherals.GPIO12, Level::High, OutputConfig::default());
// let mut input_5 = Output::new(peripherals.GPIO13, Level::High, OutputConfig::default());
// let mut input_6 = Output::new(peripherals.GPIO26, Level::High, OutputConfig::default());
// let mut input_7 = Output::new(peripherals.GPIO27, Level::High, OutputConfig::default());
while delay_start.elapsed() < Duration::from_millis(100) {}
// test_delay.delay_millis(1);
// let input_1 = Input::new(peripherals.GPIO4, pull_up_cfg);
// let input_2 = Input::new(peripherals.GPIO36, pull_up_cfg);
// let input_3 = Input::new(peripherals.GPIO22, pull_up_cfg);
// let input_4 = Input::new(peripherals.GPIO12, pull_up_cfg);
// let input_5 = Input::new(peripherals.GPIO13, pull_up_cfg);
// let input_6 = Input::new(peripherals.GPIO26, pull_up_cfg);
// let input_7 = Input::new(peripherals.GPIO27, pull_up_cfg);
let mut test_delay = Delay::new();
loop {
// log::info!("Input 1: {}", input_1.is_high());
// log::info!("Input 2: {}", input_2.is_high());
// log::info!("Input 3: {}", input_3.is_high());
// log::info!("Input 4: {}", input_4.is_high());
// log::info!("Input 5: {}", input_5.is_high());
// log::info!("Input 6: {}", input_6.is_high());
// log::info!("Input 7: {}", input_7.is_high());
// input_7.toggle();
test_delay.delay_millis(100);
}
// for inspiration have a look at the examples at https://github.com/esp-rs/esp-hal/tree/esp-hal-v1.1.0/examples