diff --git a/crates/beer/src/config.rs b/crates/beer/src/config.rs index f614ffe..e895eef 100644 --- a/crates/beer/src/config.rs +++ b/crates/beer/src/config.rs @@ -144,10 +144,6 @@ pub struct Colors { pub struct Main { /// Primary font family, resolved via fontconfig. pub font: String, - /// Bold font family, resolved via fontconfig. - pub font_bold: Option, - /// Italic font family, resolved via fontconfig. - pub font_italic: Option, /// Font size in pixels. pub font_size: u32, /// `TERM` value exported to the child shell. @@ -170,8 +166,6 @@ impl Default for Main { fn default() -> Self { Self { font: "monospace".to_string(), - font_italic: None, - font_bold: None, font_size: 16, term: "beer".to_string(), initial_cols: 80, diff --git a/crates/beer/src/font.rs b/crates/beer/src/font.rs index b9104f7..0f66047 100644 --- a/crates/beer/src/font.rs +++ b/crates/beer/src/font.rs @@ -16,7 +16,7 @@ use fontconfig::{CharSet, Fontconfig, Pattern}; use freetype::bitmap::PixelMode; use freetype::face::{LoadFlag, StyleFlag}; use freetype::{Face, Library, Matrix, Vector}; -use harfbuzz_rs_now::{self as harfbuzz}; +use harfbuzz_rs_now as harfbuzz; use lru::LruCache; use thiserror::Error; @@ -128,15 +128,11 @@ struct FaceEntry { pub struct Fonts { library: Library, fontconfig: Fontconfig, - primary_family: String, - italic_family: Option, - bold_family: Option, + family: String, size_px: u32, metrics: CellMetrics, /// All loaded faces; indices into this vector are stable. - primary_faces: Vec, - italic_faces: Option>, - bold_faces: Option>, + faces: Vec, /// Index of each style variant, by [`Style::index`]; filled on demand. styled: [Option; 4], /// Fallback faces resolved by coverage, deduplicated by file path. @@ -149,30 +145,13 @@ pub struct Fonts { shape_cache: LruCache<(Box, usize), Option>, } -#[derive(Clone)] -pub struct FontStyles { - primary: String, - italics: Option, - bold: Option, -} - -impl FontStyles { - pub fn all(regular: impl Into, italics: Option, bold: Option) -> Self { - FontStyles { - primary: regular.into(), - italics, - bold, - } - } -} - impl fmt::Debug for Fonts { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Fonts") - .field("family", &self.primary_family) + .field("family", &self.family) .field("size_px", &self.size_px) .field("metrics", &self.metrics) - .field("faces", &self.primary_faces.len()) + .field("faces", &self.faces.len()) .field("cached", &self.cache.len()) .finish() } @@ -180,71 +159,21 @@ impl fmt::Debug for Fonts { impl Fonts { /// Resolve `family` at `size_px` and compute the cell metrics. - pub fn new(family: FontStyles, size_px: u32) -> Result { + pub fn new(family: &str, size_px: u32) -> Result { let library = Library::init()?; let fontconfig = Fontconfig::new().ok_or(FontError::FontconfigInit)?; - let regular = resolve_face( - &library, - &fontconfig, - &family.primary, - Style::default(), - size_px, - )?; - let italic = family - .italics - .as_ref() - .and_then(|i| { - Some( - resolve_face( - &library, - &fontconfig, - &i, - Style { - italic: true, - bold: false, - }, - size_px, - ) - .ok(), - ) - .flatten() - }) - .map(|x| vec![x]); - let bold = family - .bold - .as_ref() - .and_then(|b| { - Some( - resolve_face( - &library, - &fontconfig, - &b, - Style { - bold: true, - italic: false, - }, - size_px, - ) - .ok(), - ) - .flatten() - }) - .map(|x| vec![x]); - let metrics = cell_metrics(®ular.face, &family.primary)?; + let regular = resolve_face(&library, &fontconfig, family, Style::default(), size_px)?; + let metrics = cell_metrics(®ular.face, family)?; let cap = |n| NonZeroUsize::new(n).expect("cache cap is nonzero"); Ok(Self { library, fontconfig, - primary_family: family.primary.to_owned(), - bold_family: family.bold.and_then(|i| Some(i.to_owned())), - italic_family: family.italics.and_then(|i| Some(i.to_owned())), - bold_faces: bold, - italic_faces: italic, + family: family.to_owned(), size_px, metrics, - primary_faces: vec![regular], + faces: vec![regular], styled: [Some(0), None, None, None], fallbacks: HashMap::new(), cache: LruCache::new(cap(GLYPH_CACHE_CAP)), @@ -257,28 +186,13 @@ impl Fonts { self.metrics } - fn get_face(&self, face_idx: usize, style: Style) -> &FaceEntry { - match style { - Style { italic: true, .. } => { - &self.italic_faces.as_ref().unwrap_or(&self.primary_faces)[face_idx] - } - Style { bold: true, .. } => { - &self.bold_faces.as_ref().unwrap_or(&self.primary_faces)[face_idx] - } - Style { - bold: false, - italic: false, - } => &self.primary_faces[face_idx], - } - } - /// Return the rasterized glyph for `c` in `style`, rasterizing and caching /// it on first use. pub fn glyph(&mut self, c: char, style: Style) -> Result<&Glyph, FontError> { let key = (c, style.index()); if self.cache.get(&key).is_none() { let idx = self.face_for(c, style)?; - let face = &self.get_face(idx, style).face; + let face = &self.faces[idx].face; // Synthesize bold/italic only when the resolved face lacks the real // variant (most monospace families ship both). let (synth_bold, synth_italic) = synth_flags(face, style); @@ -299,7 +213,7 @@ impl Fonts { ) -> Result<&Glyph, FontError> { let key = (gid, face_idx, style.index()); if self.gcache.get(&key).is_none() { - let face = &self.get_face(face_idx, style).face; + let face = &self.faces[face_idx].face; let (synth_bold, synth_italic) = synth_flags(face, style); let glyph = rasterize_index(face, gid, synth_bold, synth_italic)?; self.gcache.put(key, glyph); @@ -317,7 +231,7 @@ impl Fonts { /// those at blit time instead. pub fn glyph_scaled(&mut self, c: char, style: Style, scale: f32) -> Result { let idx = self.face_for(c, style)?; - let face = &self.get_face(idx, style).face; + let face = &self.faces[idx].face; let (synth_bold, synth_italic) = synth_flags(face, style); rasterize_scaled(face, c, scale.max(0.01), synth_bold, synth_italic) } @@ -347,7 +261,7 @@ impl Fonts { fn shape_uncached(&mut self, base: char, cluster: &str, style: Style) -> Option { let face_idx = self.face_for(base, style).ok()?; - let font = self.hb_font(style, face_idx)?; + let font = self.hb_font(face_idx)?; let buffer = harfbuzz::UnicodeBuffer::new().add_str(cluster); let output = harfbuzz::shape(font, buffer, &[]); let infos = output.get_glyph_infos(); @@ -374,26 +288,9 @@ impl Fonts { /// Lazily build the HarfBuzz font for `face_idx` from the same file bytes /// FreeType loaded. The bytes are leaked to `'static`: a face lives for the /// process, and only the handful actually used to shape clusters allocate. - fn hb_font( - &mut self, - style: Style, - face_idx: usize, - ) -> Option<&harfbuzz::Owned>> { - let entry = match style { - Style { italic: true, .. } => &mut self - .italic_faces - .as_mut() - .unwrap_or(&mut self.primary_faces)[face_idx], - Style { bold: true, .. } => { - &mut self.bold_faces.as_mut().unwrap_or(&mut self.primary_faces)[face_idx] - } - Style { - bold: false, - italic: false, - } => &mut self.primary_faces[face_idx], - }; - - if entry.hb.is_none() { + fn hb_font(&mut self, face_idx: usize) -> Option<&harfbuzz::Owned>> { + if self.faces[face_idx].hb.is_none() { + let entry = &self.faces[face_idx]; let bytes = std::fs::read(&entry.path).ok()?; let leaked: &'static [u8] = Box::leak(bytes.into_boxed_slice()); let face = harfbuzz::Face::from_bytes(leaked, entry.index); @@ -401,9 +298,9 @@ impl Fonts { let scale = self.size_px as i32 * 64; font.set_scale(scale, scale); font.set_ppem(self.size_px, self.size_px); - entry.hb = Some(font); + self.faces[face_idx].hb = Some(font); } - entry.hb.as_ref() + self.faces[face_idx].hb.as_ref() } /// Pick the face that should render `c`: the requested style if it has the @@ -411,17 +308,17 @@ impl Fonts { /// coverage match. Falls back to the styled face (rendering `.notdef`). fn face_for(&mut self, c: char, style: Style) -> Result { let styled = self.styled_face(style)?; - if face_has_glyph(&self.get_face(styled, style).face, c) { + if face_has_glyph(&self.faces[styled].face, c) { return Ok(styled); } if let Some(regular) = self.styled[0] && regular != styled - && face_has_glyph(&self.get_face(styled, style).face, c) + && face_has_glyph(&self.faces[regular].face, c) { return Ok(regular); } for &idx in self.fallbacks.values() { - if face_has_glyph(&self.get_face(styled, style).face, c) { + if face_has_glyph(&self.faces[idx].face, c) { return Ok(idx); } } @@ -435,33 +332,16 @@ impl Fonts { return Ok(idx); } let regular = self.styled[0].expect("regular face is loaded at construction"); - let family = match style { - Style { italic: true, .. } => { - self.italic_family.as_ref().unwrap_or(&self.primary_family) - } - Style { bold: true, .. } => self.bold_family.as_ref().unwrap_or(&self.primary_family), - _ => &self.primary_family, - }; - let faces = match style { - Style { italic: true, .. } => { - &mut self.bold_faces.as_mut().unwrap_or(&mut self.primary_faces) - } - Style { bold: true, .. } => &mut self - .italic_faces - .as_mut() - .unwrap_or(&mut self.primary_faces), - _ => &mut self.primary_faces, - }; let idx = match resolve_face( &self.library, &self.fontconfig, - &family, + &self.family, style, self.size_px, ) { Ok(entry) => { - faces.push(entry); - faces.len() - 1 + self.faces.push(entry); + self.faces.len() - 1 } Err(_) => regular, }; @@ -487,13 +367,13 @@ impl Fonts { if size_face(&face, self.size_px).is_err() { return Ok(None); } - self.primary_faces.push(FaceEntry { + self.faces.push(FaceEntry { face, path: path.clone(), index: index as u32, hb: None, }); - let idx = self.primary_faces.len() - 1; + let idx = self.faces.len() - 1; self.fallbacks.insert(path, idx); Ok(Some(idx)) } @@ -771,18 +651,8 @@ fn expand_mono(src: &[u8], width: usize, pitch: i32, height: usize) -> Vec { mod tests { use super::*; - impl FontStyles { - pub fn regular(name: impl Into) -> Self { - FontStyles { - primary: name.into(), - italics: None, - bold: None, - } - } - } - fn fonts() -> Fonts { - Fonts::new(FontStyles::regular("monospace"), 16).expect("system has a monospace font") + Fonts::new("monospace", 16).expect("system has a monospace font") } #[test] diff --git a/crates/beer/src/render.rs b/crates/beer/src/render.rs index 61bf950..e7850f4 100644 --- a/crates/beer/src/render.rs +++ b/crates/beer/src/render.rs @@ -10,7 +10,7 @@ use std::num::NonZeroU16; use beer_protocols::graphics::{PLACEHOLDER, diacritic_value}; use beer_protocols::text_size::{HAlign, VAlign}; -use crate::font::{CellMetrics, FontStyles, Fonts, Glyph, GlyphData, Style}; +use crate::font::{CellMetrics, Fonts, Glyph, GlyphData, Style}; use crate::grid::{Cell, Color, CursorShape, Flags, Grid, Underline}; use crate::theme::{Plane, Rgb, Theme}; @@ -141,12 +141,8 @@ impl Renderer { } /// Rebuild the font set at a new size (font-resize bindings). - pub fn set_font( - &mut self, - fonts: FontStyles, - size_px: u32, - ) -> Result<(), crate::font::FontError> { - self.fonts = Fonts::new(fonts, size_px)?; + pub fn set_font(&mut self, family: &str, size_px: u32) -> Result<(), crate::font::FontError> { + self.fonts = Fonts::new(family, size_px)?; Ok(()) } diff --git a/crates/beer/src/wayland/mod.rs b/crates/beer/src/wayland/mod.rs index 4d805f5..b426db1 100644 --- a/crates/beer/src/wayland/mod.rs +++ b/crates/beer/src/wayland/mod.rs @@ -22,7 +22,7 @@ use calloop::{EventLoop, Interest, LoopHandle, Mode, PostAction, RegistrationTok use calloop_wayland_source::WaylandSource; use crate::config::Config; -use crate::font::{FontStyles, Fonts}; +use crate::font::Fonts; use crate::grid::{Cell, CursorShape, Grid, MouseProtocol, UrlHit}; use crate::pty::Pty; use crate::render::Renderer; @@ -207,15 +207,7 @@ pub fn run(config: Config, config_path: Option) -> anyhow::R // First commit with no buffer kicks off the initial configure. window.commit(); - let fonts = Fonts::new( - FontStyles::all( - &config.main.font, - config.main.font_italic.clone(), - config.main.font_bold.clone(), - ), - config.main.font_size, - ) - .context("load font")?; + let fonts = Fonts::new(&config.main.font, config.main.font_size).context("load font")?; let mut renderer = Renderer::new(fonts); renderer.set_padding(config.main.pad_x, config.main.pad_y); @@ -1184,14 +1176,7 @@ impl App { self.to_phys(self.config.main.pad_y), ); let px = self.to_phys(self.font_size).max(1); - if let Err(err) = self.renderer.set_font( - FontStyles::all( - &self.config.main.font, - self.config.main.font_italic.clone(), - self.config.main.font_bold.clone(), - ), - px, - ) { + if let Err(err) = self.renderer.set_font(&self.config.main.font, px) { tracing::warn!("rasterize font at scale {}: {err:#}", self.scale120); } }