From 5438f0982043e339a067d3232c36d7d3505596e6 Mon Sep 17 00:00:00 2001 From: Sofia Date: Sat, 16 May 2026 19:42:02 +0300 Subject: [PATCH] Improve text rendering capabilities --- src/display.rs | 16 +++++----- src/font.rs | 81 +++++++++++++++++++++++++++++++++++++++----------- src/main.rs | 44 ++++++++++++++++++++++----- 3 files changed, 109 insertions(+), 32 deletions(-) diff --git a/src/display.rs b/src/display.rs index 85417d9..24bc249 100644 --- a/src/display.rs +++ b/src/display.rs @@ -64,14 +64,14 @@ impl Mul 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]; diff --git a/src/font.rs b/src/font.rs index d16814a..60ef25b 100644 --- a/src/font.rs +++ b/src/font.rs @@ -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, } +pub struct RawRenderData<'a> { + glyphs: Vec>, + 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::>(); - 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( diff --git a/src/main.rs b/src/main.rs index c18e08a..6f47b23 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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