Skip to content

Commit

Permalink
Only try monospace fonts that support at least one requested script
Browse files Browse the repository at this point in the history
 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 <[email protected]>
  • Loading branch information
MoSal committed Mar 10, 2024
1 parent 941dddd commit af605f6
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 5 deletions.
19 changes: 18 additions & 1 deletion src/font/fallback/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use alloc::collections::BTreeSet;
use alloc::sync::Arc;
use alloc::vec::Vec;
use fontdb::Family;
use unicode_script::Script;

Expand Down Expand Up @@ -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
Expand All @@ -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);
Expand Down
19 changes: 17 additions & 2 deletions src/font/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub struct Font {
data: Arc<dyn AsRef<[u8]> + Send + Sync>,
id: fontdb::ID,
monospace_em_width: Option<f32>,
scripts: Vec<[u8; 4]>,
unicode_codepoints: Vec<u32>,
}

Expand All @@ -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
}
Expand Down Expand Up @@ -77,7 +82,7 @@ impl Font {
pub fn new(db: &fontdb::Database, id: fontdb::ID) -> Option<Self> {
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
Expand All @@ -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()
Expand All @@ -111,7 +125,7 @@ impl Font {

unicode_codepoints.shrink_to_fit();

Some((monospace_em_width, unicode_codepoints))
Some((monospace_em_width, scripts, unicode_codepoints))
})?
}?;

Expand All @@ -129,6 +143,7 @@ impl Font {
Some(Self {
id: info.id,
monospace_em_width,
scripts,
unicode_codepoints,
#[cfg(feature = "swash")]
swash: {
Expand Down
37 changes: 35 additions & 2 deletions src/font/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ pub struct FontSystem {
/// Sorted unique ID's of all Monospace fonts in DB
monospace_font_ids: Vec<fontdb::ID>,

/// 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<fontdb::ID>>,

/// Cache for font codepoint support info
font_codepoint_support_info_cache: HashMap<fontdb::ID, FontCachedCodepointSupportInfo>,

Expand Down Expand Up @@ -153,17 +158,32 @@ impl FontSystem {
.collect::<Vec<_>>();
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.
Expand Down Expand Up @@ -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<Item = [u8; 4]>,
) -> Vec<fontdb::ID> {
let mut ret = scripts
.filter_map(|script| self.per_script_monospace_font_ids.get(&script))
.flat_map(|ids| ids.iter().copied())
.collect::<Vec<_>>();
ret.sort();
ret.dedup();
ret
}

#[inline(always)]
pub fn get_font_supported_codepoints_in_word(
&mut self,
Expand Down

0 comments on commit af605f6

Please sign in to comment.