clone
ssh://git@git.notashelf.dev:33/notashelf/beer.gitCommit 5682027a947c
tarballunverified2 files changed+62-29
@@ -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()@@ -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(_) => {} } } }