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 struct Position {
pub x: u16, pub x: i16,
pub y: u16, pub y: i16,
} }
impl Position { impl Position {
pub fn new(x: u16, y: u16) -> Position { pub fn new(x: i16, y: i16) -> Position {
Position { x, y } 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) { pub fn set_window(&mut self, pos0: Position, pos1: Position) {
self.set_columns(pos0.x, pos1.x); self.set_columns(pos0.x.max(0) as u16, pos1.x.max(0) as u16);
self.set_rows(pos0.y, pos1.y); self.set_rows(pos0.y.min(240) as u16, pos1.y.min(240) as u16);
self.write(Writeable::Command(Command::RamWR)); 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) { pub fn draw_rect(&mut self, pos0: Position, pos1: Position, color: Color) {
self.set_window(pos0, pos1); self.set_window(pos0, pos1);
let width = pos1.x - pos0.x; let width = pos1.x as u16 - pos0.x as u16;
let height = pos1.y - pos0.y; let height = pos1.y as u16 - pos0.y as u16;
let pixels = width * height; let pixels = width * height;
let chunks = pixels / 256; let chunks = pixels / 256;
let mut full_buf = [0; 512]; let mut full_buf = [0; 512];

View File

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

View File

@ -15,7 +15,7 @@ use esp_alloc::export::enumset::EnumSet;
use esp_hal::{ use esp_hal::{
clock::CpuClock, clock::CpuClock,
delay::Delay, delay::Delay,
gpio::{Level, Output, OutputConfig}, gpio::{Input, InputConfig, Level, Output, OutputConfig},
main, main,
spi::master::{Config, Spi}, spi::master::{Config, Spi},
time::{Duration, Instant, Rate}, time::{Duration, Instant, Rate},
@ -27,7 +27,7 @@ use esp_backtrace as _;
use crate::{ use crate::{
at_commands::ATCommands, at_commands::ATCommands,
display::{Display, Position, Rgb565, SetAddressMode}, display::{Display, Position, Rgb565, SetAddressMode},
font::FontRenderer, font::{FontRenderer, HorizontalAlignment, VerticalAlignment},
}; };
extern crate alloc; extern crate alloc;
@ -111,7 +111,13 @@ fn main() -> ! {
let font_renderer = FontRenderer::create(30); 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_rst = Output::new(peripherals.GPIO15, Level::High, OutputConfig::default());
let sim_pwr_key = Output::new(peripherals.GPIO33, Level::High, OutputConfig::default()); let sim_pwr_key = Output::new(peripherals.GPIO33, Level::High, OutputConfig::default());
@ -151,11 +157,35 @@ fn main() -> ! {
// ) // )
// ); // );
loop { let pull_down_cfg = InputConfig::default().with_pull(esp_hal::gpio::Pull::None);
let delay_start = Instant::now(); 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) {} // let input_1 = Input::new(peripherals.GPIO4, pull_up_cfg);
// test_delay.delay_millis(1); // 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 // for inspiration have a look at the examples at https://github.com/esp-rs/esp-hal/tree/esp-hal-v1.1.0/examples