use std::{fs, path::Path};
use anyhow::{Result, anyhow};
use glam::{UVec2, Vec2};
use hashbrown::HashMap;
use crate::{
handle::Handle,
text::{Text, TextSection},
};
use fontdue::layout::{CoordinateSystem, Layout, LayoutSettings, TextStyle};
use image::{DynamicImage, ImageBuffer, Rgba};
const DEFAULT_FONT_SIZE: f32 = 40.0;
#[derive(Clone)]
pub struct Font {
pub inner: fontdue::Font,
}
impl Font {
pub fn from_bytes(bytes: Vec<u8>) -> Option<Self> {
Self::from_bytes_with_size(bytes, DEFAULT_FONT_SIZE)
}
pub fn from_bytes_with_size(bytes: Vec<u8>, font_size: f32) -> Option<Self> {
let inner = fontdue::Font::from_bytes(
bytes,
fontdue::FontSettings {
scale: font_size,
..Default::default()
},
)
.ok()?;
Some(Self { inner })
}
pub fn open_with_size<P: AsRef<Path>>(path: P, font_size: f32) -> Result<Self> {
let bytes = fs::read(path)?;
let font =
Self::from_bytes_with_size(bytes, font_size).ok_or_else(|| anyhow!("Invalid font"))?;
Ok(font)
}
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
Self::open_with_size(path, DEFAULT_FONT_SIZE)
}
}
pub struct FontConfig {
pub scale: f32,
pub offset: Vec2,
pub oversample_scale: f32,
}
impl Default for FontConfig {
fn default() -> Self {
Self {
scale: 1.0,
offset: Vec2::ZERO,
oversample_scale: 2.0,
}
}
}
#[derive(Default)]
pub struct FontManager {
pub(crate) fonts: HashMap<u64, Font>,
pub(crate) default_font: Option<Handle>,
pub(crate) font_config: FontConfig,
}
impl FontManager {
pub fn get_font(&self, handle: Option<&Handle>) -> Option<&Font> {
let h = handle.or(self.default_font.as_ref())?;
self.fonts.get(&h.id())
}
pub fn add_font(&mut self, handle_id: u64, font: Font) {
self.fonts.insert(handle_id, font);
}
pub fn remove_font(&mut self, handle_id: u64) {
self.fonts.remove(&handle_id);
}
pub(crate) fn set_default_font(&mut self, handle: Handle) {
self.default_font.replace(handle);
}
pub(crate) fn set_font_config(&mut self, config: FontConfig) {
self.font_config = config;
}
pub(crate) fn render_text_texture(
&self,
text: &Text,
dpi_scale_factor: f32,
) -> (ImageBuffer<Rgba<u8>, Vec<u8>>, UVec2) {
let Text {
sections,
max_width,
max_height,
wrap_style,
..
} = text;
let sample_scale_factor = dpi_scale_factor * self.font_config.oversample_scale;
let inv_sample_scale_factor = sample_scale_factor.recip();
let mut layout = Layout::new(CoordinateSystem::PositiveYDown);
layout.reset(&LayoutSettings {
x: 0.0,
y: 0.0,
max_height: max_height.map(|h| h * sample_scale_factor),
max_width: max_width.map(|w| w * sample_scale_factor),
wrap_style: (*wrap_style).into(),
..Default::default()
});
let mut fonts = Vec::with_capacity(sections.len());
for TextSection {
text,
font,
scale,
color,
} in sections
{
let Some(font) = self.get_font(font.as_ref()) else {
log::error!("Cannot find font {:?} when render text", font.as_ref());
continue;
};
let font_index = fonts.len();
fonts.push(&font.inner);
let scale = scale * self.font_config.scale * sample_scale_factor;
layout.append(
&fonts,
&TextStyle::with_user_data(text, scale, font_index, color),
);
}
let glyphs = layout.glyphs();
let height = layout.height().ceil() as u32;
let mut width = glyphs
.iter()
.filter(|g| !g.parent.is_control())
.map(|g| g.x.ceil() as usize + g.width)
.max()
.map(|w| w as f32)
.unwrap_or(0.0) as u32;
if width == 0 {
width = 1;
}
let mut image = DynamicImage::new_rgba8(width, height).to_rgba8();
for glyph in glyphs.iter().filter(|g| !g.parent.is_control()) {
let font = &fonts[glyph.font_index];
let (metrics, bitmap) = font.rasterize(glyph.parent, glyph.key.px);
for y in 0..metrics.height {
for x in 0..metrics.width {
let alpha = bitmap[y * metrics.width + x];
let x = (glyph.x as i32 + x as i32).max(0) as u32;
let y = (glyph.y as i32 + y as i32).max(0) as u32;
if let Some(pixel) = image.get_pixel_mut_checked(x, y) {
let (r, g, b, _a) = glyph.user_data.to_rgba_u8();
*pixel = Rgba([r, g, b, alpha]);
}
}
}
}
let size = (UVec2::new(image.width(), image.height()).as_vec2() * inv_sample_scale_factor)
.as_uvec2();
(image, size)
}
}