Skip to main content

cranpose_ui/text/
font.rs

1/// One font file of a file-backed family, with the weight and style the file provides.
2#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3pub struct FontFile {
4    /// Path of the font file.
5    pub path: String,
6    /// Weight of the face in the file.
7    pub weight: FontWeight,
8    /// Upright or italic style of the face in the file.
9    pub style: FontStyle,
10}
11
12impl FontFile {
13    /// Creates an entry for the font file at `path` with normal weight and upright style.
14    pub fn new(path: impl Into<String>) -> Self {
15        Self {
16            path: path.into(),
17            weight: FontWeight::NORMAL,
18            style: FontStyle::Normal,
19        }
20    }
21
22    /// Sets the weight the file provides.
23    pub fn with_weight(mut self, weight: FontWeight) -> Self {
24        self.weight = weight;
25        self
26    }
27
28    /// Sets the style the file provides.
29    pub fn with_style(mut self, style: FontStyle) -> Self {
30        self.style = style;
31        self
32    }
33}
34
35/// A font family made of font files, one for each weight and style.
36#[derive(Clone, Debug, PartialEq, Eq, Hash)]
37pub struct FileBackedFontFamily {
38    /// The files of the family. Never empty when built with [`FileBackedFontFamily::new`].
39    pub fonts: Vec<FontFile>,
40}
41
42/// Error returned when a font family cannot be built.
43#[derive(Clone, Copy, Debug, PartialEq, Eq)]
44pub enum FontFamilyError {
45    /// The list of font files was empty.
46    EmptyFileList,
47}
48
49impl std::fmt::Display for FontFamilyError {
50    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51        match self {
52            FontFamilyError::EmptyFileList => {
53                write!(f, "file-backed font family requires at least one font file")
54            }
55        }
56    }
57}
58
59impl std::error::Error for FontFamilyError {}
60
61impl FileBackedFontFamily {
62    /// Builds a family from `fonts`, or returns [`FontFamilyError::EmptyFileList`] when the list
63    /// is empty.
64    pub fn new(fonts: Vec<FontFile>) -> Result<Self, FontFamilyError> {
65        if fonts.is_empty() {
66            return Err(FontFamilyError::EmptyFileList);
67        }
68        Ok(Self { fonts })
69    }
70}
71
72/// A font family identified by the path of one typeface file.
73#[derive(Clone, Debug, PartialEq, Eq, Hash)]
74pub struct LoadedTypefacePath {
75    /// Path of the typeface file.
76    pub path: String,
77}
78
79impl LoadedTypefacePath {
80    /// Creates a family for the typeface file at `path`.
81    pub fn new(path: impl Into<String>) -> Self {
82        Self { path: path.into() }
83    }
84}
85
86/// The font family of a text. Compose's `FontFamily`.
87#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)]
88pub enum FontFamily {
89    /// The default font of the platform.
90    #[default]
91    Default,
92    /// A generic sans-serif font.
93    SansSerif,
94    /// A generic serif font.
95    Serif,
96    /// A generic monospace font.
97    Monospace,
98    /// A generic cursive font.
99    Cursive,
100    /// A generic fantasy font.
101    Fantasy,
102    /// A family looked up by name among the fonts the renderer has.
103    Named(String),
104    /// A family built from font files.
105    FileBacked(FileBackedFontFamily),
106    /// A family identified by the path of a typeface file.
107    LoadedTypeface(LoadedTypefacePath),
108}
109
110impl FontFamily {
111    /// Returns the family for `name`, as [`FontFamily::from_name`] does.
112    pub fn named(name: impl Into<String>) -> Self {
113        Self::from_name(&name.into())
114    }
115
116    /// Returns the generic family for `name` when it is a generic name such as `serif` or
117    /// `sans-serif`, or an empty or default name, and `Named` for any other name.
118    pub fn from_name(name: &str) -> Self {
119        match name {
120            "" | "Default" | "default" => Self::Default,
121            "SansSerif" | "sans-serif" => Self::SansSerif,
122            "Serif" | "serif" => Self::Serif,
123            "Monospace" | "monospace" => Self::Monospace,
124            "Cursive" | "cursive" => Self::Cursive,
125            "Fantasy" | "fantasy" => Self::Fantasy,
126            value => Self::Named(value.to_string()),
127        }
128    }
129
130    /// Builds a `FileBacked` family from `fonts`, or fails when the list is empty.
131    pub fn file_backed(fonts: Vec<FontFile>) -> Result<Self, FontFamilyError> {
132        FileBackedFontFamily::new(fonts).map(Self::FileBacked)
133    }
134
135    /// Builds a `LoadedTypeface` family for the typeface file at `path`.
136    pub fn loaded_typeface_path(path: impl Into<String>) -> Self {
137        Self::LoadedTypeface(LoadedTypefacePath::new(path))
138    }
139
140    /// Returns the name of a `Named` family, and `None` for every other variant.
141    pub fn family_name(&self) -> Option<&str> {
142        match self {
143            Self::Named(name) => Some(name.as_str()),
144            _ => None,
145        }
146    }
147}
148
149impl From<&str> for FontFamily {
150    fn from(value: &str) -> Self {
151        Self::from_name(value)
152    }
153}
154
155impl From<String> for FontFamily {
156    fn from(value: String) -> Self {
157        Self::from_name(value.as_str())
158    }
159}
160
161/// Weight of a font, from 1 (thinnest) to 1000 (heaviest), as in Compose's `FontWeight`.
162/// 400 is normal and 700 is bold.
163#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
164pub struct FontWeight(pub u16);
165
166impl FontWeight {
167    /// Creates a weight, clamping `weight` into the range 1 to 1000.
168    pub const fn new(weight: u16) -> Self {
169        Self::clamped(weight)
170    }
171
172    /// Creates a weight, or returns `None` when `weight` is outside the range 1 to 1000.
173    pub const fn try_new(weight: u16) -> Option<Self> {
174        if weight >= 1 && weight <= 1000 {
175            Some(Self(weight))
176        } else {
177            None
178        }
179    }
180
181    /// Creates a weight, clamping `weight` into the range 1 to 1000.
182    pub const fn clamped(weight: u16) -> Self {
183        if weight < 1 {
184            Self(1)
185        } else if weight > 1000 {
186            Self(1000)
187        } else {
188            Self(weight)
189        }
190    }
191
192    /// Returns the weight as a number from 1 to 1000.
193    pub const fn value(self) -> u16 {
194        self.0
195    }
196
197    /// Weight 100.
198    pub const THIN: Self = Self(100);
199    /// Weight 200.
200    pub const EXTRA_LIGHT: Self = Self(200);
201    /// Weight 300.
202    pub const LIGHT: Self = Self(300);
203    /// Weight 400.
204    pub const NORMAL: Self = Self(400);
205    /// Weight 500.
206    pub const MEDIUM: Self = Self(500);
207    /// Weight 600.
208    pub const SEMI_BOLD: Self = Self(600);
209    /// Weight 700.
210    pub const BOLD: Self = Self(700);
211    /// Weight 800.
212    pub const EXTRA_BOLD: Self = Self(800);
213    /// Weight 900.
214    pub const BLACK: Self = Self(900);
215    /// Weight 100, the same as [`FontWeight::THIN`].
216    pub const W100: Self = Self(100);
217    /// Weight 200, the same as [`FontWeight::EXTRA_LIGHT`].
218    pub const W200: Self = Self(200);
219    /// Weight 300, the same as [`FontWeight::LIGHT`].
220    pub const W300: Self = Self(300);
221    /// Weight 400, the same as [`FontWeight::NORMAL`].
222    pub const W400: Self = Self(400);
223    /// Weight 500, the same as [`FontWeight::MEDIUM`].
224    pub const W500: Self = Self(500);
225    /// Weight 600, the same as [`FontWeight::SEMI_BOLD`].
226    pub const W600: Self = Self(600);
227    /// Weight 700, the same as [`FontWeight::BOLD`].
228    pub const W700: Self = Self(700);
229    /// Weight 800, the same as [`FontWeight::EXTRA_BOLD`].
230    pub const W800: Self = Self(800);
231    /// Weight 900, the same as [`FontWeight::BLACK`].
232    pub const W900: Self = Self(900);
233}
234
235impl Default for FontWeight {
236    fn default() -> Self {
237        Self::NORMAL
238    }
239}
240
241/// Whether glyphs are upright or italic. Compose's `FontStyle`.
242#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
243pub enum FontStyle {
244    /// Upright glyphs.
245    #[default]
246    Normal,
247    /// Italic glyphs.
248    Italic,
249}
250
251/// Which missing styles the renderer may fake when the chosen font lacks them.
252/// Compose's `FontSynthesis`.
253#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
254pub enum FontSynthesis {
255    /// Nothing is synthesized.
256    #[default]
257    None,
258    /// Both bold and italic may be synthesized.
259    All,
260    /// Only bold may be synthesized.
261    Weight,
262    /// Only italic may be synthesized.
263    Style,
264}
265
266#[cfg(test)]
267#[path = "tests/font_tests.rs"]
268mod tests;