Files
ghostty-rs/crates/ghostty-font/src/shaper/noop.rs
T
ddidderr fc7bb5c025 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

88 lines
2.3 KiB
Rust

//! Shaper that doesn't do any shaping. Port of `font/shaper/noop.zig`.
//! Each individual codepoint is mapped directly to the detected text run
//! font's glyph index.
use crate::face::freetype::Face;
use crate::shape::{self, Cell};
#[derive(Debug, Clone, Copy)]
struct Codepoint {
codepoint: u32,
cluster: u32,
}
/// Shaper that doesn't do any shaping.
#[derive(Debug, Default)]
pub struct Shaper {
/// The codepoints of the current run.
codepoints: Vec<Codepoint>,
/// The shared memory used for shaping results.
cell_buf: Vec<Cell>,
}
impl Shaper {
pub fn new(_opts: &shape::Options) -> Shaper {
Shaper::default()
}
pub fn end_frame(&self) {}
// ---- RunIterator hooks ----
pub fn prepare(&mut self) {
self.codepoints.clear();
}
pub fn add_codepoint(&mut self, cp: u32, cluster: u32) {
self.codepoints.push(Codepoint {
codepoint: cp,
cluster,
});
}
pub fn finalize(&mut self) {}
/// Shape the current run. `face` is `None` for special fonts (sprite),
/// whose codepoint == glyph index.
///
/// Panics if a codepoint has no glyph in `face` (the run iterator
/// guarantees this never happens).
pub fn shape(&mut self, face: Option<&Face>) -> &[Cell] {
self.cell_buf.clear();
for entry in &self.codepoints {
let glyph_index = match face {
// Special fonts aren't shaped and their codepoint == glyph.
None => entry.codepoint,
Some(face) => face
.glyph_index(entry.codepoint)
.expect("run iterator guarantees a glyph for every codepoint"),
};
self.cell_buf.push(Cell {
x: entry.cluster as u16,
glyph_index,
..Cell::default()
});
}
&self.cell_buf
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn noop_special_passthrough() {
let mut s = Shaper::new(&shape::Options::default());
s.prepare();
s.add_codepoint(0x41, 0);
s.add_codepoint(0x42, 2);
s.finalize();
let cells = s.shape(None);
assert_eq!(2, cells.len());
assert_eq!(0x41, cells[0].glyph_index);
assert_eq!(2, cells[1].x);
}
}