diff --git a/wezterm-font/src/lib.rs b/wezterm-font/src/lib.rs index 35ec972ed..a7e2fd4da 100644 --- a/wezterm-font/src/lib.rs +++ b/wezterm-font/src/lib.rs @@ -11,6 +11,7 @@ use rangeset::RangeSet; use std::cell::RefCell; use std::collections::{HashMap, HashSet}; use std::rc::{Rc, Weak}; +use std::sync::mpsc::{channel, Sender}; use std::sync::{Arc, Mutex}; use std::time::Duration; use termwiz::cell::Presentation; @@ -172,6 +173,150 @@ impl LoadedFont { } } +struct FallbackResolveInfo { + no_glyphs: Vec, + pending: Arc>>, + completion: Box, + font_dirs: Arc, + built_in: Arc, + locator: Arc, + config: ConfigHandle, +} + +impl FallbackResolveInfo { + fn process(self) { + let fallback_str = self.no_glyphs.iter().collect::(); + let mut extra_handles = vec![]; + + log::trace!( + "Looking for {} in fallback fonts", + fallback_str.escape_unicode() + ); + + match self.locator.locate_fallback_for_codepoints(&self.no_glyphs) { + Ok(ref mut handles) => extra_handles.append(handles), + Err(err) => log::error!( + "Error: {:#} while resolving fallback for {} from font-locator", + err, + fallback_str.escape_unicode() + ), + } + + if self.config.search_font_dirs_for_fallback { + match self + .font_dirs + .locate_fallback_for_codepoints(&self.no_glyphs) + { + Ok(ref mut handles) => extra_handles.append(handles), + Err(err) => log::error!( + "Error: {:#} while resolving fallback for {} from font_dirs", + err, + fallback_str.escape_unicode() + ), + } + } + + match self + .built_in + .locate_fallback_for_codepoints(&self.no_glyphs) + { + Ok(ref mut handles) => extra_handles.append(handles), + Err(err) => log::error!( + "Error: {:#} while resolving fallback for {} for built-in fonts", + err, + fallback_str.escape_unicode() + ), + } + + let mut wanted = RangeSet::new(); + for c in self.no_glyphs { + wanted.add(c as u32); + } + log::trace!( + "Fallback fonts that match {} before sorting are: {:#?}", + fallback_str.escape_unicode(), + extra_handles + ); + + if wanted.len() > 1 && self.config.sort_fallback_fonts_by_coverage { + // Sort by ascending coverage + extra_handles.sort_by_cached_key(|p| { + p.coverage_intersection(&wanted) + .map(|r| r.len()) + .unwrap_or(0) + }); + // Re-arrange to descending coverage + extra_handles.reverse(); + log::trace!( + "Fallback fonts that match {} after sorting are: {:#?}", + fallback_str.escape_unicode(), + extra_handles + ); + } + + // iteratively reduce to just the fonts that we need + extra_handles.retain(|p| match p.coverage_intersection(&wanted) { + Ok(cov) if cov.is_empty() => false, + Ok(cov) => { + // Remove the matches from the set, so that we avoid + // picking up multiple fonts for the same glyphs + wanted = wanted.difference(&cov); + true + } + Err(_) => false, + }); + + if !extra_handles.is_empty() { + let mut pending = self.pending.lock().unwrap(); + pending.append(&mut extra_handles); + (self.completion)(); + } + + if !wanted.is_empty() { + // There were some glyphs we couldn't resolve! + let fallback_str = wanted + .iter_values() + .map(|c| std::char::from_u32(c).unwrap_or(' ')) + .collect::(); + + if self.config.warn_about_missing_glyphs { + let url = "https://wezfurlong.org/wezterm/config/fonts.html"; + log::warn!( + "No fonts contain glyphs for these codepoints: {}.\n\ + Placeholder 'Last Resort' glyphs are being displayed instead.\n\ + You may wish to install additional fonts, or adjust your\n\ + configuration so that it can find them.\n\ + {} has more information about configuring fonts.\n\ + Set warn_about_missing_glyphs=false to suppress this message.", + fallback_str.escape_unicode(), + url, + ); + + ToastNotification { + title: "Font problem".to_string(), + message: format!( + "No fonts contain glyphs for these codepoints: {}.\n\ + Placeholder glyphs are being displayed instead.\n\ + You may wish to install additional fonts, or adjust\n\ + your configuration so that it can find them.\n\ + Set warn_about_missing_glyphs=false to suppress this\n\ + message.", + fallback_str.escape_unicode() + ), + url: Some(url.to_string()), + timeout: Some(Duration::from_secs(15)), + } + .show(); + } else { + log::debug!( + "No fonts contain glyphs for these codepoints: {}", + fallback_str.escape_unicode() + ); + } + } + } +} + struct FontConfigInner { fonts: RefCell>>, metrics: RefCell>, @@ -183,6 +328,7 @@ struct FontConfigInner { built_in: RefCell>, no_glyphs: RefCell>, title_font: RefCell>>, + fallback_channel: RefCell>>, } /// Matches and loads fonts for a given input style @@ -206,6 +352,7 @@ impl FontConfigInner { font_dirs: RefCell::new(Arc::new(FontDatabase::with_font_dirs(&config)?)), built_in: RefCell::new(Arc::new(FontDatabase::with_built_in()?)), no_glyphs: RefCell::new(HashSet::new()), + fallback_channel: RefCell::new(None), }) } @@ -236,136 +383,33 @@ impl FontConfigInner { return; } - let font_dirs = Arc::clone(&*self.font_dirs.borrow()); - let built_in = Arc::clone(&*self.built_in.borrow()); - let locator = Arc::clone(&self.locator); - let pending = Arc::clone(pending); - let config = self.config.borrow().clone(); - std::thread::spawn(move || { - let fallback_str = no_glyphs.iter().collect::(); - let mut extra_handles = vec![]; + let info = FallbackResolveInfo { + completion: Box::new(completion), + no_glyphs, + pending: Arc::clone(pending), + font_dirs: Arc::clone(&*self.font_dirs.borrow()), + built_in: Arc::clone(&*self.built_in.borrow()), + locator: Arc::clone(&self.locator), + config: self.config.borrow().clone(), + }; - log::trace!( - "Looking for {} in fallback fonts", - fallback_str.escape_unicode() - ); + let mut fallback = self.fallback_channel.borrow_mut(); - match locator.locate_fallback_for_codepoints(&no_glyphs) { - Ok(ref mut handles) => extra_handles.append(handles), - Err(err) => log::error!( - "Error: {:#} while resolving fallback for {} from font-locator", - err, - fallback_str.escape_unicode() - ), - } + if fallback.is_none() { + let (tx, rx) = channel::(); - if config.search_font_dirs_for_fallback { - match font_dirs.locate_fallback_for_codepoints(&no_glyphs) { - Ok(ref mut handles) => extra_handles.append(handles), - Err(err) => log::error!( - "Error: {:#} while resolving fallback for {} from font_dirs", - err, - fallback_str.escape_unicode() - ), + std::thread::spawn(move || { + for info in rx { + info.process(); } - } - - match built_in.locate_fallback_for_codepoints(&no_glyphs) { - Ok(ref mut handles) => extra_handles.append(handles), - Err(err) => log::error!( - "Error: {:#} while resolving fallback for {} for built-in fonts", - err, - fallback_str.escape_unicode() - ), - } - - let mut wanted = RangeSet::new(); - for c in no_glyphs { - wanted.add(c as u32); - } - log::trace!( - "Fallback fonts that match {} before sorting are: {:#?}", - fallback_str.escape_unicode(), - extra_handles - ); - - if wanted.len() > 1 && config.sort_fallback_fonts_by_coverage { - // Sort by ascending coverage - extra_handles.sort_by_cached_key(|p| { - p.coverage_intersection(&wanted) - .map(|r| r.len()) - .unwrap_or(0) - }); - // Re-arrange to descending coverage - extra_handles.reverse(); - log::trace!( - "Fallback fonts that match {} after sorting are: {:#?}", - fallback_str.escape_unicode(), - extra_handles - ); - } - - // iteratively reduce to just the fonts that we need - extra_handles.retain(|p| match p.coverage_intersection(&wanted) { - Ok(cov) if cov.is_empty() => false, - Ok(cov) => { - // Remove the matches from the set, so that we avoid - // picking up multiple fonts for the same glyphs - wanted = wanted.difference(&cov); - true - } - Err(_) => false, }); - if !extra_handles.is_empty() { - let mut pending = pending.lock().unwrap(); - pending.append(&mut extra_handles); - completion(); - } + fallback.replace(tx); + } - if !wanted.is_empty() { - // There were some glyphs we couldn't resolve! - let fallback_str = wanted - .iter_values() - .map(|c| std::char::from_u32(c).unwrap_or(' ')) - .collect::(); - - if config.warn_about_missing_glyphs { - let url = "https://wezfurlong.org/wezterm/config/fonts.html"; - log::warn!( - "No fonts contain glyphs for these codepoints: {}.\n\ - Placeholder 'Last Resort' glyphs are being displayed instead.\n\ - You may wish to install additional fonts, or adjust your\n\ - configuration so that it can find them.\n\ - {} has more information about configuring fonts.\n\ - Set warn_about_missing_glyphs=false to suppress this message.", - fallback_str.escape_unicode(), - url, - ); - - ToastNotification { - title: "Font problem".to_string(), - message: format!( - "No fonts contain glyphs for these codepoints: {}.\n\ - Placeholder glyphs are being displayed instead.\n\ - You may wish to install additional fonts, or adjust\n\ - your configuration so that it can find them.\n\ - Set warn_about_missing_glyphs=false to suppress this\n\ - message.", - fallback_str.escape_unicode() - ), - url: Some(url.to_string()), - timeout: Some(Duration::from_secs(15)), - } - .show(); - } else { - log::debug!( - "No fonts contain glyphs for these codepoints: {}", - fallback_str.escape_unicode() - ); - } - } - }); + if let Err(err) = fallback.as_mut().expect("channel to exist").send(info) { + log::error!("Failed to schedule font fallback resolve: {:#}", err); + } } fn title_font(&self, myself: &Rc) -> anyhow::Result> {