1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-22 21:01:36 +03:00

fonts: ls-fonts now handles system fallback lists, adds Menlo on macos

It appears as though Menlo is the only font on macos to contain the
heavy ballot cross symbol, which is commonly used on macos (eg: in
`brew` output).

Our fallback list, despite starting with Menlo, didn't include menlo
itself in the candidates.

Furthermore, `ls-fonts` wouldn never see the result of the system
fallback resolution because it didn't know to try again, and was
using the list of handles from before the fallback.

This commit resolves all of these concerns.

refs: #849
This commit is contained in:
Wez Furlong 2021-11-08 22:49:52 -07:00
parent 7c5d53c689
commit 1ec7e10c19
6 changed files with 96 additions and 11 deletions

View File

@ -69,6 +69,8 @@ As features stabilize some brief notes about them will accumulate here.
* the whole tab was closed when only the zoomed pane exited. [#1235](https://github.com/wez/wezterm/issues/1235)
* multiplexer: wrong `WEZTERM_UNIX_SOCKET` environment passed to children when using unix domain sockets and `connect_automatically=true` [#1222](https://github.com/wez/wezterm/issues/1222)
* multiplexer: spawning remote tabs didn't correctly record local tab mapping, resulting in phantom additional tabs showing in the client. [#1222](https://github.com/wez/wezterm/issues/1222)
* `wezterm ls-fonts --text "✘"` didn't account for the system fallback list. [#849](https://github.com/wez/wezterm/issues/849)
* macOS: The `Menlo` font is now implicitly included in the system fallback list, as it is the only font that contains U+2718 ✘
#### Updated and Improved

View File

@ -68,7 +68,7 @@ impl LoadedFont {
}
}
if loaded {
log::trace!("revised fallback: {:?}", handles);
log::trace!("revised fallback: {:#?}", handles);
}
}
if loaded {
@ -80,13 +80,57 @@ impl LoadedFont {
Ok(loaded)
}
pub fn shape<F: FnOnce() + Send + Sync + 'static, FS: FnOnce(&mut Vec<char>)>(
pub fn blocking_shape(
&self,
text: &str,
presentation: Option<Presentation>,
) -> anyhow::Result<Vec<GlyphInfo>> {
loop {
let (tx, rx) = channel();
let (async_resolve, res) = match self.shape_impl(
text,
move || {
let _ = tx.send(());
},
|_| {},
presentation,
) {
Ok(tuple) => tuple,
Err(err) if err.downcast_ref::<ClearShapeCache>().is_some() => {
continue;
}
Err(err) => return Err(err),
};
if !async_resolve {
return Ok(res);
}
if rx.recv().is_err() {
return Ok(res);
}
}
}
pub fn shape<F: FnOnce() + Send + 'static, FS: FnOnce(&mut Vec<char>)>(
&self,
text: &str,
completion: F,
filter_out_synthetic: FS,
presentation: Option<Presentation>,
) -> anyhow::Result<Vec<GlyphInfo>> {
let (_async_resolve, res) =
self.shape_impl(text, completion, filter_out_synthetic, presentation)?;
Ok(res)
}
fn shape_impl<F: FnOnce() + Send + 'static, FS: FnOnce(&mut Vec<char>)>(
&self,
text: &str,
completion: F,
filter_out_synthetic: FS,
presentation: Option<Presentation>,
) -> anyhow::Result<(bool, Vec<GlyphInfo>)> {
let mut no_glyphs = vec![];
{
@ -113,6 +157,8 @@ impl LoadedFont {
no_glyphs.retain(|&c| c != '\u{FE0F}' && c != '\u{FE0E}');
filter_out_synthetic(&mut no_glyphs);
let mut async_resolve = false;
if !no_glyphs.is_empty() {
if let Some(font_config) = self.font_config.upgrade() {
font_config.schedule_fallback_resolve(
@ -120,10 +166,11 @@ impl LoadedFont {
&self.pending_fallback,
completion,
);
async_resolve = true;
}
}
result
result.map(|r| (async_resolve, r))
}
pub fn metrics_for_idx(&self, font_idx: usize) -> anyhow::Result<FontMetrics> {
@ -176,7 +223,7 @@ impl LoadedFont {
struct FallbackResolveInfo {
no_glyphs: Vec<char>,
pending: Arc<Mutex<Vec<ParsedFont>>>,
completion: Box<dyn FnOnce() + Send + Sync>,
completion: Box<dyn FnOnce() + Send>,
font_dirs: Arc<FontDatabase>,
built_in: Arc<FontDatabase>,
locator: Arc<dyn FontLocator + Send + Sync>,
@ -368,7 +415,7 @@ impl FontConfigInner {
Ok(())
}
fn schedule_fallback_resolve<F: FnOnce() + Send + Sync + 'static>(
fn schedule_fallback_resolve<F: FnOnce() + Send + 'static>(
&self,
mut no_glyphs: Vec<char>,
pending: &Arc<Mutex<Vec<ParsedFont>>>,

View File

@ -148,14 +148,18 @@ fn build_fallback_list() -> Vec<ParsedFont> {
}
fn build_fallback_list_impl() -> anyhow::Result<Vec<ParsedFont>> {
let font =
let menlo =
new_from_name("Menlo", 0.0).map_err(|_| anyhow::anyhow!("failed to get Menlo font"))?;
let lang = "en"
.parse::<CFString>()
.map_err(|_| anyhow::anyhow!("failed to parse lang name en as CFString"))?;
let langs = CFArray::from_CFTypes(&[lang]);
let cascade = cascade_list_for_languages(&font, &langs);
let cascade = cascade_list_for_languages(&menlo, &langs);
let mut fonts = vec![];
// Explicitly include Menlo itself, as it appears to be the only
// font on macOS that contains U+2718.
// <https://github.com/wez/wezterm/issues/849>
fonts.append(&mut handles_from_descriptor(&menlo.copy_descriptor()));
for descriptor in &cascade {
fonts.append(&mut handles_from_descriptor(&descriptor));
}
@ -183,6 +187,19 @@ fn build_fallback_list_impl() -> anyhow::Result<Vec<ParsedFont>> {
f.weight() == FontWeight::REGULAR && f.stretch() == FontStretch::Normal && !f.italic()
});
let mut seen = HashSet::new();
let fonts: Vec<ParsedFont> = fonts
.into_iter()
.filter_map(|f| {
if seen.contains(&f.handle) {
None
} else {
seen.insert(f.handle.clone());
Some(f)
}
})
.collect();
// Pre-compute coverage
let empty = RangeSet::new();
for font in &fonts {

View File

@ -12,7 +12,7 @@ pub mod core_text;
pub mod font_config;
pub mod gdi;
#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub enum FontOrigin {
FontConfig,
FontConfigMatch(String),
@ -31,7 +31,7 @@ impl Display for FontOrigin {
}
}
#[derive(Clone)]
#[derive(Clone, Hash)]
pub enum FontDataSource {
OnDisk(PathBuf),
BuiltIn {
@ -125,6 +125,15 @@ pub struct FontDataHandle {
pub coverage: Option<rangeset::RangeSet<u32>>,
}
impl std::hash::Hash for FontDataHandle {
fn hash<H>(&self, hasher: &mut H)
where
H: std::hash::Hasher,
{
(&self.source, self.index, self.variation, &self.origin).hash(hasher)
}
}
impl PartialOrd for FontDataHandle {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
(&self.source, self.index, self.variation, &self.origin).partial_cmp(&(

View File

@ -455,11 +455,14 @@ pub fn run_ls_fonts(config: config::ConfigHandle, cmd: &LsFontsCommand) -> anyho
for cluster in cell_clusters {
let style = font_config.match_style(&config, &cluster.attrs);
let font = font_config.resolve_font(style)?;
let handles = font.clone_handles();
let infos = font
.shape(&cluster.text, || {}, |_| {}, Some(cluster.presentation))
.blocking_shape(&cluster.text, Some(cluster.presentation))
.unwrap();
// We must grab the handles after shaping, so that we get the
// revised list that includes system fallbacks!
let handles = font.clone_handles();
for info in infos {
let parsed = &handles[info.font_idx];
let escaped = format!("{}", cluster.text.escape_unicode());

View File

@ -94,6 +94,13 @@ struct SendId(id);
unsafe impl Send for SendId {}
pub fn show_notif(toast: ToastNotification) -> Result<(), Box<dyn std::error::Error>> {
if Protocol::get("NSUserNotificationCenterDelegate").is_none() {
// Just pretend that we did it.
// This case occurs eg: when we're running `wezterm ls-fonts` and we haven't
// fully set ourselves up as a gui app yet
return Ok(());
}
unsafe {
let center: id = msg_send![
class!(NSUserNotificationCenter),