diff --git a/hui-painter/src/lib.rs b/hui-painter/src/lib.rs index 1a1752e..cb339e0 100644 --- a/hui-painter/src/lib.rs +++ b/hui-painter/src/lib.rs @@ -2,12 +2,13 @@ pub mod paint; pub mod texture; pub mod text; +use text::FontManager; use texture::TextureAtlas; #[derive(Default)] pub struct Painter { - pub(crate) atlas: TextureAtlas, - // ftm: FontTextureManager, + pub atlas: TextureAtlas, + pub fonts: FontManager, } impl Painter { @@ -15,7 +16,19 @@ impl Painter { Self::default() } - pub fn atlas(&self) -> &TextureAtlas { - &self.atlas - } + // pub fn atlas(&self) -> &TextureAtlas { + // &self.atlas + // } + + // pub fn atlas_mut(&mut self) -> &mut TextureAtlas { + // &mut self.atlas + // } + + // pub fn fonts(&self) -> &FontManager { + // &self.fonts + // } + + // pub fn fonts_mut(&mut self) -> &mut FontManager { + // &mut self.fonts + // } } diff --git a/hui-painter/src/paint/command.rs b/hui-painter/src/paint/command.rs index 338fae4..106222e 100644 --- a/hui-painter/src/paint/command.rs +++ b/hui-painter/src/paint/command.rs @@ -1,5 +1,9 @@ +use glam::Vec2; use crate::{paint::buffer::PaintBuffer, Painter}; +// mod root; +// pub use root::RootCommand; + mod transform; pub use transform::PaintTransform; @@ -10,5 +14,20 @@ mod text; pub use text::PaintText; pub trait PaintCommand { + /// Called before actual paint command is executed\ + /// Opportunity to pre-cache bitmaps, etc. + /// + /// Make sure to propagate this call to children! + #[allow(unused_variables)] + fn pre_paint(&self, ctx: &mut Painter) {} + + /// Paint the command into the buffer + /// + /// Do not allocate new textures or cache glyphs here, use `pre_paint` instead!\ + /// (Doing this WILL lead to atlas corruption flicker for a single frame if it's forced to resize!) fn paint(&self, ctx: &mut Painter, into: &mut PaintBuffer); } + +pub trait Measurable: PaintCommand { + fn size(&self, ctx: &Painter) -> Vec2; +} diff --git a/hui-painter/src/paint/command/text.rs b/hui-painter/src/paint/command/text.rs index 20ad856..ebdd7f7 100644 --- a/hui-painter/src/paint/command/text.rs +++ b/hui-painter/src/paint/command/text.rs @@ -1,16 +1,20 @@ use std::{borrow::Cow, sync::Arc}; -use fontdue::layout::{CoordinateSystem, Layout}; +use fontdue::layout::{self, CoordinateSystem, GlyphRasterConfig, Layout}; +use glam::{vec2, Vec2}; use crate::{ - Painter, paint::{ buffer::PaintBuffer, command::PaintCommand, - }, + }, text::FontHandle, Painter }; +use super::Measurable; + +// TODO align, multichunk etc + pub struct TextChunk { pub text: Cow<'static, str>, - pub font: (), + pub font: FontHandle, pub size: f32, } @@ -20,25 +24,74 @@ pub struct PaintText { } impl PaintText { - pub fn new(text: impl Into>, size: f32) -> Self { + pub fn new(text: impl Into>, font: FontHandle, size: f32) -> Self { Self { text: TextChunk { text: text.into(), - font: todo!(), + font, size, } } } + + fn build_font_array<'a>(&self, ctx: &'a Painter) -> Vec<&'a fontdue::Font> { + let font = ctx.fonts.get_fontdue_font(self.text.font) + .expect("FontHandle is invalid"); + vec![&font] + } + + fn build_layout(&self, font_array: &[&fontdue::Font]) -> Layout { + let mut layout = Layout::new(CoordinateSystem::PositiveYDown); + layout.append( + &font_array, + &fontdue::layout::TextStyle::new( + &self.text.text, + self.text.size, + 0 + ) + ); + layout + } } impl PaintCommand for PaintText { + fn pre_paint(&self, ctx: &mut Painter) { + let font_array = self.build_font_array(ctx); + let layout = self.build_layout(&font_array); + + for glyph in layout.glyphs() { + ctx.fonts.render_glyph(&mut ctx.atlas, self.text.font, glyph.key); + } + } + fn paint(&self, ctx: &mut Painter, into: &mut PaintBuffer) { - // let mut layout = Layout::new(CoordinateSystem::PositiveYDown); - // layout.append( - // &[text_renderer.internal_font(*font_handle)], - // &TextStyle::new(text, *size as f32, 0) - // ); + // let font_array = self.build_font_array(ctx); + // let layout = self.build_layout(&font_array); + + // for glyph in layout.glyphs() { + // let config = GlyphRasterConfig { + // glyph_index: glyph.font_index + // }; + // let glyph_raster = ctx.fonts().render_glyph(atlas, font, config); + // } todo!() } } + +impl Measurable for PaintText { + fn size(&self, ctx: &Painter) -> Vec2 { + let font_array = self.build_font_array(ctx); + let layout = self.build_layout(&font_array); + + let width = layout.lines().map(|lines| { + lines.iter().fold(0.0_f32, |acc, x| { + let glyph = layout.glyphs().get(x.glyph_end).unwrap(); + acc.max(glyph.x + glyph.width as f32) + }) + }).unwrap_or(0.); + let height = layout.height(); + + vec2(width, height) + } +} diff --git a/hui-painter/src/paint/command/transform.rs b/hui-painter/src/paint/command/transform.rs index 7ef4c38..314709e 100644 --- a/hui-painter/src/paint/command/transform.rs +++ b/hui-painter/src/paint/command/transform.rs @@ -12,6 +12,12 @@ pub struct PaintTransform { } impl PaintCommand for PaintTransform { + fn pre_paint(&self, ctx: &mut Painter) { + for child in &self.children { + child.pre_paint(ctx); + } + } + fn paint(&self, ctx: &mut Painter, into: &mut PaintBuffer) { // remember the starting index let starting_index = into.vertices.len(); diff --git a/hui-painter/src/text.rs b/hui-painter/src/text.rs index 90567ca..9b8897b 100644 --- a/hui-painter/src/text.rs +++ b/hui-painter/src/text.rs @@ -1,2 +1,69 @@ -pub mod ftm; -pub mod font; +use fontdue::layout::GlyphRasterConfig; +use crate::texture::{TextureAtlas, TextureHandle}; + +pub(crate) mod ftm; +pub(crate) mod font; + +pub use font::FontHandle; + +pub struct FontManager { + fonts: font::FontHandleManager, + ftm: ftm::FontTextureManager, +} + +impl FontManager { + pub fn new() -> Self { + Self { + fonts: font::FontHandleManager::new(), + ftm: ftm::FontTextureManager::new(), + } + } + + /// Add a font to the manager. + /// + /// Panics: + /// - If the font data is invalid. + pub fn add_font(&mut self, data: &[u8]) -> FontHandle { + let font = self.fonts.add_font(data); + self.ftm.init_font(font); + font + } + + /// Remove and deallocate a font from the manager. + /// + /// Panics: + /// - If the font handle is invalid. + pub fn remove_font(&mut self, font: FontHandle, atlas: &mut TextureAtlas) { + self.ftm.drop_font(font, atlas); + self.fonts.remove_font(font); + } + + /// Render a glyph and cache it in the texture atlas. + /// + /// Panics: + /// - If the font handle is invalid or not initialized. + /// - Fuck around and find out, this api is unstable + pub(crate) fn render_glyph( + &mut self, + atlas: &mut TextureAtlas, + font: FontHandle, + config: GlyphRasterConfig + ) -> TextureHandle { + self.ftm.render_glyph(font, &self.fonts, config, atlas) + } + + /// Internal API + pub(crate) fn get_fontdue_font( + &self, + handle: FontHandle + ) -> Option<&fontdue::Font> { + self.fonts.get_font_repr(handle) + .map(|x| &x.font) + } +} + +impl Default for FontManager { + fn default() -> Self { + Self::new() + } +} diff --git a/hui-painter/src/text/font.rs b/hui-painter/src/text/font.rs index e69de29..a84c9fa 100644 --- a/hui-painter/src/text/font.rs +++ b/hui-painter/src/text/font.rs @@ -0,0 +1,52 @@ +use hashbrown::HashMap; +use nohash_hasher::BuildNoHashHasher; + +pub(crate) type FontId = u16; + +#[derive(Clone, Copy)] +pub struct FontHandle(pub(crate) FontId); + +pub(crate) struct FontRepr { + pub(crate) font: fontdue::Font, +} + +pub struct FontHandleManager { + idc: FontId, + fonts: HashMap>, +} + +impl FontHandleManager { + pub fn new() -> Self { + Self { + idc: 0, + fonts: HashMap::default(), + } + } + + /// Add a font to the manager. + /// + /// Panics: + /// - If the font data is invalid. + pub fn add_font(&mut self, data: &[u8]) -> FontHandle { + let font = fontdue::Font::from_bytes(data, fontdue::FontSettings::default()).unwrap(); + self.fonts.insert_unique_unchecked(self.idc, FontRepr { font }); + self.idc += 1; + FontHandle(self.idc - 1) + } + + /// Internal function + /// + /// Remove and deallocate a font from the manager if the font handle is valid. + /// + /// Panics: + /// - If the font handle is invalid. + pub(crate) fn remove_font(&mut self, handle: FontHandle) { + self.fonts.remove(&handle.0).unwrap(); + } + + /// Get the font handle for the specified font. + pub(crate) fn get_font_repr(&self, handle: FontHandle) -> Option<&FontRepr> { + self.fonts.get(&handle.0) + } +} + diff --git a/hui-painter/src/text/ftm.rs b/hui-painter/src/text/ftm.rs index 65118de..51570cf 100644 --- a/hui-painter/src/text/ftm.rs +++ b/hui-painter/src/text/ftm.rs @@ -1,22 +1,19 @@ use fontdue::layout::GlyphRasterConfig; use hashbrown::HashMap; use nohash_hasher::BuildNoHashHasher; -use crate::texture::{TextureAtlas, TextureHandle}; - -type FontId = u16; - -#[derive(Clone, Copy)] -pub struct FontHandle(FontId); +use crate::texture::{SourceTextureFormat, TextureAtlas, TextureHandle}; +use super::font::{FontHandle, FontHandleManager, FontId}; /// Maps to the actual texture handle. -struct GlyphCacheItem { +struct RasterizedGlyphInternal { handle: TextureHandle, + metrics: fontdue::Metrics, } /// Map from raster config to glyph cache item. /// /// Partitioned by font id in FtM :3 -type PartitionKey = HashMap; +type PartitionKey = HashMap; /// Manages glyph cache items in a texture atlas. pub struct FontTextureManager { @@ -24,17 +21,74 @@ pub struct FontTextureManager { } impl FontTextureManager { + pub fn new() -> Self { + Self { + partition: HashMap::default(), + } + } + /// Drop cached data for the specified font. /// /// Panics: - /// - If the font handle is invalid. + /// - If the font handle is invalid or not initialized. /// - If any of the cached items are not found in the texture atlas or became invalid.\ /// This may happen if, for example, a different atlas is passed than the one used to allocate the items. - fn drop_font(&mut self, font: FontHandle, atlas: &mut TextureAtlas) { + pub(crate) fn drop_font(&mut self, font: FontHandle, atlas: &mut TextureAtlas) { let dump = self.partition.remove(&font.0).expect("Font handle is invalid"); for (_, item) in dump { atlas.deallocate(item.handle); } } + + /// Initialize the partition for the specified font. + /// + /// Panics: + /// - If the partition for the font already exists. + pub(crate) fn init_font(&mut self, font: FontHandle) { + assert!(!self.partition.contains_key(&font.0), "Font handle already initialized"); + self.partition.insert_unique_unchecked(font.0, HashMap::default()); + } + + /// Render a glyph and cache it in the texture atlas. + /// + /// Panics: + /// - If the font handle is invalid or not initialized. + /// - Fuck around and find out, this api is unstable + pub(crate) fn render_glyph( + &mut self, + font_handle: FontHandle, + fhm_internal: &FontHandleManager, + config: GlyphRasterConfig, + atlas: &mut TextureAtlas + ) -> TextureHandle { + // Get partiton + let partition = self.partition.get_mut(&font_handle.0) + .expect("Font handle is not registered in FtM"); + + // Check if glyph is alr cached + if let Some(item) = partition.get(&config) { + return item.handle; + } + + // Get fontdue font from the manager + let font = &fhm_internal.get_font_repr(font_handle) + .expect("Font handle is invalid") + .font; + + // Rasterize the font and copy the texture data + let (metrics, data) = font.rasterize_config(config); + let handle = atlas.allocate_with_data(SourceTextureFormat::A8, &data, metrics.width); + + // Create a texture item struct and insert it into the partition + let itm = RasterizedGlyphInternal { handle, metrics }; + partition.insert_unique_unchecked(config, itm); + + return handle; + } } +impl Default for FontTextureManager { + fn default() -> Self { + Self::new() + } +}