cotis-utils 0.1.0-alpha

Modular Rust UI framework core
Documentation
//! Thread-safe font registry abstractions.
//!
//! This module underpins renderer-side font ownership. The API is functional but
//! expected to evolve with the planned text/font refactor.
use std::sync::{Arc, RwLock, RwLockReadGuard};

/// Shared, thread-safe handle to a font storage implementation.
pub struct FontManager<Font> {
    font_data: Arc<RwLock<Box<dyn FontsHolder<Font>>>>,
}

/// Read guard over the underlying font holder.
pub type FontsGuard<'a, Font> = RwLockReadGuard<'a, Box<dyn FontsHolder<Font>>>;

impl<Font> FontManager<Font> {
    /// Creates a new manager from a concrete font holder.
    pub fn new<Holder: FontsHolder<Font> + 'static>(font_data: Holder) -> Self {
        Self {
            font_data: Arc::new(RwLock::new(Box::new(font_data))),
        }
    }

    /// Leaks the manager and returns a `'static` reference to it.
    ///
    /// This is an advanced escape hatch for global callback-driven integrations.
    /// Most applications should prefer regular owned values.
    pub fn leak(self) -> &'static Self {
        Box::leak(Box::new(self))
    }
    /// Returns read-only access to the underlying holder.
    ///
    /// # Panics
    ///
    /// Panics if the internal `RwLock` is poisoned.
    pub fn get_fonts(&self) -> FontsGuard<'_, Font> {
        self.font_data.read().unwrap()
    }
    /// Inserts a font and returns its slot index.
    ///
    /// # Panics
    ///
    /// Panics if the internal `RwLock` is poisoned.
    pub fn push_font(&self, font: Font) -> usize {
        self.font_data.write().unwrap().push_font(font)
    }
    /// Marks a font slot as free.
    ///
    /// # Panics
    ///
    /// Panics if the internal `RwLock` is poisoned.
    pub fn delete_font(&self, index: usize) {
        self.font_data.write().unwrap().delete_font(index);
    }
}

/// Storage contract used by [`FontManager`].
pub trait FontsHolder<Font> {
    /// Returns all stored fonts.
    fn get_fonts(&self) -> &[Font];
    /// Stores a font and returns the slot index.
    fn push_font(&mut self, font: Font) -> usize;
    /// Marks the slot as removed or reusable.
    fn delete_font(&mut self, index: usize);
}

/// Default in-memory font holder used by most integrations.
pub struct GenericFontsHolder<Font> {
    fonts: Vec<Font>,
    free_fonts: Vec<usize>,
}

impl<Font> Default for GenericFontsHolder<Font> {
    fn default() -> Self {
        Self::new()
    }
}

impl<Font> GenericFontsHolder<Font> {
    /// Creates an empty holder.
    pub fn new() -> Self {
        Self {
            fonts: vec![],
            free_fonts: vec![],
        }
    }
}

impl<Font> FontsHolder<Font> for GenericFontsHolder<Font> {
    fn get_fonts(&self) -> &[Font] {
        self.fonts.as_slice()
    }

    /// Inserts a font.
    ///
    /// If a free index exists, this implementation uses `Vec::insert` at that
    /// index, otherwise it appends.
    fn push_font(&mut self, font: Font) -> usize {
        if let Some(index) = self.free_fonts.pop() {
            self.fonts.insert(index, font);
            index
        } else {
            self.fonts.push(font);
            self.fonts.len() - 1
        }
    }

    fn delete_font(&mut self, index: usize) {
        self.free_fonts.push(index);
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn generic_fonts_holder_reuses_deleted_slots() {
        let mut holder = GenericFontsHolder::new();
        assert_eq!(holder.push_font("a"), 0);
        assert_eq!(holder.push_font("b"), 1);
        holder.delete_font(0);
        assert_eq!(holder.push_font("c"), 0);
        assert_eq!(holder.get_fonts()[0], "c");
        assert_eq!(holder.get_fonts()[1], "a");
        assert_eq!(holder.get_fonts()[2], "b");
    }

    #[test]
    fn font_manager_round_trips_through_rwlock() {
        let manager = FontManager::new(GenericFontsHolder::new());
        assert_eq!(manager.push_font("font-a"), 0);
        assert_eq!(manager.get_fonts().get_fonts()[0], "font-a");
        manager.delete_font(0);
        assert_eq!(manager.push_font("font-b"), 0);
        assert_eq!(manager.get_fonts().get_fonts()[0], "font-b");
    }
}