mirror of
https://github.com/wez/wezterm.git
synced 2024-11-27 02:25:28 +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:
parent
9b7ae8fb23
commit
22c4407ae9
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user