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 face = fontmesh::parse_font(&self.data).ok()?;
let advance = fontmesh::glyph_advance(&face, character)?;
let has_outline = fontmesh::char_to_mesh_2d(&face, character, 1).is_ok();
Some(GlyphMetrics {
advance,
has_outline,
})
}
pub fn font_metrics(&self) -> Option<FontMetrics> {
let face = fontmesh::parse_font(&self.data).ok()?;
let ascender = fontmesh::ascender(&face);
let descender = fontmesh::descender(&face);
let line_gap = fontmesh::line_gap(&face);
Some(FontMetrics {
ascender,
descender,
line_gap,
line_height: ascender - descender + line_gap,
})
}
pub fn text_width(&self, text: &str) -> f32 {
let face = match fontmesh::parse_font(&self.data) {
Ok(f) => f,
Err(_) => return 0.0,
};
text.chars()
.map(|ch| {
fontmesh::glyph_advance(&face, ch).unwrap_or_else(|| {
if ch.is_whitespace() {
(fontmesh::ascender(&face) - fontmesh::descender(&face)) * 0.25
} else {
0.0
}
})
})
.sum()
}
pub fn char_positions(&self, text: &str) -> Vec<(usize, f32)> {
let face = match fontmesh::parse_font(&self.data) {
Ok(f) => f,
Err(_) => return Vec::new(),
};
text.chars()
.enumerate()
.scan(0.0, |x, (idx, ch)| {
let current_x = *x;
*x += fontmesh::glyph_advance(&face, ch).unwrap_or_else(|| {
if ch.is_whitespace() {
(fontmesh::ascender(&face) - fontmesh::descender(&face)) * 0.25
} else {
0.0
}
});
Some((idx, current_x))
})
.collect()
}
}
#[derive(Default, TypePath)]
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"]
}
}