rocketsplash-formats 0.2.2

Shared data types and serialization formats for Rocketsplash TUI animations
Documentation
// <FILE>crates/rocketsplash-formats/src/font_atlas.rs</FILE>
// <DESC>Font atlas format definitions and validation</DESC>
// <VERS>VERSION: 1.0.0</VERS>
// <WCTX>Runtime library implementation</WCTX>
// <CLOG>Define FontAtlas, GlyphData, and validation helpers</CLOG>

use std::collections::BTreeMap;

use serde::{Deserialize, Serialize};

use crate::{validate_dimensions, RenderMode, StyleFlags};

/// Format version constants for compatibility checking.
pub const FONT_ATLAS_VERSION: u8 = 1;
pub const FONT_ATLAS_MIN_SUPPORTED: u8 = 1;

/// Complete font atlas for runtime text rendering.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct FontAtlas {
    pub version: u8,
    pub meta: FontMeta,
    pub glyphs: BTreeMap<char, GlyphVariants>,
    pub line_height: u32,
    pub mode: RenderMode,
    pub available_styles: StyleFlags,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct FontMeta {
    pub font_name: String,
    pub font_size: f32,
    pub created_at: u64,
    pub editor_version: String,
}

/// All style variants for a single character.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GlyphVariants {
    pub base: GlyphData,
    pub bold: Option<GlyphData>,
    pub italic: Option<GlyphData>,
    pub bold_italic: Option<GlyphData>,
    pub reverse: Option<GlyphData>,
}

impl GlyphVariants {
    /// Validate base and all optional variants.
    pub fn validate(&self) -> Result<(), String> {
        self.base.validate()?;
        if let Some(glyph) = &self.bold {
            glyph.validate()?;
        }
        if let Some(glyph) = &self.italic {
            glyph.validate()?;
        }
        if let Some(glyph) = &self.bold_italic {
            glyph.validate()?;
        }
        if let Some(glyph) = &self.reverse {
            glyph.validate()?;
        }
        Ok(())
    }

    /// Select the best available variant given style flags.
    pub fn select(&self, bold: bool, italic: bool, reverse: bool) -> &GlyphData {
        if reverse {
            if let Some(glyph) = &self.reverse {
                return glyph;
            }
        }
        if bold && italic {
            if let Some(glyph) = &self.bold_italic {
                return glyph;
            }
        }
        if bold {
            if let Some(glyph) = &self.bold {
                return glyph;
            }
        }
        if italic {
            if let Some(glyph) = &self.italic {
                return glyph;
            }
        }
        &self.base
    }
}

/// Serializable glyph data (flat chars + optional opacity).
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GlyphData {
    pub chars: String,
    pub width: u32,
    pub height: u32,
    pub opacity: Option<Vec<u8>>,
}

impl GlyphData {
    pub fn validate(&self) -> Result<(), String> {
        let cells = validate_dimensions(self.width, self.height)?;
        let char_count = self.chars.chars().count();
        if char_count != cells {
            return Err(format!(
                "Glyph char count {} does not match {}x{}",
                char_count, self.width, self.height
            ));
        }
        if let Some(opacity) = &self.opacity {
            if opacity.len() != cells {
                return Err(format!(
                    "Glyph opacity length {} does not match {}x{}",
                    opacity.len(),
                    self.width,
                    self.height
                ));
            }
        }
        Ok(())
    }
}

// <FILE>crates/rocketsplash-formats/src/font_atlas.rs</FILE>
// <VERS>END OF VERSION: 1.0.0</VERS>