You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
trezor-firmware/core/embed/rust/src/ui/shape/text.rs

135 lines
3.6 KiB

use crate::ui::{
display::{Color, Font},
geometry::{Alignment, Offset, Point, Rect},
};
use super::{BitmapView, Canvas, DrawingCache, Renderer, Shape, ShapeClone};
use without_alloc::alloc::LocalAllocLeakExt;
/// A shape for text strings rendering.
pub struct Text<'a> {
// Text position
pos: Point,
// Text string
text: &'a str,
// Text color
color: Color,
// Text font
font: Font,
// Horizontal alignment
align: Alignment,
// Final bounds calculated when rendered
bounds: Rect,
}
impl<'a> Text<'a> {
/// Creates a `shape::Text` structure with a specified
/// text (`str`) and the top-left corner (`pos`).
pub fn new(pos: Point, text: &'a str) -> Self {
Self {
pos,
text,
color: Color::white(),
font: Font::NORMAL,
align: Alignment::Start,
bounds: Rect::zero(),
}
}
pub fn with_fg(self, color: Color) -> Self {
Self { color, ..self }
}
pub fn with_font(self, font: Font) -> Self {
Self { font, ..self }
}
pub fn with_align(self, align: Alignment) -> Self {
Self { align, ..self }
}
pub fn render<'r>(mut self, renderer: &mut impl Renderer<'r>) {
self.bounds = self.calc_bounds();
renderer.render_shape(self);
}
fn aligned_pos(&self) -> Point {
match self.align {
Alignment::Start => self.pos,
Alignment::Center => Point::new(
self.font.horz_center(self.pos.x, self.pos.x, self.text),
self.pos.y,
),
Alignment::End => Point::new(self.pos.x - self.font.text_width(self.text), self.pos.y),
}
}
fn calc_bounds(&self) -> Rect {
let pos = self.aligned_pos();
let (ascent, descent) = self.font.visible_text_height_ex(self.text);
Rect {
x0: pos.x,
y0: pos.y - ascent,
x1: pos.x + self.font.text_width(self.text),
y1: pos.y + descent,
}
}
}
impl<'a> Shape<'_> for Text<'a> {
fn bounds(&self, _cache: &DrawingCache) -> Rect {
self.bounds
}
fn cleanup(&mut self, _cache: &DrawingCache) {}
fn draw(&mut self, canvas: &mut dyn Canvas, cache: &DrawingCache) {
let mut r = self.bounds(cache);
let max_ascent = self.pos.y - r.y0;
// TODO: optimize text clipping, use canvas.viewport()
for ch in self.text.chars() {
if r.x0 >= r.x1 {
break;
}
let glyph = self.font.get_glyph(ch);
let glyph_bitmap = glyph.bitmap();
let glyph_view = BitmapView::new(&glyph_bitmap)
.with_fg(self.color)
.with_offset(Offset::new(
-glyph.bearing_x,
-(max_ascent - glyph.bearing_y),
));
canvas.blend_bitmap(r, glyph_view);
r.x0 += glyph.adv;
}
}
}
impl<'a, 's> ShapeClone<'s> for Text<'a> {
fn clone_at_bump<'alloc, T>(self, bump: &'alloc T) -> Option<&'alloc mut dyn Shape<'s>>
where
T: LocalAllocLeakExt<'alloc>,
{
let clone = bump.alloc_t::<Text>()?;
let text = bump.copy_str(self.text)?;
Some(clone.uninit.init(Text { text, ..self }))
}
}
impl Font {
fn visible_text_height_ex(&self, text: &str) -> (i16, i16) {
let (mut ascent, mut descent) = (0, 0);
for c in text.chars() {
let glyph = self.get_glyph(c);
ascent = ascent.max(glyph.bearing_y);
descent = descent.max(glyph.height - glyph.bearing_y);
}
(ascent, descent)
}
}