Skip to main content

polyfont_core/
font.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
4#[serde(rename_all = "kebab-case")]
5pub enum FontWeight {
6    Thin = 100,
7    ExtraLight = 200,
8    Light = 300,
9    #[default]
10    Regular = 400,
11    Medium = 500,
12    SemiBold = 600,
13    Bold = 700,
14    ExtraBold = 800,
15    Black = 900,
16}
17
18impl std::fmt::Display for FontWeight {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        match self {
21            Self::Thin => write!(f, "thin"),
22            Self::ExtraLight => write!(f, "extra-light"),
23            Self::Light => write!(f, "light"),
24            Self::Regular => write!(f, "regular"),
25            Self::Medium => write!(f, "medium"),
26            Self::SemiBold => write!(f, "semi-bold"),
27            Self::Bold => write!(f, "bold"),
28            Self::ExtraBold => write!(f, "extra-bold"),
29            Self::Black => write!(f, "black"),
30        }
31    }
32}
33
34#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
35#[serde(rename_all = "kebab-case")]
36pub enum FontStyle {
37    #[default]
38    Normal,
39    Italic,
40    Oblique,
41}
42
43impl std::fmt::Display for FontStyle {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        match self {
46            Self::Normal => write!(f, "normal"),
47            Self::Italic => write!(f, "italic"),
48            Self::Oblique => write!(f, "oblique"),
49        }
50    }
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct FontSpec {
55    pub family: String,
56    #[serde(default)]
57    pub fallbacks: Vec<String>,
58    #[serde(default)]
59    pub weight: FontWeight,
60    #[serde(default)]
61    pub style: FontStyle,
62    #[serde(default)]
63    pub size: Option<f32>,
64}
65
66impl FontSpec {
67    #[must_use]
68    pub fn default_font(family: &str) -> Self {
69        Self {
70            family: family.to_string(),
71            fallbacks: vec![],
72            weight: FontWeight::default(),
73            style: FontStyle::default(),
74            size: None,
75        }
76    }
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct FontRule {
81    pub scope: String,
82    pub font: FontSpec,
83}
84
85impl FontRule {
86    #[must_use]
87    pub fn specificity(&self) -> usize {
88        self.scope.split('.').count()
89    }
90}
91
92#[derive(Debug, Clone)]
93pub struct FontAssignment {
94    pub scope: String,
95    pub font: FontSpec,
96    pub specificity: usize,
97    pub is_active: bool,
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn test_specificity_single() {
106        let rule = FontRule {
107            scope: "keyword".to_string(),
108            font: FontSpec::default_font("F"),
109        };
110        assert_eq!(rule.specificity(), 1);
111    }
112
113    #[test]
114    fn test_specificity_dotted() {
115        let rule = FontRule {
116            scope: "entity.name.function".to_string(),
117            font: FontSpec::default_font("F"),
118        };
119        assert_eq!(rule.specificity(), 3);
120    }
121
122    #[test]
123    fn test_specificity_wildcard() {
124        let rule = FontRule {
125            scope: "*".to_string(),
126            font: FontSpec::default_font("F"),
127        };
128        assert_eq!(rule.specificity(), 1);
129    }
130
131    #[test]
132    fn test_font_spec_default() {
133        let spec = FontSpec::default_font("Test");
134        assert_eq!(spec.family, "Test");
135        assert!(spec.fallbacks.is_empty());
136        assert_eq!(spec.weight, FontWeight::Regular);
137        assert_eq!(spec.style, FontStyle::Normal);
138        assert!(spec.size.is_none());
139    }
140
141    #[test]
142    fn test_font_weight_display() {
143        assert_eq!(FontWeight::Thin.to_string(), "thin");
144        assert_eq!(FontWeight::ExtraLight.to_string(), "extra-light");
145        assert_eq!(FontWeight::Light.to_string(), "light");
146        assert_eq!(FontWeight::Regular.to_string(), "regular");
147        assert_eq!(FontWeight::Medium.to_string(), "medium");
148        assert_eq!(FontWeight::SemiBold.to_string(), "semi-bold");
149        assert_eq!(FontWeight::Bold.to_string(), "bold");
150        assert_eq!(FontWeight::ExtraBold.to_string(), "extra-bold");
151        assert_eq!(FontWeight::Black.to_string(), "black");
152    }
153
154    #[test]
155    fn test_font_style_display() {
156        assert_eq!(FontStyle::Normal.to_string(), "normal");
157        assert_eq!(FontStyle::Italic.to_string(), "italic");
158        assert_eq!(FontStyle::Oblique.to_string(), "oblique");
159    }
160}