mirror of
https://github.com/wez/wezterm.git
synced 2025-01-08 23:17:36 +03:00
fonts/shaping: respect the Presentation selection for a cluster
This commit annotates fonts with a boolean that indicates whether we think it contains glyphs with emoji presentation, and then passes the cluster.presentation field down to the shaper. If the presentation doesn't match the current font in the fallback, then it will be skipped until we exhaust its options. `wezterm ls-fonts` also shows whether we think a font has emoji presentation. refs: #997
This commit is contained in:
parent
8d93222000
commit
0866e5d213
@ -76,6 +76,7 @@ As features stabilize some brief notes about them will accumulate here.
|
||||
* Improved: wezterm now uses the Dual Source Blending feature of OpenGL to manage subpixel anti-aliasing alpha blending, resulting in improved appearance particularly when using a transparent window over the top of something with a light background. [#932](https://github.com/wez/wezterm/issues/932)
|
||||
* Fixed: copying really long lines could falsely introduce line breaks on line wrap boundaries [#874](https://github.com/wez/wezterm/issues/874)
|
||||
* New: [wezterm.add_to_config_reload_watch_list](config/lua/wezterm/add_to_config_reload_watch_list.md) function to aid with automatically reloading the config when you've split your config across multiple files. Thanks to [@AusCyberman](https://github.com/AusCyberman)! [#989](https://github.com/wez/wezterm/pull/989)
|
||||
* Improved: wezterm now respects default emoji presentation and explicit emoji variation selectors (VS15 and VS16) so that glyphs that have both textual (usually monochrome, single cell width) and emoji (color, double width) presentations can be more faithfully rendered. [#997](https://github.com/wez/wezterm/issues/997)
|
||||
|
||||
### 20210502-154244-3f7122cb
|
||||
|
||||
|
9
test-data/emoji-presentation.sh
Executable file
9
test-data/emoji-presentation.sh
Executable file
@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo -e "\u270c Victory hand, text presentation by default"
|
||||
echo -e "\u270c\ufe0e Victory hand, explicit text presentation"
|
||||
echo -e "\u270c\ufe0f Victory hand, explicit emoji presentation"
|
||||
|
||||
echo -e "\u270a Raised fist, emoji presentation by default"
|
||||
echo -e "\u270a\ufe0e Raised fist, explicit text presentation"
|
||||
echo -e "\u270a\ufe0f Raised fist, explicit emoji presentation"
|
@ -13,6 +13,7 @@ use std::collections::{HashMap, HashSet};
|
||||
use std::rc::{Rc, Weak};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
use termwiz::cell::Presentation;
|
||||
use thiserror::Error;
|
||||
use wezterm_term::CellAttributes;
|
||||
use wezterm_toast_notification::ToastNotification;
|
||||
@ -83,6 +84,7 @@ impl LoadedFont {
|
||||
text: &str,
|
||||
completion: F,
|
||||
filter_out_synthetic: FS,
|
||||
presentation: Option<Presentation>,
|
||||
) -> anyhow::Result<Vec<GlyphInfo>> {
|
||||
let mut no_glyphs = vec![];
|
||||
|
||||
@ -99,10 +101,13 @@ impl LoadedFont {
|
||||
}
|
||||
}
|
||||
|
||||
let result = self
|
||||
.shaper
|
||||
.borrow()
|
||||
.shape(text, self.font_size, self.dpi, &mut no_glyphs);
|
||||
let result = self.shaper.borrow().shape(
|
||||
text,
|
||||
self.font_size,
|
||||
self.dpi,
|
||||
&mut no_glyphs,
|
||||
presentation,
|
||||
);
|
||||
|
||||
filter_out_synthetic(&mut no_glyphs);
|
||||
|
||||
|
@ -24,6 +24,7 @@ pub struct ParsedFont {
|
||||
pub synthesize_italic: bool,
|
||||
pub synthesize_bold: bool,
|
||||
pub synthesize_dim: bool,
|
||||
pub assume_emoji_presentation: bool,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for ParsedFont {
|
||||
@ -35,6 +36,10 @@ impl std::fmt::Debug for ParsedFont {
|
||||
.field("italic", &self.italic)
|
||||
.field("handle", &self.handle)
|
||||
.field("cap_height", &self.cap_height)
|
||||
.field("synthesize_italic", &self.synthesize_italic)
|
||||
.field("synthesize_bold", &self.synthesize_bold)
|
||||
.field("synthesize_dim", &self.synthesize_dim)
|
||||
.field("assume_emoji_presentation", &self.assume_emoji_presentation)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@ -49,6 +54,7 @@ impl Clone for ParsedFont {
|
||||
synthesize_italic: self.synthesize_italic,
|
||||
synthesize_bold: self.synthesize_bold,
|
||||
synthesize_dim: self.synthesize_dim,
|
||||
assume_emoji_presentation: self.assume_emoji_presentation,
|
||||
handle: self.handle.clone(),
|
||||
cap_height: self.cap_height.clone(),
|
||||
coverage: Mutex::new(self.coverage.lock().unwrap().clone()),
|
||||
@ -147,6 +153,9 @@ impl ParsedFont {
|
||||
} else if p.synthesize_dim {
|
||||
code.push_str(" -- Will synthesize dim\n");
|
||||
}
|
||||
if p.assume_emoji_presentation {
|
||||
code.push_str(" -- Assumed to have Emoji Presentation\n");
|
||||
}
|
||||
|
||||
if p.weight == FontWeight::REGULAR && p.stretch == FontStretch::Normal && !p.italic {
|
||||
code.push_str(&format!(" \"{}\",\n", p.names.family));
|
||||
@ -175,6 +184,10 @@ impl ParsedFont {
|
||||
let weight = FontWeight::from_opentype_weight(ot_weight);
|
||||
let stretch = FontStretch::from_opentype_stretch(width);
|
||||
let cap_height = face.cap_height();
|
||||
let has_color = unsafe {
|
||||
(((*face.face).face_flags as u32) & (crate::ftwrap::FT_FACE_FLAG_COLOR as u32)) != 0
|
||||
};
|
||||
let assume_emoji_presentation = has_color;
|
||||
|
||||
Ok(Self {
|
||||
names: Names::from_ft_face(&face),
|
||||
@ -184,6 +197,7 @@ impl ParsedFont {
|
||||
synthesize_italic: false,
|
||||
synthesize_bold: false,
|
||||
synthesize_dim: false,
|
||||
assume_emoji_presentation,
|
||||
handle,
|
||||
coverage: Mutex::new(RangeSet::new()),
|
||||
cap_height,
|
||||
|
@ -13,7 +13,7 @@ use allsorts::tables::{
|
||||
};
|
||||
use allsorts::tag;
|
||||
use anyhow::{anyhow, bail, Context};
|
||||
use termwiz::cell::unicode_column_width;
|
||||
use termwiz::cell::{unicode_column_width, Presentation};
|
||||
use tinyvec::*;
|
||||
use unicode_general_category::{get_general_category, GeneralCategory};
|
||||
|
||||
@ -570,6 +570,7 @@ impl FontShaper for AllsortsShaper {
|
||||
size: f64,
|
||||
dpi: u32,
|
||||
no_glyphs: &mut Vec<char>,
|
||||
_presentation: Option<Presentation>,
|
||||
) -> anyhow::Result<Vec<GlyphInfo>> {
|
||||
let mut results = vec![];
|
||||
let script = allsorts::tag::LATN;
|
||||
|
@ -9,7 +9,7 @@ use log::error;
|
||||
use ordered_float::NotNan;
|
||||
use std::cell::{RefCell, RefMut};
|
||||
use std::collections::HashMap;
|
||||
use termwiz::cell::unicode_column_width;
|
||||
use termwiz::cell::{unicode_column_width, Presentation};
|
||||
use thiserror::Error;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
@ -53,6 +53,7 @@ struct FontPair {
|
||||
face: ftwrap::Face,
|
||||
font: harfbuzz::Font,
|
||||
shaped_any: bool,
|
||||
presentation: Presentation,
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone)]
|
||||
@ -140,8 +141,9 @@ impl HarfbuzzShaper {
|
||||
Some(opt_pair) => {
|
||||
let mut opt_pair = opt_pair.borrow_mut();
|
||||
if opt_pair.is_none() {
|
||||
log::trace!("shaper wants {} {:?}", font_idx, &self.handles[font_idx]);
|
||||
let face = self.lib.face_from_locator(&self.handles[font_idx].handle)?;
|
||||
let handle = &self.handles[font_idx];
|
||||
log::trace!("shaper wants {} {:?}", font_idx, handle);
|
||||
let face = self.lib.face_from_locator(&handle.handle)?;
|
||||
let mut font = harfbuzz::Font::new(face.face);
|
||||
let (load_flags, _) = ftwrap::compute_load_flags_from_config();
|
||||
font.set_load_flags(load_flags);
|
||||
@ -149,6 +151,11 @@ impl HarfbuzzShaper {
|
||||
face,
|
||||
font,
|
||||
shaped_any: false,
|
||||
presentation: if handle.assume_emoji_presentation {
|
||||
Presentation::Emoji
|
||||
} else {
|
||||
Presentation::Text
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -161,11 +168,12 @@ impl HarfbuzzShaper {
|
||||
|
||||
fn do_shape(
|
||||
&self,
|
||||
font_idx: FallbackIdx,
|
||||
mut font_idx: FallbackIdx,
|
||||
s: &str,
|
||||
font_size: f64,
|
||||
dpi: u32,
|
||||
no_glyphs: &mut Vec<char>,
|
||||
presentation: Option<Presentation>,
|
||||
) -> anyhow::Result<Vec<GlyphInfo>> {
|
||||
let mut buf = harfbuzz::Buffer::new()?;
|
||||
buf.set_script(harfbuzz::hb_script_t::HB_SCRIPT_LATIN);
|
||||
@ -179,13 +187,20 @@ impl HarfbuzzShaper {
|
||||
let cell_width;
|
||||
let shaped_any;
|
||||
|
||||
{
|
||||
loop {
|
||||
match self.load_fallback(font_idx).context("load_fallback")? {
|
||||
Some(mut pair) => {
|
||||
if let Some(p) = presentation {
|
||||
if pair.presentation != p {
|
||||
font_idx += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let size = pair.face.set_font_size(font_size, dpi)?;
|
||||
cell_width = size.width;
|
||||
shaped_any = pair.shaped_any;
|
||||
pair.font.shape(&mut buf, self.features.as_slice());
|
||||
break;
|
||||
}
|
||||
None => {
|
||||
// Note: since we added a last resort font, this case
|
||||
@ -283,12 +298,25 @@ impl HarfbuzzShaper {
|
||||
}
|
||||
*/
|
||||
|
||||
let mut shape = match self.do_shape(font_idx + 1, substr, font_size, dpi, no_glyphs)
|
||||
{
|
||||
let mut shape = match self.do_shape(
|
||||
font_idx + 1,
|
||||
substr,
|
||||
font_size,
|
||||
dpi,
|
||||
no_glyphs,
|
||||
presentation,
|
||||
) {
|
||||
Ok(shape) => Ok(shape),
|
||||
Err(e) => {
|
||||
error!("{:?} for {:?}", e, substr);
|
||||
self.do_shape(0, &make_question_string(substr), font_size, dpi, no_glyphs)
|
||||
self.do_shape(
|
||||
0,
|
||||
&make_question_string(substr),
|
||||
font_size,
|
||||
dpi,
|
||||
no_glyphs,
|
||||
presentation,
|
||||
)
|
||||
}
|
||||
}?;
|
||||
|
||||
@ -368,10 +396,11 @@ impl FontShaper for HarfbuzzShaper {
|
||||
size: f64,
|
||||
dpi: u32,
|
||||
no_glyphs: &mut Vec<char>,
|
||||
presentation: Option<Presentation>,
|
||||
) -> anyhow::Result<Vec<GlyphInfo>> {
|
||||
log::trace!("shape {} `{}`", text.len(), text);
|
||||
let start = std::time::Instant::now();
|
||||
let result = self.do_shape(0, text, size, dpi, no_glyphs);
|
||||
let result = self.do_shape(0, text, size, dpi, no_glyphs, presentation);
|
||||
metrics::histogram!("shape.harfbuzz", start.elapsed());
|
||||
/*
|
||||
if let Ok(glyphs) = &result {
|
||||
@ -516,7 +545,7 @@ mod test {
|
||||
let shaper = HarfbuzzShaper::new(&config, &[handle]).unwrap();
|
||||
{
|
||||
let mut no_glyphs = vec![];
|
||||
let info = shaper.shape("abc", 10., 72, &mut no_glyphs).unwrap();
|
||||
let info = shaper.shape("abc", 10., 72, &mut no_glyphs, None).unwrap();
|
||||
assert!(no_glyphs.is_empty(), "{:?}", no_glyphs);
|
||||
assert_eq!(
|
||||
info,
|
||||
@ -565,7 +594,7 @@ mod test {
|
||||
}
|
||||
{
|
||||
let mut no_glyphs = vec![];
|
||||
let info = shaper.shape("<", 10., 72, &mut no_glyphs).unwrap();
|
||||
let info = shaper.shape("<", 10., 72, &mut no_glyphs, None).unwrap();
|
||||
assert!(no_glyphs.is_empty(), "{:?}", no_glyphs);
|
||||
assert_eq!(
|
||||
info,
|
||||
@ -588,7 +617,7 @@ mod test {
|
||||
// This is a ligatured sequence, but you wouldn't know
|
||||
// from this info :-/
|
||||
let mut no_glyphs = vec![];
|
||||
let info = shaper.shape("<-", 10., 72, &mut no_glyphs).unwrap();
|
||||
let info = shaper.shape("<-", 10., 72, &mut no_glyphs, None).unwrap();
|
||||
assert!(no_glyphs.is_empty(), "{:?}", no_glyphs);
|
||||
assert_eq!(
|
||||
info,
|
||||
@ -624,7 +653,7 @@ mod test {
|
||||
}
|
||||
{
|
||||
let mut no_glyphs = vec![];
|
||||
let info = shaper.shape("<--", 10., 72, &mut no_glyphs).unwrap();
|
||||
let info = shaper.shape("<--", 10., 72, &mut no_glyphs, None).unwrap();
|
||||
assert!(no_glyphs.is_empty(), "{:?}", no_glyphs);
|
||||
assert_eq!(
|
||||
info,
|
||||
|
@ -69,6 +69,7 @@ pub trait FontShaper {
|
||||
size: f64,
|
||||
dpi: u32,
|
||||
no_glyphs: &mut Vec<char>,
|
||||
presentation: Option<termwiz::cell::Presentation>,
|
||||
) -> anyhow::Result<Vec<GlyphInfo>>;
|
||||
|
||||
/// Compute the font metrics for the preferred font
|
||||
|
@ -438,7 +438,9 @@ pub fn run_ls_fonts(config: config::ConfigHandle, cmd: &LsFontsCommand) -> anyho
|
||||
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, || {}, |_| {}).unwrap();
|
||||
let infos = font
|
||||
.shape(&cluster.text, || {}, |_| {}, Some(cluster.presentation))
|
||||
.unwrap();
|
||||
|
||||
for info in infos {
|
||||
let parsed = &handles[info.font_idx];
|
||||
|
@ -276,7 +276,7 @@ mod test {
|
||||
let cell_clusters = line.cluster();
|
||||
assert_eq!(cell_clusters.len(), 1);
|
||||
let cluster = &cell_clusters[0];
|
||||
let infos = font.shape(&cluster.text, || {}, |_| {}).unwrap();
|
||||
let infos = font.shape(&cluster.text, || {}, |_| {}, None).unwrap();
|
||||
let glyphs = infos
|
||||
.iter()
|
||||
.map(|info| {
|
||||
@ -387,7 +387,7 @@ mod test {
|
||||
let cluster = &cell_clusters[0];
|
||||
|
||||
measurer.measure(|| {
|
||||
let _x = font.shape(&cluster.text, || {}, |_| {}).unwrap();
|
||||
let _x = font.shape(&cluster.text, || {}, |_| {}, None).unwrap();
|
||||
// println!("{:?}", &x[0..2]);
|
||||
});
|
||||
})
|
||||
|
@ -1488,6 +1488,7 @@ impl super::TermWindow {
|
||||
&cluster.text,
|
||||
move || window.notify(TermWindowNotif::InvalidateShapeCache),
|
||||
BlockKey::filter_out_synthetic,
|
||||
Some(cluster.presentation),
|
||||
) {
|
||||
Ok(info) => {
|
||||
let glyphs = self.glyph_infos_to_glyphs(
|
||||
|
@ -456,6 +456,7 @@ impl ConceptFrame {
|
||||
|_| {
|
||||
// We don't do synthesis here, so no need to filter
|
||||
},
|
||||
None,
|
||||
)
|
||||
.ok()?;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user