mirror of
https://github.com/wez/wezterm.git
synced 2024-12-27 15:37:29 +03:00
195 lines
6.1 KiB
Rust
195 lines
6.1 KiB
Rust
//! A font-database to keep track of fonts that we've located
|
|
|
|
use crate::ftwrap::Library;
|
|
use crate::locator::FontDataSource;
|
|
use crate::parser::{load_built_in_fonts, parse_and_collect_font_info, ParsedFont};
|
|
use anyhow::Context;
|
|
use config::{Config, FontAttributes};
|
|
use rangeset::RangeSet;
|
|
use std::cmp::Ordering;
|
|
use std::collections::{HashMap, HashSet};
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
struct Entry {
|
|
parsed: ParsedFont,
|
|
coverage: Mutex<Option<RangeSet<u32>>>,
|
|
}
|
|
|
|
impl Entry {
|
|
/// Parses out the underlying TTF data and produces a RangeSet holding
|
|
/// the set of codepoints for which the font has coverage.
|
|
fn compute_coverage(&self) -> anyhow::Result<RangeSet<u32>> {
|
|
let lib = Library::new()?;
|
|
let face = lib
|
|
.face_from_locator(&self.parsed.handle)
|
|
.with_context(|| format!("freetype parsing {:?}", self.parsed))?;
|
|
|
|
Ok(face.compute_coverage())
|
|
}
|
|
|
|
/// Computes the intersection of the wanted set of codepoints with
|
|
/// the set of codepoints covered by this font entry.
|
|
/// Computes the codepoint coverage for this font entry if we haven't
|
|
/// already done so.
|
|
fn coverage_intersection(&self, wanted: &RangeSet<u32>) -> anyhow::Result<RangeSet<u32>> {
|
|
let mut coverage = self.coverage.lock().unwrap();
|
|
if coverage.is_none() {
|
|
let t = std::time::Instant::now();
|
|
coverage.replace(self.compute_coverage().context("compute_coverage")?);
|
|
let elapsed = t.elapsed();
|
|
metrics::histogram!("font.compute.codepoint.coverage", elapsed);
|
|
log::debug!(
|
|
"{} codepoint coverage computed in {:?}",
|
|
self.parsed.names().full_name,
|
|
elapsed
|
|
);
|
|
}
|
|
|
|
Ok(wanted.intersection(coverage.as_ref().unwrap()))
|
|
}
|
|
}
|
|
|
|
pub struct FontDatabase {
|
|
by_full_name: HashMap<String, Arc<Entry>>,
|
|
}
|
|
|
|
impl FontDatabase {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
by_full_name: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
fn load_font_info(&mut self, font_info: Vec<ParsedFont>) {
|
|
for parsed in font_info {
|
|
let entry = Arc::new(Entry {
|
|
parsed,
|
|
coverage: Mutex::new(None),
|
|
});
|
|
|
|
self.by_full_name
|
|
.entry(entry.parsed.names().full_name.clone())
|
|
.or_insert(entry);
|
|
}
|
|
}
|
|
|
|
/// Build up the database from the fonts found in the configured font dirs
|
|
/// and from the built-in selection of fonts
|
|
pub fn with_font_dirs(config: &Config) -> anyhow::Result<Self> {
|
|
let mut font_info = vec![];
|
|
for path in &config.font_dirs {
|
|
for entry in walkdir::WalkDir::new(path).into_iter() {
|
|
let entry = match entry {
|
|
Ok(entry) => entry,
|
|
Err(_) => continue,
|
|
};
|
|
|
|
let source = FontDataSource::OnDisk(entry.path().to_path_buf());
|
|
parse_and_collect_font_info(&source, &mut font_info)
|
|
.map_err(|err| {
|
|
log::trace!("failed to read {:?}: {:#}", source, err);
|
|
err
|
|
})
|
|
.ok();
|
|
}
|
|
}
|
|
|
|
let mut db = Self::new();
|
|
db.load_font_info(font_info);
|
|
db.print_available();
|
|
Ok(db)
|
|
}
|
|
|
|
pub fn print_available(&self) {
|
|
let mut names = self.by_full_name.keys().collect::<Vec<_>>();
|
|
names.sort();
|
|
for name in names {
|
|
log::debug!("available font: wezterm.font(\"{}\") ", name);
|
|
}
|
|
}
|
|
|
|
pub fn with_built_in() -> anyhow::Result<Self> {
|
|
let mut font_info = vec![];
|
|
load_built_in_fonts(&mut font_info)?;
|
|
let mut db = Self::new();
|
|
db.load_font_info(font_info);
|
|
db.print_available();
|
|
Ok(db)
|
|
}
|
|
|
|
pub fn resolve_multiple(
|
|
&self,
|
|
fonts: &[FontAttributes],
|
|
handles: &mut Vec<ParsedFont>,
|
|
loaded: &mut HashSet<FontAttributes>,
|
|
) {
|
|
for attr in fonts {
|
|
if loaded.contains(attr) {
|
|
continue;
|
|
}
|
|
if let Some(handle) = self.resolve(attr) {
|
|
handles.push(handle.clone());
|
|
loaded.insert(attr.clone());
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Equivalent to FontLocator::locate_fallback_for_codepoints
|
|
pub fn locate_fallback_for_codepoints(
|
|
&self,
|
|
codepoints: &[char],
|
|
) -> anyhow::Result<Vec<ParsedFont>> {
|
|
let mut wanted_range = RangeSet::new();
|
|
for &c in codepoints {
|
|
wanted_range.add(c as u32);
|
|
}
|
|
|
|
let mut matches = vec![];
|
|
|
|
for entry in self.by_full_name.values() {
|
|
let covered = entry
|
|
.coverage_intersection(&wanted_range)
|
|
.with_context(|| format!("coverage_interaction for {:?}", entry.parsed))?;
|
|
let len = covered.len();
|
|
if len > 0 {
|
|
matches.push((len, entry.parsed.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())
|
|
}
|
|
|
|
pub fn resolve(&self, font_attr: &FontAttributes) -> Option<&ParsedFont> {
|
|
let candidates: Vec<&ParsedFont> = self
|
|
.by_full_name
|
|
.values()
|
|
.filter_map(|entry| {
|
|
if entry.parsed.matches_name(font_attr) {
|
|
Some(&entry.parsed)
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
if let Some(idx) = ParsedFont::best_matching_index(font_attr, &candidates) {
|
|
return candidates.get(idx).map(|&p| p);
|
|
}
|
|
|
|
None
|
|
}
|
|
}
|