use ab_glyph::Font;
use ab_glyph::{FontVec, ScaleFont};
use fontdb::{Database, Family, Query};
pub struct TextRender {
font: FontVec,
db: Database,
}
impl TextRender {
pub fn new() -> Self {
let mut db = Database::new();
db.load_system_fonts();
let source = include_bytes!("maple.ttf");
let font = FontVec::try_from_vec(source.to_vec()).unwrap();
Self { font, db }
}
pub fn has_family(&self, family: &str) -> bool {
let query = Query {
families: &[Family::Name(family), Family::SansSerif],
..Default::default()
};
self.db.query(&query).is_some()
}
pub fn load_font(&mut self, famile_name: &str) {
let query = Query {
families: &[Family::Name(famile_name), Family::SansSerif],
..Default::default()
};
if let Some(id) = self.db.query(&query) {
self.db.with_face_data(id, |source, index| {
self.font = FontVec::try_from_vec_and_index(source.to_vec(), index).unwrap();
});
} else {
self.load_default_font();
}
}
pub fn draw(
&self, pixmap: &mut tiny_skia::Pixmap, text: &str, x: f32, y: f32, size: f32,
color: tiny_skia::Color,
) {
let font = &self.font;
let scale = ab_glyph::PxScale::from(size);
let scaled_font = font.as_scaled(scale);
let adjusted_y = y + scaled_font.ascent();
let mut x_cursor = x;
let img_w = pixmap.width();
let img_h = pixmap.height();
let pixels = pixmap.pixels_mut();
let r_f = color.red();
let g_f = color.green();
let b_f = color.blue();
let a_f = color.alpha();
for c in text.chars() {
let glyph_id = font.glyph_id(c);
let glyph = glyph_id.with_scale_and_position(
size,
ab_glyph::Point {
x: x_cursor,
y: adjusted_y,
},
);
if let Some(outlined) = font.outline_glyph(glyph) {
let bounds = outlined.px_bounds();
outlined.draw(|ox, oy, coverage| {
if coverage > 0.0 {
let px = (bounds.min.x as i32) + ox as i32;
let py = (bounds.min.y as i32) + oy as i32;
if px >= 0 && py >= 0 && (px as u32) < img_w && (py as u32) < img_h {
let idx = (py as u32 * img_w + px as u32) as usize;
let old_px = pixels[idx];
let alpha = coverage * a_f;
let inv_alpha = 1.0 - alpha;
let out_r = (r_f * alpha + (old_px.red() as f32 / 255.0) * inv_alpha).clamp(0.0, 1.0);
let out_g =
(g_f * alpha + (old_px.green() as f32 / 255.0) * inv_alpha).clamp(0.0, 1.0);
let out_b =
(b_f * alpha + (old_px.blue() as f32 / 255.0) * inv_alpha).clamp(0.0, 1.0);
let out_a = (alpha + (old_px.alpha() as f32 / 255.0) * inv_alpha).clamp(0.0, 1.0);
if let Some(new_color) = tiny_skia::Color::from_rgba(out_r, out_g, out_b, out_a) {
pixels[idx] = new_color.premultiply().to_color_u8();
}
}
}
});
}
x_cursor += scaled_font.h_advance(glyph_id);
}
}
}
impl TextRender {
fn load_default_font(&mut self) {
let source = include_bytes!("maple.ttf");
match FontVec::try_from_vec(source.to_vec()) {
Ok(font) => self.font = font,
Err(e) => println!("Error loading font: {}", e),
}
}
}
#[test]
fn test_has_font() {
let tr = TextRender::new();
assert!(tr.has_family("Noto Serif Test"));
assert!(!tr.has_family("FiraCode"));
}