Skip to main content

topcoat_font/
font.rs

1use std::sync::LazyLock;
2
3use topcoat_core::fnv1a::Fnv1a;
4
5use crate::FontFaces;
6
7/// The owned data backing a [`Font`]: its family name, its faces, and the
8/// content hash derived from them.
9#[derive(Debug, Clone, PartialEq)]
10pub struct FontData {
11    family: String,
12    faces: FontFaces,
13    hash: u64,
14}
15
16impl FontData {
17    /// Creates the data for a font named `family`, backed by `faces`.
18    ///
19    /// # Panics
20    ///
21    /// Panics if `faces` cannot be converted into a non-empty [`FontFaces`].
22    #[must_use]
23    #[track_caller]
24    pub fn new(family: impl Into<String>, faces: impl TryInto<FontFaces>) -> Self {
25        let family = family.into();
26        let faces = faces
27            .try_into()
28            .unwrap_or_else(|_| panic!("font faces must not be empty"));
29        let h = Fnv1a::<u64>::new().write(family.as_bytes()).write(b"\0");
30        let hash = faces.hash(h).finish();
31        Self {
32            family,
33            faces,
34            hash,
35        }
36    }
37
38    /// The font's family name.
39    #[must_use]
40    pub fn family(&self) -> &str {
41        &self.family
42    }
43
44    /// The font's faces.
45    #[must_use]
46    pub fn faces(&self) -> &FontFaces {
47        &self.faces
48    }
49
50    /// The content hash of the family name and every face setting.
51    #[must_use]
52    pub fn hash(&self) -> u64 {
53        self.hash
54    }
55}
56
57/// A lightweight, [`Copy`] handle to a font.
58///
59/// It holds a reference to a lazily-initialized [`FontData`], so copying a
60/// `Font` is just copying a pointer; the underlying family name, faces, and
61/// hash are built once, on first access.
62///
63/// See the `font!` macro on how to construct a [`Font`] handle.
64#[derive(Debug, Clone, Copy)]
65pub struct Font(&'static LazyLock<FontData>);
66
67impl Font {
68    /// Creates a font handle backed by `data`.
69    #[must_use]
70    pub const fn new(data: &'static LazyLock<FontData>) -> Self {
71        Self(data)
72    }
73
74    /// The font's family name.
75    #[must_use]
76    pub fn family(&self) -> &str {
77        self.0.family()
78    }
79
80    /// The font's faces.
81    #[must_use]
82    pub fn faces(&self) -> &FontFaces {
83        self.0.faces()
84    }
85
86    /// The content hash of the family name and every face setting.
87    ///
88    /// It is computed once when the font data is initialized, stable across
89    /// builds for identical settings, and distinct when they differ, so it can
90    /// drive a cache-busting, immutable font URL.
91    #[must_use]
92    pub fn hash(&self) -> u64 {
93        self.0.hash()
94    }
95}
96
97impl PartialEq for Font {
98    fn eq(&self, other: &Self) -> bool {
99        std::ptr::eq(self.0, other.0)
100    }
101}
102
103impl Eq for Font {}
104
105#[cfg(feature = "discover")]
106inventory::collect!(Font);
107
108/// Registers a [`Font`] for discovery when the `discover` feature is enabled,
109/// and expands to nothing otherwise.
110///
111/// The feature gate lives here, in the defining crate, so it reflects
112/// topcoat-font's own `discover` feature rather than the calling crate's.
113#[doc(hidden)]
114#[cfg(feature = "discover")]
115#[macro_export]
116macro_rules! register_font {
117    ($font:expr) => {
118        $crate::internal::inventory::submit! { $font }
119    };
120}
121
122#[doc(hidden)]
123#[cfg(not(feature = "discover"))]
124#[macro_export]
125macro_rules! register_font {
126    ($font:expr) => {};
127}