1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-25 06:12:16 +03:00

linux: allow specifying fallback fonts explicitly

previously, if you had defined a list of fonts, we'd show a todo error
and ignore everything but the first entry on linux.

We now parse the list into a set of fontconfig patterns and compose
them together, giving prefernce to the explicitly listed fonts.
(details in the comments in the code).

This allows color emoji to render for user defined fonts without
forcing the user to muck around in fontconfig config.

Added Noto Color Emoji to the fallback; this is used in our
ssh password prompting UI when available.
This commit is contained in:
Wez Furlong 2019-11-10 00:13:35 -08:00
parent 8a07a66e97
commit e1069e0a7d
2 changed files with 112 additions and 70 deletions

View File

@ -762,6 +762,13 @@ impl TextStyle {
italic: None,
});
#[cfg(all(unix, not(target_os = "macos")))]
font.push(FontAttributes {
family: "Noto Color Emoji".into(),
bold: None,
italic: None,
});
font
}
}

View File

@ -25,17 +25,9 @@ impl FontSystem for FontConfigAndFreeType {
style: &TextStyle,
font_scale: f64,
) -> Result<Box<dyn NamedFont>, Error> {
let fonts = style.font_with_fallback();
let mut pattern = if !fonts.is_empty() {
let mut fonts = vec![];
for attr in style.font_with_fallback() {
let mut pattern = FontPattern::new()?;
if fonts.len() > 1 {
error!(
"FIXME: fontconfig loader currently only processes
the first in your set of fonts for {:?}",
style
);
}
let attr = &fonts[0];
pattern.family(&attr.family)?;
if *attr.bold.as_ref().unwrap_or(&false) {
pattern.add_integer("weight", 200)?;
@ -43,14 +35,106 @@ impl FontSystem for FontConfigAndFreeType {
if *attr.italic.as_ref().unwrap_or(&false) {
pattern.add_integer("slant", 100)?;
}
pattern
} else {
bail!("no fonts specified!? {:?}", fonts);
};
pattern.add_double("size", config.font_size * font_scale)?;
pattern.add_double("dpi", config.dpi)?;
pattern.add_double("size", config.font_size * font_scale)?;
pattern.add_double("dpi", config.dpi)?;
fonts.push(NamedFontImpl::new(pattern)?);
}
Ok(Box::new(NamedFontImpl::new(pattern)?))
if fonts.is_empty() {
bail!("no fonts specified!?");
}
Ok(Box::new(NamedFontListImpl::new(fonts)))
}
}
pub struct NamedFontListImpl {
fallback: Vec<NamedFontImpl>,
fonts: Vec<FreeTypeFontImpl>,
}
impl NamedFontListImpl {
fn new(fallback: Vec<NamedFontImpl>) -> Self {
Self {
fallback,
fonts: vec![],
}
}
/// We prefer the termwiz config specified set of fallbacks,
/// so if the user specified two fonts then idx=0 and idx=1
/// map to those explicit names. indices idx=2..N are the first
/// fontconfig provided fallback for idx=0, through Nth fallback.
/// Index=N+1 is the first fontconfig provided fallback for idx=1
/// and so on.
/// This function decodes the idx into the pair of user specified
/// font and the index into its set of fallbacks
fn idx_to_fallback(&mut self, idx: usize) -> Option<(&mut NamedFontImpl, usize)> {
if idx < self.fallback.len() {
return Some((&mut self.fallback[idx], 0));
}
let mut candidate = idx - self.fallback.len();
for f in &mut self.fallback {
if candidate < f.font_list_size {
return Some((f, candidate));
}
candidate = candidate - f.font_list_size;
}
None
}
fn load_next_fallback(&mut self) -> Result<(), Error> {
let idx = self.fonts.len();
let (f, idx) = self
.idx_to_fallback(idx)
.ok_or_else(|| err_msg("no more fallbacks"))?;
let pat = f
.font_list
.iter()
.nth(idx)
.ok_or_else(|| err_msg("no more fallbacks"))?;
let pat = f.pattern.render_prepare(&pat)?;
let file = pat.get_file()?;
debug!("load_next_fallback: file={}", file);
debug!("{}", pat.format("%{=unparse}")?);
let size = pat.get_double("size")?;
let dpi = pat.get_double("dpi")? as u32;
let face = f.lib.new_face(file, 0)?;
self.fonts
.push(FreeTypeFontImpl::with_face_size_and_dpi(face, size, dpi)?);
Ok(())
}
fn get_font(&mut self, idx: usize) -> Result<&mut FreeTypeFontImpl, Error> {
if idx >= self.fonts.len() {
self.load_next_fallback()?;
ensure!(
idx < self.fonts.len(),
"should not ask for a font later than the next prepared font"
);
}
Ok(&mut self.fonts[idx])
}
}
impl NamedFont for NamedFontListImpl {
fn get_fallback(&mut self, idx: FallbackIdx) -> Result<&dyn Font, Error> {
Ok(self.get_font(idx)?)
}
fn shape(&mut self, s: &str) -> Result<Vec<GlyphInfo>, Error> {
shape_with_harfbuzz(self, 0, s)
}
}
impl Drop for NamedFontListImpl {
fn drop(&mut self) {
// Ensure that we drop the fonts before we drop the
// library, otherwise we will end up faulting
self.fonts.clear();
}
}
@ -60,29 +144,12 @@ pub struct NamedFontImpl {
lib: ftwrap::Library,
pattern: fcwrap::Pattern,
font_list: fcwrap::FontSet,
fonts: Vec<FreeTypeFontImpl>,
}
impl Drop for NamedFontImpl {
fn drop(&mut self) {
// Ensure that we drop the fonts before we drop the
// library, otherwise we will end up faulting
self.fonts.clear();
}
}
impl NamedFont for NamedFontImpl {
fn get_fallback(&mut self, idx: FallbackIdx) -> Result<&dyn Font, Error> {
Ok(self.get_font(idx)?)
}
fn shape(&mut self, s: &str) -> Result<Vec<GlyphInfo>, Error> {
shape_with_harfbuzz(self, 0, s)
}
font_list_size: usize,
}
impl NamedFontImpl {
/// Construct a new Font from the user supplied pattern
pub fn new(mut pattern: FontPattern) -> Result<Self, Error> {
fn new(mut pattern: FontPattern) -> Result<Self, Error> {
let mut lib = ftwrap::Library::new()?;
// Some systems don't support this mode, so if it fails, we don't
@ -101,45 +168,13 @@ impl NamedFontImpl {
// and obtain the selection with the best preference
// at index 0.
let font_list = pattern.sort(true)?;
let font_list_size = font_list.iter().count();
Ok(Self {
lib,
font_list,
font_list_size,
pattern,
fonts: Vec::new(),
})
}
fn load_next_fallback(&mut self) -> Result<(), Error> {
let idx = self.fonts.len();
let pat = self
.font_list
.iter()
.nth(idx)
.ok_or_else(|| err_msg("no more fallbacks"))?;
let pat = self.pattern.render_prepare(&pat)?;
let file = pat.get_file()?;
debug!("load_next_fallback: file={}", file);
debug!("{}", pat.format("%{=unparse}")?);
let size = pat.get_double("size")?;
let dpi = pat.get_double("dpi")? as u32;
let face = self.lib.new_face(file, 0)?;
self.fonts
.push(FreeTypeFontImpl::with_face_size_and_dpi(face, size, dpi)?);
Ok(())
}
fn get_font(&mut self, idx: usize) -> Result<&mut FreeTypeFontImpl, Error> {
if idx >= self.fonts.len() {
self.load_next_fallback()?;
ensure!(
idx < self.fonts.len(),
"should not ask for a font later than the next prepared font"
);
}
Ok(&mut self.fonts[idx])
}
}