Skip to main content

ferroui_base/media/fonts/
font_collection_key.rs

1use std::cmp::Ordering;
2
3use crate::media::{FontStretch, FontStyle, FontWeight, GlyphTypeface, IPlatformTypeface, Typeface};
4
5/// Represents a unique key for identifying a font within a font collection
6/// based on style, weight and stretch.
7///
8/// Keys order by style, then weight, then stretch (numeric values).
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
10pub struct FontCollectionKey {
11    pub style: FontStyle,
12    pub weight: FontWeight,
13    pub stretch: FontStretch,
14}
15
16impl FontCollectionKey {
17    pub const fn new(style: FontStyle, weight: FontWeight, stretch: FontStretch) -> Self {
18        Self { style, weight, stretch }
19    }
20}
21
22impl Ord for FontCollectionKey {
23    fn cmp(&self, other: &Self) -> Ordering {
24        (self.style as i32)
25            .cmp(&(other.style as i32))
26            .then_with(|| self.weight.value().cmp(&other.weight.value()))
27            .then_with(|| (self.stretch as i32).cmp(&(other.stretch as i32)))
28    }
29}
30
31impl PartialOrd for FontCollectionKey {
32    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
33        Some(self.cmp(other))
34    }
35}
36
37// The `ToFontCollectionKey` extension methods (upstream `FontCollectionKeyExtensions.cs`).
38
39impl From<&Typeface> for FontCollectionKey {
40    fn from(typeface: &Typeface) -> Self {
41        FontCollectionKey::new(typeface.style(), typeface.weight(), typeface.stretch())
42    }
43}
44
45impl From<&GlyphTypeface> for FontCollectionKey {
46    fn from(glyph_typeface: &GlyphTypeface) -> Self {
47        FontCollectionKey::new(glyph_typeface.style(), glyph_typeface.weight(), glyph_typeface.stretch())
48    }
49}
50
51impl From<&dyn IPlatformTypeface> for FontCollectionKey {
52    fn from(platform_typeface: &dyn IPlatformTypeface) -> Self {
53        FontCollectionKey::new(platform_typeface.style(), platform_typeface.weight(), platform_typeface.stretch())
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60
61    #[test]
62    fn equal_keys_compare_equal() {
63        let a = FontCollectionKey::new(FontStyle::Italic, FontWeight::Bold, FontStretch::Condensed);
64        let b = FontCollectionKey::new(FontStyle::Italic, FontWeight::Bold, FontStretch::Condensed);
65
66        assert_eq!(a.cmp(&b), Ordering::Equal);
67        assert_eq!(a.partial_cmp(&b), Some(Ordering::Equal));
68        assert!(a <= b);
69        assert!(a >= b);
70    }
71
72    #[test]
73    fn style_is_the_primary_sort_key() {
74        // Normal style, max weight/stretch
75        let normal = FontCollectionKey::new(FontStyle::Normal, FontWeight::Black, FontStretch::UltraExpanded);
76        // Italic style, min weight/stretch
77        let italic = FontCollectionKey::new(FontStyle::Italic, FontWeight::Thin, FontStretch::UltraCondensed);
78
79        assert_eq!(normal.cmp(&italic), Ordering::Less);
80        assert_eq!(italic.cmp(&normal), Ordering::Greater);
81        assert!(normal < italic);
82    }
83
84    #[test]
85    fn weight_is_the_secondary_sort_key_when_style_matches() {
86        let light = FontCollectionKey::new(FontStyle::Normal, FontWeight::Light, FontStretch::UltraExpanded);
87        let bold = FontCollectionKey::new(FontStyle::Normal, FontWeight::Bold, FontStretch::UltraCondensed);
88
89        assert_eq!(light.cmp(&bold), Ordering::Less);
90        assert!(light < bold);
91    }
92
93    #[test]
94    fn stretch_is_the_tertiary_sort_key_when_style_and_weight_match() {
95        let condensed = FontCollectionKey::new(FontStyle::Normal, FontWeight::Normal, FontStretch::Condensed);
96        let expanded = FontCollectionKey::new(FontStyle::Normal, FontWeight::Normal, FontStretch::Expanded);
97
98        assert_eq!(condensed.cmp(&expanded), Ordering::Less);
99        assert!(condensed < expanded);
100    }
101
102    #[test]
103    fn array_sort_produces_lexicographic_order() {
104        let mut keys = [
105            FontCollectionKey::new(FontStyle::Italic, FontWeight::Normal, FontStretch::Normal),
106            FontCollectionKey::new(FontStyle::Normal, FontWeight::Bold, FontStretch::Normal),
107            FontCollectionKey::new(FontStyle::Normal, FontWeight::Normal, FontStretch::Expanded),
108            FontCollectionKey::new(FontStyle::Normal, FontWeight::Normal, FontStretch::Normal),
109        ];
110
111        keys.sort();
112
113        assert_eq!(
114            keys,
115            [
116                FontCollectionKey::new(FontStyle::Normal, FontWeight::Normal, FontStretch::Normal),
117                FontCollectionKey::new(FontStyle::Normal, FontWeight::Normal, FontStretch::Expanded),
118                FontCollectionKey::new(FontStyle::Normal, FontWeight::Bold, FontStretch::Normal),
119                FontCollectionKey::new(FontStyle::Italic, FontWeight::Normal, FontStretch::Normal),
120            ]
121        );
122    }
123
124    #[test]
125    fn compare_to_is_consistent_with_equality() {
126        let a = FontCollectionKey::new(FontStyle::Oblique, FontWeight::Medium, FontStretch::SemiExpanded);
127        let b = FontCollectionKey::new(FontStyle::Oblique, FontWeight::Medium, FontStretch::SemiExpanded);
128        let c = FontCollectionKey::new(FontStyle::Oblique, FontWeight::Medium, FontStretch::SemiCondensed);
129
130        assert_eq!(a, b);
131        assert_eq!(a.cmp(&b), Ordering::Equal);
132        assert_ne!(a, c);
133        assert_ne!(a.cmp(&c), Ordering::Equal);
134    }
135
136    #[test]
137    fn keys_are_made_from_typefaces() {
138        let typeface = Typeface::from_name_with_style("A", FontStyle::Italic, FontWeight::Bold, FontStretch::Expanded);
139
140        assert_eq!(
141            FontCollectionKey::from(&typeface),
142            FontCollectionKey::new(FontStyle::Italic, FontWeight::Bold, FontStretch::Expanded)
143        );
144    }
145}