This commit is contained in:
griffi-gh 2024-09-22 03:02:29 +02:00
parent 70f7641214
commit 881861baef
7 changed files with 292 additions and 28 deletions

View file

@ -2,12 +2,13 @@ pub mod paint;
pub mod texture; pub mod texture;
pub mod text; pub mod text;
use text::FontManager;
use texture::TextureAtlas; use texture::TextureAtlas;
#[derive(Default)] #[derive(Default)]
pub struct Painter { pub struct Painter {
pub(crate) atlas: TextureAtlas, pub atlas: TextureAtlas,
// ftm: FontTextureManager, pub fonts: FontManager,
} }
impl Painter { impl Painter {
@ -15,7 +16,19 @@ impl Painter {
Self::default() Self::default()
} }
pub fn atlas(&self) -> &TextureAtlas { // pub fn atlas(&self) -> &TextureAtlas {
&self.atlas // &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
// }
} }

View file

@ -1,5 +1,9 @@
use glam::Vec2;
use crate::{paint::buffer::PaintBuffer, Painter}; use crate::{paint::buffer::PaintBuffer, Painter};
// mod root;
// pub use root::RootCommand;
mod transform; mod transform;
pub use transform::PaintTransform; pub use transform::PaintTransform;
@ -10,5 +14,20 @@ mod text;
pub use text::PaintText; pub use text::PaintText;
pub trait PaintCommand { 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); fn paint(&self, ctx: &mut Painter, into: &mut PaintBuffer);
} }
pub trait Measurable: PaintCommand {
fn size(&self, ctx: &Painter) -> Vec2;
}

View file

@ -1,16 +1,20 @@
use std::{borrow::Cow, sync::Arc}; use std::{borrow::Cow, sync::Arc};
use fontdue::layout::{CoordinateSystem, Layout}; use fontdue::layout::{self, CoordinateSystem, GlyphRasterConfig, Layout};
use glam::{vec2, Vec2};
use crate::{ use crate::{
Painter,
paint::{ paint::{
buffer::PaintBuffer, buffer::PaintBuffer,
command::PaintCommand, command::PaintCommand,
}, }, text::FontHandle, Painter
}; };
use super::Measurable;
// TODO align, multichunk etc
pub struct TextChunk { pub struct TextChunk {
pub text: Cow<'static, str>, pub text: Cow<'static, str>,
pub font: (), pub font: FontHandle,
pub size: f32, pub size: f32,
} }
@ -20,25 +24,74 @@ pub struct PaintText {
} }
impl PaintText { impl PaintText {
pub fn new(text: impl Into<Cow<'static, str>>, size: f32) -> Self { pub fn new(text: impl Into<Cow<'static, str>>, font: FontHandle, size: f32) -> Self {
Self { Self {
text: TextChunk { text: TextChunk {
text: text.into(), text: text.into(),
font: todo!(), font,
size, 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 { 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) { fn paint(&self, ctx: &mut Painter, into: &mut PaintBuffer) {
// let mut layout = Layout::new(CoordinateSystem::PositiveYDown); // let font_array = self.build_font_array(ctx);
// layout.append( // let layout = self.build_layout(&font_array);
// &[text_renderer.internal_font(*font_handle)],
// &TextStyle::new(text, *size as f32, 0) // for glyph in layout.glyphs() {
// ); // let config = GlyphRasterConfig {
// glyph_index: glyph.font_index
// };
// let glyph_raster = ctx.fonts().render_glyph(atlas, font, config);
// }
todo!() 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)
}
}

View file

@ -12,6 +12,12 @@ pub struct PaintTransform {
} }
impl PaintCommand for 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) { fn paint(&self, ctx: &mut Painter, into: &mut PaintBuffer) {
// remember the starting index // remember the starting index
let starting_index = into.vertices.len(); let starting_index = into.vertices.len();

View file

@ -1,2 +1,69 @@
pub mod ftm; use fontdue::layout::GlyphRasterConfig;
pub mod font; 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()
}
}

View file

@ -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<FontId, FontRepr,BuildNoHashHasher<FontId>>,
}
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)
}
}

View file

@ -1,22 +1,19 @@
use fontdue::layout::GlyphRasterConfig; use fontdue::layout::GlyphRasterConfig;
use hashbrown::HashMap; use hashbrown::HashMap;
use nohash_hasher::BuildNoHashHasher; use nohash_hasher::BuildNoHashHasher;
use crate::texture::{TextureAtlas, TextureHandle}; use crate::texture::{SourceTextureFormat, TextureAtlas, TextureHandle};
use super::font::{FontHandle, FontHandleManager, FontId};
type FontId = u16;
#[derive(Clone, Copy)]
pub struct FontHandle(FontId);
/// Maps to the actual texture handle. /// Maps to the actual texture handle.
struct GlyphCacheItem { struct RasterizedGlyphInternal {
handle: TextureHandle, handle: TextureHandle,
metrics: fontdue::Metrics,
} }
/// Map from raster config to glyph cache item. /// Map from raster config to glyph cache item.
/// ///
/// Partitioned by font id in FtM :3 /// Partitioned by font id in FtM :3
type PartitionKey = HashMap<GlyphRasterConfig, GlyphCacheItem>; type PartitionKey = HashMap<GlyphRasterConfig, RasterizedGlyphInternal>;
/// Manages glyph cache items in a texture atlas. /// Manages glyph cache items in a texture atlas.
pub struct FontTextureManager { pub struct FontTextureManager {
@ -24,17 +21,74 @@ pub struct FontTextureManager {
} }
impl FontTextureManager { impl FontTextureManager {
pub fn new() -> Self {
Self {
partition: HashMap::default(),
}
}
/// Drop cached data for the specified font. /// Drop cached data for the specified font.
/// ///
/// Panics: /// 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.\ /// - 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. /// 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"); let dump = self.partition.remove(&font.0).expect("Font handle is invalid");
for (_, item) in dump { for (_, item) in dump {
atlas.deallocate(item.handle); 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()
}
}