forked from NotAShelf/beer
font: render colour emoji from bitmap strikes, scaled to the cell
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: If30e5f13ee24e691b417ad35c588a6226a6a6964
This commit is contained in:
parent
8e737dd2ff
commit
5682027a94
2 changed files with 62 additions and 29 deletions
39
src/font.rs
39
src/font.rs
|
|
@ -213,9 +213,7 @@ impl Fonts {
|
||||||
}
|
}
|
||||||
let index = matched.face_index().unwrap_or(0) as isize;
|
let index = matched.face_index().unwrap_or(0) as isize;
|
||||||
let face = self.library.new_face(&path, index)?;
|
let face = self.library.new_face(&path, index)?;
|
||||||
// Bitmap-strike-only fonts reject an arbitrary pixel size; skip them
|
if size_face(&face, self.size_px).is_err() {
|
||||||
// rather than guess a strike.
|
|
||||||
if face.set_pixel_sizes(0, self.size_px).is_err() {
|
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
self.faces.push(face);
|
self.faces.push(face);
|
||||||
|
|
@ -240,10 +238,43 @@ fn resolve_face(
|
||||||
.find(family, Some(style.fontconfig_style()))
|
.find(family, Some(style.fontconfig_style()))
|
||||||
.map_err(|_| FontError::NoFamily(family.to_owned()))?;
|
.map_err(|_| FontError::NoFamily(family.to_owned()))?;
|
||||||
let face = library.new_face(&font.path, font.index.unwrap_or(0) as isize)?;
|
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)
|
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> {
|
fn cell_metrics(face: &Face, family: &str) -> Result<CellMetrics, FontError> {
|
||||||
let metrics = face
|
let metrics = face
|
||||||
.size_metrics()
|
.size_metrics()
|
||||||
|
|
|
||||||
|
|
@ -154,7 +154,7 @@ impl Renderer {
|
||||||
cell.c,
|
cell.c,
|
||||||
cell_style(cell),
|
cell_style(cell),
|
||||||
origin_x,
|
origin_x,
|
||||||
cell_top + m.ascent as i32,
|
cell_top,
|
||||||
fg,
|
fg,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -194,14 +194,7 @@ impl Renderer {
|
||||||
let cell = grid.cell(cx, cy);
|
let cell = grid.cell(cx, cy);
|
||||||
if cell.c != ' ' && !cell.flags.contains(Flags::WIDE_CONT) {
|
if cell.c != ' ' && !cell.flags.contains(Flags::WIDE_CONT) {
|
||||||
let (_, bg) = cell_colors(cell);
|
let (_, bg) = cell_colors(cell);
|
||||||
self.draw_glyph(
|
self.draw_glyph(canvas, cell.c, cell_style(cell), x0, top, bg);
|
||||||
canvas,
|
|
||||||
cell.c,
|
|
||||||
cell_style(cell),
|
|
||||||
x0,
|
|
||||||
top + m.ascent as i32,
|
|
||||||
bg,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CursorShape::Underline => {
|
CursorShape::Underline => {
|
||||||
|
|
@ -217,9 +210,10 @@ impl Renderer {
|
||||||
c: char,
|
c: char,
|
||||||
style: Style,
|
style: Style,
|
||||||
origin_x: i32,
|
origin_x: i32,
|
||||||
baseline: i32,
|
cell_top: i32,
|
||||||
fg: Rgb,
|
fg: Rgb,
|
||||||
) {
|
) {
|
||||||
|
let m = self.fonts.metrics();
|
||||||
let glyph = match self.fonts.glyph(c, style) {
|
let glyph = match self.fonts.glyph(c, style) {
|
||||||
Ok(glyph) => glyph,
|
Ok(glyph) => glyph,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
|
@ -227,31 +221,39 @@ impl Renderer {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let (left, top, w, h) = (
|
let (gw, gh) = (glyph.width as i32, glyph.height as i32);
|
||||||
glyph.left,
|
|
||||||
glyph.top,
|
|
||||||
glyph.width as i32,
|
|
||||||
glyph.height as i32,
|
|
||||||
);
|
|
||||||
match &glyph.data {
|
match &glyph.data {
|
||||||
GlyphData::Mask(mask) => {
|
GlyphData::Mask(mask) => {
|
||||||
for gy in 0..h {
|
let baseline = cell_top + m.ascent as i32;
|
||||||
for gx in 0..w {
|
for gy in 0..gh {
|
||||||
let a = mask[(gy * w + gx) as usize];
|
for gx in 0..gw {
|
||||||
|
let a = mask[(gy * gw + gx) as usize];
|
||||||
if a != 0 {
|
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) => {
|
// Colour glyphs (emoji) come from a fixed strike at native size;
|
||||||
for gy in 0..h {
|
// scale them to the line height with nearest-neighbour sampling.
|
||||||
for gx in 0..w {
|
GlyphData::Color(bgra) if gh > 0 => {
|
||||||
let i = ((gy * w + gx) * 4) as usize;
|
let scale = m.height as f32 / gh as f32;
|
||||||
canvas.over(origin_x + left + gx, baseline - top + gy, &bgra[i..i + 4]);
|
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(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue