mirror of
https://github.com/wez/wezterm.git
synced 2024-11-10 15:04:32 +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:
parent
8a07a66e97
commit
e1069e0a7d
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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])
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user