Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(text): 🐛 default fonts from fontface #500

Merged
merged 1 commit into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
/// 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 @@
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 @@
}
}

// 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()),

Check warning on line 116 in core/src/builtin_widgets/theme.rs

View check run for this annotation

Codecov / codecov/patch

core/src/builtin_widgets/theme.rs#L116

Added line #L116 was not covered by tests
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()

Check warning on line 142 in core/src/builtin_widgets/theme.rs

View check run for this annotation

Codecov / codecov/patch

core/src/builtin_widgets/theme.rs#L142

Added line #L142 was not covered by tests
}
}

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

impl Default for Theme {
fn default() -> Self { Theme::Full(<_>::default()) }
}
Expand Down Expand Up @@ -339,3 +381,65 @@
#[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 @@
};

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 @@
continue;
}

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

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

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],
) {

Check warning on line 226 in core/src/widget_tree.rs

View check run for this annotation

Codecov / codecov/patch

core/src/widget_tree.rs#L223-L226

Added lines #L223 - L226 were not covered by tests
for font in new_fonts {
if set.insert(font.clone()) {
fonts.push(font.clone());
}

Check warning on line 230 in core/src/widget_tree.rs

View check run for this annotation

Codecov / codecov/patch

core/src/widget_tree.rs#L229-L230

Added lines #L229 - L230 were not covered by tests
}
}

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);
}

Check warning on line 249 in core/src/widget_tree.rs

View check run for this annotation

Codecov / codecov/patch

core/src/widget_tree.rs#L240-L249

Added lines #L240 - L249 were not covered by tests
}
}
false

Check warning on line 252 in core/src/widget_tree.rs

View check run for this annotation

Codecov / codecov/patch

core/src/widget_tree.rs#L252

Added line #L252 was not covered by tests
});
});

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