1use std::{fmt::Write, ops::Deref};
4
5use topcoat_core::{context::Cx, fnv1a::Fnv1a};
6
7use crate::{CssString, FontDisplay, FontSources, FontStyle, FontWeightRange, UnicodeRanges};
8
9#[derive(Debug, Clone, PartialEq)]
16pub struct FontFace {
17 family: String,
18 src: FontSources,
19 weight: Option<FontWeightRange>,
20 style: Option<FontStyle>,
21 display: Option<FontDisplay>,
22 unicode_range: Option<UnicodeRanges>,
23}
24
25impl FontFace {
26 #[must_use]
37 #[track_caller]
38 pub fn new(family: impl Into<String>, src: impl TryInto<FontSources>) -> Self {
39 Self {
40 family: family.into(),
41 src: src
42 .try_into()
43 .unwrap_or_else(|_| panic!("font sources must not be empty")),
44 weight: None,
45 style: None,
46 display: None,
47 unicode_range: None,
48 }
49 }
50
51 #[must_use]
53 pub fn with_weight(mut self, weight: FontWeightRange) -> Self {
54 self.weight = Some(weight);
55 self
56 }
57
58 #[must_use]
60 pub fn with_style(mut self, style: FontStyle) -> Self {
61 self.style = Some(style);
62 self
63 }
64
65 #[must_use]
67 pub fn with_display(mut self, display: FontDisplay) -> Self {
68 self.display = Some(display);
69 self
70 }
71
72 #[must_use]
74 pub fn with_unicode_range(mut self, unicode_range: UnicodeRanges) -> Self {
75 self.unicode_range = Some(unicode_range);
76 self
77 }
78
79 #[track_caller]
85 pub fn fmt(&self, cx: &Cx, f: &mut dyn Write) -> std::fmt::Result {
86 f.write_str("@font-face { font-family: \"")?;
87 CssString(&mut *f).write_str(&self.family)?;
88 f.write_str("\"; src: ")?;
89 self.src.fmt(cx, &mut *f)?;
90 if let Some(weight) = self.weight {
91 write!(f, "; font-weight: {weight}")?;
92 }
93 if let Some(style) = self.style {
94 write!(f, "; font-style: {style}")?;
95 }
96 if let Some(display) = self.display {
97 write!(f, "; font-display: {display}")?;
98 }
99 if let Some(unicode_range) = self.unicode_range {
100 write!(f, "; unicode-range: {unicode_range}")?;
101 }
102 f.write_str(" }")?;
103 Ok(())
104 }
105
106 pub(crate) fn hash(&self, h: Fnv1a<u64>) -> Fnv1a<u64> {
108 let h = h.write(self.family.as_bytes());
109 let h = self.src.hash(h);
110 let h = match self.weight {
111 Some(weight) => weight.hash(h.write(&[1])),
112 None => h.write(&[0]),
113 };
114 let h = match self.style {
115 Some(style) => style.hash(h.write(&[1])),
116 None => h.write(&[0]),
117 };
118 let h = match self.display {
119 Some(display) => display.hash(h.write(&[1])),
120 None => h.write(&[0]),
121 };
122 match self.unicode_range {
123 Some(unicode_range) => unicode_range.hash(h.write(&[1])),
124 None => h.write(&[0]),
125 }
126 }
127
128 #[must_use]
130 pub fn family(&self) -> &str {
131 &self.family
132 }
133
134 #[must_use]
136 pub fn src(&self) -> &FontSources {
137 &self.src
138 }
139
140 #[must_use]
142 pub fn weight(&self) -> Option<FontWeightRange> {
143 self.weight
144 }
145
146 #[must_use]
148 pub fn style(&self) -> Option<FontStyle> {
149 self.style
150 }
151
152 #[must_use]
154 pub fn display(&self) -> Option<FontDisplay> {
155 self.display
156 }
157
158 #[must_use]
160 pub fn unicode_range(&self) -> Option<UnicodeRanges> {
161 self.unicode_range
162 }
163}
164
165#[derive(Debug, Clone, PartialEq)]
169pub struct FontFaces(Vec<FontFace>);
170
171impl FontFaces {
172 #[must_use]
178 #[track_caller]
179 pub fn new(faces: impl Into<Vec<FontFace>>) -> Self {
180 let faces = faces.into();
181 assert!(!faces.is_empty(), "font faces must not be empty");
182 Self(faces)
183 }
184
185 pub(crate) fn hash(&self, mut h: Fnv1a<u64>) -> Fnv1a<u64> {
187 for face in &self.0 {
188 h = face.hash(h);
189 }
190 h
191 }
192
193 #[track_caller]
199 pub fn fmt(&self, cx: &Cx, f: &mut dyn Write) -> std::fmt::Result {
200 for (index, face) in self.0.iter().enumerate() {
201 if index > 0 {
202 f.write_str(" ")?;
203 }
204 face.fmt(cx, &mut *f)?;
205 }
206 Ok(())
207 }
208
209 #[must_use]
214 pub fn as_slice(&self) -> &[FontFace] {
215 &self.0
216 }
217
218 fn try_from_vec(faces: Vec<FontFace>) -> Result<Self, EmptyFontFacesError> {
220 if faces.is_empty() {
221 return Err(EmptyFontFacesError);
222 }
223 Ok(Self(faces))
224 }
225}
226
227#[derive(Debug, Clone, Copy, PartialEq, Eq)]
229pub struct EmptyFontFacesError;
230
231impl std::fmt::Display for EmptyFontFacesError {
232 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
233 f.write_str("font faces must not be empty")
234 }
235}
236
237impl std::error::Error for EmptyFontFacesError {}
238
239impl TryFrom<Vec<FontFace>> for FontFaces {
240 type Error = EmptyFontFacesError;
241
242 fn try_from(faces: Vec<FontFace>) -> Result<Self, Self::Error> {
243 Self::try_from_vec(faces)
244 }
245}
246
247impl TryFrom<&[FontFace]> for FontFaces {
248 type Error = EmptyFontFacesError;
249
250 fn try_from(faces: &[FontFace]) -> Result<Self, Self::Error> {
251 Self::try_from_vec(faces.to_vec())
252 }
253}
254
255impl Deref for FontFaces {
256 type Target = [FontFace];
257
258 fn deref(&self) -> &Self::Target {
259 self.as_slice()
260 }
261}