Finished font support

Font families are now cached. There probably should be a mechanism to
refresh the cache, but we don't currently have a "signal" to notify us
when a font is installed. Presumably, this could be something that
fontdb would eventually add, rather than us building our own.

Closes #48
This commit is contained in:
Jonathan Johnson 2023-12-10 07:19:32 -08:00
parent 35576f9214
commit 09a1590c7e
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
3 changed files with 94 additions and 60 deletions

View file

@ -1,6 +1,6 @@
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use ahash::{HashSet, HashSetExt}; use ahash::AHashSet;
use kludgine::cosmic_text::FamilyOwned; use kludgine::cosmic_text::FamilyOwned;
use kludgine::figures::units::{Px, UPx}; use kludgine::figures::units::{Px, UPx};
use kludgine::figures::{ use kludgine::figures::{
@ -19,7 +19,7 @@ use crate::styles::FontFamilyList;
pub struct Graphics<'clip, 'gfx, 'pass> { pub struct Graphics<'clip, 'gfx, 'pass> {
renderer: RenderContext<'clip, 'gfx, 'pass>, renderer: RenderContext<'clip, 'gfx, 'pass>,
region: Rect<Px>, region: Rect<Px>,
current_font_family: &'clip mut Option<FontFamilyList>, font_state: &'clip mut FontState,
} }
enum RenderContext<'clip, 'gfx, 'pass> { enum RenderContext<'clip, 'gfx, 'pass> {
@ -30,14 +30,11 @@ enum RenderContext<'clip, 'gfx, 'pass> {
impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> { impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> {
/// Returns a new graphics context for the given [`Renderer`]. /// Returns a new graphics context for the given [`Renderer`].
#[must_use] #[must_use]
pub fn new( pub fn new(renderer: Renderer<'gfx, 'pass>, font_state: &'clip mut FontState) -> Self {
renderer: Renderer<'gfx, 'pass>,
current_font_family: &'clip mut Option<FontFamilyList>,
) -> Self {
Self { Self {
region: renderer.clip_rect().into_signed(), region: renderer.clip_rect().into_signed(),
renderer: RenderContext::Renderer(renderer), renderer: RenderContext::Renderer(renderer),
current_font_family, font_state,
} }
} }
@ -66,33 +63,16 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> {
) )
} }
pub(crate) fn inner_find_available_font_family(
db: &cosmic_text::fontdb::Database,
list: &FontFamilyList,
) -> Option<FamilyOwned> {
let mut fonts = HashSet::new();
for (family, _) in db.faces().filter_map(|f| f.families.first()) {
fonts.insert(family.clone());
}
list.iter()
.find(|family| match family {
FamilyOwned::Name(name) => fonts.contains(name),
_ => true,
})
.cloned()
}
/// Returns the first font family in `list` that is currently in the font /// Returns the first font family in `list` that is currently in the font
/// system, or None if no font families match. /// system, or None if no font families match.
pub fn find_available_font_family(&mut self, list: &FontFamilyList) -> Option<FamilyOwned> { pub fn find_available_font_family(&mut self, list: &FontFamilyList) -> Option<FamilyOwned> {
Self::inner_find_available_font_family(self.font_system().db(), list) self.font_state.find_available_font_family(list)
} }
/// Sets the font family to the first family in `list`. /// Sets the font family to the first family in `list`.
pub fn set_available_font_family(&mut self, list: &FontFamilyList) { pub fn set_available_font_family(&mut self, list: &FontFamilyList) {
if self.current_font_family.as_ref() != Some(list) { if self.font_state.current_font_family.as_ref() != Some(list) {
*self.current_font_family = Some(list.clone()); self.font_state.current_font_family = Some(list.clone());
if let Some(family) = self.find_available_font_family(list) { if let Some(family) = self.find_available_font_family(list) {
self.set_font_family(family); self.set_font_family(family);
} }
@ -131,7 +111,7 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> {
Graphics { Graphics {
renderer: RenderContext::Clipped(self.renderer.clipped_to(new_clip)), renderer: RenderContext::Clipped(self.renderer.clipped_to(new_clip)),
region, region,
current_font_family: &mut *self.current_font_family, font_state: &mut *self.font_state,
} }
} }
@ -330,3 +310,35 @@ impl<'gfx, 'pass> DerefMut for RenderContext<'_, 'gfx, 'pass> {
} }
} }
} }
pub struct FontState {
fonts: AHashSet<String>,
current_font_family: Option<FontFamilyList>,
}
impl FontState {
pub fn new(db: &cosmic_text::fontdb::Database) -> Self {
let faces = db.faces();
let mut fonts = AHashSet::with_capacity(faces.size_hint().0);
for (family, _) in faces.filter_map(|f| f.families.first()) {
fonts.insert(family.clone());
}
Self {
fonts,
current_font_family: None,
}
}
pub fn next_frame(&mut self) {
self.current_font_family = None;
}
pub fn find_available_font_family(&self, list: &FontFamilyList) -> Option<FamilyOwned> {
list.iter()
.find(|family| match family {
FamilyOwned::Name(name) => self.fonts.contains(name),
_ => true,
})
.cloned()
}
}

