use ab_glyph::{FontVec, Glyph, InvalidFont, OutlinedGlyph, Point, PxScale, ScaleFont}; use bevy_math::Vec2; use bevy_render::{ color::Color, texture::{Texture, TextureFormat}, }; use bevy_type_registry::TypeUuid; #[derive(Debug, TypeUuid)] #[uuid = "97059ac6-c9ba-4da9-95b6-bed82c3ce198"] pub struct Font { pub font: FontVec, } unsafe impl Send for Font {} unsafe impl Sync for Font {} impl Font { pub fn try_from_bytes(font_data: Vec) -> Result { let font = FontVec::try_from_vec(font_data)?; Ok(Font { font }) } pub fn get_outlined_glyph_texture(outlined_glyph: OutlinedGlyph) -> Texture { let bounds = outlined_glyph.px_bounds(); let width = bounds.width() as usize; let height = bounds.height() as usize; let mut alpha = vec![0.0; width * height]; outlined_glyph.draw(|x, y, v| { alpha[y as usize * width + x as usize] = v; }); // TODO: make this texture grayscale let color = Color::WHITE; let color_u8 = [ (color.r() * 255.0) as u8, (color.g() * 255.0) as u8, (color.b() * 255.0) as u8, ]; Texture::new( Vec2::new(width as f32, height as f32), alpha .iter() .map(|a| { vec![ color_u8[0], color_u8[1], color_u8[2], (color.a() * a * 255.0) as u8, ] }) .flatten() .collect::>(), TextureFormat::Rgba8UnormSrgb, ) } // adapted from ab_glyph example: https://github.com/alexheretic/ab-glyph/blob/master/dev/examples/image.rs pub fn render_text( &self, text: &str, color: Color, font_size: f32, width: usize, height: usize, ) -> Texture { let scale = PxScale::from(font_size); let scaled_font = ab_glyph::Font::as_scaled(&self.font, scale); let mut glyphs = Vec::new(); layout_paragraph( scaled_font, ab_glyph::point(0.0, 0.0), width as f32, text, &mut glyphs, ); let color_u8 = [ (color.r() * 255.0) as u8, (color.g() * 255.0) as u8, (color.b() * 255.0) as u8, ]; // TODO: this offset is a bit hackey let mut alpha = vec![0.0; width * height]; for glyph in glyphs { if let Some(outlined) = scaled_font.outline_glyph(glyph) { let bounds = outlined.px_bounds(); // Draw the glyph into the image per-pixel by using the draw closure outlined.draw(|x, y, v| { // Offset the position by the glyph bounding box // Turn the coverage into an alpha value (blended with any previous) let offset_x = x as usize + bounds.min.x as usize; let offset_y = y as usize + bounds.min.y as usize; if offset_x >= width || offset_y >= height { return; } alpha[offset_y * width + offset_x] = v; }); } } Texture::new( Vec2::new(width as f32, height as f32), alpha .iter() .map(|a| { vec![ color_u8[0], color_u8[1], color_u8[2], (color.a() * a * 255.0) as u8, ] }) .flatten() .collect::>(), TextureFormat::Rgba8UnormSrgb, ) } } fn layout_paragraph( font: SF, position: Point, max_width: f32, text: &str, target: &mut Vec, ) where F: ab_glyph::Font, SF: ScaleFont, { let v_advance = font.height() + font.line_gap(); let mut caret = position + ab_glyph::point(0.0, font.ascent()); let mut last_glyph: Option = None; for c in text.chars() { if c.is_control() { if c == '\n' { caret = ab_glyph::point(position.x, caret.y + v_advance); last_glyph = None; } continue; } let mut glyph = font.scaled_glyph(c); if let Some(previous) = last_glyph.take() { caret.x += font.kern(previous.id, glyph.id); } glyph.position = caret; last_glyph = Some(glyph.clone()); caret.x += font.h_advance(glyph.id); if !c.is_whitespace() && caret.x > position.x + max_width { caret = ab_glyph::point(position.x, caret.y + v_advance); glyph.position = caret; last_glyph = None; } target.push(glyph); } }