1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-21 04:11:44 +03:00
wezterm/wezterm/src/font/mod.rs

255 lines
8.0 KiB
Rust
Raw Normal View History

2019-12-15 08:43:05 +03:00
use anyhow::{anyhow, Error};
mod hbwrap;
2018-02-21 01:32:28 +03:00
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::rc::Rc;
2019-12-10 00:56:34 +03:00
pub mod ftwrap;
2019-12-10 07:06:12 +03:00
pub mod locator;
pub mod parser;
pub mod rasterizer;
2019-12-09 11:06:47 +03:00
pub mod shaper;
pub mod units;
#[cfg(all(unix, not(target_os = "macos")))]
2018-02-21 07:55:03 +03:00
pub mod fcwrap;
use crate::font::locator::{new_locator, FontDataHandle, FontLocator, FontLocatorSelection};
2019-12-10 00:56:34 +03:00
pub use crate::font::rasterizer::RasterizedGlyph;
use crate::font::rasterizer::{new_rasterizer, FontRasterizer, FontRasterizerSelection};
use crate::font::shaper::{new_shaper, FontShaper, FontShaperSelection};
2019-12-10 00:56:34 +03:00
pub use crate::font::shaper::{FallbackIdx, FontMetrics, GlyphInfo};
2020-10-02 21:50:50 +03:00
use config::{configuration, ConfigHandle, TextStyle};
use wezterm_term::CellAttributes;
pub struct LoadedFont {
rasterizers: Vec<RefCell<Option<Box<dyn FontRasterizer>>>>,
handles: Vec<FontDataHandle>,
shaper: Box<dyn FontShaper>,
metrics: FontMetrics,
font_size: f64,
dpi: u32,
}
impl LoadedFont {
pub fn metrics(&self) -> FontMetrics {
self.metrics
}
2019-12-15 08:43:05 +03:00
pub fn shape(&self, text: &str) -> anyhow::Result<Vec<GlyphInfo>> {
self.shaper.shape(text, self.font_size, self.dpi)
}
pub fn rasterize_glyph(
&self,
glyph_pos: u32,
fallback: FallbackIdx,
2019-12-15 08:43:05 +03:00
) -> anyhow::Result<RasterizedGlyph> {
let cell = self
.rasterizers
.get(fallback)
2019-12-15 08:43:05 +03:00
.ok_or_else(|| anyhow!("no such fallback index: {}", fallback))?;
let mut opt_raster = cell.borrow_mut();
if opt_raster.is_none() {
let raster = new_rasterizer(
FontRasterizerSelection::get_default(),
&self.handles[fallback],
)?;
opt_raster.replace(raster);
}
opt_raster
.as_ref()
.unwrap()
.rasterize_glyph(glyph_pos, self.font_size, self.dpi)
}
}
/// Matches and loads fonts for a given input style
pub struct FontConfiguration {
fonts: RefCell<HashMap<TextStyle, Rc<LoadedFont>>>,
metrics: RefCell<Option<FontMetrics>>,
dpi_scale: RefCell<f64>,
font_scale: RefCell<f64>,
config_generation: RefCell<usize>,
2019-12-10 07:06:12 +03:00
locator: Box<dyn FontLocator>,
}
impl FontConfiguration {
/// Create a new empty configuration
pub fn new() -> Self {
let locator = new_locator(FontLocatorSelection::get_default());
Self {
fonts: RefCell::new(HashMap::new()),
2019-12-10 07:06:12 +03:00
locator,
metrics: RefCell::new(None),
font_scale: RefCell::new(1.0),
dpi_scale: RefCell::new(1.0),
config_generation: RefCell::new(configuration().generation()),
}
}
/// Given a text style, load (with caching) the font that best
/// matches according to the fontconfig pattern.
2019-12-15 08:43:05 +03:00
pub fn resolve_font(&self, style: &TextStyle) -> anyhow::Result<Rc<LoadedFont>> {
let mut fonts = self.fonts.borrow_mut();
let config = configuration();
let current_generation = config.generation();
if current_generation != *self.config_generation.borrow() {
// Config was reloaded, invalidate our caches
fonts.clear();
self.metrics.borrow_mut().take();
*self.config_generation.borrow_mut() = current_generation;
}
if let Some(entry) = fonts.get(style) {
return Ok(Rc::clone(entry));
}
let attributes = style.font_with_fallback();
let preferred_attributes = attributes
.iter()
.filter(|a| !a.is_fallback)
.map(|a| a.clone())
.collect::<Vec<_>>();
let fallback_attributes = attributes
.iter()
.filter(|a| a.is_fallback)
.map(|a| a.clone())
.collect::<Vec<_>>();
let mut loaded = HashSet::new();
let mut handles =
parser::ParsedFont::load_fonts(&config, &preferred_attributes, &mut loaded)?;
handles.append(
&mut self
.locator
.load_fonts(&preferred_attributes, &mut loaded)?,
);
handles.append(&mut parser::ParsedFont::load_built_in_fonts(
&preferred_attributes,
&mut loaded,
)?);
handles.append(&mut parser::ParsedFont::load_fonts(
&config,
&fallback_attributes,
&mut loaded,
)?);
handles.append(&mut self.locator.load_fonts(&fallback_attributes, &mut loaded)?);
handles.append(&mut parser::ParsedFont::load_built_in_fonts(
&fallback_attributes,
&mut loaded,
)?);
for attr in &attributes {
if !attr.is_fallback && !loaded.contains(attr) {
crate::connui::show_configuration_error_message(&format!(
"Unable to load a font matching {:?}. Fallback(s)
are being used instead, and the terminal may not
render as intended",
attr
));
}
}
let mut rasterizers = vec![];
for _ in &handles {
rasterizers.push(RefCell::new(None));
}
let shaper = new_shaper(FontShaperSelection::get_default(), &handles)?;
let config = configuration();
let font_size = config.font_size * *self.font_scale.borrow();
let dpi = *self.dpi_scale.borrow() as u32 * config.dpi as u32;
let metrics = shaper.metrics(font_size, dpi)?;
let loaded = Rc::new(LoadedFont {
rasterizers,
handles,
shaper,
metrics,
font_size,
dpi,
});
fonts.insert(style.clone(), Rc::clone(&loaded));
Ok(loaded)
}
pub fn change_scaling(&self, font_scale: f64, dpi_scale: f64) {
*self.dpi_scale.borrow_mut() = dpi_scale;
*self.font_scale.borrow_mut() = font_scale;
self.fonts.borrow_mut().clear();
self.metrics.borrow_mut().take();
}
/// Returns the baseline font specified in the configuration
2019-12-15 08:43:05 +03:00
pub fn default_font(&self) -> anyhow::Result<Rc<LoadedFont>> {
self.resolve_font(&configuration().font)
}
pub fn get_font_scale(&self) -> f64 {
*self.font_scale.borrow()
}
pub fn default_font_metrics(&self) -> Result<FontMetrics, Error> {
{
let metrics = self.metrics.borrow();
if let Some(metrics) = metrics.as_ref() {
return Ok(*metrics);
}
}
let font = self.default_font()?;
let metrics = font.metrics();
2019-02-24 21:33:21 +03:00
*self.metrics.borrow_mut() = Some(metrics);
Ok(metrics)
}
/// Apply the defined font_rules from the user configuration to
/// produce the text style that best matches the supplied input
/// cell attributes.
pub fn match_style<'a>(
&self,
config: &'a ConfigHandle,
attrs: &CellAttributes,
) -> &'a TextStyle {
// a little macro to avoid boilerplate for matching the rules.
// If the rule doesn't specify a value for an attribute then
// it will implicitly match. If it specifies an attribute
// then it has to have the same value as that in the input attrs.
macro_rules! attr_match {
($ident:ident, $rule:expr) => {
if let Some($ident) = $rule.$ident {
if $ident != attrs.$ident() {
// Does not match
continue;
}
}
// matches so far...
};
};
for rule in &config.font_rules {
attr_match!(intensity, &rule);
attr_match!(underline, &rule);
attr_match!(italic, &rule);
attr_match!(blink, &rule);
attr_match!(reverse, &rule);
attr_match!(strikethrough, &rule);
attr_match!(invisible, &rule);
// If we get here, then none of the rules didn't match,
// so we therefore assume that it did match overall.
return &rule.font;
}
&config.font
}
}