Skip to main content

cranpose_ui/text/
font.rs

1#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2pub struct FontFile {
3    pub path: String,
4    pub weight: FontWeight,
5    pub style: FontStyle,
6}
7
8impl FontFile {
9    pub fn new(path: impl Into<String>) -> Self {
10        Self {
11            path: path.into(),
12            weight: FontWeight::NORMAL,
13            style: FontStyle::Normal,
14        }
15    }
16
17    pub fn with_weight(mut self, weight: FontWeight) -> Self {
18        self.weight = weight;
19        self
20    }
21
22    pub fn with_style(mut self, style: FontStyle) -> Self {
23        self.style = style;
24        self
25    }
26}
27
28#[derive(Clone, Debug, PartialEq, Eq, Hash)]
29pub struct FileBackedFontFamily {
30    pub fonts: Vec<FontFile>,
31}
32
33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
34pub enum FontFamilyError {
35    EmptyFileList,
36}
37
38impl std::fmt::Display for FontFamilyError {
39    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40        match self {
41            FontFamilyError::EmptyFileList => {
42                write!(f, "file-backed font family requires at least one font file")
43            }
44        }
45    }
46}
47
48impl std::error::Error for FontFamilyError {}
49
50impl FileBackedFontFamily {
51    pub fn new(fonts: Vec<FontFile>) -> Result<Self, FontFamilyError> {
52        if fonts.is_empty() {
53            return Err(FontFamilyError::EmptyFileList);
54        }
55        Ok(Self { fonts })
56    }
57}
58
59#[derive(Clone, Debug, PartialEq, Eq, Hash)]
60pub struct LoadedTypefacePath {
61    pub path: String,
62}
63
64impl LoadedTypefacePath {
65    pub fn new(path: impl Into<String>) -> Self {
66        Self { path: path.into() }
67    }
68}
69
70#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)]
71pub enum FontFamily {
72    #[default]
73    Default,
74    SansSerif,
75    Serif,
76    Monospace,
77    Cursive,
78    Fantasy,
79    Named(String),
80    FileBacked(FileBackedFontFamily),
81    LoadedTypeface(LoadedTypefacePath),
82}
83
84impl FontFamily {
85    pub fn named(name: impl Into<String>) -> Self {
86        Self::from_name(&name.into())
87    }
88
89    pub fn from_name(name: &str) -> Self {
90        match name {
91            "" | "Default" | "default" => Self::Default,
92            "SansSerif" | "sans-serif" => Self::SansSerif,
93            "Serif" | "serif" => Self::Serif,
94            "Monospace" | "monospace" => Self::Monospace,
95            "Cursive" | "cursive" => Self::Cursive,
96            "Fantasy" | "fantasy" => Self::Fantasy,
97            value => Self::Named(value.to_string()),
98        }
99    }
100
101    pub fn file_backed(fonts: Vec<FontFile>) -> Result<Self, FontFamilyError> {
102        FileBackedFontFamily::new(fonts).map(Self::FileBacked)
103    }
104
105    pub fn loaded_typeface_path(path: impl Into<String>) -> Self {
106        Self::LoadedTypeface(LoadedTypefacePath::new(path))
107    }
108
109    pub fn family_name(&self) -> Option<&str> {
110        match self {
111            Self::Named(name) => Some(name.as_str()),
112            _ => None,
113        }
114    }
115}
116
117impl From<&str> for FontFamily {
118    fn from(value: &str) -> Self {
119        Self::from_name(value)
120    }
121}
122
123impl From<String> for FontFamily {
124    fn from(value: String) -> Self {
125        Self::from_name(value.as_str())
126    }
127}
128
129#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
130pub struct FontWeight(pub u16);
131
132impl FontWeight {
133    pub const fn new(weight: u16) -> Self {
134        Self::clamped(weight)
135    }
136
137    pub const fn try_new(weight: u16) -> Option<Self> {
138        if weight >= 1 && weight <= 1000 {
139            Some(Self(weight))
140        } else {
141            None
142        }
143    }
144
145    pub const fn clamped(weight: u16) -> Self {
146        if weight < 1 {
147            Self(1)
148        } else if weight > 1000 {
149            Self(1000)
150        } else {
151            Self(weight)
152        }
153    }
154
155    pub const fn value(self) -> u16 {
156        self.0
157    }
158
159    pub const THIN: Self = Self(100);
160    pub const EXTRA_LIGHT: Self = Self(200);
161    pub const LIGHT: Self = Self(300);
162    pub const NORMAL: Self = Self(400);
163    pub const MEDIUM: Self = Self(500);
164    pub const SEMI_BOLD: Self = Self(600);
165    pub const BOLD: Self = Self(700);
166    pub const EXTRA_BOLD: Self = Self(800);
167    pub const BLACK: Self = Self(900);
168    pub const W100: Self = Self(100);
169    pub const W200: Self = Self(200);
170    pub const W300: Self = Self(300);
171    pub const W400: Self = Self(400);
172    pub const W500: Self = Self(500);
173    pub const W600: Self = Self(600);
174    pub const W700: Self = Self(700);
175    pub const W800: Self = Self(800);
176    pub const W900: Self = Self(900);
177}
178
179impl Default for FontWeight {
180    fn default() -> Self {
181        Self::NORMAL
182    }
183}
184
185#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
186pub enum FontStyle {
187    #[default]
188    Normal,
189    Italic,
190}
191
192#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
193pub enum FontSynthesis {
194    #[default]
195    None,
196    All,
197    Weight,
198    Style,
199}
200
201#[cfg(test)]
202#[path = "tests/font_tests.rs"]
203mod tests;