nightshade 0.14.1

A cross-platform data-oriented game engine.
Documentation
use cosmic_text::{
    Attrs, Buffer, Family, FontSystem, Metrics, Shaping, Stretch, Style, SwashCache, Weight,
};

pub const DEFAULT_FONT_DATA: &[u8] =
    include_bytes!("../../../render/wgpu/fonts/Roboto-Regular.ttf");
pub const DEFAULT_MONO_FONT_DATA: &[u8] =
    include_bytes!("../../../render/wgpu/fonts/JetBrainsMono-Regular.ttf");
pub const ICONS_MATERIAL_FONT_DATA: &[u8] =
    include_bytes!("../../../render/wgpu/fonts/MaterialIcons-Regular.ttf");
pub const ICONS_LUCIDE_FONT_DATA: &[u8] = include_bytes!("../../../render/wgpu/fonts/Lucide.ttf");

#[derive(
    Clone, Copy, Debug, Default, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize,
)]
pub enum FontKind {
    #[default]
    Default,
    Mono,
    IconsMaterial,
    IconsLucide,
}

pub struct FontEngine {
    pub font_system: FontSystem,
    pub swash_cache: SwashCache,
    default_family: String,
    monospace_family: String,
    icons_material_family: String,
    icons_lucide_family: String,
}

impl FontEngine {
    pub fn new() -> Self {
        let mut db = cosmic_text::fontdb::Database::new();
        db.load_font_data(DEFAULT_FONT_DATA.to_vec());
        db.load_font_data(DEFAULT_MONO_FONT_DATA.to_vec());
        db.load_font_data(ICONS_MATERIAL_FONT_DATA.to_vec());
        db.load_font_data(ICONS_LUCIDE_FONT_DATA.to_vec());

        let mut default_family = String::from("Roboto");
        let mut monospace_family = String::from("JetBrains Mono");
        let mut icons_material_family = String::from("Material Icons");
        let mut icons_lucide_family = String::from("lucide");

        for face in db.faces() {
            if let Some((family, _)) = face.families.first() {
                let lower = family.to_lowercase();
                if lower.contains("material icons") || lower.contains("material symbols") {
                    icons_material_family = family.clone();
                } else if lower.contains("lucide") {
                    icons_lucide_family = family.clone();
                } else if face.monospaced {
                    monospace_family = family.clone();
                } else {
                    default_family = family.clone();
                }
            }
        }

        db.set_sans_serif_family(default_family.clone());
        db.set_monospace_family(monospace_family.clone());

        let font_system = FontSystem::new_with_locale_and_db("en-US".to_string(), db);

        Self {
            font_system,
            swash_cache: SwashCache::new(),
            default_family,
            monospace_family,
            icons_material_family,
            icons_lucide_family,
        }
    }

    pub fn load_font(&mut self, data: Vec<u8>) {
        self.font_system.db_mut().load_font_data(data);
    }

    fn family_name(&self, font_kind: FontKind) -> &str {
        match font_kind {
            FontKind::Default => &self.default_family,
            FontKind::Mono => &self.monospace_family,
            FontKind::IconsMaterial => &self.icons_material_family,
            FontKind::IconsLucide => &self.icons_lucide_family,
        }
    }

    pub fn shape_buffer(
        &mut self,
        text: &str,
        font_size: f32,
        line_height: f32,
        max_width: Option<f32>,
        font_kind: FontKind,
    ) -> Buffer {
        let metrics = Metrics::new(font_size, line_height);
        let mut buffer = Buffer::new(&mut self.font_system, metrics);
        buffer.set_size(&mut self.font_system, max_width, None);
        let family_name = self.family_name(font_kind).to_string();
        let attrs = Attrs::new()
            .family(Family::Name(&family_name))
            .weight(Weight::NORMAL)
            .style(Style::Normal)
            .stretch(Stretch::Normal);
        buffer.set_text(&mut self.font_system, text, &attrs, Shaping::Advanced, None);
        buffer.shape_until_scroll(&mut self.font_system, true);
        buffer
    }
}

impl Default for FontEngine {
    fn default() -> Self {
        Self::new()
    }
}