Clean up FontRequest interface

Represent a non-specified pixel size and font weight through a None option.

This is cleaner and has the added benefit that the backend can now
easily decide what the default size/weight should be.
This commit is contained in:
Simon Hausmann 2021-01-08 14:56:24 +01:00
parent 36c41ef758
commit 35f51fe339
4 changed files with 81 additions and 78 deletions

View file

@ -268,14 +268,16 @@ impl<T> CachedGraphicsData<T> {
pub type RenderingCache<T> = vec_arena::Arena<CachedGraphicsData<T>>; pub type RenderingCache<T> = vec_arena::Arena<CachedGraphicsData<T>>;
/// FontRequest collects all the developer-configurable properties for fonts, such as family, weight, etc. /// FontRequest collects all the developer-configurable properties for fonts, such as family, weight, etc.
/// It is submitted as a request to the platform font system (i.e. CoreText on macOS) and in exchange we /// It is submitted as a request to the platform font system (i.e. CoreText on macOS) and in exchange the
/// store a Rc<FontHandle> /// backend returns a Box<dyn Font>.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
#[repr(C)] #[repr(C)]
pub struct FontRequest { pub struct FontRequest {
pub family: SharedString, pub family: SharedString,
pub weight: i32, /// If the weight is None, the the system default font weight should be used.
pub pixel_size: f32, pub weight: Option<i32>,
/// If the pixel size is None, the system default font size should be used.
pub pixel_size: Option<f32>,
} }
pub trait Font { pub trait Font {

View file

@ -66,9 +66,6 @@ impl Default for TextVerticalAlignment {
} }
} }
const DEFAULT_FONT_SIZE: f32 = 12.;
const DEFAULT_FONT_WEIGHT: i32 = 400;
/// The implementation of the `Text` element /// The implementation of the `Text` element
#[repr(C)] #[repr(C)]
#[derive(FieldOffsets, Default, SixtyFPSElement)] #[derive(FieldOffsets, Default, SixtyFPSElement)]
@ -99,7 +96,7 @@ impl Item for Text {
fn layouting_info(self: Pin<&Self>, window: &ComponentWindow) -> LayoutInfo { fn layouting_info(self: Pin<&Self>, window: &ComponentWindow) -> LayoutInfo {
let text = self.text(); let text = self.text();
if let Some(font) = self.font(window) { if let Some(font) = window.0.font(self.font_request()) {
let width = font.text_width(&text); let width = font.text_width(&text);
let height = font.height(); let height = font.height();
LayoutInfo { min_width: width, min_height: height, ..LayoutInfo::default() } LayoutInfo { min_width: width, min_height: height, ..LayoutInfo::default() }
@ -134,35 +131,26 @@ impl ItemConsts for Text {
} }
impl Text { impl Text {
pub fn font_pixel_size(self: Pin<&Self>, scale_factor: f32) -> f32 { pub fn font_request(self: Pin<&Self>) -> crate::graphics::FontRequest {
let font_size = self.font_size();
if font_size == 0.0 {
DEFAULT_FONT_SIZE * scale_factor
} else {
font_size
}
}
pub fn font_request(self: Pin<&Self>, scale_factor: f32) -> crate::graphics::FontRequest {
crate::graphics::FontRequest { crate::graphics::FontRequest {
family: self.font_family(), family: self.font_family(),
weight: { weight: {
let weight = self.font_weight(); let weight = self.font_weight();
if weight == 0 { if weight == 0 {
DEFAULT_FONT_WEIGHT None
} else { } else {
weight Some(weight)
} }
}, },
pixel_size: self.font_pixel_size(scale_factor), pixel_size: {
let font_size = self.font_size();
if font_size == 0.0 {
None
} else {
Some(font_size)
} }
},
} }
pub fn font(
self: Pin<&Self>,
window: &ComponentWindow,
) -> Option<Box<dyn crate::graphics::Font>> {
window.0.font(self.font_request(window.scale_factor()))
} }
} }
@ -205,7 +193,7 @@ impl Item for TextInput {
} }
fn layouting_info(self: Pin<&Self>, window: &ComponentWindow) -> LayoutInfo { fn layouting_info(self: Pin<&Self>, window: &ComponentWindow) -> LayoutInfo {
if let Some(font) = self.font(window) { if let Some(font) = window.0.font(self.font_request()) {
let width = font.text_width("********************"); let width = font.text_width("********************");
let height = font.height(); let height = font.height();
@ -231,7 +219,7 @@ impl Item for TextInput {
} }
let text = self.text(); let text = self.text();
let font = match self.font(window) { let font = match window.0.font(self.font_request()) {
Some(font) => font, Some(font) => font,
None => return InputEventResult::EventIgnored, None => return InputEventResult::EventIgnored,
}; };
@ -545,35 +533,26 @@ impl TextInput {
} }
} }
fn font_pixel_size(self: Pin<&Self>, scale_factor: f32) -> f32 { pub fn font_request(self: Pin<&Self>) -> crate::graphics::FontRequest {
let font_size = self.font_size();
if font_size == 0.0 {
DEFAULT_FONT_SIZE * scale_factor
} else {
font_size
}
}
pub fn font_request(self: Pin<&Self>, scale_factor: f32) -> crate::graphics::FontRequest {
crate::graphics::FontRequest { crate::graphics::FontRequest {
family: self.font_family(), family: self.font_family(),
weight: { weight: {
let weight = self.font_weight(); let weight = self.font_weight();
if weight == 0 { if weight == 0 {
DEFAULT_FONT_WEIGHT None
} else { } else {
weight Some(weight)
} }
}, },
pixel_size: self.font_pixel_size(scale_factor), pixel_size: {
let font_size = self.font_size();
if font_size == 0.0 {
None
} else {
Some(font_size)
} }
},
} }
pub fn font(
self: Pin<&Self>,
window: &ComponentWindow,
) -> Option<Box<dyn crate::graphics::Font>> {
window.0.font(self.font_request(window.scale_factor()))
} }
} }

View file

@ -89,6 +89,11 @@ pub trait GenericWindow {
/// Close the active popup if any /// Close the active popup if any
fn close_popup(&self); fn close_popup(&self);
/// Return a font trait object for the given font request. This is typically provided by the backend and
/// requested by text related items in order to measure text metrics with the item's chosen font.
/// Note that if the FontRequest's pixel_size is 0, it is interpreted as the undefined size and that the
/// system default font size should be used for the returned font.
/// With some backends this may return none unless the window is mapped.
fn font(&self, request: crate::graphics::FontRequest) fn font(&self, request: crate::graphics::FontRequest)
-> Option<Box<dyn crate::graphics::Font>>; -> Option<Box<dyn crate::graphics::Font>>;
} }

