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/display/font.rs

316 lines
9.3 KiB

use crate::{
trezorhal::display,
ui::{
canvas::{Bitmap, BitmapFormat},
constant,
geometry::{Offset, Point, Rect},
},
};
use core::slice;
use super::{get_color_table, get_offset, pixeldata, set_window, Color};
/// Representation of a single glyph.
/// We use standard typographic terms. For a nice explanation, see, e.g.,
/// the FreeType docs at https://www.freetype.org/freetype2/docs/glyphs/glyphs-3.html
pub struct Glyph {
/// Total width of the glyph itself
pub width: i16,
/// Total height of the glyph itself
pub height: i16,
/// Advance - how much to move the cursor after drawing this glyph
pub adv: i16,
/// Left-side horizontal bearing
pub bearing_x: i16,
/// Top-side vertical bearing
pub bearing_y: i16,
data: &'static [u8],
}
impl Glyph {
/// Construct a `Glyph` from a raw pointer.
///
/// # Safety
///
/// This function is unsafe because the caller has to guarantee that `data`
/// is pointing to a memory containing a valid glyph data, that is:
/// - contains valid glyph metadata
/// - data has appropriate size
/// - data must have static lifetime
pub unsafe fn load(data: *const u8) -> Self {
unsafe {
let width = *data.offset(0) as i16;
let height = *data.offset(1) as i16;
let data_bytes = match constant::FONT_BPP {
1 => (width * height + 7) / 8, // packed bits
2 => (width * height + 3) / 4, // packed bits
4 => (width + 1) / 2 * height, // row aligned to bytes
8 => width * height,
_ => panic!(),
};
Glyph {
width,
height,
adv: *data.offset(2) as i16,
bearing_x: *data.offset(3) as i16,
bearing_y: *data.offset(4) as i16,
data: slice::from_raw_parts(data.offset(5), data_bytes as usize),
}
}
}
/// Space between the right edge of the glyph and the left edge of the next
/// bounding box.
pub const fn right_side_bearing(&self) -> i16 {
self.adv - self.width - self.bearing_x
}
pub fn print(&self, pos: Point, colortable: [Color; 16]) -> i16 {
let bearing = Offset::new(self.bearing_x, -self.bearing_y);
let size = Offset::new(self.width, self.height);
let pos_adj = pos + bearing;
let r = Rect::from_top_left_and_size(pos_adj, size);
let area = r.translate(get_offset());
let window = area.clamp(constant::screen());
set_window(window);
for y in window.y0..window.y1 {
for x in window.x0..window.x1 {
let p = Point::new(x, y);
let r = p - pos_adj;
let c = self.get_pixel_data(r);
pixeldata(colortable[c as usize]);
}
}
self.adv
}
pub fn unpack_bpp1(&self, a: i16) -> u8 {
let c_data = self.data[(a / 8) as usize];
((c_data >> (7 - (a % 8))) & 0x01) * 15
}
pub fn unpack_bpp2(&self, a: i16) -> u8 {
let c_data = self.data[(a / 4) as usize];
((c_data >> (6 - (a % 4) * 2)) & 0x03) * 5
}
pub fn unpack_bpp4(&self, a: i16) -> u8 {
let c_data = self.data[(a / 2) as usize];
(c_data >> (4 - (a % 2) * 4)) & 0x0F
}
pub fn unpack_bpp8(&self, a: i16) -> u8 {
let c_data = self.data[a as usize];
c_data >> 4
}
pub fn get_pixel_data(&self, p: Offset) -> u8 {
let a = p.x + p.y * self.width;
match constant::FONT_BPP {
1 => self.unpack_bpp1(a),
2 => self.unpack_bpp2(a),
4 => self.unpack_bpp4(a),
8 => self.unpack_bpp8(a),
_ => 0,
}
}
pub fn bitmap(&self) -> Bitmap<'static> {
match constant::FONT_BPP {
1 => unwrap!(Bitmap::new(
BitmapFormat::MONO1P,
None,
Offset::new(self.width, self.height),
None,
self.data,
)),
2 => panic!(),
4 => unwrap!(Bitmap::new(
BitmapFormat::MONO4,
None,
Offset::new(self.width, self.height),
None,
self.data,
)),
8 => panic!(),
_ => panic!(),
}
}
}
/// Font constants. Keep in sync with FONT_ definitions in
/// `extmod/modtrezorui/fonts/fonts.h`.
#[derive(Copy, Clone, PartialEq, Eq, FromPrimitive)]
#[repr(u8)]
pub enum Font {
NORMAL = 1,
BOLD = 2,
MONO = 3,
BIG = 4,
DEMIBOLD = 5,
}
impl From<Font> for i32 {
fn from(font: Font) -> i32 {
-(font as i32)
}
}
impl Font {
pub fn text_width(self, text: &str) -> i16 {
display::text_width(text, self.into())
}
/// Supports UTF8 characters
fn get_first_glyph_from_text(self, text: &str) -> Option<Glyph> {
text.chars().next().map(|c| self.get_glyph(c))
}
/// Supports UTF8 characters
fn get_last_glyph_from_text(self, text: &str) -> Option<Glyph> {
text.chars().next_back().map(|c| self.get_glyph(c))
}
/// Width of the text that is visible.
/// Not including the spaces before the first and after the last character.
pub fn visible_text_width(self, text: &str) -> i16 {
if text.is_empty() {
// No text, no width.
return 0;
}
let first_char_bearing = if let Some(glyph) = self.get_first_glyph_from_text(text) {
glyph.bearing_x
} else {
0
};
let last_char_bearing = if let Some(glyph) = self.get_last_glyph_from_text(text) {
glyph.right_side_bearing()
} else {
0
};
// Strip leftmost and rightmost spaces/bearings/margins.
self.text_width(text) - first_char_bearing - last_char_bearing
}
pub fn visible_text_height(&self, text: &str) -> 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
}
/// Returning the x-bearing (offset) of the first character.
/// Useful to enforce that the text is positioned correctly (e.g. centered).
pub fn start_x_bearing(self, text: &str) -> i16 {
if text.is_empty() {
return 0;
}
if let Some(glyph) = self.get_first_glyph_from_text(text) {
glyph.bearing_x
} else {
0
}
}
pub fn char_width(self, ch: char) -> i16 {
display::char_width(ch, self.into())
}
pub fn text_height(self) -> i16 {
display::text_height(self.into())
}
pub fn text_max_height(self) -> i16 {
display::text_max_height(self.into())
}
pub fn text_baseline(self) -> i16 {
display::text_baseline(self.into())
}
pub fn max_height(self) -> i16 {
display::text_max_height(self.into())
}
pub fn line_height(self) -> i16 {
constant::LINE_SPACE + self.text_height()
}
// Returns x-coordinate of the text start (including left bearing)
pub fn horz_center(&self, start: i16, end: i16, text: &str) -> i16 {
(start + end - self.visible_text_width(text)) / 2 - self.start_x_bearing(text)
}
// Returns y-coordinate of the text baseline
pub fn vert_center(&self, start: i16, end: i16, text: &str) -> i16 {
(start + end + self.visible_text_height(text)) / 2
}
pub fn get_glyph(self, ch: char) -> Glyph {
let gl_data = display::get_char_glyph(ch as u16, self.into());
ensure!(!gl_data.is_null(), "Failed to load glyph");
// SAFETY: Glyph::load is valid for data returned by get_char_glyph
unsafe { Glyph::load(gl_data) }
}
pub fn display_text(self, text: &str, baseline: Point, fg_color: Color, bg_color: Color) {
let colortable = get_color_table(fg_color, bg_color);
let mut adv_total = 0;
for c in text.chars() {
let gly = self.get_glyph(c);
let adv = gly.print(baseline + Offset::new(adv_total, 0), colortable);
adv_total += adv;
}
}
/// Get the length of the longest suffix from a given `text`
/// that will fit into the area `width` pixels wide.
pub fn longest_suffix(self, width: i16, text: &str) -> usize {
let mut text_width = 0;
for (chars_from_right, c) in text.chars().rev().enumerate() {
let c_width = self.char_width(c);
if text_width + c_width > width {
// Another character cannot be fitted, we're done.
return chars_from_right;
}
text_width += c_width;
}
text.len() // it fits in its entirety
}
}
pub trait GlyphMetrics {
fn char_width(&self, ch: char) -> i16;
fn text_width(&self, text: &str) -> i16;
fn line_height(&self) -> i16;
}
impl GlyphMetrics for Font {
fn char_width(&self, ch: char) -> i16 {
Font::char_width(*self, ch)
}
fn text_width(&self, text: &str) -> i16 {
Font::text_width(*self, text)
}
fn line_height(&self) -> i16 {
Font::line_height(*self)
}
}