brewery
notashelf /
5682027a947c61a72347078812dc47bf4da05253

beer

public

A terminal worth pouring time into

Star0tarball
clone
ssh://git@git.notashelf.dev:33/notashelf/beer.git

Commit 5682027a947c

tarball

NotAShelf <raf@notashelf.dev> · 2026-06-26 07:21 UTC

unverified2 files changed+62-29
font: render colour emoji from bitmap strikes, scaled to the cell

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: If30e5f13ee24e691b417ad35c588a6226a6a6964
diff --git a/src/font.rs b/src/font.rsindex 16587be..3ac11c8 100644--- a/src/font.rs+++ b/src/font.rs@@ -213,9 +213,7 @@ impl Fonts {         }         let index = matched.face_index().unwrap_or(0) as isize;         let face = self.library.new_face(&path, index)?;-        // Bitmap-strike-only fonts reject an arbitrary pixel size; skip them-        // rather than guess a strike.-        if face.set_pixel_sizes(0, self.size_px).is_err() {+        if size_face(&face, self.size_px).is_err() {             return Ok(None);         }         self.faces.push(face);@@ -240,10 +238,43 @@ fn resolve_face(         .find(family, Some(style.fontconfig_style()))         .map_err(|_| FontError::NoFamily(family.to_owned()))?;     let face = library.new_face(&font.path, font.index.unwrap_or(0) as isize)?;-    face.set_pixel_sizes(0, size_px)?;+    size_face(&face, size_px)?;     Ok(face) } +/// Set a face to `size_px`. Scalable faces size directly; bitmap-strike faces+/// (e.g. colour-emoji fonts) cannot, so select the nearest available strike and+/// let the renderer scale its glyphs into the cell.+fn size_face(face: &Face, size_px: u32) -> Result<(), FontError> {+    match face.set_pixel_sizes(0, size_px) {+        Ok(()) => Ok(()),+        Err(_) if face.has_fixed_sizes() => {+            face.select_size(nearest_strike(face, size_px))?;+            Ok(())+        }+        Err(err) => Err(err.into()),+    }+}++/// Index of the fixed strike whose pixel height is closest to `target`.+fn nearest_strike(face: &Face, target: u32) -> i32 {+    let rec = face.raw();+    let target = i32::try_from(target).unwrap_or(i32::MAX);+    let mut best = 0;+    let mut best_delta = i32::MAX;+    for i in 0..rec.num_fixed_sizes {+        // SAFETY: `available_sizes` points to `num_fixed_sizes` valid+        // `FT_Bitmap_Size` entries for the face's lifetime; `i` is in range.+        let height = i32::from(unsafe { (*rec.available_sizes.offset(i as isize)).height });+        let delta = (height - target).abs();+        if delta < best_delta {+            best = i;+            best_delta = delta;+        }+    }+    best+}+ fn cell_metrics(face: &Face, family: &str) -> Result<CellMetrics, FontError> {     let metrics = face         .size_metrics()diff --git a/src/render.rs b/src/render.rsindex a449b63..9ea9bb4 100644--- a/src/render.rs+++ b/src/render.rs@@ -154,7 +154,7 @@ impl Renderer {                         cell.c,                         cell_style(cell),                         origin_x,-                        cell_top + m.ascent as i32,+                        cell_top,                         fg,                     );                 }@@ -194,14 +194,7 @@ impl Renderer {                 let cell = grid.cell(cx, cy);                 if cell.c != ' ' && !cell.flags.contains(Flags::WIDE_CONT) {                     let (_, bg) = cell_colors(cell);-                    self.draw_glyph(-                        canvas,-                        cell.c,-                        cell_style(cell),-                        x0,-                        top + m.ascent as i32,-                        bg,-                    );+                    self.draw_glyph(canvas, cell.c, cell_style(cell), x0, top, bg);                 }             }             CursorShape::Underline => {@@ -217,9 +210,10 @@ impl Renderer {         c: char,         style: Style,         origin_x: i32,-        baseline: i32,+        cell_top: i32,         fg: Rgb,     ) {+        let m = self.fonts.metrics();         let glyph = match self.fonts.glyph(c, style) {             Ok(glyph) => glyph,             Err(err) => {@@ -227,31 +221,39 @@ impl Renderer {                 return;             }         };-        let (left, top, w, h) = (-            glyph.left,-            glyph.top,-            glyph.width as i32,-            glyph.height as i32,-        );+        let (gw, gh) = (glyph.width as i32, glyph.height as i32);         match &glyph.data {             GlyphData::Mask(mask) => {-                for gy in 0..h {-                    for gx in 0..w {-                        let a = mask[(gy * w + gx) as usize];+                let baseline = cell_top + m.ascent as i32;+                for gy in 0..gh {+                    for gx in 0..gw {+                        let a = mask[(gy * gw + gx) as usize];                         if a != 0 {-                            canvas.blend(origin_x + left + gx, baseline - top + gy, fg, a);+                            canvas.blend(+                                origin_x + glyph.left + gx,+                                baseline - glyph.top + gy,+                                fg,+                                a,+                            );                         }                     }                 }             }-            GlyphData::Color(bgra) => {-                for gy in 0..h {-                    for gx in 0..w {-                        let i = ((gy * w + gx) * 4) as usize;-                        canvas.over(origin_x + left + gx, baseline - top + gy, &bgra[i..i + 4]);+            // Colour glyphs (emoji) come from a fixed strike at native size;+            // scale them to the line height with nearest-neighbour sampling.+            GlyphData::Color(bgra) if gh > 0 => {+                let scale = m.height as f32 / gh as f32;+                let target_w = (gw as f32 * scale) as i32;+                for ty in 0..m.height as i32 {+                    let sy = ((ty as f32 / scale) as i32).min(gh - 1);+                    for tx in 0..target_w {+                        let sx = ((tx as f32 / scale) as i32).min(gw - 1);+                        let i = ((sy * gw + sx) * 4) as usize;+                        canvas.over(origin_x + tx, cell_top + ty, &bgra[i..i + 4]);                     }                 }             }+            GlyphData::Color(_) => {}         }     } }