spottedcat 1.0.0

Rusty SpottedCat simple game engine
Documentation
//! Font registration and glyph rendering.

use crate::glyph_cache::GlyphEntry;
use ab_glyph::FontArc;

use super::core::Graphics;
use crate::Pt;

// Font management is now handled by the Context for persistence.
// Graphics only caches the parsed FontArc for performance.

impl Graphics {
    /// Render a single glyph to the atlas and cache it
    pub(super) fn render_single_glyph(
        &mut self,
        ctx: &mut crate::Context,
        font_id: u32,
        font_size: f32,
        glyph_id: u32,
    ) -> anyhow::Result<GlyphEntry> {
        use ab_glyph::{Font as _, FontArc, Glyph, PxScale, ScaleFont as _};

        let font_data = ctx
            .registry
            .fonts
            .get(&font_id)
            .ok_or_else(|| anyhow::anyhow!("Font ID {} not found", font_id))?;

        let font = if let Some(cached_font) = self.get_cached_font(font_id as u64) {
            cached_font
        } else {
            let font = FontArc::try_from_vec(font_data.clone())
                .map_err(|e| anyhow::anyhow!("Failed to parse font: {:?}", e))?;
            self.cache_font(font_id as u64, font.clone());
            font
        };

        let px_size = font_size.max(1.0);
        let scale = PxScale::from(px_size);
        let scaled = font.as_scaled(scale);

        let glyph = Glyph {
            id: ab_glyph::GlyphId(glyph_id as u16),
            scale,
            position: ab_glyph::point(0.0, 0.0),
        };

        let h_advance = scaled.h_advance(glyph.id);

        let outlined = scaled
            .outline_glyph(glyph)
            .ok_or_else(|| anyhow::anyhow!("Cannot outline glyph"))?;

        let bounds = outlined.px_bounds();
        let glyph_width = (bounds.max.x - bounds.min.x).ceil().max(1.0) as u32;
        let glyph_height = (bounds.max.y - bounds.min.y).ceil().max(1.0) as u32;

        let mut rgba_data = vec![0u8; (glyph_width * glyph_height * 4) as usize];

        outlined.draw(|x, y, v| {
            if x < glyph_width && y < glyph_height {
                let idx = ((y * glyph_width + x) * 4) as usize;
                let alpha = (v * 255.0).round().clamp(0.0, 255.0) as u8;
                rgba_data[idx] = 255;
                rgba_data[idx + 1] = 255;
                rgba_data[idx + 2] = 255;
                rgba_data[idx + 3] = alpha;
            }
        });

        let scale_factor = ctx.scale_factor();
        let logical_w = Pt::from_physical_px(glyph_width as f64, scale_factor);
        let logical_h = Pt::from_physical_px(glyph_height as f64, scale_factor);
        let image = self
            .font_atlas
            .as_mut()
            .ok_or_else(|| anyhow::anyhow!("Font atlas not initialized"))?
            .add_region(
                &mut ctx.registry,
                scale_factor,
                logical_w,
                logical_h,
                glyph_width,
                glyph_height,
                &rgba_data,
            )?;

        Ok(GlyphEntry {
            image,
            offset: [bounds.min.x, bounds.min.y],
            advance: h_advance,
        })
    }

    pub(super) fn get_cached_font(&self, font_hash: u64) -> Option<FontArc> {
        self.font_cache.get(&font_hash).cloned()
    }

    pub(super) fn cache_font(&mut self, font_hash: u64, font: FontArc) {
        self.font_cache.insert(font_hash, font);
    }
}