use etagere::{AllocId, AtlasAllocator};
use fontdue::{
layout::{CoordinateSystem, Layout, LayoutSettings, TextStyle},
Font,
};
use glam::{ivec2, IVec2};
use image::RgbaImage;
use std::collections::HashMap;
use crate::{
elements::{rect::Aabb, BindableTexture, Rect, Texture},
modules::{
arenas::{Key, OwnedKey},
Arenas, GraphicsContext, Prepare, Renderer,
},
Dependencies, Handle, Module,
};
#[derive(Debug, Dependencies)]
pub struct Deps {
ctx: Handle<GraphicsContext>,
renderer: Handle<Renderer>,
arenas: Handle<Arenas>,
}
const ATLAS_SIZE: u32 = 4096;
pub struct FontCache {
deps: Deps,
atlas_texture: OwnedKey<BindableTexture>,
atlas_allocator: AtlasAllocator,
default_font: OwnedKey<Font>,
glyphs: HashMap<GlyphKey, Glyph>,
texture_writes: Vec<GlyphKey>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct GlyphKey {
font: Key<fontdue::Font>,
font_size: FontSize,
char: char,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct FontSize(pub u32);
impl From<f32> for FontSize {
fn from(value: f32) -> Self {
if value < 0.0 {
panic!("Cannot create Fontsize from negative number")
}
Self(value as u32)
}
}
impl From<u32> for FontSize {
fn from(value: u32) -> Self {
Self(value)
}
}
impl From<FontSize> for f32 {
fn from(value: FontSize) -> Self {
value.0 as f32
}
}
struct Glyph {
_alloc_id: AllocId,
metrics: fontdue::Metrics,
bitmap: Vec<u8>,
offset_in_atlas: IVec2,
atlas_uv: Aabb,
}
impl Module for FontCache {
type Config = ();
type Dependencies = Deps;
fn new(_config: Self::Config, mut deps: Self::Dependencies) -> anyhow::Result<Self> {
const DEFAULT_FONT_BYTES: &[u8] = include_bytes!("../../../assets/Oswald-Medium.ttf");
let default_font = fontdue::Font::from_bytes(DEFAULT_FONT_BYTES, Default::default())
.expect("could not load default font");
let default_font = deps.arenas.insert(default_font);
let atlas_width: u32 = ATLAS_SIZE;
let atlas_height: u32 = ATLAS_SIZE;
let atlas_allocator =
AtlasAllocator::new(etagere::size2(atlas_width as i32, atlas_height as i32));
let image = RgbaImage::new(atlas_width, atlas_height);
let atlas_texture = Texture::from_image(&deps.ctx.device, &deps.ctx.queue, &image);
let atlas_texture = BindableTexture::new(&deps.ctx.device, atlas_texture);
let atlas_texture = deps.arenas.insert(atlas_texture);
Ok(FontCache {
deps,
default_font,
atlas_texture,
atlas_allocator,
glyphs: HashMap::new(),
texture_writes: vec![],
})
}
fn intialize(handle: Handle<Self>) -> anyhow::Result<()> {
let mut renderer = handle.deps.renderer;
renderer.register_prepare(handle);
Ok(())
}
}
impl Prepare for FontCache {
fn prepare(
&mut self,
_device: &wgpu::Device,
queue: &wgpu::Queue,
_encoder: &mut wgpu::CommandEncoder,
) {
let atlas_texture = &self.deps.arenas[&self.atlas_texture];
for key in self.texture_writes.iter() {
let glyph = self.glyphs.get(key).unwrap();
let glyph_image = glyph_to_rgba_image(glyph);
update_texture_region(
&atlas_texture.texture,
&glyph_image,
glyph.offset_in_atlas,
queue,
);
}
self.texture_writes.clear();
}
}
impl FontCache {
pub fn default_font(&self) -> Key<Font> {
self.default_font.key()
}
pub fn atlas_texture(&self) -> Key<BindableTexture> {
self.atlas_texture.key()
}
fn get_glyph_atlas_uv_or_rasterize(&mut self, key: GlyphKey) -> Option<Aabb> {
if let Some(glyph) = self.glyphs.get_mut(&key) {
return Some(glyph.atlas_uv);
}
let font = &self.deps.arenas[key.font];
let (metrics, bitmap) = font.rasterize(key.char, key.font_size.into());
debug_assert_eq!(bitmap.len(), metrics.width * metrics.height);
if metrics.height == 0 || metrics.width == 0 {
return None;
}
const PAD_PX: usize = 2;
let allocation = self
.atlas_allocator
.allocate(etagere::size2(
(metrics.width + 2 * PAD_PX) as i32,
(metrics.height + 2 * PAD_PX) as i32,
))
.expect("Allocation in atlas allocator failed!");
let offset_in_atlas = ivec2(
allocation.rectangle.min.x + PAD_PX as i32,
allocation.rectangle.min.y + PAD_PX as i32,
);
let uv = Aabb::new(
offset_in_atlas.x as f32 / ATLAS_SIZE as f32,
offset_in_atlas.y as f32 / ATLAS_SIZE as f32,
(offset_in_atlas.x + metrics.width as i32) as f32 / ATLAS_SIZE as f32,
(offset_in_atlas.y + metrics.height as i32) as f32 / ATLAS_SIZE as f32,
);
self.texture_writes.push(key);
let glyph = Glyph {
metrics,
bitmap,
offset_in_atlas,
atlas_uv: uv,
_alloc_id: allocation.id,
};
self.glyphs.insert(key, glyph);
Some(uv)
}
pub fn perform_text_layout(
&mut self,
text: &str,
font_size: FontSize,
layout_font_size: f32,
layout_settings: &LayoutSettings,
font: Option<Key<Font>>,
) -> TextLayoutResult {
let font = font.unwrap_or_else(|| self.default_font.key());
let font_obj = &self.deps.arenas[font];
let mut layout: Layout<()> = Layout::new(CoordinateSystem::PositiveYDown);
layout.reset(layout_settings);
layout.append(
&[font_obj],
&TextStyle {
text,
px: layout_font_size,
font_index: 0,
user_data: (),
},
);
let mut glyph_pos_and_atlas_uv: Vec<(Aabb, Aabb)> = vec![];
let mut max_x: f32 = layout_settings.x; let mut max_y: f32 = layout_settings.y;
for glyph_pos in layout.glyphs() {
let key = GlyphKey {
font,
font_size,
char: glyph_pos.parent,
};
let Some(glyph_uv) = self.get_glyph_atlas_uv_or_rasterize(key) else {
continue;
};
max_x = max_x.max(glyph_pos.x + glyph_pos.width as f32);
max_y = max_y.max(glyph_pos.y + glyph_pos.height as f32);
let pos = Aabb::new(
glyph_pos.x,
glyph_pos.y,
glyph_pos.x + glyph_pos.width as f32,
glyph_pos.y + glyph_pos.height as f32,
);
glyph_pos_and_atlas_uv.push((pos, glyph_uv));
}
TextLayoutResult {
glyph_pos_and_atlas_uv,
total_rect: Rect::new(
layout_settings.x,
layout_settings.y,
max_x - layout_settings.x,
max_y - layout_settings.y,
),
}
}
}
#[derive(Debug)]
pub struct TextLayoutResult {
pub glyph_pos_and_atlas_uv: Vec<(Aabb, Aabb)>,
pub total_rect: Rect,
}
fn update_texture_region(texture: &Texture, image: &RgbaImage, offset: IVec2, queue: &wgpu::Queue) {
let size = wgpu::Extent3d {
width: image.width(),
height: image.height(),
depth_or_array_layers: 1,
};
queue.write_texture(
wgpu::ImageCopyTexture {
aspect: wgpu::TextureAspect::All,
texture: &texture.texture,
mip_level: 0,
origin: wgpu::Origin3d {
x: offset.x as u32,
y: offset.y as u32,
z: 0,
},
},
image,
wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(4 * image.width()),
rows_per_image: None,
},
size,
);
}
fn glyph_to_rgba_image(glyph: &Glyph) -> RgbaImage {
let mut image = RgbaImage::new(glyph.metrics.width as u32, glyph.metrics.height as u32);
for (pix, v) in image.pixels_mut().zip(glyph.bitmap.iter()) {
let pixel = image::Rgba([*v, *v, *v, *v]);
*pix = pixel;
}
image
}