Skip to main content

topcoat_font/
display.rs

1//! Font display strategies for the `font-display` descriptor on `@font-face`
2//! rules.
3
4/// How a font face is displayed while it loads, as named by the CSS
5/// `font-display` descriptor of an `@font-face` rule.
6///
7/// The strategy governs the *block* period (during which text renders
8/// invisibly, awaiting the face) and the *swap* period (during which a fallback
9/// renders but is swapped for the face once it loads). [`FontDisplay::default`]
10/// is [`Auto`](FontDisplay::Auto), matching CSS.
11///
12/// Displays as the CSS keyword (`auto`, `block`, `swap`, `fallback`,
13/// `optional`).
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
15pub enum FontDisplay {
16    /// The font display strategy is defined by the user agent, CSS `auto`.
17    #[default]
18    Auto,
19    /// Gives the font face a short block period and an infinite swap period,
20    /// CSS `block`.
21    Block,
22    /// Gives the font face an extremely small block period and an infinite swap
23    /// period, CSS `swap`.
24    Swap,
25    /// Gives the font face an extremely small block period and a short swap
26    /// period, CSS `fallback`.
27    Fallback,
28    /// Gives the font face an extremely small block period and no swap period,
29    /// CSS `optional`.
30    Optional,
31}
32
33impl FontDisplay {
34    /// The CSS keyword for this strategy, as written for the `font-display`
35    /// descriptor.
36    #[must_use]
37    pub const fn keyword(self) -> &'static str {
38        match self {
39            Self::Auto => "auto",
40            Self::Block => "block",
41            Self::Swap => "swap",
42            Self::Fallback => "fallback",
43            Self::Optional => "optional",
44        }
45    }
46
47    /// The strategy named by the given CSS `font-display` keyword, if the
48    /// keyword names a known strategy.
49    #[must_use]
50    pub fn from_keyword(keyword: &str) -> Option<Self> {
51        Some(match keyword {
52            "auto" => Self::Auto,
53            "block" => Self::Block,
54            "swap" => Self::Swap,
55            "fallback" => Self::Fallback,
56            "optional" => Self::Optional,
57            _ => return None,
58        })
59    }
60
61    /// Folds this strategy into a running content hash.
62    pub(crate) const fn hash(
63        self,
64        h: topcoat_core::fnv1a::Fnv1a<u64>,
65    ) -> topcoat_core::fnv1a::Fnv1a<u64> {
66        h.write(self.keyword().as_bytes())
67    }
68}
69
70impl std::fmt::Display for FontDisplay {
71    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72        f.write_str(self.keyword())
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn default_is_auto() {
82        assert_eq!(FontDisplay::default(), FontDisplay::Auto);
83    }
84
85    #[test]
86    fn displays_as_its_keyword() {
87        assert_eq!(FontDisplay::Auto.to_string(), "auto");
88        assert_eq!(FontDisplay::Block.to_string(), "block");
89        assert_eq!(FontDisplay::Swap.to_string(), "swap");
90        assert_eq!(FontDisplay::Fallback.to_string(), "fallback");
91        assert_eq!(FontDisplay::Optional.to_string(), "optional");
92    }
93
94    #[test]
95    fn from_keyword_round_trips() {
96        for strategy in [
97            FontDisplay::Auto,
98            FontDisplay::Block,
99            FontDisplay::Swap,
100            FontDisplay::Fallback,
101            FontDisplay::Optional,
102        ] {
103            assert_eq!(
104                FontDisplay::from_keyword(strategy.keyword()),
105                Some(strategy)
106            );
107        }
108    }
109
110    #[test]
111    fn from_keyword_rejects_unknown() {
112        assert_eq!(FontDisplay::from_keyword("infinite"), None);
113    }
114}