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 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<Px>,
current_font_family: &'clip mut Option<FontFamilyList>,
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<FontFamilyList>,
) -> 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<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
/// system, or None if no font families match.
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`.
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<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::{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<TickData>,
handled_keys: HashSet<Key>,
handled_keys: AHashSet<Key>,
}
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<Key>,
pub keys: AHashSet<Key>,
/// The state of the mouse cursor and any buttons pressed.
pub mouse: Option<Mouse>,
}
@ -155,7 +155,7 @@ pub struct InputState {
#[derive(Debug, Default)]
pub struct Mouse {
pub position: Point<Px>,
pub buttons: HashSet<MouseButton>,
pub buttons: AHashSet<MouseButton>,
}
#[derive(Debug)]

View file

@ -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<Vec<u8>>,
occluded: Option<Dynamic<bool>>,
focused: Option<Dynamic<bool>>,
@ -215,6 +218,15 @@ impl Window<WidgetInstance> {
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<u8>) -> Self {
self.font_data_to_load.push(font_data);
self
}
}
impl<Behavior> Window<Behavior>
@ -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<T> {
current_theme: ThemePair,
theme_mode: Value<ThemeMode>,
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<Value<ThemePair>>,
pub theme_mode: Option<Value<ThemeMode>>,
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<Vec<u8>>,
}
#[derive(Clone)]