alacritty/alacritty/src/renderer/text/glyph_cache.rs

323 lines
10 KiB
Rust

use std::collections::HashMap;
use std::hash::BuildHasherDefault;
use crossfont::{
Error as RasterizerError, FontDesc, FontKey, GlyphKey, Metrics, Rasterize, RasterizedGlyph,
Rasterizer, Size, Slant, Style, Weight,
};
use fnv::FnvHasher;
use log::{error, info};
use unicode_width::UnicodeWidthChar;
use crate::config::font::{Font, FontDescription};
use crate::config::ui_config::Delta;
use crate::gl::types::*;
use super::builtin_font;
/// `LoadGlyph` allows for copying a rasterized glyph into graphics memory.
pub trait LoadGlyph {
/// Load the rasterized glyph into GPU memory.
fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph;
/// Clear any state accumulated from previous loaded glyphs.
///
/// This can, for instance, be used to reset the texture Atlas.
fn clear(&mut self);
}
#[derive(Copy, Clone, Debug)]
pub struct Glyph {
pub tex_id: GLuint,
pub multicolor: bool,
pub top: i16,
pub left: i16,
pub width: i16,
pub height: i16,
pub uv_bot: f32,
pub uv_left: f32,
pub uv_width: f32,
pub uv_height: f32,
}
/// Naïve glyph cache.
///
/// Currently only keyed by `char`, and thus not possible to hold different
/// representations of the same code point.
pub struct GlyphCache {
/// Cache of buffered glyphs.
cache: HashMap<GlyphKey, Glyph, BuildHasherDefault<FnvHasher>>,
/// Rasterizer for loading new glyphs.
rasterizer: Rasterizer,
/// Regular font.
pub font_key: FontKey,
/// Bold font.
pub bold_key: FontKey,
/// Italic font.
pub italic_key: FontKey,
/// Bold italic font.
pub bold_italic_key: FontKey,
/// Font size.
pub font_size: crossfont::Size,
/// Font offset.
font_offset: Delta<i8>,
/// Glyph offset.
glyph_offset: Delta<i8>,
/// Font metrics.
metrics: Metrics,
/// Whether to use the built-in font for box drawing characters.
builtin_box_drawing: bool,
}
impl GlyphCache {
pub fn new(mut rasterizer: Rasterizer, font: &Font) -> Result<GlyphCache, crossfont::Error> {
let (regular, bold, italic, bold_italic) = Self::compute_font_keys(font, &mut rasterizer)?;
// Need to load at least one glyph for the face before calling metrics.
// The glyph requested here ('m' at the time of writing) has no special
// meaning.
rasterizer.get_glyph(GlyphKey { font_key: regular, character: 'm', size: font.size() })?;
let metrics = rasterizer.metrics(regular, font.size())?;
Ok(Self {
cache: HashMap::default(),
rasterizer,
font_size: font.size(),
font_key: regular,
bold_key: bold,
italic_key: italic,
bold_italic_key: bold_italic,
font_offset: font.offset,
glyph_offset: font.glyph_offset,
metrics,
builtin_box_drawing: font.builtin_box_drawing,
})
}
fn load_glyphs_for_font<L: LoadGlyph>(&mut self, font: FontKey, loader: &mut L) {
let size = self.font_size;
// Cache all ascii characters.
for i in 32u8..=126u8 {
self.get(GlyphKey { font_key: font, character: i as char, size }, loader, true);
}
}
/// Computes font keys for (Regular, Bold, Italic, Bold Italic).
fn compute_font_keys(
font: &Font,
rasterizer: &mut Rasterizer,
) -> Result<(FontKey, FontKey, FontKey, FontKey), crossfont::Error> {
let size = font.size();
// Load regular font.
let regular_desc = Self::make_desc(font.normal(), Slant::Normal, Weight::Normal);
let regular = Self::load_regular_font(rasterizer, &regular_desc, size)?;
// Helper to load a description if it is not the `regular_desc`.
let mut load_or_regular = |desc: FontDesc| {
if desc == regular_desc {
regular
} else {
rasterizer.load_font(&desc, size).unwrap_or(regular)
}
};
// Load bold font.
let bold_desc = Self::make_desc(&font.bold(), Slant::Normal, Weight::Bold);
let bold = load_or_regular(bold_desc);
// Load italic font.
let italic_desc = Self::make_desc(&font.italic(), Slant::Italic, Weight::Normal);
let italic = load_or_regular(italic_desc);
// Load bold italic font.
let bold_italic_desc = Self::make_desc(&font.bold_italic(), Slant::Italic, Weight::Bold);
let bold_italic = load_or_regular(bold_italic_desc);
Ok((regular, bold, italic, bold_italic))
}
fn load_regular_font(
rasterizer: &mut Rasterizer,
description: &FontDesc,
size: Size,
) -> Result<FontKey, crossfont::Error> {
match rasterizer.load_font(description, size) {
Ok(font) => Ok(font),
Err(err) => {
error!("{}", err);
let fallback_desc =
Self::make_desc(Font::default().normal(), Slant::Normal, Weight::Normal);
rasterizer.load_font(&fallback_desc, size)
},
}
}
fn make_desc(desc: &FontDescription, slant: Slant, weight: Weight) -> FontDesc {
let style = if let Some(ref spec) = desc.style {
Style::Specific(spec.to_owned())
} else {
Style::Description { slant, weight }
};
FontDesc::new(desc.family.clone(), style)
}
/// Get a glyph from the font.
///
/// If the glyph has never been loaded before, it will be rasterized and inserted into the
/// cache.
///
/// # Errors
///
/// This will fail when the glyph could not be rasterized. Usually this is due to the glyph
/// not being present in any font.
pub fn get<L: ?Sized>(
&mut self,
glyph_key: GlyphKey,
loader: &mut L,
show_missing: bool,
) -> Glyph
where
L: LoadGlyph,
{
// Try to load glyph from cache.
if let Some(glyph) = self.cache.get(&glyph_key) {
return *glyph;
};
// Rasterize the glyph using the built-in font for special characters or the user's font
// for everything else.
let rasterized = self
.builtin_box_drawing
.then(|| {
builtin_font::builtin_glyph(
glyph_key.character,
&self.metrics,
&self.font_offset,
&self.glyph_offset,
)
})
.flatten()
.map_or_else(|| self.rasterizer.get_glyph(glyph_key), Ok);
let glyph = match rasterized {
Ok(rasterized) => self.load_glyph(loader, rasterized),
// Load fallback glyph.
Err(RasterizerError::MissingGlyph(rasterized)) if show_missing => {
// Use `\0` as "missing" glyph to cache it only once.
let missing_key = GlyphKey { character: '\0', ..glyph_key };
if let Some(glyph) = self.cache.get(&missing_key) {
*glyph
} else {
// If no missing glyph was loaded yet, insert it as `\0`.
let glyph = self.load_glyph(loader, rasterized);
self.cache.insert(missing_key, glyph);
glyph
}
},
Err(_) => self.load_glyph(loader, Default::default()),
};
// Cache rasterized glyph.
*self.cache.entry(glyph_key).or_insert(glyph)
}
/// Load glyph into the atlas.
///
/// This will apply all transforms defined for the glyph cache to the rasterized glyph before
pub fn load_glyph<L: ?Sized>(&self, loader: &mut L, mut glyph: RasterizedGlyph) -> Glyph
where
L: LoadGlyph,
{
glyph.left += i32::from(self.glyph_offset.x);
glyph.top += i32::from(self.glyph_offset.y);
glyph.top -= self.metrics.descent as i32;
// The metrics of zero-width characters are based on rendering
// the character after the current cell, with the anchor at the
// right side of the preceding character. Since we render the
// zero-width characters inside the preceding character, the
// anchor has been moved to the right by one cell.
if glyph.character.width() == Some(0) {
glyph.left += self.metrics.average_advance as i32;
}
// Add glyph to cache.
loader.load_glyph(&glyph)
}
/// Clear currently cached data in both GL and the registry.
pub fn clear_glyph_cache<L: LoadGlyph>(&mut self, loader: &mut L) {
loader.clear();
self.cache = HashMap::default();
self.load_common_glyphs(loader);
}
pub fn update_font_size<L: LoadGlyph>(
&mut self,
font: &Font,
scale_factor: f64,
loader: &mut L,
) -> Result<(), crossfont::Error> {
// Update dpi scaling.
self.rasterizer.update_dpr(scale_factor as f32);
self.font_offset = font.offset;
// Recompute font keys.
let (regular, bold, italic, bold_italic) =
Self::compute_font_keys(font, &mut self.rasterizer)?;
self.rasterizer.get_glyph(GlyphKey {
font_key: regular,
character: 'm',
size: font.size(),
})?;
let metrics = self.rasterizer.metrics(regular, font.size())?;
info!("Font size changed to {:?} with scale factor of {}", font.size(), scale_factor);
self.font_size = font.size();
self.font_key = regular;
self.bold_key = bold;
self.italic_key = italic;
self.bold_italic_key = bold_italic;
self.metrics = metrics;
self.builtin_box_drawing = font.builtin_box_drawing;
self.clear_glyph_cache(loader);
Ok(())
}
pub fn font_metrics(&self) -> crossfont::Metrics {
self.metrics
}
/// Prefetch glyphs that are almost guaranteed to be loaded anyways.
pub fn load_common_glyphs<L: LoadGlyph>(&mut self, loader: &mut L) {
self.load_glyphs_for_font(self.font_key, loader);
self.load_glyphs_for_font(self.bold_key, loader);
self.load_glyphs_for_font(self.italic_key, loader);
self.load_glyphs_for_font(self.bold_italic_key, loader);
}
}