use swash::scale::{Render, ScaleContext, Source, StrikeWith};
use swash::zeno::Format;
use swash::{FontRef, GlyphId};
#[derive(Debug, Clone)]
pub struct GlyphBitmap {
pub width: u32,
pub height: u32,
pub left: i32,
pub top: i32,
pub pixels: Vec<u8>,
}
pub fn rasterize_glyph(
face_data: &[u8],
face_index: u32,
glyph_id: u32,
size_px: f32,
) -> Option<GlyphBitmap> {
let font = FontRef::from_index(face_data, face_index as usize)?;
let mut context = ScaleContext::new();
let mut scaler = context.builder(font).size(size_px).hint(true).build();
let image = Render::new(&[
Source::Outline,
Source::ColorOutline(0),
Source::ColorBitmap(StrikeWith::BestFit),
])
.format(Format::Alpha)
.render(&mut scaler, GlyphId::from(glyph_id as u16))?;
Some(GlyphBitmap {
width: image.placement.width,
height: image.placement.height,
left: image.placement.left,
top: image.placement.top,
pixels: image.data,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::canvas::text::font_database::FontDatabase;
use crate::canvas::text::shaper;
fn shape_single(ch: &str, size_px: f32) -> (shaper::Glyph, &'static [u8], u32) {
let db = FontDatabase::get();
let id = db.query("Arial", 400, false, "Linux").unwrap();
let (data, idx) = db.face_data(id).unwrap();
let run = shaper::shape(ch, data, idx, size_px);
assert!(
!run.glyphs.is_empty(),
"shaper produced no glyphs for {ch:?}"
);
(run.glyphs[0].clone(), data, idx)
}
#[test]
fn rasterizes_latin_glyph() {
let (glyph, data, idx) = shape_single("A", 24.0);
let bitmap = rasterize_glyph(data, idx, glyph.glyph_id, 24.0).expect("A should rasterize");
assert!(bitmap.width > 0 && bitmap.height > 0);
assert!(
(10..=35).contains(&(bitmap.height as i32)),
"24px A height {} out of range",
bitmap.height
);
let nonzero = bitmap.pixels.iter().filter(|&&p| p > 0).count();
assert!(nonzero > 0, "rasterized glyph has all-zero coverage");
}
#[test]
fn space_has_no_outline() {
let (glyph, data, idx) = shape_single(" ", 24.0);
let bitmap = rasterize_glyph(data, idx, glyph.glyph_id, 24.0);
match bitmap {
None => { }
Some(b) => {
let nonzero = b.pixels.iter().filter(|&&p| p > 0).count();
assert_eq!(nonzero, 0, "space glyph has ink");
}
}
}
#[test]
fn bitmap_height_scales_with_size() {
let (small_glyph, data, idx) = shape_single("H", 14.0);
let small = rasterize_glyph(data, idx, small_glyph.glyph_id, 14.0).unwrap();
let (big_glyph, _, _) = shape_single("H", 48.0);
let big = rasterize_glyph(data, idx, big_glyph.glyph_id, 48.0).unwrap();
assert!(
big.height > small.height * 2,
"48/14 should be ~3.4x; got {} / {}",
big.height,
small.height
);
}
}