1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-28 07:55:03 +03:00

fonts: macos: improve fallback glyph resolution performance

Previously, we would add a list of ~100 or so fallback fonts to
the shaper's fallback list.

In pathological cases where a wide range of glyphs that have no
corresponding font are repeatedly emitted to the output, we'd
keep loading and unloading that large list of fallbacks in the
hope of finding a match.

Since that code was written, we're now able to compute the
codepoint coverage for ourselves, so teach the core text locator
how to reduce the the list of fallback fonts to just those that
contain the missing glyphs.

Furthermore, we restrict that list to just the normal/regular
weight/stretch/style fonts.

refs: https://github.com/wez/wezterm/issues/671
This commit is contained in:
Wez Furlong 2021-04-10 21:10:22 -07:00
parent 9b7ae8fb23
commit 22c4407ae9
2 changed files with 54 additions and 5 deletions

View File

@ -10,6 +10,8 @@ use core_foundation::number::CFNumber;
use core_foundation::string::CFString;
use core_text::font::*;
use core_text::font_descriptor::*;
use rangeset::RangeSet;
use std::cmp::Ordering;
use std::collections::HashSet;
lazy_static::lazy_static! {
@ -103,11 +105,34 @@ impl FontLocator for CoreTextFontLocator {
fn locate_fallback_for_codepoints(
&self,
_codepoints: &[char],
codepoints: &[char],
) -> anyhow::Result<Vec<ParsedFont>> {
// We don't have an API to resolve a font for the codepoints, so instead we
// just get the system fallback list and add the whole thing to the fallback.
Ok(FALLBACK.clone())
let mut wanted = RangeSet::new();
for &c in codepoints {
wanted.add(c as u32);
}
let mut matches = vec![];
for font in FALLBACK.iter() {
if let Ok(cov) = font.coverage_intersection(&wanted) {
if !cov.is_empty() {
matches.push((cov.len(), font.clone()));
}
}
}
// Add the handles in order of descending coverage; the idea being
// that if a font has a large coverage then it is probably a better
// candidate and more likely to result in other glyphs matching
// in future shaping calls.
matches.sort_by(|(a_len, a), (b_len, b)| {
let primary = a_len.cmp(&b_len).reverse();
if primary == Ordering::Equal {
a.cmp(b)
} else {
primary
}
});
Ok(matches.into_iter().map(|(_len, handle)| handle).collect())
}
}
@ -147,5 +172,18 @@ fn build_fallback_list_impl() -> anyhow::Result<Vec<ParsedFont>> {
fonts.append(&mut handles_from_descriptor(&descriptor));
}
// Constrain to default weight/stretch/style
fonts.retain(|f| {
f.weight() == FontWeight::Regular && f.stretch() == FontStretch::Normal && !f.italic()
});
// Pre-compute coverage
let empty = RangeSet::new();
for font in &fonts {
if let Err(err) = font.coverage_intersection(&empty) {
log::error!("Error computing coverage for {:?}: {:#}", font, err);
}
}
Ok(fonts)
}

View File

@ -13,7 +13,6 @@ pub enum MaybeShaped {
}
/// Represents a parsed font
#[derive(Debug)]
pub struct ParsedFont {
names: Names,
weight: FontWeight,
@ -23,6 +22,18 @@ pub struct ParsedFont {
coverage: Mutex<RangeSet<u32>>,
}
impl std::fmt::Debug for ParsedFont {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
fmt.debug_struct("ParsedFont")
.field("names", &self.names)
.field("weight", &self.weight)
.field("stretch", &self.stretch)
.field("italic", &self.italic)
.field("handle", &self.handle)
.finish()
}
}
impl Clone for ParsedFont {
fn clone(&self) -> Self {
Self {