From 09a1590c7e5e7d5e4bc785483c5a858a17a153d8 Mon Sep 17 00:00:00 2001 From: Jonathan Johnson Date: Sun, 10 Dec 2023 07:19:32 -0800 Subject: [PATCH] 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 --- src/graphics.rs | 68 +++++++++++++++++++++++++------------------ src/tick.rs | 10 +++---- src/window.rs | 76 +++++++++++++++++++++++++++++++------------------ 3 files changed, 94 insertions(+), 60 deletions(-) diff --git a/src/graphics.rs b/src/graphics.rs index d8a31de..86e00d3 100644 --- a/src/graphics.rs +++ b/src/graphics.rs @@ -1,6 +1,6 @@ use std::ops::{Deref, DerefMut}; -use ahash::{HashSet, HashSetExt}; +use ahash::AHashSet; use kludgine::cosmic_text::FamilyOwned; use kludgine::figures::units::{Px, UPx}; use kludgine::figures::{ @@ -19,7 +19,7 @@ use crate::styles::FontFamilyList; pub struct Graphics<'clip, 'gfx, 'pass> { renderer: RenderContext<'clip, 'gfx, 'pass>, region: Rect, - current_font_family: &'clip mut Option, + font_state: &'clip mut FontState, } enum RenderContext<'clip, 'gfx, 'pass> { @@ -30,14 +30,11 @@ enum RenderContext<'clip, 'gfx, 'pass> { impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> { /// Returns a new graphics context for the given [`Renderer`]. #[must_use] - pub fn new( - renderer: Renderer<'gfx, 'pass>, - current_font_family: &'clip mut Option, - ) -> Self { + pub fn new(renderer: Renderer<'gfx, 'pass>, font_state: &'clip mut FontState) -> Self { Self { region: renderer.clip_rect().into_signed(), 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 { - 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 /// system, or None if no font families match. pub fn find_available_font_family(&mut self, list: &FontFamilyList) -> Option { - 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`. pub fn set_available_font_family(&mut self, list: &FontFamilyList) { - if self.current_font_family.as_ref() != Some(list) { - *self.current_font_family = Some(list.clone()); + if self.font_state.current_font_family.as_ref() != Some(list) { + self.font_state.current_font_family = Some(list.clone()); if let Some(family) = self.find_available_font_family(list) { self.set_font_family(family); } @@ -131,7 +111,7 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> { Graphics { renderer: RenderContext::Clipped(self.renderer.clipped_to(new_clip)), 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, + current_font_family: Option, +} + +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 { + list.iter() + .find(|family| match family { + FamilyOwned::Name(name) => self.fonts.contains(name), + _ => true, + }) + .cloned() + } +} diff --git a/src/tick.rs b/src/tick.rs index e910c9e..6946f4c 100644 --- a/src/tick.rs +++ b/src/tick.rs @@ -1,8 +1,8 @@ -use std::collections::HashSet; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, Mutex, MutexGuard}; use std::time::{Duration, Instant}; +use ahash::AHashSet; use intentional::Assert; use kludgine::app::winit::event::{ElementState, KeyEvent, MouseButton}; use kludgine::app::winit::keyboard::Key; @@ -20,7 +20,7 @@ use crate::widget::{EventHandling, HANDLED, IGNORED}; #[must_use] pub struct Tick { data: Arc, - handled_keys: HashSet, + handled_keys: AHashSet, } impl Tick { @@ -114,7 +114,7 @@ impl Tick { Self { data, - handled_keys: HashSet::new(), + handled_keys: AHashSet::new(), } } @@ -147,7 +147,7 @@ impl Tick { #[derive(Default, Debug)] pub struct InputState { /// A collection of all keys currently pressed. - pub keys: HashSet, + pub keys: AHashSet, /// The state of the mouse cursor and any buttons pressed. pub mouse: Option, } @@ -155,7 +155,7 @@ pub struct InputState { #[derive(Debug, Default)] pub struct Mouse { pub position: Point, - pub buttons: HashSet, + pub buttons: AHashSet, } #[derive(Debug)] diff --git a/src/window.rs b/src/window.rs index 4163e14..150408a 100644 --- a/src/window.rs +++ b/src/window.rs @@ -33,7 +33,7 @@ use crate::context::{ AsEventContext, EventContext, Exclusive, GraphicsContext, InvalidationStatus, LayoutContext, WidgetContext, }; -use crate::graphics::Graphics; +use crate::graphics::{FontState, Graphics}; use crate::styles::{Edges, FontFamilyList, ThemePair}; use crate::tree::Tree; use crate::utils::ModifiersExt; @@ -136,6 +136,9 @@ where /// The list of font families to try to find when a [`FamilyOwned::Cursive`] /// font is requested. 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>, occluded: Option>, focused: Option>, @@ -215,6 +218,15 @@ impl Window { self.theme = theme.into_value(); 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) -> Self { + self.font_data_to_load.push(font_data); + self + } } impl Window @@ -256,6 +268,10 @@ where fantasy_font_family: FontFamilyList::default(), monospace_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, theme: Some(self.theme), 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, sans_serif_font_family: self.sans_serif_font_family, fantasy_font_family: self.fantasy_font_family, @@ -338,6 +354,7 @@ struct GooeyWindow { current_theme: ThemePair, theme_mode: Value, transparent: bool, + fonts: FontState, gooey: Gooey, } @@ -535,10 +552,15 @@ where let theme = settings.theme.take().expect("theme always present"); 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)) = - Graphics::inner_find_available_font_family(fontdb, &settings.serif_font_family) - .or_else(|| default_family(Family::Serif)) + let fonts = FontState::new(fontdb); + + 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); } @@ -546,7 +568,6 @@ where let bundled_font_name; #[cfg(feature = "roboto-flex")] { - fontdb.load_font_data(include_bytes!("../assets/RobotoFlex.ttf").to_vec()); bundled_font_name = Some(String::from("Roboto Flex")); } #[cfg(not(feature = "roboto-flex"))] @@ -554,34 +575,34 @@ where bundled_font_name = None; } - if let Some(FamilyOwned::Name(name)) = - Graphics::inner_find_available_font_family(fontdb, &settings.sans_serif_font_family) - .or_else(|| { - if let Some(name) = bundled_font_name { - Some(FamilyOwned::Name(name)) - } else { - default_family(Family::SansSerif) - } - }) + if let Some(FamilyOwned::Name(name)) = fonts + .find_available_font_family(&settings.sans_serif_font_family) + .or_else(|| { + if let Some(name) = bundled_font_name { + Some(FamilyOwned::Name(name)) + } else { + default_family(Family::SansSerif) + } + }) { fontdb.set_sans_serif_family(name); } - if let Some(FamilyOwned::Name(name)) = - Graphics::inner_find_available_font_family(fontdb, &settings.fantasy_font_family) - .or_else(|| default_family(Family::Fantasy)) + if let Some(FamilyOwned::Name(name)) = fonts + .find_available_font_family(&settings.fantasy_font_family) + .or_else(|| default_family(Family::Fantasy)) { fontdb.set_fantasy_family(name); } - if let Some(FamilyOwned::Name(name)) = - Graphics::inner_find_available_font_family(fontdb, &settings.monospace_font_family) - .or_else(|| default_family(Family::Monospace)) + if let Some(FamilyOwned::Name(name)) = fonts + .find_available_font_family(&settings.monospace_font_family) + .or_else(|| default_family(Family::Monospace)) { fontdb.set_monospace_family(name); } - if let Some(FamilyOwned::Name(name)) = - Graphics::inner_find_available_font_family(fontdb, &settings.cursive_font_family) - .or_else(|| default_family(Family::Cursive)) + if let Some(FamilyOwned::Name(name)) = fonts + .find_available_font_family(&settings.cursive_font_family) + .or_else(|| default_family(Family::Cursive)) { fontdb.set_cursive_family(name); } @@ -627,6 +648,7 @@ where theme, theme_mode, transparent, + fonts, gooey, } } @@ -655,8 +677,8 @@ where let mut window = RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded); let root_mode = self.constrain_window_resizing(resizable, &mut window, graphics); + self.fonts.next_frame(); let graphics = self.contents.new_frame(graphics); - let mut current_font_family = None; let mut context = GraphicsContext { widget: WidgetContext::new( self.root.clone(), @@ -666,7 +688,7 @@ where self.theme_mode.get(), &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); let mut layout_context = LayoutContext::new(&mut context); @@ -1231,12 +1253,12 @@ pub(crate) mod sealed { pub theme: Option>, pub theme_mode: Option>, pub transparent: bool, - pub load_system_fonts: bool, pub serif_font_family: FontFamilyList, pub sans_serif_font_family: FontFamilyList, pub fantasy_font_family: FontFamilyList, pub monospace_font_family: FontFamilyList, pub cursive_font_family: FontFamilyList, + pub font_data_to_load: Vec>, } #[derive(Clone)]