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:
parent
7c5d53c689
commit
1ec7e10c19
@ -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)
|
* 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: 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)
|
* 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
|
#### Updated and Improved
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ impl LoadedFont {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if loaded {
|
if loaded {
|
||||||
log::trace!("revised fallback: {:?}", handles);
|
log::trace!("revised fallback: {:#?}", handles);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if loaded {
|
if loaded {
|
||||||
@ -80,13 +80,57 @@ impl LoadedFont {
|
|||||||
Ok(loaded)
|
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,
|
&self,
|
||||||
text: &str,
|
text: &str,
|
||||||
completion: F,
|
completion: F,
|
||||||
filter_out_synthetic: FS,
|
filter_out_synthetic: FS,
|
||||||
presentation: Option<Presentation>,
|
presentation: Option<Presentation>,
|
||||||
) -> anyhow::Result<Vec<GlyphInfo>> {
|
) -> 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![];
|
let mut no_glyphs = vec![];
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -113,6 +157,8 @@ impl LoadedFont {
|
|||||||
no_glyphs.retain(|&c| c != '\u{FE0F}' && c != '\u{FE0E}');
|
no_glyphs.retain(|&c| c != '\u{FE0F}' && c != '\u{FE0E}');
|
||||||
filter_out_synthetic(&mut no_glyphs);
|
filter_out_synthetic(&mut no_glyphs);
|
||||||
|
|
||||||
|
let mut async_resolve = false;
|
||||||
|
|
||||||
if !no_glyphs.is_empty() {
|
if !no_glyphs.is_empty() {
|
||||||
if let Some(font_config) = self.font_config.upgrade() {
|
if let Some(font_config) = self.font_config.upgrade() {
|
||||||
font_config.schedule_fallback_resolve(
|
font_config.schedule_fallback_resolve(
|
||||||
@ -120,10 +166,11 @@ impl LoadedFont {
|
|||||||
&self.pending_fallback,
|
&self.pending_fallback,
|
||||||
completion,
|
completion,
|
||||||
);
|
);
|
||||||
|
async_resolve = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
result.map(|r| (async_resolve, r))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn metrics_for_idx(&self, font_idx: usize) -> anyhow::Result<FontMetrics> {
|
pub fn metrics_for_idx(&self, font_idx: usize) -> anyhow::Result<FontMetrics> {
|
||||||
@ -176,7 +223,7 @@ impl LoadedFont {
|
|||||||
struct FallbackResolveInfo {
|
struct FallbackResolveInfo {
|
||||||
no_glyphs: Vec<char>,
|
no_glyphs: Vec<char>,
|
||||||
pending: Arc<Mutex<Vec<ParsedFont>>>,
|
pending: Arc<Mutex<Vec<ParsedFont>>>,
|
||||||
completion: Box<dyn FnOnce() + Send + Sync>,
|
completion: Box<dyn FnOnce() + Send>,
|
||||||
font_dirs: Arc<FontDatabase>,
|
font_dirs: Arc<FontDatabase>,
|
||||||
built_in: Arc<FontDatabase>,
|
built_in: Arc<FontDatabase>,
|
||||||
locator: Arc<dyn FontLocator + Send + Sync>,
|
locator: Arc<dyn FontLocator + Send + Sync>,
|
||||||
@ -368,7 +415,7 @@ impl FontConfigInner {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn schedule_fallback_resolve<F: FnOnce() + Send + Sync + 'static>(
|
fn schedule_fallback_resolve<F: FnOnce() + Send + 'static>(
|
||||||
&self,
|
&self,
|
||||||
mut no_glyphs: Vec<char>,
|
mut no_glyphs: Vec<char>,
|
||||||
pending: &Arc<Mutex<Vec<ParsedFont>>>,
|
pending: &Arc<Mutex<Vec<ParsedFont>>>,
|
||||||
|
@ -148,14 +148,18 @@ fn build_fallback_list() -> Vec<ParsedFont> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn build_fallback_list_impl() -> anyhow::Result<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"))?;
|
new_from_name("Menlo", 0.0).map_err(|_| anyhow::anyhow!("failed to get Menlo font"))?;
|
||||||
let lang = "en"
|
let lang = "en"
|
||||||
.parse::<CFString>()
|
.parse::<CFString>()
|
||||||
.map_err(|_| anyhow::anyhow!("failed to parse lang name en as CFString"))?;
|
.map_err(|_| anyhow::anyhow!("failed to parse lang name en as CFString"))?;
|
||||||
let langs = CFArray::from_CFTypes(&[lang]);
|
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![];
|
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 {
|
for descriptor in &cascade {
|
||||||
fonts.append(&mut handles_from_descriptor(&descriptor));
|
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()
|
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
|
// Pre-compute coverage
|
||||||
let empty = RangeSet::new();
|
let empty = RangeSet::new();
|
||||||
for font in &fonts {
|
for font in &fonts {
|
||||||
|
@ -12,7 +12,7 @@ pub mod core_text;
|
|||||||
pub mod font_config;
|
pub mod font_config;
|
||||||
pub mod gdi;
|
pub mod gdi;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
|
#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
|
||||||
pub enum FontOrigin {
|
pub enum FontOrigin {
|
||||||
FontConfig,
|
FontConfig,
|
||||||
FontConfigMatch(String),
|
FontConfigMatch(String),
|
||||||
@ -31,7 +31,7 @@ impl Display for FontOrigin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Hash)]
|
||||||
pub enum FontDataSource {
|
pub enum FontDataSource {
|
||||||
OnDisk(PathBuf),
|
OnDisk(PathBuf),
|
||||||
BuiltIn {
|
BuiltIn {
|
||||||
@ -125,6 +125,15 @@ pub struct FontDataHandle {
|
|||||||
pub coverage: Option<rangeset::RangeSet<u32>>,
|
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 {
|
impl PartialOrd for FontDataHandle {
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
(&self.source, self.index, self.variation, &self.origin).partial_cmp(&(
|
(&self.source, self.index, self.variation, &self.origin).partial_cmp(&(
|
||||||
|
@ -455,11 +455,14 @@ pub fn run_ls_fonts(config: config::ConfigHandle, cmd: &LsFontsCommand) -> anyho
|
|||||||
for cluster in cell_clusters {
|
for cluster in cell_clusters {
|
||||||
let style = font_config.match_style(&config, &cluster.attrs);
|
let style = font_config.match_style(&config, &cluster.attrs);
|
||||||
let font = font_config.resolve_font(style)?;
|
let font = font_config.resolve_font(style)?;
|
||||||
let handles = font.clone_handles();
|
|
||||||
let infos = font
|
let infos = font
|
||||||
.shape(&cluster.text, || {}, |_| {}, Some(cluster.presentation))
|
.blocking_shape(&cluster.text, Some(cluster.presentation))
|
||||||
.unwrap();
|
.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 {
|
for info in infos {
|
||||||
let parsed = &handles[info.font_idx];
|
let parsed = &handles[info.font_idx];
|
||||||
let escaped = format!("{}", cluster.text.escape_unicode());
|
let escaped = format!("{}", cluster.text.escape_unicode());
|
||||||
|
@ -94,6 +94,13 @@ struct SendId(id);
|
|||||||
unsafe impl Send for SendId {}
|
unsafe impl Send for SendId {}
|
||||||
|
|
||||||
pub fn show_notif(toast: ToastNotification) -> Result<(), Box<dyn std::error::Error>> {
|
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 {
|
unsafe {
|
||||||
let center: id = msg_send![
|
let center: id = msg_send![
|
||||||
class!(NSUserNotificationCenter),
|
class!(NSUserNotificationCenter),
|
||||||
|
Loading…
Reference in New Issue
Block a user