polyfont-core 0.8.0

Core types and traits for polyfont multi-font rendering
Documentation
use serde::{Deserialize, Serialize};

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum FontWeight {
    Thin = 100,
    ExtraLight = 200,
    Light = 300,
    #[default]
    Regular = 400,
    Medium = 500,
    SemiBold = 600,
    Bold = 700,
    ExtraBold = 800,
    Black = 900,
}

impl std::fmt::Display for FontWeight {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Thin => write!(f, "thin"),
            Self::ExtraLight => write!(f, "extra-light"),
            Self::Light => write!(f, "light"),
            Self::Regular => write!(f, "regular"),
            Self::Medium => write!(f, "medium"),
            Self::SemiBold => write!(f, "semi-bold"),
            Self::Bold => write!(f, "bold"),
            Self::ExtraBold => write!(f, "extra-bold"),
            Self::Black => write!(f, "black"),
        }
    }
}

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum FontStyle {
    #[default]
    Normal,
    Italic,
    Oblique,
}

impl std::fmt::Display for FontStyle {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Normal => write!(f, "normal"),
            Self::Italic => write!(f, "italic"),
            Self::Oblique => write!(f, "oblique"),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FontSpec {
    pub family: String,
    #[serde(default)]
    pub fallbacks: Vec<String>,
    #[serde(default)]
    pub weight: FontWeight,
    #[serde(default)]
    pub style: FontStyle,
    #[serde(default)]
    pub size: Option<f32>,
}

impl FontSpec {
    #[must_use]
    pub fn default_font(family: &str) -> Self {
        Self {
            family: family.to_string(),
            fallbacks: vec![],
            weight: FontWeight::default(),
            style: FontStyle::default(),
            size: None,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FontRule {
    pub scope: String,
    pub font: FontSpec,
}

impl FontRule {
    #[must_use]
    pub fn specificity(&self) -> usize {
        self.scope.split('.').count()
    }
}

#[derive(Debug, Clone)]
pub struct FontAssignment {
    pub scope: String,
    pub font: FontSpec,
    pub specificity: usize,
    pub is_active: bool,
}

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

    #[test]
    fn test_specificity_single() {
        let rule = FontRule {
            scope: "keyword".to_string(),
            font: FontSpec::default_font("F"),
        };
        assert_eq!(rule.specificity(), 1);
    }

    #[test]
    fn test_specificity_dotted() {
        let rule = FontRule {
            scope: "entity.name.function".to_string(),
            font: FontSpec::default_font("F"),
        };
        assert_eq!(rule.specificity(), 3);
    }

    #[test]
    fn test_specificity_wildcard() {
        let rule = FontRule {
            scope: "*".to_string(),
            font: FontSpec::default_font("F"),
        };
        assert_eq!(rule.specificity(), 1);
    }

    #[test]
    fn test_font_spec_default() {
        let spec = FontSpec::default_font("Test");
        assert_eq!(spec.family, "Test");
        assert!(spec.fallbacks.is_empty());
        assert_eq!(spec.weight, FontWeight::Regular);
        assert_eq!(spec.style, FontStyle::Normal);
        assert!(spec.size.is_none());
    }

    #[test]
    fn test_font_weight_display() {
        assert_eq!(FontWeight::Thin.to_string(), "thin");
        assert_eq!(FontWeight::ExtraLight.to_string(), "extra-light");
        assert_eq!(FontWeight::Light.to_string(), "light");
        assert_eq!(FontWeight::Regular.to_string(), "regular");
        assert_eq!(FontWeight::Medium.to_string(), "medium");
        assert_eq!(FontWeight::SemiBold.to_string(), "semi-bold");
        assert_eq!(FontWeight::Bold.to_string(), "bold");
        assert_eq!(FontWeight::ExtraBold.to_string(), "extra-bold");
        assert_eq!(FontWeight::Black.to_string(), "black");
    }

    #[test]
    fn test_font_style_display() {
        assert_eq!(FontStyle::Normal.to_string(), "normal");
        assert_eq!(FontStyle::Italic.to_string(), "italic");
        assert_eq!(FontStyle::Oblique.to_string(), "oblique");
    }
}