Cache codepoint support info for monospace fonts

For the simplest case of " " words, a quick binary search in
 `supported` vec will suffice, instead of using `slice::contains()`
 for all monospace fonts, where some of them may support thousands of
 codepoints.

Signed-off-by: Mohammad AlSaleh <CE.Mohammad.AlSaleh@gmail.com>
This commit is contained in:
Mohammad AlSaleh 2024-02-25 22:39:27 +03:00 committed by Jeremy Soller
parent a53a0b3a8c
commit 3e02ae1ea6
2 changed files with 87 additions and 9 deletions

View file

@ -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),

View file

@ -16,6 +16,65 @@ pub struct FontMatchKey {
pub(crate) id: fontdb::ID,
}
struct FontCachedCodepointSupportInfo {
supported: Vec<u32>,
not_supported: Vec<u32>,
}
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<fontdb::ID, Option<Arc<Font>>>,
/// Cache for font codepoint support info
font_codepoint_support_info_cache: HashMap<fontdb::ID, FontCachedCodepointSupportInfo>,
/// Cache for font matches.
font_matches_cache: HashMap<FontMatchAttrs, Arc<Vec<FontMatchKey>>>,
@ -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<usize> {
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<Vec<FontMatchKey>> {
// Clear the cache first if it reached the size limit
if self.font_matches_cache.len() >= Self::FONT_MATCHES_CACHE_SIZE_LIMIT {