use crate::core::engine::freetype::{
done_face, done_freetype, get_glyph_bitmap, get_glyph_metrics, init_freetype, load_char,
new_face, set_pixel_sizes, FT_Face, FT_Library,
};
use crate::core::engine::opengl::{
gl_bind_texture, gl_delete_texture, gl_gen_texture, gl_pixel_storei, gl_tex_image_2d,
gl_tex_parameteri, gl_tex_sub_image_2d, GL_CLAMP_TO_EDGE, GL_LINEAR, GL_RED, GL_TEXTURE_2D,
GL_TEXTURE_MAG_FILTER, GL_TEXTURE_MIN_FILTER, GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T,
GL_UNPACK_ALIGNMENT, GL_UNSIGNED_BYTE,
};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy)]
pub struct GlyphInfo {
pub uv_x: f32,
pub uv_y: f32,
pub uv_width: f32,
pub uv_height: f32,
pub width: i32,
pub height: i32,
pub bearing_x: i32,
pub bearing_y: i32,
pub advance: f32,
}
pub struct FontAtlas {
library: FT_Library,
face: FT_Face,
texture_id: u32,
atlas_width: u32,
atlas_height: u32,
cursor_x: u32,
cursor_y: u32,
row_height: u32,
glyphs: HashMap<char, GlyphInfo>,
font_size: u32,
}
impl FontAtlas {
pub fn new(font_path: &str, font_size: u32, atlas_size: u32) -> Result<Self, String> {
let library = init_freetype().map_err(|e| format!("Failed to init FreeType: {}", e))?;
let face =
new_face(library, font_path, 0).map_err(|e| format!("Failed to load font: {}", e))?;
set_pixel_sizes(face, 0, font_size)
.map_err(|e| format!("Failed to set font size: {}", e))?;
let texture_id = gl_gen_texture();
gl_bind_texture(GL_TEXTURE_2D, texture_id);
gl_tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
gl_tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
gl_tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
gl_tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
gl_pixel_storei(GL_UNPACK_ALIGNMENT, 1);
gl_tex_image_2d(
GL_TEXTURE_2D,
0,
GL_RED as i32,
atlas_size as i32,
atlas_size as i32,
0,
GL_RED,
GL_UNSIGNED_BYTE,
std::ptr::null(),
);
Ok(Self {
library,
face,
texture_id,
atlas_width: atlas_size,
atlas_height: atlas_size,
cursor_x: 0,
cursor_y: 0,
row_height: 0,
glyphs: HashMap::new(),
font_size,
})
}
pub fn texture_id(&self) -> u32 {
self.texture_id
}
pub fn get_glyph(&mut self, ch: char) -> Option<GlyphInfo> {
if let Some(&info) = self.glyphs.get(&ch) {
return Some(info);
}
self.cache_glyph(ch)
}
fn cache_glyph(&mut self, ch: char) -> Option<GlyphInfo> {
if load_char(self.face, ch).is_err() {
return None;
}
let metrics = get_glyph_metrics(self.face);
let (bitmap_ptr, _pitch) = get_glyph_bitmap(self.face);
if bitmap_ptr.is_null() || metrics.width == 0 || metrics.height == 0 {
let info = GlyphInfo {
uv_x: 0.0,
uv_y: 0.0,
uv_width: 0.0,
uv_height: 0.0,
width: 0,
height: 0,
bearing_x: metrics.bearing_x,
bearing_y: metrics.bearing_y,
advance: (metrics.advance >> 6) as f32, };
self.glyphs.insert(ch, info);
return Some(info);
}
let glyph_width = metrics.width as u32;
let glyph_height = metrics.height as u32;
if self.cursor_x + glyph_width > self.atlas_width {
self.cursor_x = 0;
self.cursor_y += self.row_height + 1; self.row_height = 0;
}
if self.cursor_y + glyph_height > self.atlas_height {
eprintln!("Font atlas is full!");
return None;
}
gl_bind_texture(GL_TEXTURE_2D, self.texture_id);
gl_pixel_storei(GL_UNPACK_ALIGNMENT, 1);
gl_tex_sub_image_2d(
GL_TEXTURE_2D,
0,
self.cursor_x as i32,
self.cursor_y as i32,
glyph_width as i32,
glyph_height as i32,
GL_RED,
GL_UNSIGNED_BYTE,
bitmap_ptr as *const std::ffi::c_void,
);
let uv_x = self.cursor_x as f32 / self.atlas_width as f32;
let uv_y = self.cursor_y as f32 / self.atlas_height as f32;
let uv_width = glyph_width as f32 / self.atlas_width as f32;
let uv_height = glyph_height as f32 / self.atlas_height as f32;
let info = GlyphInfo {
uv_x,
uv_y,
uv_width,
uv_height,
width: metrics.width,
height: metrics.height,
bearing_x: metrics.bearing_x,
bearing_y: metrics.bearing_y,
advance: (metrics.advance >> 6) as f32,
};
self.cursor_x += glyph_width + 1; self.row_height = self.row_height.max(glyph_height);
self.glyphs.insert(ch, info);
Some(info)
}
pub fn cache_ascii(&mut self) {
for ch in 32u8..127u8 {
self.get_glyph(ch as char);
}
}
pub fn measure_text(&mut self, text: &str) -> f32 {
let mut width = 0.0;
for ch in text.chars() {
if let Some(glyph) = self.get_glyph(ch) {
width += glyph.advance;
}
}
width
}
pub fn font_size(&self) -> u32 {
self.font_size
}
}
impl Drop for FontAtlas {
fn drop(&mut self) {
gl_delete_texture(self.texture_id);
done_face(self.face);
done_freetype(self.library);
}
}