nightshade-text 0.57.0

Text shaping, styling, and glyph geometry shared by the nightshade renderer and UI.
Documentation
//! Font loading, text shaping, and glyph rasterization.

use cosmic_text::{
    Attrs, Buffer, Family, FontSystem, Metrics, Shaping, Stretch, Style, SwashCache, Weight,
};

/// Embedded Roboto Regular font data used as the default sans-serif family.
pub const DEFAULT_FONT_DATA: &[u8] = include_bytes!("fonts/Roboto-Regular.ttf");
/// Embedded `JetBrains Mono` font data used as the default monospace family.
pub const DEFAULT_MONO_FONT_DATA: &[u8] = include_bytes!("fonts/JetBrainsMono-Regular.ttf");
/// Embedded Material Icons font data.
pub const ICONS_MATERIAL_FONT_DATA: &[u8] = include_bytes!("fonts/MaterialIcons-Regular.ttf");
/// Embedded Lucide icon font data.
pub const ICONS_LUCIDE_FONT_DATA: &[u8] = include_bytes!("fonts/Lucide.ttf");

use crate::text_data::FontKind;

/// Owns the font database, shaping context, and glyph rasterization cache.
pub struct FontEngine {
    /// The `cosmic_text` font system holding the loaded font database.
    pub font_system: FontSystem,
    /// Glyph rasterization cache.
    pub swash_cache: SwashCache,
    default_family: String,
    monospace_family: String,
    icons_material_family: String,
    icons_lucide_family: String,
}

impl FontEngine {
    /// Creates a font engine with the embedded default, monospace, and icon fonts loaded.
    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,
        }
    }

    /// Loads additional font data into the database.
    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,
        }
    }

    /// Shapes `text` into a laid-out buffer at the given size, line height, optional wrap width, and font kind.
    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(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(text, &attrs, Shaping::Advanced, None);
        buffer.shape_until_scroll(&mut self.font_system, true);
        buffer
    }
}

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