diff --git a/src/font/fallback/mod.rs b/src/font/fallback/mod.rs index d35ab9e..10f7d99 100644 --- a/src/font/fallback/mod.rs +++ b/src/font/fallback/mod.rs @@ -176,15 +176,12 @@ impl<'a> Iterator for FontFallbackIter<'a> { if let Some(face_info) = self.font_system.db().face(m_key.id) { // Don't use emoji fonts as Monospace if face_info.monospaced && !face_info.post_script_name.contains("Emoji") { - if let Some(font) = self.font_system.get_font(m_key.id) { - let codepoint_non_matches = self.word.chars().count() - - self - .word - .chars() - .filter(|ch| { - font.unicode_codepoints().contains(&u32::from(*ch)) - }) - .count(); + let supported_cp_count_opt = self + .font_system + .get_font_supported_codepoints_in_word(m_key.id, self.word); + if let Some(supported_cp_count) = supported_cp_count_opt { + let codepoint_non_matches = + self.word.chars().count() - supported_cp_count; let fallback_info = MonospaceFallbackInfo { font_weight_diff: Some(m_key.font_weight_diff), diff --git a/src/font/system.rs b/src/font/system.rs index c238584..c1a3763 100644 --- a/src/font/system.rs +++ b/src/font/system.rs @@ -16,6 +16,65 @@ pub struct FontMatchKey { pub(crate) id: fontdb::ID, } +struct FontCachedCodepointSupportInfo { + supported: Vec, + not_supported: Vec, +} + +impl FontCachedCodepointSupportInfo { + const SUPPORTED_MAX_SZ: usize = 512; + const NOT_SUPPORTED_MAX_SZ: usize = 1024; + + fn new() -> Self { + Self { + supported: Vec::with_capacity(Self::SUPPORTED_MAX_SZ), + not_supported: Vec::with_capacity(Self::NOT_SUPPORTED_MAX_SZ), + } + } + + #[inline(always)] + fn unknown_has_codepoint( + &mut self, + font_codepoints: &[u32], + codepoint: u32, + supported_insert_pos: usize, + not_supported_insert_pos: usize, + ) -> bool { + let ret = font_codepoints.contains(&codepoint); + if ret { + // don't bother inserting if we are going to truncate the entry away + if supported_insert_pos != Self::SUPPORTED_MAX_SZ { + self.supported.insert(supported_insert_pos, codepoint); + self.supported.truncate(Self::SUPPORTED_MAX_SZ); + } + } else { + // don't bother inserting if we are going to truncate the entry away + if not_supported_insert_pos != Self::NOT_SUPPORTED_MAX_SZ { + self.not_supported + .insert(not_supported_insert_pos, codepoint); + self.not_supported.truncate(Self::NOT_SUPPORTED_MAX_SZ); + } + } + ret + } + + #[inline(always)] + fn has_codepoint(&mut self, font_codepoints: &[u32], codepoint: u32) -> bool { + match self.supported.binary_search(&codepoint) { + Ok(_) => true, + Err(supported_insert_pos) => match self.not_supported.binary_search(&codepoint) { + Ok(_) => false, + Err(not_supported_insert_pos) => self.unknown_has_codepoint( + font_codepoints, + codepoint, + supported_insert_pos, + not_supported_insert_pos, + ), + }, + } + } +} + /// Access to the system fonts. pub struct FontSystem { /// The locale of the system. @@ -27,6 +86,9 @@ pub struct FontSystem { /// Cache for loaded fonts from the database. font_cache: HashMap>>, + /// Cache for font codepoint support info + font_codepoint_support_info_cache: HashMap, + /// Cache for font matches. font_matches_cache: HashMap>>, @@ -84,6 +146,7 @@ impl FontSystem { db, 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(), @@ -139,6 +202,24 @@ impl FontSystem { .clone() } + #[inline(always)] + pub fn get_font_supported_codepoints_in_word( + &mut self, + id: fontdb::ID, + word: &str, + ) -> Option { + self.get_font(id).map(|font| { + let code_points = font.unicode_codepoints(); + let cache = self + .font_codepoint_support_info_cache + .entry(id) + .or_insert_with(FontCachedCodepointSupportInfo::new); + word.chars() + .filter(|ch| cache.has_codepoint(code_points, u32::from(*ch))) + .count() + }) + } + pub fn get_font_matches(&mut self, attrs: Attrs<'_>) -> Arc> { // Clear the cache first if it reached the size limit if self.font_matches_cache.len() >= Self::FONT_MATCHES_CACHE_SIZE_LIMIT {