fontpm_api/
font.rs

1use std::cmp::Ordering;
2use std::fmt::{Debug, Display, Formatter, Write};
3use std::str::FromStr;
4use crate::Error;
5
6#[derive(Eq, PartialEq, Copy, Clone, Debug, Hash)]
7pub enum DefinedFontWeight {
8    Fixed(u32),
9    Variable
10}
11impl DefinedFontWeight {
12    pub const REGULAR: Self = DefinedFontWeight::Fixed(400);
13    pub fn is_covered_by(&self, other: &DefinedFontWeight) -> bool {
14        match other {
15            DefinedFontWeight::Fixed(other) => match self {
16                DefinedFontWeight::Fixed(me) => *other == *me,
17                _ => false
18            },
19            DefinedFontWeight::Variable => *self == DefinedFontWeight::Variable
20        }
21    }
22    pub fn is_fixed(&self) -> bool {
23        match self {
24            DefinedFontWeight::Fixed(_) => true,
25            DefinedFontWeight::Variable => false
26        }
27    }
28}
29impl Display for DefinedFontWeight {
30    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
31        match self {
32            DefinedFontWeight::Fixed(w) => Display::fmt(w, f),
33            DefinedFontWeight::Variable => f.write_str("variable")
34        }
35    }
36}
37
38impl Ord for DefinedFontWeight {
39    /// [`Ord`] implementation for [`DefinedFontWeight`].
40    /// The implementation defines the ordering as follows (from least to greatest):
41    /// - Variable
42    /// - Regular weight (400)
43    /// - Int comparison
44    fn cmp(&self, other: &Self) -> Ordering {
45        match self {
46            Self::Variable => match other {
47                Self::Variable => Ordering::Equal,
48                Self::Fixed(_) => Ordering::Less
49            },
50            Self::Fixed(400) => match other {
51                Self::Variable => Ordering::Greater,
52                Self::Fixed(400) => Ordering::Equal,
53                Self::Fixed(_) => Ordering::Less
54            },
55            Self::Fixed(self_w) => match other {
56                Self::Variable => Ordering::Greater,
57                Self::Fixed(400) => Ordering::Greater,
58                Self::Fixed(other_w) => self_w.cmp(other_w)
59            }
60        }
61    }
62}
63impl PartialOrd<Self> for DefinedFontWeight {
64    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
65        Some(self.cmp(other))
66    }
67}
68
69#[derive(Eq, PartialEq, Copy, Clone, Debug, Hash)]
70pub enum FontWeight {
71    Defined(DefinedFontWeight),
72    AllFixed, // All available fixed variants
73    All, // All weights (incl. fixed *and* variable)
74}
75impl FontWeight {
76    pub fn is_covered_by(&self, other: &FontWeight) -> bool {
77        match other {
78            FontWeight::All => true, // all weights are covered
79            FontWeight::AllFixed => match self {
80                // only fixed-weight fonts are downloaded for AllFixed, not Variable
81                FontWeight::AllFixed | FontWeight::Defined(DefinedFontWeight::Fixed(_)) => true,
82                _ => false
83            },
84            FontWeight::Defined(other_defined) => match self {
85                FontWeight::Defined(self_defined) => self_defined.is_covered_by(other_defined),
86                _ => false
87            }
88        }
89    }
90}
91
92#[derive(Eq, PartialEq, Copy, Clone, Debug, Hash, Ord, PartialOrd)]
93pub enum DefinedFontStyle {
94    Regular,
95    Italic
96}
97impl DefinedFontStyle {
98    pub fn is_covered_by(&self, other: &DefinedFontStyle) -> bool {
99        // NOTE(tecc): this only exists to have a somewhat consistent API
100        self.eq(other)
101    }
102}
103impl FromStr for DefinedFontStyle {
104    type Err = Error;
105
106    fn from_str(s: &str) -> Result<Self, Self::Err> {
107        if s.is_empty() {
108            return Ok(Self::Regular)
109        }
110        match s {
111            "regular" | "normal" => Ok(Self::Regular),
112            "italic" => Ok(Self::Italic),
113            _ => Err(Error::Deserialisation(format!("No such font style: {}", s)))
114        }
115    }
116}
117impl AsRef<str> for DefinedFontStyle {
118    fn as_ref(&self) -> &str {
119        match self {
120            Self::Regular => "regular",
121            Self::Italic => "italic"
122        }
123    }
124}
125impl Display for DefinedFontStyle {
126    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
127        f.write_str(self.as_ref())
128    }
129}
130
131#[derive(Eq, PartialEq, Copy, Clone, Debug, Hash)]
132pub enum FontStyle {
133    Defined(DefinedFontStyle),
134    All
135}
136
137impl FontStyle {
138    pub fn is_covered_by(&self, other: &FontStyle) -> bool {
139        match other {
140            FontStyle::All => true,
141            FontStyle::Defined(other_defined) => match self {
142                FontStyle::Defined(self_defined) => self_defined.is_covered_by(other_defined),
143                _ => false
144            }
145        }
146    }
147}
148
149#[derive(Eq, PartialEq, Copy, Clone, Debug, Hash)]
150pub struct DefinedFontVariantSpec {
151    pub weight: DefinedFontWeight,
152    pub style: DefinedFontStyle
153}
154
155impl DefinedFontVariantSpec {
156    pub const REGULAR: Self = DefinedFontVariantSpec {
157        weight: DefinedFontWeight::REGULAR,
158        style: DefinedFontStyle::Regular
159    };
160    pub fn is_covered_by(&self, other: &DefinedFontVariantSpec) -> bool {
161        let weight_is_covered = self.weight.is_covered_by(&other.weight);
162        let style_is_covered = self.style.is_covered_by(&other.style);
163        weight_is_covered && style_is_covered
164    }
165}
166
167impl Ord for DefinedFontVariantSpec {
168    fn cmp(&self, other: &Self) -> Ordering {
169        self.weight.cmp(&other.weight)
170            .then_with(|| self.style.cmp(&other.style))
171    }
172}
173
174impl PartialOrd<Self> for DefinedFontVariantSpec {
175    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
176        Some(self.cmp(other))
177    }
178}
179
180#[derive(Eq, PartialEq, Copy, Clone, Debug, Hash)]
181pub struct FontVariantSpec {
182    pub weight: FontWeight,
183    pub style: FontStyle
184}
185
186impl FontVariantSpec {
187    pub fn is_covered_by(&self, other: &FontVariantSpec) -> bool {
188        let weight_is_covered = self.weight.is_covered_by(&other.weight);
189        let style_is_covered = self.style.is_covered_by(&other.style);
190        weight_is_covered && style_is_covered
191    }
192}
193
194#[derive(Eq, PartialEq, Clone, Debug, Hash)]
195pub struct DefinedFontInstallSpec {
196    pub id: String,
197    pub styles: Vec<DefinedFontVariantSpec>
198}
199impl DefinedFontInstallSpec {
200    pub fn new<S, I, F>(id: S, styles: I) -> Self where S: ToString, I: IntoIterator<Item = F>, F: Into<DefinedFontVariantSpec> {
201        Self {
202            id: id.to_string(),
203            styles: styles.into_iter().map(|v| v.into()).collect()
204        }
205    }
206}
207
208#[derive(Eq, PartialEq, Clone, Debug, Hash)]
209pub struct FontInstallSpec {
210    pub id: String,
211    pub styles: Vec<FontVariantSpec>
212}
213
214impl FontInstallSpec {
215    pub fn new<S, I, F>(id: S, styles: I) -> Self where S: ToString, I: IntoIterator<Item = F>, F: Into<FontVariantSpec> {
216        Self {
217            id: id.to_string(),
218            styles: styles.into_iter().map(|v| v.into()).collect()
219        }
220    }
221    pub fn new_all_styles<S>(id: S) -> Self where S: ToString {
222        Self::new(id, vec![FontVariantSpec { style: FontStyle::All, weight: FontWeight::All }])
223    }
224}
225#[derive(Eq, PartialEq, Clone, Debug, Hash)]
226pub struct FontDescription {
227    pub name: String,
228    pub id: String,
229    pub version: String
230}
231impl FontDescription {
232    pub fn new(name: impl ToString, id: impl ToString, version: impl ToString) -> Self {
233        Self {
234            name: name.to_string(),
235            id: id.to_string(),
236            version: version.to_string()
237        }
238    }
239}
240impl AsRef<FontDescription> for FontDescription {
241    fn as_ref(&self) -> &Self {
242        self
243    }
244}
245
246#[cfg(test)]
247mod tests {
248    use super::*;
249
250    #[test]
251    fn weight_ordering() {
252        use DefinedFontWeight as W;
253        assert!(W::Fixed(100) < W::Fixed(200));
254        assert!(W::Fixed(200) < W::Fixed(300));
255        assert!(W::Fixed(900) > W::Fixed(800));
256        assert!(W::Fixed(800) > W::Fixed(700));
257        // Variable is considered the least out of all weights
258        assert!(W::Variable < W::Fixed(100));
259        assert!(W::Variable < W::Fixed(300));
260        assert!(W::Variable < W::Fixed(500));
261        assert!(W::Variable < W::Fixed(700));
262        assert!(W::Variable < W::Fixed(900));
263        assert!(W::Variable < W::REGULAR);
264        /* Regular (or Fixed(400)) is next up */
265        // The following should pass either way
266        assert!(W::REGULAR < W::Fixed(900));
267        assert!(W::REGULAR < W::Fixed(800));
268        assert!(W::REGULAR < W::Fixed(700));
269        assert!(W::REGULAR < W::Fixed(600));
270        assert!(W::REGULAR < W::Fixed(500));
271        // The next ones would not pass without the specifics of the current implementation
272        assert!(W::REGULAR < W::Fixed(300));
273        assert!(W::REGULAR < W::Fixed(200));
274        assert!(W::REGULAR < W::Fixed(100));
275        // Next up, we do the *exact same thing* but in reverse
276        assert!(W::Fixed(900) > W::Fixed(400));
277        assert!(W::Fixed(800) > W::Fixed(400));
278        assert!(W::Fixed(700) > W::Fixed(400));
279        assert!(W::Fixed(600) > W::Fixed(400));
280        assert!(W::Fixed(500) > W::Fixed(400));
281        assert!(W::Fixed(300) > W::Fixed(400));
282        assert!(W::Fixed(200) > W::Fixed(400));
283        assert!(W::Fixed(100) > W::Fixed(400));
284    }
285}