use bevy::asset::{io::Reader, AssetLoader, LoadContext};
use bevy::prelude::*;
use thiserror::Error;
#[derive(Asset, TypePath, Debug)]
pub struct FontMesh {
pub data: Vec<u8>,
}
#[derive(Debug, Clone, Copy)]
pub struct GlyphMetrics {
pub advance: f32,
pub has_outline: bool,
}
#[derive(Debug, Clone, Copy)]
pub struct FontMetrics {
pub ascender: f32,
pub descender: f32,
pub line_gap: f32,
pub line_height: f32,
}
impl FontMesh {
pub fn glyph_metrics(&self, character: char) -> Option<GlyphMetrics> {
let font = fontmesh::Font::from_bytes(&self.data).ok()?;
let glyph = font.glyph_by_char(character).ok()?;
Some(GlyphMetrics {
advance: glyph.advance(),
has_outline: glyph.outline().is_ok(),
})
}
pub fn font_metrics(&self) -> Option<FontMetrics> {
let font = fontmesh::Font::from_bytes(&self.data).ok()?;
let ascender = font.ascender();
let descender = font.descender();
let line_gap = font.line_gap();
Some(FontMetrics {
ascender,
descender,
line_gap,
line_height: ascender - descender + line_gap,
})
}
pub fn text_width(&self, text: &str) -> f32 {
let font = match fontmesh::Font::from_bytes(&self.data) {
Ok(f) => f,
Err(_) => return 0.0,
};
text.chars()
.map(|ch| {
font.glyph_by_char(ch)
.map(|g| g.advance())
.unwrap_or_else(|_| {
if ch.is_whitespace() {
(font.ascender() - font.descender()) * 0.25
} else {
0.0
}
})
})
.sum()
}
pub fn char_positions(&self, text: &str) -> Vec<(usize, f32)> {
let font = match fontmesh::Font::from_bytes(&self.data) {
Ok(f) => f,
Err(_) => return Vec::new(),
};
text.chars()
.enumerate()
.scan(0.0, |x, (idx, ch)| {
let current_x = *x;
*x += font
.glyph_by_char(ch)
.map(|g| g.advance())
.unwrap_or_else(|_| {
if ch.is_whitespace() {
(font.ascender() - font.descender()) * 0.25
} else {
0.0
}
});
Some((idx, current_x))
})
.collect()
}
}
#[derive(Default)]
pub struct FontMeshLoader;
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum FontMeshLoaderError {
#[error("Could not load font file: {0}")]
Io(#[from] std::io::Error),
}
impl AssetLoader for FontMeshLoader {
type Asset = FontMesh;
type Settings = ();
type Error = FontMeshLoaderError;
async fn load(
&self,
reader: &mut dyn Reader,
_settings: &(),
_load_context: &mut LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
let mut data = Vec::new();
reader.read_to_end(&mut data).await?;
Ok(FontMesh { data })
}
fn extensions(&self) -> &[&str] {
&["ttf", "otf"]
}
}