From 729dc868c29d200b7a19dd8ae65cb7b61035d4f4 Mon Sep 17 00:00:00 2001 From: Mohammad AlSaleh Date: Sun, 10 Mar 2024 21:43:31 +0300 Subject: [PATCH] Only try monospace fonts that support at least one requested script When font fallback involves monospace fonts, and if word has known scripts, limit the fonts tried for fallback to ones that support at least one requested script. Codepoint support info is still collected for these fonts to guide the fallback order selection process. A map of per-script monospace font-ids is pre-populated in font system to acquire lists of wanted font ids efficiently. Signed-off-by: Mohammad AlSaleh --- src/font/fallback/mod.rs | 19 ++++++++++++++++++- src/font/mod.rs | 19 +++++++++++++++++-- src/font/system.rs | 37 +++++++++++++++++++++++++++++++++++-- 3 files changed, 70 insertions(+), 5 deletions(-) diff --git a/src/font/fallback/mod.rs b/src/font/fallback/mod.rs index 02d5fcb..d090be5 100644 --- a/src/font/fallback/mod.rs +++ b/src/font/fallback/mod.rs @@ -2,6 +2,7 @@ use alloc::collections::BTreeSet; use alloc::sync::Arc; +use alloc::vec::Vec; use fontdb::Family; use unicode_script::Script; @@ -150,6 +151,16 @@ impl<'a> Iterator for FontFallbackIter<'a> { self.default_i += 1; let is_mono = self.default_families[self.default_i - 1] == &Family::Monospace; + let mono_ids_for_scripts = if is_mono && !self.scripts.is_empty() { + let scripts = self.scripts.iter().filter_map(|script| { + let script_as_lower = script.short_name().to_lowercase(); + <[u8; 4]>::try_from(script_as_lower.as_bytes()).ok() + }); + self.font_system.get_monospace_ids_for_scripts(scripts) + } else { + Vec::new() + }; + for m_key in font_match_keys_iter(is_mono) { let default_family = self .font_system @@ -173,7 +184,13 @@ impl<'a> Iterator for FontFallbackIter<'a> { } // Set a monospace fallback if Monospace family is not found if is_mono { - if self.font_system.is_monospace(m_key.id) { + let include_mono_id = if mono_ids_for_scripts.is_empty() { + self.font_system.is_monospace(m_key.id) + } else { + mono_ids_for_scripts.binary_search(&m_key.id).is_ok() + }; + + if include_mono_id { let supported_cp_count_opt = self .font_system .get_font_supported_codepoints_in_word(m_key.id, self.word); diff --git a/src/font/mod.rs b/src/font/mod.rs index 25dbf9b..a25d8b2 100644 --- a/src/font/mod.rs +++ b/src/font/mod.rs @@ -30,6 +30,7 @@ pub struct Font { data: Arc + Send + Sync>, id: fontdb::ID, monospace_em_width: Option, + scripts: Vec<[u8; 4]>, unicode_codepoints: Vec, } @@ -50,6 +51,10 @@ impl Font { self.monospace_em_width } + pub fn scripts(&self) -> &[[u8; 4]] { + &self.scripts + } + pub fn unicode_codepoints(&self) -> &[u32] { &self.unicode_codepoints } @@ -77,7 +82,7 @@ impl Font { pub fn new(db: &fontdb::Database, id: fontdb::ID) -> Option { let info = db.face(id)?; - let (monospace_em_width, unicode_codepoints) = { + let (monospace_em_width, scripts, unicode_codepoints) = { db.with_face_data(id, |font_data, face_index| { let face = ttf_parser::Face::parse(font_data, face_index).ok()?; let monospace_em_width = info @@ -93,6 +98,15 @@ impl Font { None?; } + let scripts = face + .tables() + .gpos + .into_iter() + .chain(face.tables().gsub) + .flat_map(|table| table.scripts) + .map(|script| script.tag.to_bytes()) + .collect(); + let mut unicode_codepoints = Vec::new(); face.tables() @@ -111,7 +125,7 @@ impl Font { unicode_codepoints.shrink_to_fit(); - Some((monospace_em_width, unicode_codepoints)) + Some((monospace_em_width, scripts, unicode_codepoints)) })? }?; @@ -129,6 +143,7 @@ impl Font { Some(Self { id: info.id, monospace_em_width, + scripts, unicode_codepoints, #[cfg(feature = "swash")] swash: { diff --git a/src/font/system.rs b/src/font/system.rs index 5cdf24a..0872ba4 100644 --- a/src/font/system.rs +++ b/src/font/system.rs @@ -89,6 +89,11 @@ pub struct FontSystem { /// Sorted unique ID's of all Monospace fonts in DB monospace_font_ids: Vec, + /// Sorted unique ID's of all Monospace fonts in DB per script. + /// A font may support multiple scripts of course, so the same ID + /// may appear in multiple map value vecs. + per_script_monospace_font_ids: HashMap<[u8; 4], Vec>, + /// Cache for font codepoint support info font_codepoint_support_info_cache: HashMap, @@ -153,17 +158,32 @@ impl FontSystem { .collect::>(); monospace_font_ids.sort(); - Self { + let cloned_monospace_font_ids = monospace_font_ids.clone(); + + let mut ret = Self { locale, db, monospace_font_ids, + per_script_monospace_font_ids: Default::default(), font_cache: Default::default(), font_matches_cache: Default::default(), font_codepoint_support_info_cache: Default::default(), shape_plan_cache: ShapePlanCache::default(), #[cfg(feature = "shape-run-cache")] shape_run_cache: crate::ShapeRunCache::default(), - } + }; + + cloned_monospace_font_ids.into_iter().for_each(|id| { + if let Some(font) = ret.get_font(id) { + font.scripts().iter().copied().for_each(|script| { + ret.per_script_monospace_font_ids + .entry(script) + .or_default() + .push(font.id); + }); + } + }); + ret } /// Get the locale. @@ -219,6 +239,19 @@ impl FontSystem { self.monospace_font_ids.binary_search(&id).is_ok() } + pub fn get_monospace_ids_for_scripts( + &self, + scripts: impl Iterator, + ) -> Vec { + let mut ret = scripts + .filter_map(|script| self.per_script_monospace_font_ids.get(&script)) + .flat_map(|ids| ids.iter().copied()) + .collect::>(); + ret.sort(); + ret.dedup(); + ret + } + #[inline(always)] pub fn get_font_supported_codepoints_in_word( &mut self,