diff --git a/kubi-ui/src/draw.rs b/kubi-ui/src/draw.rs index 2b539b0..7113536 100644 --- a/kubi-ui/src/draw.rs +++ b/kubi-ui/src/draw.rs @@ -1,6 +1,5 @@ use std::borrow::Cow; use glam::{Vec2, Vec4, vec2}; -use crate::text::TextRenderer; #[derive(Clone, Debug, PartialEq)] pub enum UiDrawCommand { @@ -62,7 +61,7 @@ pub struct UiDrawPlan { } impl UiDrawPlan { - pub fn build(calls: &UiDrawCommands, tr: &mut TextRenderer) -> Self { + pub fn build(calls: &UiDrawCommands) -> Self { let mut call = UiDrawCall::default(); for command in &calls.commands { match command { diff --git a/kubi-ui/src/lib.rs b/kubi-ui/src/lib.rs index 7cff89c..b2a2da4 100644 --- a/kubi-ui/src/lib.rs +++ b/kubi-ui/src/lib.rs @@ -13,7 +13,6 @@ use element::UiElement; use state::StateRepo; use event::UiEvent; use draw::{UiDrawCommands, UiDrawPlan}; -use text::TextRenderer; // pub struct ElementContext<'a> { // pub state: &'a mut StateRepo, @@ -29,7 +28,7 @@ pub struct KubiUi { draw_commands: UiDrawCommands, draw_plan: UiDrawPlan, draw_plan_modified: bool, - font_renderer: TextRenderer, + // ftm: FontTextureManager, } impl KubiUi { @@ -43,7 +42,7 @@ impl KubiUi { draw_commands: UiDrawCommands::default(), draw_plan: UiDrawPlan::default(), draw_plan_modified: false, - font_renderer: TextRenderer::default(), + // ftm: FontTextureManager::default(), } } @@ -67,7 +66,7 @@ impl KubiUi { if self.draw_commands.commands == self.prev_draw_commands.commands { return } - self.draw_plan = UiDrawPlan::build(&self.draw_commands, &mut self.font_renderer); + self.draw_plan = UiDrawPlan::build(&self.draw_commands); self.draw_plan_modified = true; } diff --git a/kubi-ui/src/text.rs b/kubi-ui/src/text.rs index a47b984..82e5891 100644 --- a/kubi-ui/src/text.rs +++ b/kubi-ui/src/text.rs @@ -1,90 +1,2 @@ -use std::sync::Arc; -use fontdue::{Font, Metrics}; -use glam::{IVec2, UVec2, uvec2, ivec2}; -use hashbrown::HashMap; -use rect_packer::DensePacker; - -#[cfg(feature = "builtin_font")] -const BIN_FONT: &[u8] = include_bytes!("../assets/font/ProggyTiny.ttf"); - -#[derive(Clone, Copy, PartialEq, Eq, Hash)] -pub struct FontHandle(pub(crate) usize); - -#[cfg(feature = "builtin_font")] -pub const BUILTIN_FONT: FontHandle = FontHandle(0); - -#[derive(PartialEq, Eq, Hash)] -struct GlyphCacheKey { - font_index: usize, - character: char, - size: u8, -} - -struct GlyphCacheEntry { - pub data: Vec, - pub metrics: Metrics, - pub texture_position: IVec2, - pub texture_size: UVec2, -} - -pub struct TextRenderer { - fonts: Vec, - glyph_cache: HashMap>, - packer: DensePacker, - font_texture: Vec, - font_texture_size: UVec2, -} - -impl TextRenderer { - pub fn new(size: UVec2) -> Self { - let mut renderer = TextRenderer { - fonts: Vec::new(), - glyph_cache: HashMap::new(), - packer: DensePacker::new(size.x as i32, size.y as i32), - font_texture: vec![0; (size.x * size.y) as usize], - font_texture_size: size, - }; - #[cfg(feature = "builtin_font")] - { - let font = Font::from_bytes(BIN_FONT, fontdue::FontSettings::default()).unwrap(); - renderer.add_font(font); - } - renderer - } - - /// Add a (fontdue) font to the renderer. - pub fn add_font(&mut self, font: Font) -> FontHandle { - self.fonts.push(font); - FontHandle(self.fonts.len() - 1) - } - - /// Either looks up the glyph in the cache or renders it and adds it to the cache. - pub fn glyph(&mut self, font: FontHandle, character: char, size: u8) -> Arc { - let key = GlyphCacheKey { - font_index: font.0, - character, - size, - }; - if let Some(entry) = self.glyph_cache.get(&key) { - return Arc::clone(entry); - } - let font = &self.fonts[key.font_index]; - let (metrics, bitmap) = font.rasterize(character, size as f32); - let texture_position = self.packer.pack(metrics.width as i32, metrics.height as i32, false).unwrap(); - let texture_size = uvec2(metrics.width as u32, metrics.height as u32); - let entry = Arc::new(GlyphCacheEntry { - data: bitmap, - metrics, - texture_position: ivec2(texture_position.x, texture_position.y), - texture_size, - }); - self.glyph_cache.insert_unique_unchecked(key, Arc::clone(&entry)); - entry - } -} - -impl Default for TextRenderer { - fn default() -> Self { - Self::new(uvec2(2048, 2048)) - } -} +pub mod font; +pub mod ftm; diff --git a/kubi-ui/src/text/font.rs b/kubi-ui/src/text/font.rs new file mode 100644 index 0000000..fb8d6dc --- /dev/null +++ b/kubi-ui/src/text/font.rs @@ -0,0 +1,38 @@ +use fontdue::Font; + +#[cfg(feature = "builtin_font")] +const BIN_FONT: &[u8] = include_bytes!("../../assets/font/ProggyTiny.ttf"); + +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub struct FontHandle(pub(crate) usize); + +#[cfg(feature = "builtin_font")] +pub const BUILTIN_FONT: FontHandle = FontHandle(0); + +pub struct FontManager { + fonts: Vec, +} + +impl FontManager { + pub fn new() -> Self { + let mut this = Self { + fonts: Vec::new(), + }; + #[cfg(feature = "builtin_font")] + { + let font = Font::from_bytes(BIN_FONT, fontdue::FontSettings::default()).unwrap(); + this.add_font(font); + }; + this + } + + /// Add a (fontdue) font to the renderer. + pub fn add_font(&mut self, font: Font) -> FontHandle { + self.fonts.push(font); + FontHandle(self.fonts.len() - 1) + } + + pub fn get(&self, handle: FontHandle) -> Option<&Font> { + self.fonts.get(handle.0) + } +} diff --git a/kubi-ui/src/text/ftm.rs b/kubi-ui/src/text/ftm.rs new file mode 100644 index 0000000..4baea0d --- /dev/null +++ b/kubi-ui/src/text/ftm.rs @@ -0,0 +1,96 @@ +use std::sync::Arc; +use fontdue::{Font, Metrics}; +use glam::{IVec2, UVec2, uvec2, ivec2}; +use hashbrown::HashMap; +use rect_packer::DensePacker; + +use super::font::{FontHandle, FontManager}; + + + +#[derive(PartialEq, Eq, Hash)] +struct GlyphCacheKey { + font_index: usize, + character: char, + size: u8, +} + +struct GlyphCacheEntry { + pub data: Vec, + pub metrics: Metrics, + pub position: IVec2, + pub size: UVec2, +} + +pub struct FontTextureManager { + glyph_cache: HashMap>, + packer: DensePacker, + font_texture: Vec, + font_texture_size: UVec2, + modified: bool, +} + +impl FontTextureManager { + pub fn new(size: UVec2) -> Self { + let mut renderer = FontTextureManager { + glyph_cache: HashMap::new(), + packer: DensePacker::new(size.x as i32, size.y as i32), + font_texture: vec![0; (size.x * size.y) as usize], + font_texture_size: size, + modified: false, + }; + renderer + } + + /// Either looks up the glyph in the cache or renders it and adds it to the cache. + fn glyph_allocate(&mut self, font_manager: &FontManager, font_handle: FontHandle, character: char, size: u8) -> (bool, Arc) { + let key = GlyphCacheKey { + font_index: font_handle.0, + character, + size, + }; + if let Some(entry) = self.glyph_cache.get(&key) { + return (false, Arc::clone(entry)); + } + let font = font_manager.get(font_handle).unwrap(); + let (metrics, bitmap) = font.rasterize(character, size as f32); + let texture_position = self.packer.pack(metrics.width as i32, metrics.height as i32, false).unwrap(); + let texture_size = uvec2(metrics.width as u32, metrics.height as u32); + let entry = Arc::new(GlyphCacheEntry { + data: bitmap, + metrics, + position: ivec2(texture_position.x, texture_position.y), + size: texture_size, + }); + self.glyph_cache.insert_unique_unchecked(key, Arc::clone(&entry)); + (true, entry) + } + + /// Place glyph onto the font texture. + fn glyph_place(&mut self, entry: &GlyphCacheEntry) { + let tex_size = self.font_texture_size; + let GlyphCacheEntry { size, position, .. } = entry; + for y in 0..size.y { + for x in 0..size.x { + let src = (size.x * y + x) as usize; + let dst = (tex_size.x * (y + position.y as u32) + (x + position.x as u32)) as usize; + self.font_texture[dst] = entry.data[src]; + } + } + } + + pub fn glyph(&mut self, font_manager: &FontManager, font_handle: FontHandle, character: char, size: u8) -> Arc { + let (is_new, glyph) = self.glyph_allocate(font_manager, font_handle, character, size); + if is_new { + self.glyph_place(&glyph); + self.modified = true; + } + glyph + } +} + +impl Default for FontTextureManager { + fn default() -> Self { + Self::new(uvec2(2048, 2048)) + } +}