Skip to content

Commit

Permalink
fix(text): 🐛 install default fonts from ThemeWidget for subtree
Browse files Browse the repository at this point in the history
  • Loading branch information
wjian23 authored and M-Adoo committed Dec 21, 2023
1 parent 0b00fff commit e9289de
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 31 deletions.
124 changes: 114 additions & 10 deletions core/src/builtin_widgets/theme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
//! to app-wide or particular part of the application.
use crate::{fill_svgs, prelude::*, widget::WidgetBuilder};
use ahash::{HashMap, HashSet};
use ribir_algo::Sc;
pub use ribir_algo::{CowArc, ShareResource};
use ribir_geom::Size;
use ribir_macros::Declare;
use ribir_text::TextStyle;
use std::{collections::HashMap, rc::Rc};
use ribir_text::{font_db::ID, TextStyle};
use std::{rc::Rc, vec};

mod palette;
pub use palette::*;
Expand Down Expand Up @@ -54,7 +55,7 @@ pub struct InheritTheme {
/// icon size standard
pub icon_size: Option<IconSize>,
/// a collection of icons.
pub icons: Option<HashMap<NamedSvg, ShareResource<Svg>, ahash::RandomState>>,
pub icons: Option<HashMap<NamedSvg, ShareResource<Svg>>>,
pub transitions_theme: Option<TransitionTheme>,
pub compose_decorators: Option<ComposeDecorators>,
pub custom_styles: Option<CustomStyles>,
Expand Down Expand Up @@ -88,13 +89,7 @@ impl ComposeChild for ThemeWidget {
let mut themes = ctx!().themes().clone();
themes.push(theme.clone());

// Keep the empty node, because the subtree may be hold its id.
//
// And not try to swap child and the empty node, and remove the child
// node, because the subtree may be hold its id.
//
// A `Void` is cheap for a theme.
let p = Void.widget_build(ctx!()).attach_data(theme, ctx!());
let p = ThemeRender{ theme: theme.clone() }.widget_build(ctx!()).attach_data(theme, ctx!());
// shadow the context with the theme.
let ctx = BuildCtx::new_with_data(Some(p.id()), ctx!().tree, themes);
let child = child.gen_widget(&ctx);
Expand All @@ -105,6 +100,53 @@ impl ComposeChild for ThemeWidget {
}
}

// ThemeRender will install default font for the subtree in the
// perform_layout.
#[derive(Query, SingleChild)]
struct ThemeRender {
theme: Sc<Theme>,
}

impl Render for ThemeRender {
#[inline]
fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size {
let mut fonts = vec![];
let font_db = AppCtx::font_db().clone();
let font_families = match self.theme.deref() {
Theme::Full(f) => Some(f.typography_theme.default_font_family.clone()),
Theme::Inherit(i) => i
.typography_theme
.as_ref()
.map(|typography_theme| typography_theme.default_font_family.clone()),
};

if let Some(families) = font_families {
fonts = font_db
.borrow_mut()
.select_all_match(&FontFace { families, ..<_>::default() });
}
if !fonts.is_empty() {
// install default font for the subtree.
let old_fonts = font_db.borrow().default_fonts().to_vec();
let mut set: HashSet<ID> = HashSet::from_iter(fonts.iter().cloned());
for font in old_fonts {
if set.insert(font) {
fonts.push(font);
}
}
font_db.borrow_mut().set_default_fonts(fonts);
}
if let Some(mut l) = ctx.single_child_layouter() {
l.perform_widget_layout(clamp)
} else {
Size::zero()
}
}

#[inline]
fn paint(&self, _: &mut PaintingCtx) {}
}

impl Default for Theme {
fn default() -> Self { Theme::Full(<_>::default()) }
}
Expand Down Expand Up @@ -339,3 +381,65 @@ impl From<InheritTheme> for Theme {
#[inline]
fn from(value: InheritTheme) -> Self { Theme::Inherit(value) }
}

#[cfg(test)]
mod tests {
use std::cell::RefCell;

use super::*;
use crate::{reset_test_env, test_helper::*};

#[derive(Query, Declare)]
struct QueryFont {
ids: Rc<RefCell<Vec<ID>>>,
}

impl Render for QueryFont {
fn perform_layout(&self, _: BoxClamp, _: &mut LayoutCtx) -> Size {
*self.ids.borrow_mut() = AppCtx::font_db().borrow().default_fonts().to_vec();
Size::zero()
}
fn paint(&self, _: &mut PaintingCtx) {}
}
#[test]
fn theme_font() {
reset_test_env!();

let font_db = AppCtx::font_db().clone();
let font_ids = Rc::new(RefCell::new(vec![]));
let font_ids2 = font_ids.clone();
let w = fn_widget! {
let mut typography_theme = TypographyTheme::of(ctx!()).clone();
typography_theme.default_font_family = Box::new([FontFamily::Name("DejaVu Sans".into())]);
@ThemeWidget {
theme: Sc::new(Theme::Inherit(InheritTheme {
typography_theme: Some(typography_theme),
font_files: Some(vec![env!("CARGO_MANIFEST_DIR").to_owned() + "/../fonts/DejaVuSans.ttf"]),
..<_>::default()
})),
@{
Box::new(fn_widget!{
@QueryFont {
ids: font_ids2.clone(),
}
})
}
}
};

let old_path = font_db.borrow().default_fonts().to_vec();
let mut wnd = TestWindow::new(w);
wnd.draw_frame();

let ids = font_db.borrow_mut().select_all_match(&FontFace {
families: Box::new([
FontFamily::Name("DejaVu Sans".into()),
FontFamily::Name("Lato".into()),
]),
..<_>::default()
});

assert_eq!(*font_ids.borrow(), ids);
assert_eq!(&old_path, font_db.borrow().default_fonts());
}
}
3 changes: 1 addition & 2 deletions core/src/context/build_ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ impl<'a> BuildCtx<'a> {
}

pub(crate) fn find_cfg<T>(&self, f: impl Fn(&Theme) -> Option<&T>) -> Option<&T> {
for t in self.themes().iter().rev() {
for t in self.themes().iter() {
let v = f(t);
if v.is_some() {
return v;
Expand Down Expand Up @@ -178,7 +178,6 @@ mod tests {
theme: Sc::new(Theme::Inherit(InheritTheme {
palette: Some(Rc::new(light_palette)),
..<_>::default()

})),
@ {
Box::new(fn_widget!{
Expand Down
63 changes: 63 additions & 0 deletions core/src/widget_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use std::{
};

pub mod widget_id;
use ribir_algo::Sc;
use ribir_text::font_db::ID;
pub(crate) use widget_id::TreeArena;
pub use widget_id::WidgetId;
mod layout_info;
Expand Down Expand Up @@ -56,6 +58,8 @@ impl WidgetTree {
continue;
}

let _guard = DefaultFontGuard::install_default_font(self, wid);

let clamp = self
.store
.layout_info(wid)
Expand Down Expand Up @@ -203,6 +207,65 @@ impl WidgetTree {
}
}

struct DefaultFontGuard {
old_ids: Vec<ID>,
}
impl Drop for DefaultFontGuard {
fn drop(&mut self) {
let font_db = AppCtx::font_db().clone();
font_db.borrow_mut().set_default_fonts(self.old_ids.clone());
}
}

impl DefaultFontGuard {
fn install_default_font(tree: &WidgetTree, wid: WidgetId) -> Self {
fn add_font(
fonts: &mut Vec<FontFamily>,
set: &mut HashSet<FontFamily>,
new_fonts: &[FontFamily],
) {
for font in new_fonts {
if set.insert(font.clone()) {
fonts.push(font.clone());
}
}
}

let mut fonts = vec![];
let mut set: HashSet<FontFamily> = HashSet::default();
let arena = &tree.arena;
wid.ancestors(arena).skip(1).for_each(|p| {
p.assert_get(arena)
.query_type_inside_first(|t: &Sc<Theme>| {
match t.deref() {
Theme::Full(f) => add_font(
&mut fonts,
&mut set,
&f.typography_theme.default_font_family,
),
Theme::Inherit(i) => {
if let Some(f) = &i.typography_theme {
add_font(&mut fonts, &mut set, &f.default_font_family);
}
}
}
false
});
});

let font_db = AppCtx::font_db().clone();
let old_ids = font_db.borrow().default_fonts().to_vec();
let mut ids = font_db.borrow_mut().select_all_match(&FontFace {
families: fonts.into_boxed_slice(),
..<_>::default()
});
let set: HashSet<ID> = HashSet::from_iter(ids.iter().cloned());
ids.extend(old_ids.iter().filter(|id| !set.contains(id)));
font_db.borrow_mut().set_default_fonts(ids);
Self { old_ids }
}
}

impl Default for WidgetTree {
fn default() -> Self {
let mut arena = TreeArena::new();
Expand Down
8 changes: 4 additions & 4 deletions text/src/font_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::svg_glyph_cache::SvgGlyphCache;
use crate::{FontFace, FontFamily};
/// A wrapper of fontdb and cache font data.
pub struct FontDB {
default_font: ID,
default_fonts: Vec<ID>,
data_base: fontdb::Database,
cache: HashMap<ID, Option<Face>>,
}
Expand All @@ -31,9 +31,9 @@ pub struct Face {
}

impl FontDB {
pub fn set_default_font(&mut self, face_id: ID) { self.default_font = face_id; }
pub fn set_default_fonts(&mut self, ids: Vec<ID>) { self.default_fonts = ids; }

pub fn default_font(&self) -> ID { self.default_font }
pub fn default_fonts(&self) -> &[ID] { &self.default_fonts }

pub fn try_get_face_data(&self, face_id: ID) -> Option<&Face> {
self.cache.get(&face_id)?.as_ref()
Expand Down Expand Up @@ -248,7 +248,7 @@ impl Default for FontDB {
data_base.load_font_data(include_bytes!("../Lato-Regular.ttf").to_vec());
let default_font = data_base.faces().next().map(|f| f.id).unwrap();
let mut this = FontDB {
default_font,
default_fonts: vec![default_font],
data_base,
cache: <_>::default(),
};
Expand Down
45 changes: 31 additions & 14 deletions text/src/shaper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,37 +310,54 @@ impl From<TextDirection> for rustybuzz::Direction {

#[derive(Clone)]
struct FallBackFaceHelper<'a> {
ids: &'a [ID],
ids: Vec<ID>,
font_db: &'a RefCell<FontDB>,
face_idx: usize,
}

impl<'a> FallBackFaceHelper<'a> {
fn new(ids: &'a [ID], font_db: &'a RefCell<FontDB>) -> Self { Self { ids, font_db, face_idx: 0 } }
fn new(ids: &'a [ID], font_db: &'a RefCell<FontDB>) -> Self {
let mut ids = ids.to_vec();
let set: ahash::HashSet<ID> = ahash::HashSet::from_iter(ids.iter().cloned());

{
let font_db = font_db.borrow();
let default_ids = font_db.default_fonts();
for id in default_ids.iter() {
if set.contains(id) {
continue;
}
ids.push(*id);
}
}

Self { ids, font_db, face_idx: 0 }
}

fn next_fallback_face(&mut self, text: &str) -> Option<Face> {
let font_db = self.font_db.borrow();
loop {
if self.face_idx > self.ids.len() {
if self.face_idx >= self.ids.len() {
return None;
}
let face_idx = self.face_idx;
self.face_idx += 1;
if face_idx == self.ids.len() {
return font_db.try_get_face_data(font_db.default_font()).cloned();
}

let face = self
.ids
.get(face_idx)
.get(self.face_idx)
.and_then(|id| font_db.try_get_face_data(*id))
.filter(|f| match text.is_empty() {
true => true,
false => text.chars().any(|c| f.has_char(c)),
})
.cloned();
if face.is_some() {

self.face_idx += 1;
if self.face_idx == self.ids.len() {
return face;
} else {
let face = face.filter(|f| match text.is_empty() {
true => true,
false => text.chars().any(|c| f.has_char(c)),
});
if face.is_some() {
return face;
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion text/src/svg_glyph_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ mod tests {
</svg>"##;
let doc = super::SvgDocument::new(GlyphId(2428)..=GlyphId(2428), content.as_bytes());
let mut db = FontDB::default();
let dummy_face = db.face_data_or_insert(db.default_font()).unwrap();
let dummy_face = db.face_data_or_insert(db.default_fonts()[0]).unwrap();
assert_eq!(doc.elems.len(), 4);
assert!(
doc
Expand Down

0 comments on commit e9289de

Please sign in to comment.