View file

@ -29,6 +29,9 @@ pub(crate) mod eventloop;
type CanvasRc = Rc<RefCell<femtovg::Canvas<femtovg::renderer::OpenGl>>>; type CanvasRc = Rc<RefCell<femtovg::Canvas<femtovg::renderer::OpenGl>>>;
type ItemRenderingCacheRc = Rc<RefCell<RenderingCache<Option<GPUCachedData>>>>; type ItemRenderingCacheRc = Rc<RefCell<RenderingCache<Option<GPUCachedData>>>>;
pub const DEFAULT_FONT_SIZE: f32 = 12.;
pub const DEFAULT_FONT_WEIGHT: i32 = 400; // CSS normal
struct CachedImage { struct CachedImage {
id: femtovg::ImageId, id: femtovg::ImageId,
canvas: CanvasRc, canvas: CanvasRc,
@ -95,7 +98,7 @@ fn try_load_app_font(canvas: &CanvasRc, request: &FontRequest) -> Option<femtovg
}; };
let query = fontdb::Query { let query = fontdb::Query {
families: &[family], families: &[family],
weight: fontdb::Weight(request.weight as u16), weight: fontdb::Weight(request.weight.unwrap() as u16),
..Default::default() ..Default::default()
}; };
APPLICATION_FONTS.with(|font_db| { APPLICATION_FONTS.with(|font_db| {
@ -121,7 +124,7 @@ fn load_system_font(canvas: &CanvasRc, request: &FontRequest) -> femtovg::FontId
.select_best_match( .select_best_match(
&[family_name, font_kit::family_name::FamilyName::SansSerif], &[family_name, font_kit::family_name::FamilyName::SansSerif],
&font_kit::properties::Properties::new() &font_kit::properties::Properties::new()
.weight(font_kit::properties::Weight(request.weight as f32)), .weight(font_kit::properties::Weight(request.weight.unwrap() as f32)),
) )
.unwrap(); .unwrap();
@ -138,14 +141,25 @@ fn load_system_font(canvas: &CanvasRc, request: &FontRequest) -> femtovg::FontId
} }
impl FontDatabase { impl FontDatabase {
fn font(&mut self, canvas: &CanvasRc, request: FontRequest) -> femtovg::FontId { fn font(&mut self, canvas: &CanvasRc, mut request: FontRequest, scale_factor: f32) -> GLFont {
self.0 request.pixel_size = request.pixel_size.or(Some(DEFAULT_FONT_SIZE * scale_factor));
.entry(FontCacheKey::new(&request)) request.weight = request.weight.or(Some(DEFAULT_FONT_WEIGHT));
GLFont {
font_id: self
.0
.entry(FontCacheKey {
family: request.family.clone(),
weight: request.weight.unwrap(),
})
.or_insert_with(|| { .or_insert_with(|| {
try_load_app_font(canvas, &request) try_load_app_font(canvas, &request)
.unwrap_or_else(|| load_system_font(canvas, &request)) .unwrap_or_else(|| load_system_font(canvas, &request))
}) })
.clone() .clone(),
canvas: canvas.clone(),
pixel_size: request.pixel_size.unwrap(),
}
} }
} }
@ -357,12 +371,11 @@ impl GraphicsBackend for GLRenderer {
} }
fn font(&mut self, request: FontRequest) -> Box<dyn Font> { fn font(&mut self, request: FontRequest) -> Box<dyn Font> {
let pixel_size = request.pixel_size; Box::new(self.loaded_fonts.borrow_mut().font(
Box::new(GLFont { &self.canvas,
font_id: self.loaded_fonts.borrow_mut().font(&self.canvas, request), request,
canvas: self.canvas.clone(), self.window().scale_factor() as f32,
pixel_size, ))
})
} }
} }
@ -583,13 +596,13 @@ impl ItemRenderer for GLItemRenderer {
fn draw_text(&mut self, pos: Point, text: std::pin::Pin<&sixtyfps_corelib::items::Text>) { fn draw_text(&mut self, pos: Point, text: std::pin::Pin<&sixtyfps_corelib::items::Text>) {
use sixtyfps_corelib::items::{TextHorizontalAlignment, TextVerticalAlignment}; use sixtyfps_corelib::items::{TextHorizontalAlignment, TextVerticalAlignment};
let font_id = let font = self.loaded_fonts.borrow_mut().font(
self.loaded_fonts.borrow_mut().font(&self.canvas, text.font_request(self.scale_factor)); &self.canvas,
text.font_request(),
self.scale_factor,
);
let mut paint = femtovg::Paint::color(text.color().into()); let paint = font.paint(text.color().into());
paint.set_font(&[font_id]);
paint.set_font_size(text.font_pixel_size(self.scale_factor()));
paint.set_text_baseline(femtovg::Baseline::Top);
let text_str = text.text(); let text_str = text.text();
@ -705,12 +718,6 @@ struct FontCacheKey {
weight: i32, weight: i32,
} }
impl FontCacheKey {
fn new(request: &FontRequest) -> Self {
Self { family: request.family.clone(), weight: request.weight }
}
}
struct GLFont { struct GLFont {
font_id: femtovg::FontId, font_id: femtovg::FontId,
pixel_size: f32, pixel_size: f32,
@ -738,6 +745,16 @@ impl Font for GLFont {
} }
} }
impl GLFont {
fn paint(&self, color: Color) -> femtovg::Paint {
let mut paint = femtovg::Paint::color(color.into());
paint.set_font(&[self.font_id]);
paint.set_font_size(self.pixel_size);
paint.set_text_baseline(femtovg::Baseline::Top);
paint
}
}
pub fn create_window() -> ComponentWindow { pub fn create_window() -> ComponentWindow {
ComponentWindow::new(GraphicsWindow::new(|event_loop, window_builder| { ComponentWindow::new(GraphicsWindow::new(|event_loop, window_builder| {
GLRenderer::new( GLRenderer::new(