1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-12 17:38:13 +00:00

refactor(core): safe iface for get_glyph_data

This commit is contained in:
obrusvit 2024-12-09 19:39:26 +01:00
parent 5fced7c71d
commit 02aba6b8d3
4 changed files with 161 additions and 117 deletions

View File

@ -240,7 +240,7 @@ impl<'a> Translations<'a> {
/// translations object. This is to facilitate safe interface to /// translations object. This is to facilitate safe interface to
/// flash-based translations. See docs for `flash::get` for details. /// flash-based translations. See docs for `flash::get` for details.
#[allow(clippy::needless_lifetimes)] #[allow(clippy::needless_lifetimes)]
pub fn font<'b>(&'b self, index: u16) -> Option<Table<'b>> { fn font<'b>(&'b self, index: u16) -> Option<Table<'b>> {
self.fonts self.fonts
.get(index) .get(index)
.and_then(|data| Table::new(InputStream::new(data)).ok()) .and_then(|data| Table::new(InputStream::new(data)).ok())
@ -258,6 +258,22 @@ impl<'a> Translations<'a> {
pub fn header<'b>(&'b self) -> &'b TranslationsHeader<'b> { pub fn header<'b>(&'b self) -> &'b TranslationsHeader<'b> {
&self.header &self.header
} }
/// Returns a pointer to the glyph data for the given UTF-8 codepoint.
///
/// SAFETY: Do not mess with the lifetimes in this signature.
///
/// The lifetimes are a useful lie that bind the lifetime of the returned
/// string not to the underlying data, but to the _reference_ to the
/// translations object. This is to facilitate safe interface to
/// flash-based translations. See docs for `flash::get` for details.
pub fn get_utf8_glyph<'b>(&'b self, codepoint: u16, font_index: u16) -> *const u8 {
if let Some(glyph) = self.font(font_index).and_then(|t| t.get(codepoint)) {
glyph.as_ptr()
} else {
core::ptr::null()
}
}
} }
pub struct TranslationsHeader<'a> { pub struct TranslationsHeader<'a> {

View File

@ -1,33 +1,12 @@
mod blob; mod blob;
mod flash; pub mod flash;
mod generated; mod generated;
#[cfg(feature = "micropython")] #[cfg(feature = "micropython")]
mod obj; mod obj;
mod public_keys; mod public_keys;
mod translated_string; mod translated_string;
pub use blob::Translations;
pub use translated_string::TranslatedString as TR; pub use translated_string::TranslatedString as TR;
use crate::ui::display::Font;
pub const DEFAULT_LANGUAGE: &str = "en-US"; pub const DEFAULT_LANGUAGE: &str = "en-US";
/// # Safety
///
/// Returned pointer will only point to valid font data for as long as
/// the flash content is not invalidated by `erase()` or `write()`.
pub unsafe fn get_utf8_glyph(codepoint: u16, font: Font) -> *const u8 {
// SAFETY: Reference is discarded at the end of the function.
// We do return a _pointer_ to the same memory location, but the pointer is
// always valid.
let Ok(translations) = flash::get() else {
return core::ptr::null();
};
let Some(tr) = translations.as_ref() else {
return core::ptr::null();
};
if let Some(glyph) = tr.font(font as u16).and_then(|t| t.get(codepoint)) {
glyph.as_ptr()
} else {
core::ptr::null()
}
}

View File

@ -1,5 +1,7 @@
use spin::RwLockReadGuard;
use crate::{ use crate::{
trezorhal::display, trezorhal::display::{self},
ui::{ ui::{
constant, constant,
geometry::Offset, geometry::Offset,
@ -9,12 +11,14 @@ use crate::{
use core::slice; use core::slice;
#[cfg(feature = "translations")] #[cfg(feature = "translations")]
use crate::translations::get_utf8_glyph; use crate::translations::flash;
#[cfg(feature = "translations")]
use crate::translations::Translations;
/// Representation of a single glyph. /// Representation of a single glyph.
/// We use standard typographic terms. For a nice explanation, see, e.g., /// 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 /// the FreeType docs at https://www.freetype.org/freetype2/docs/glyphs/glyphs-3.html
pub struct Glyph { pub struct Glyph<'a> {
/// Total width of the glyph itself /// Total width of the glyph itself
pub width: i16, pub width: i16,
/// Total height of the glyph itself /// Total height of the glyph itself
@ -25,10 +29,10 @@ pub struct Glyph {
pub bearing_x: i16, pub bearing_x: i16,
/// Top-side vertical bearing /// Top-side vertical bearing
pub bearing_y: i16, pub bearing_y: i16,
data: &'static [u8], data: &'a [u8],
} }
impl Glyph { impl<'a> Glyph<'a> {
/// Construct a `Glyph` from a raw pointer. /// Construct a `Glyph` from a raw pointer.
/// ///
/// # Safety /// # Safety
@ -88,7 +92,7 @@ impl Glyph {
c_data >> 4 c_data >> 4
} }
pub fn bitmap(&self) -> Bitmap<'static> { pub fn bitmap(&self) -> Bitmap<'a> {
match constant::FONT_BPP { match constant::FONT_BPP {
1 => unwrap!(Bitmap::new( 1 => unwrap!(Bitmap::new(
BitmapFormat::MONO1P, BitmapFormat::MONO1P,
@ -109,6 +113,79 @@ impl Glyph {
} }
} }
/// A provider of font glyphs and their metadata.
///
/// Manages access to font resources and handles UTF-8 character glyphs
///
/// The provider holds necessary lock for accessing translation data
/// and is typically used through the `Font::with_glyph_data` method
/// to ensure proper resource cleanup.
///
/// # Example
/// ```
/// let font = Font::NORMAL;
/// font.with_glyph_data(|data| {
/// let glyph = data.get_glyph('A');
/// // use glyph...
/// });
/// ```
pub struct GlyphData {
font: Font,
#[cfg(feature = "translations")]
translations_guard: Option<RwLockReadGuard<'static, Option<Translations<'static>>>>,
}
impl GlyphData {
fn new(font: Font) -> Self {
#[cfg(feature = "translations")]
let translations_guard = flash::get().ok();
Self {
font,
#[cfg(feature = "translations")]
translations_guard,
}
}
pub fn get_glyph(&self, ch: char) -> Glyph<'_> {
let ch = match ch {
'\u{00a0}' => '\u{0020}',
c => c,
};
let gl_data = self.get_glyph_data(ch as u16);
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) }
}
fn get_glyph_data(&self, codepoint: u16) -> *const u8 {
display::get_font_info(self.font.into()).map_or(core::ptr::null(), |font_info| {
if codepoint >= ' ' as u16 && codepoint < 0x7F {
// ASCII character
unsafe {
*font_info
.glyph_data
.offset((codepoint - ' ' as u16) as isize)
}
} else {
#[cfg(feature = "translations")]
{
if codepoint >= 0x7F {
// UTF8 character from embedded blob
if let Some(guard) = &self.translations_guard {
if let Some(translations) = guard.as_ref() {
return translations.get_utf8_glyph(codepoint, self.font as u16);
}
}
}
}
font_info.glyph_nonprintable
}
})
}
}
/// Font constants. Keep in sync with `font_id_t` definition in /// Font constants. Keep in sync with `font_id_t` definition in
/// `core/embed/gfx/fonts/fonts.h`. /// `core/embed/gfx/fonts/fonts.h`.
#[derive(Copy, Clone, PartialEq, Eq, FromPrimitive)] #[derive(Copy, Clone, PartialEq, Eq, FromPrimitive)]
@ -137,16 +214,6 @@ impl Font {
text.chars().fold(0, |acc, c| acc + self.char_width(c)) text.chars().fold(0, |acc, c| acc + self.char_width(c))
} }
/// 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. /// Width of the text that is visible.
/// Not including the spaces before the first and after the last character. /// Not including the spaces before the first and after the last character.
pub fn visible_text_width(self, text: &str) -> i16 { pub fn visible_text_width(self, text: &str) -> i16 {
@ -155,17 +222,17 @@ impl Font {
return 0; return 0;
} }
let first_char_bearing = if let Some(glyph) = self.get_first_glyph_from_text(text) { let (first_char_bearing, last_char_bearing) = self.with_glyph_data(|data| {
glyph.bearing_x let first = text
} else { .chars()
0 .next()
}; .map_or(0, |c| data.get_glyph(c).bearing_x);
let last = text
let last_char_bearing = if let Some(glyph) = self.get_last_glyph_from_text(text) { .chars()
glyph.right_side_bearing() .next_back()
} else { .map_or(0, |c| data.get_glyph(c).right_side_bearing());
0 (first, last)
}; });
// Strip leftmost and rightmost spaces/bearings/margins. // Strip leftmost and rightmost spaces/bearings/margins.
self.text_width(text) - first_char_bearing - last_char_bearing self.text_width(text) - first_char_bearing - last_char_bearing
@ -178,11 +245,13 @@ impl Font {
/// the glyphs representing the characters in the provided text. /// the glyphs representing the characters in the provided text.
pub fn visible_text_height(self, text: &str) -> i16 { pub fn visible_text_height(self, text: &str) -> i16 {
let (mut ascent, mut descent) = (0, 0); let (mut ascent, mut descent) = (0, 0);
for c in text.chars() { self.with_glyph_data(|data| {
let glyph = self.get_glyph(c); for c in text.chars() {
ascent = ascent.max(glyph.bearing_y); let glyph = data.get_glyph(c);
descent = descent.max(glyph.height - glyph.bearing_y); ascent = ascent.max(glyph.bearing_y);
} descent = descent.max(glyph.height - glyph.bearing_y);
}
});
ascent + descent ascent + descent
} }
@ -202,12 +271,13 @@ impl Font {
return 0; return 0;
} }
self.get_first_glyph_from_text(text) text.chars().next().map_or(0, |c| {
.map_or(0, |glyph| glyph.bearing_x) self.with_glyph_data(|data| data.get_glyph(c).bearing_x)
})
} }
pub fn char_width(self, ch: char) -> i16 { pub fn char_width(self, ch: char) -> i16 {
self.get_glyph(ch).adv self.with_glyph_data(|data| data.get_glyph(ch).adv)
} }
pub fn text_height(self) -> i16 { pub fn text_height(self) -> i16 {
@ -247,41 +317,16 @@ impl Font {
(start + end + self.visible_text_height(text)) / 2 (start + end + self.visible_text_height(text)) / 2
} }
fn get_glyph_data(&self, codepoint: u16) -> *const u8 { /// Safely manages temporary access to glyph data without risking
display::get_font_info((*self).into()).map_or(core::ptr::null(), |font_info| { /// translation lock deadlocks. See `GlyphData` for more details.
#[cfg(feature = "translations")] pub fn with_glyph_data<T, F>(&self, f: F) -> T
{ where
if codepoint >= 0x7F { F: FnOnce(&GlyphData) -> T,
// UTF8 character from embedded blob {
return unsafe { get_utf8_glyph(codepoint, *self) }; // Create a new GlyphData instance that will be dropped at the end of this
} // function, releasing any translations lock
} let glyph_data = GlyphData::new(*self);
f(&glyph_data)
if codepoint >= ' ' as u16 && codepoint < 0x7F {
// ASCII character
unsafe {
*font_info
.glyph_data
.offset((codepoint - ' ' as u16) as isize)
}
} else {
font_info.glyph_nonprintable
}
})
}
pub fn get_glyph(self, ch: char) -> Glyph {
/* have the non-breaking space counted for width but not counted as a
* breaking point */
let ch = match ch {
'\u{00a0}' => '\u{0020}',
c => c,
};
let gl_data = self.get_glyph_data(ch as u16);
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) }
} }
/// Get the longest prefix of a given `text` (breaking at word boundaries) /// Get the longest prefix of a given `text` (breaking at word boundaries)
@ -322,12 +367,14 @@ impl Font {
pub fn visible_text_height_ex(&self, text: &str) -> (i16, i16) { pub fn visible_text_height_ex(&self, text: &str) -> (i16, i16) {
let (mut ascent, mut descent) = (0, 0); let (mut ascent, mut descent) = (0, 0);
for c in text.chars() { self.with_glyph_data(|data| {
let glyph = self.get_glyph(c); for c in text.chars() {
ascent = ascent.max(glyph.bearing_y); let glyph = data.get_glyph(c);
descent = descent.max(glyph.height - glyph.bearing_y); ascent = ascent.max(glyph.bearing_y);
} descent = descent.max(glyph.height - glyph.bearing_y);
(ascent, descent) }
(ascent, descent)
})
} }
} }

View File

@ -97,24 +97,26 @@ impl<'a> Shape<'_> for Text<'a> {
// TODO: optimize text clipping, use canvas.viewport() // TODO: optimize text clipping, use canvas.viewport()
for ch in self.text.chars() { self.font.with_glyph_data(|glyph_data| {
if r.x0 >= r.x1 { for ch in self.text.chars() {
break; if r.x0 >= r.x1 {
break;
}
let glyph = glyph_data.get_glyph(ch);
let glyph_bitmap = glyph.bitmap();
let glyph_view = BitmapView::new(&glyph_bitmap)
.with_alpha(self.alpha)
.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;
} }
});
let glyph = self.font.get_glyph(ch);
let glyph_bitmap = glyph.bitmap();
let glyph_view = BitmapView::new(&glyph_bitmap)
.with_alpha(self.alpha)
.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;
}
} }
} }