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;