View file

@ -1,8 +1,8 @@
use std::collections::HashSet;
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex, MutexGuard}; use std::sync::{Arc, Mutex, MutexGuard};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use ahash::AHashSet;
use intentional::Assert; use intentional::Assert;
use kludgine::app::winit::event::{ElementState, KeyEvent, MouseButton}; use kludgine::app::winit::event::{ElementState, KeyEvent, MouseButton};
use kludgine::app::winit::keyboard::Key; use kludgine::app::winit::keyboard::Key;
@ -20,7 +20,7 @@ use crate::widget::{EventHandling, HANDLED, IGNORED};
#[must_use] #[must_use]
pub struct Tick { pub struct Tick {
data: Arc<TickData>, data: Arc<TickData>,
handled_keys: HashSet<Key>, handled_keys: AHashSet<Key>,
} }
impl Tick { impl Tick {
@ -114,7 +114,7 @@ impl Tick {
Self { Self {
data, data,
handled_keys: HashSet::new(), handled_keys: AHashSet::new(),
} }
} }
@ -147,7 +147,7 @@ impl Tick {
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct InputState { pub struct InputState {
/// A collection of all keys currently pressed. /// A collection of all keys currently pressed.
pub keys: HashSet<Key>, pub keys: AHashSet<Key>,
/// The state of the mouse cursor and any buttons pressed. /// The state of the mouse cursor and any buttons pressed.
pub mouse: Option<Mouse>, pub mouse: Option<Mouse>,
} }
@ -155,7 +155,7 @@ pub struct InputState {
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct Mouse { pub struct Mouse {
pub position: Point<Px>, pub position: Point<Px>,
pub buttons: HashSet<MouseButton>, pub buttons: AHashSet<MouseButton>,
} }
#[derive(Debug)] #[derive(Debug)]

View file

@ -33,7 +33,7 @@ use crate::context::{
AsEventContext, EventContext, Exclusive, GraphicsContext, InvalidationStatus, LayoutContext, AsEventContext, EventContext, Exclusive, GraphicsContext, InvalidationStatus, LayoutContext,
WidgetContext, WidgetContext,
}; };
use crate::graphics::Graphics; use crate::graphics::{FontState, Graphics};
use crate::styles::{Edges, FontFamilyList, ThemePair}; use crate::styles::{Edges, FontFamilyList, ThemePair};
use crate::tree::Tree; use crate::tree::Tree;
use crate::utils::ModifiersExt; use crate::utils::ModifiersExt;
@ -136,6 +136,9 @@ where
/// The list of font families to try to find when a [`FamilyOwned::Cursive`] /// The list of font families to try to find when a [`FamilyOwned::Cursive`]
/// font is requested. /// font is requested.
pub cursive_font_family: FontFamilyList, pub cursive_font_family: FontFamilyList,
/// A list of data buffers that contain font data to ensure are loaded
/// during drawing operations.
pub font_data_to_load: Vec<Vec<u8>>,
occluded: Option<Dynamic<bool>>, occluded: Option<Dynamic<bool>>,
focused: Option<Dynamic<bool>>, focused: Option<Dynamic<bool>>,
@ -215,6 +218,15 @@ impl Window<WidgetInstance> {
self.theme = theme.into_value(); self.theme = theme.into_value();
self self
} }
/// Adds `font_data` to the list of fonts to load for availability when
/// rendering.
///
/// All font families contained in `font_data` will be loaded.
pub fn loading_font(mut self, font_data: Vec<u8>) -> Self {
self.font_data_to_load.push(font_data);
self
}
} }
impl<Behavior> Window<Behavior> impl<Behavior> Window<Behavior>
@ -256,6 +268,10 @@ where
fantasy_font_family: FontFamilyList::default(), fantasy_font_family: FontFamilyList::default(),
monospace_font_family: FontFamilyList::default(), monospace_font_family: FontFamilyList::default(),
cursive_font_family: FontFamilyList::default(), cursive_font_family: FontFamilyList::default(),
font_data_to_load: vec![
#[cfg(feature = "roboto-flex")]
include_bytes!("../assets/RobotoFlex.ttf").to_vec(),
],
} }
} }
} }
@ -276,7 +292,7 @@ where
focused: self.focused, focused: self.focused,
theme: Some(self.theme), theme: Some(self.theme),
theme_mode: self.theme_mode, theme_mode: self.theme_mode,
load_system_fonts: self.load_system_fonts, font_data_to_load: self.font_data_to_load,
serif_font_family: self.serif_font_family, serif_font_family: self.serif_font_family,
sans_serif_font_family: self.sans_serif_font_family, sans_serif_font_family: self.sans_serif_font_family,
fantasy_font_family: self.fantasy_font_family, fantasy_font_family: self.fantasy_font_family,
@ -338,6 +354,7 @@ struct GooeyWindow<T> {
current_theme: ThemePair, current_theme: ThemePair,
theme_mode: Value<ThemeMode>, theme_mode: Value<ThemeMode>,
transparent: bool, transparent: bool,
fonts: FontState,
gooey: Gooey, gooey: Gooey,
} }
@ -535,10 +552,15 @@ where
let theme = settings.theme.take().expect("theme always present"); let theme = settings.theme.take().expect("theme always present");
let fontdb = graphics.font_system().db_mut(); let fontdb = graphics.font_system().db_mut();
for font_to_load in settings.font_data_to_load.drain(..) {
fontdb.load_font_data(font_to_load);
}
if let Some(FamilyOwned::Name(name)) = let fonts = FontState::new(fontdb);
Graphics::inner_find_available_font_family(fontdb, &settings.serif_font_family)
.or_else(|| default_family(Family::Serif)) if let Some(FamilyOwned::Name(name)) = fonts
.find_available_font_family(&settings.serif_font_family)
.or_else(|| default_family(Family::Serif))
{ {
fontdb.set_serif_family(name); fontdb.set_serif_family(name);
} }
@ -546,7 +568,6 @@ where
let bundled_font_name; let bundled_font_name;
#[cfg(feature = "roboto-flex")] #[cfg(feature = "roboto-flex")]
{ {
fontdb.load_font_data(include_bytes!("../assets/RobotoFlex.ttf").to_vec());
bundled_font_name = Some(String::from("Roboto Flex")); bundled_font_name = Some(String::from("Roboto Flex"));
} }
#[cfg(not(feature = "roboto-flex"))] #[cfg(not(feature = "roboto-flex"))]
@ -554,34 +575,34 @@ where
bundled_font_name = None; bundled_font_name = None;
} }
if let Some(FamilyOwned::Name(name)) = if let Some(FamilyOwned::Name(name)) = fonts
Graphics::inner_find_available_font_family(fontdb, &settings.sans_serif_font_family) .find_available_font_family(&settings.sans_serif_font_family)
.or_else(|| { .or_else(|| {
if let Some(name) = bundled_font_name { if let Some(name) = bundled_font_name {
Some(FamilyOwned::Name(name)) Some(FamilyOwned::Name(name))
} else { } else {
default_family(Family::SansSerif) default_family(Family::SansSerif)
} }
}) })
{ {
fontdb.set_sans_serif_family(name); fontdb.set_sans_serif_family(name);
} }
if let Some(FamilyOwned::Name(name)) = if let Some(FamilyOwned::Name(name)) = fonts
Graphics::inner_find_available_font_family(fontdb, &settings.fantasy_font_family) .find_available_font_family(&settings.fantasy_font_family)
.or_else(|| default_family(Family::Fantasy)) .or_else(|| default_family(Family::Fantasy))
{ {
fontdb.set_fantasy_family(name); fontdb.set_fantasy_family(name);
} }
if let Some(FamilyOwned::Name(name)) = if let Some(FamilyOwned::Name(name)) = fonts
Graphics::inner_find_available_font_family(fontdb, &settings.monospace_font_family) .find_available_font_family(&settings.monospace_font_family)
.or_else(|| default_family(Family::Monospace)) .or_else(|| default_family(Family::Monospace))
{ {
fontdb.set_monospace_family(name); fontdb.set_monospace_family(name);
} }
if let Some(FamilyOwned::Name(name)) = if let Some(FamilyOwned::Name(name)) = fonts
Graphics::inner_find_available_font_family(fontdb, &settings.cursive_font_family) .find_available_font_family(&settings.cursive_font_family)
.or_else(|| default_family(Family::Cursive)) .or_else(|| default_family(Family::Cursive))
{ {
fontdb.set_cursive_family(name); fontdb.set_cursive_family(name);
} }
@ -627,6 +648,7 @@ where
theme, theme,
theme_mode, theme_mode,
transparent, transparent,
fonts,
gooey, gooey,
} }
} }
@ -655,8 +677,8 @@ where
let mut window = RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded); let mut window = RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded);
let root_mode = self.constrain_window_resizing(resizable, &mut window, graphics); let root_mode = self.constrain_window_resizing(resizable, &mut window, graphics);
self.fonts.next_frame();
let graphics = self.contents.new_frame(graphics); let graphics = self.contents.new_frame(graphics);
let mut current_font_family = None;
let mut context = GraphicsContext { let mut context = GraphicsContext {
widget: WidgetContext::new( widget: WidgetContext::new(
self.root.clone(), self.root.clone(),
@ -666,7 +688,7 @@ where
self.theme_mode.get(), self.theme_mode.get(),
&mut self.cursor, &mut self.cursor,
), ),
gfx: Exclusive::Owned(Graphics::new(graphics, &mut current_font_family)), gfx: Exclusive::Owned(Graphics::new(graphics, &mut self.fonts)),
}; };
self.theme_mode.redraw_when_changed(&context); self.theme_mode.redraw_when_changed(&context);
let mut layout_context = LayoutContext::new(&mut context); let mut layout_context = LayoutContext::new(&mut context);
@ -1231,12 +1253,12 @@ pub(crate) mod sealed {
pub theme: Option<Value<ThemePair>>, pub theme: Option<Value<ThemePair>>,
pub theme_mode: Option<Value<ThemeMode>>, pub theme_mode: Option<Value<ThemeMode>>,
pub transparent: bool, pub transparent: bool,
pub load_system_fonts: bool,
pub serif_font_family: FontFamilyList, pub serif_font_family: FontFamilyList,
pub sans_serif_font_family: FontFamilyList, pub sans_serif_font_family: FontFamilyList,
pub fantasy_font_family: FontFamilyList, pub fantasy_font_family: FontFamilyList,
pub monospace_font_family: FontFamilyList, pub monospace_font_family: FontFamilyList,
pub cursive_font_family: FontFamilyList, pub cursive_font_family: FontFamilyList,
pub font_data_to_load: Vec<Vec<u8>>,
} }
#[derive(Clone)] #[derive(Clone)]