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}