Files
ghostty-rs/crates/ghostty-font/src/shaper/cache.rs
T
ddidderr 25801765eb feat(font): port the Linux font engine
Ports the rest of ZIG/src/font for the freetype/fontconfig/harfbuzz
backend: face loading and rasterization (synthetic bold/italic, color
emoji bitmaps, glyph constraints for nerd font icons), fontconfig
discovery, DeferredFace, Collection, CodepointResolver, CodepointMap,
SharedGrid, opentype table parsers, the nerd font attribute table, and
the harfbuzz shaper with feature parsing and shaping cache.

Embedded fonts (JetBrains Mono 2.304 variable, Symbols Nerd Font 3.4.0,
Noto emoji) and the test fonts from Ghostty live in res/ with their OFL/
MIT licenses.

The crate opts into unsafe_code = "deny"; FFI is confined to ft.rs,
fontconfig.rs and hb.rs with SAFETY comments. Glyph resampling uses an
stb-style resampler that is not bit-identical to stb.

Deferred to the next font pass (needs ghostty-terminal/config):
shaper/run.zig, SharedGridSet.zig and the 23 terminal-driven shaper
tests. Config-facing types are mirrored locally in config_types.rs.

Test Plan:
- cargo test -p ghostty-font (139 tests)
2026-10-11 22:13:06 +02:00

112 lines
2.9 KiB
Rust

//! This structure caches the shaped cells for a given text run.
//!
//! At one point, shaping was the most expensive part of rendering text
//! (accounting for 96% of frame time on my machine). To speed it up, this
//! was introduced so that shaping results can be cached depending on the
//! run.
//!
//! The cache key is the text run. The text run builds its own hash value
//! based on the font, style, codepoint, etc. This just utilizes the hash that
//! the text run provides.
use ghostty_datastruct::cache_table::{CacheContext, CacheTable};
use crate::shape::{Cell, TextRun};
/// Context for cache table.
#[derive(Debug, Default)]
pub struct CellCacheTableContext;
impl CacheContext<u64, Vec<Cell>> for CellCacheTableContext {
fn hash(&mut self, key: &u64) -> u64 {
*key
}
fn eql(&mut self, a: &u64, b: &u64) -> bool {
a == b
}
}
/// Cache table for run hash -> shaped cells.
///
/// Capacity is slightly arbitrary. These numbers are guesses: I'd expect an
/// average of 256 frequently cached runs is a safe guess for most terminal
/// screens, with 8 items per bucket to give decent resiliency to important
/// runs.
type CellCacheTable = CacheTable<u64, Vec<Cell>, CellCacheTableContext, 256, 8>;
/// The cache table of shaped cells.
#[derive(Debug)]
pub struct Cache {
map: CellCacheTable,
}
impl Default for Cache {
fn default() -> Self {
Self::new()
}
}
impl Cache {
pub fn new() -> Cache {
Cache {
map: CellCacheTable::new(CellCacheTableContext),
}
}
/// Get the shaped cells for the given text run,
/// or None if they are not in the cache.
pub fn get(&mut self, run: &TextRun) -> Option<&[Cell]> {
self.map.get(&run.hash).map(Vec::as_slice)
}
/// Insert the shaped cells for the given text run into the cache.
///
/// The cells will be duplicated.
pub fn put(&mut self, run: &TextRun, cells: &[Cell]) {
// An evicted entry is simply dropped.
let _evicted = self.map.put(run.hash, cells.to_vec());
}
/// Remove everything from the cache.
pub fn clear(&mut self) {
self.map.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::collection::Index;
#[test]
fn cache() {
let mut c = Cache::new();
let run = TextRun {
hash: 1,
offset: 0,
cells: 0,
font_index: Index::default(),
};
assert!(c.get(&run).is_none());
c.put(
&run,
&[
Cell {
x: 0,
glyph_index: 0,
..Cell::default()
},
Cell {
x: 1,
glyph_index: 1,
..Cell::default()
},
],
);
let actual = c.get(&run).unwrap();
assert_eq!(actual.len(), 2);
}
}