1use std::hash::{Hash, Hasher};
2
3use thiserror::Error;
4
5const MAX_FONT_BYTES: usize = 16 * 1024 * 1024;
6
7#[derive(Clone, Debug)]
8pub struct FontSource {
9 label: String,
10 bytes: Vec<u8>,
11 fingerprint: u64,
12}
13
14impl FontSource {
15 pub fn new(label: impl Into<String>, bytes: Vec<u8>) -> Result<Self, FontError> {
22 let label = label.into();
23 if label.trim().is_empty() || label.len() > 256 {
24 return Err(FontError::InvalidLabel(label));
25 }
26 if bytes.is_empty() || bytes.len() > MAX_FONT_BYTES {
27 return Err(FontError::InvalidSize {
28 label,
29 size: bytes.len(),
30 });
31 }
32 let header = bytes.get(..4).ok_or_else(|| FontError::UnsupportedFormat {
33 label: label.clone(),
34 })?;
35 if header != [0x00, 0x01, 0x00, 0x00]
36 && header != b"OTTO"
37 && header != b"true"
38 && header != b"ttcf"
39 {
40 return Err(FontError::UnsupportedFormat { label });
41 }
42 let mut hasher = std::collections::hash_map::DefaultHasher::new();
43 bytes.hash(&mut hasher);
44 Ok(Self {
45 label,
46 bytes,
47 fingerprint: hasher.finish(),
48 })
49 }
50
51 #[must_use]
52 pub fn label(&self) -> &str {
53 &self.label
54 }
55
56 #[must_use]
57 pub fn bytes(&self) -> &[u8] {
58 &self.bytes
59 }
60
61 #[must_use]
62 pub const fn fingerprint(&self) -> u64 {
63 self.fingerprint
64 }
65
66 pub(crate) fn into_bytes(self) -> Vec<u8> {
67 self.bytes
68 }
69}
70
71pub fn validate_font_sources(sources: &[FontSource]) -> Result<(), FontError> {
78 if sources.len() > 64 {
79 return Err(FontError::TooMany(sources.len()));
80 }
81 let mut fingerprints = std::collections::BTreeSet::new();
82 for source in sources {
83 if !fingerprints.insert(source.fingerprint) {
84 return Err(FontError::Duplicate(source.label.clone()));
85 }
86 }
87 Ok(())
88}
89
90#[derive(Clone, Debug, Error, Eq, PartialEq)]
91pub enum FontError {
92 #[error("font label `{0}` must be non-empty and no longer than 256 bytes")]
93 InvalidLabel(String),
94 #[error("font `{label}` must contain 1..=16777216 bytes, got {size}")]
95 InvalidSize { label: String, size: usize },
96 #[error("font `{label}` is not a supported TrueType/OpenType font or collection")]
97 UnsupportedFormat { label: String },
98 #[error("at most 64 font sources may be declared, got {0}")]
99 TooMany(usize),
100 #[error("font `{0}` duplicates an already declared payload")]
101 Duplicate(String),
102 #[error("GPUI failed to load declared fonts: {0}")]
103 Load(String),
104 #[error("failed to read font `{path}`: {message}")]
105 Io { path: String, message: String },
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn font_sources_validate_format_size_and_duplicates() {
114 let font = FontSource::new("Art", [b"OTTO".as_slice(), &[0, 1, 2]].concat()).unwrap();
115 validate_font_sources(std::slice::from_ref(&font)).unwrap();
116 assert!(matches!(
117 validate_font_sources(&[font.clone(), font]),
118 Err(FontError::Duplicate(_))
119 ));
120 assert!(matches!(
121 FontSource::new("bad", b"woff".to_vec()),
122 Err(FontError::UnsupportedFormat { .. })
123 ));
124 }
125}