1use crate::schema::GenericFamily;
15use std::rc::Rc;
16
17pub const FONT_REF_LEN: usize = 12;
19pub const FONT_REF_KIND_AT: usize = 0;
21pub const FONT_REF_VALUE_AT: usize = 4;
22pub const FONT_REF_LENGTH_AT: usize = 8;
23
24pub const FONT_REF_ASSET: u32 = 1;
26pub const FONT_REF_SYSTEM: u32 = 2;
29pub const FONT_REF_GENERIC: u32 = 3;
31
32pub const MAX_FONT_REFS: usize = 8;
35
36#[derive(Clone, Debug, PartialEq, Eq)]
38pub enum FontRef {
39 Asset(u32),
41 System(String),
43 Generic(GenericFamily),
45}
46
47#[derive(Clone, Default, PartialEq)]
52pub struct FontRefs {
53 bytes: Option<Rc<[u8]>>,
54}
55
56impl std::fmt::Debug for FontRefs {
57 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 formatter.debug_list().entries(self.refs()).finish()
59 }
60}
61
62impl FontRefs {
63 pub fn new(refs: impl IntoIterator<Item = FontRef>) -> Self {
65 let refs: Vec<FontRef> = refs.into_iter().take(MAX_FONT_REFS).collect();
66 if refs.is_empty() {
67 return Self::default();
68 }
69 let mut bytes = Vec::with_capacity(4 + refs.len() * FONT_REF_LEN);
70 bytes.extend_from_slice(&(refs.len() as u32).to_le_bytes());
71 let names_at = 4 + refs.len() * FONT_REF_LEN;
72 write_records(&refs, &mut bytes, names_at as u32);
73 Self {
74 bytes: Some(bytes.into()),
75 }
76 }
77
78 pub fn from_bytes(bytes: impl Into<Rc<[u8]>>) -> Self {
80 let bytes = bytes.into();
81 Self {
82 bytes: (!bytes.is_empty()).then_some(bytes),
83 }
84 }
85
86 pub fn as_bytes(&self) -> &[u8] {
89 self.bytes.as_deref().unwrap_or(&[])
90 }
91
92 pub fn is_empty(&self) -> bool {
93 self.bytes.is_none()
94 }
95
96 pub fn len(&self) -> usize {
98 let bytes = self.as_bytes();
99 if bytes.len() < 4 {
100 return 0;
101 }
102 u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize
103 }
104
105 pub fn refs(&self) -> Vec<FontRef> {
107 let bytes = self.as_bytes();
108 (0..self.len())
109 .filter_map(|index| decode_record(bytes, 4 + index * FONT_REF_LEN))
110 .collect()
111 }
112
113 pub(crate) fn write_relocated(&self, out: &mut Vec<u8>, base: u32) {
116 write_records(&self.refs(), out, base);
117 }
118}
119
120fn write_records(refs: &[FontRef], out: &mut Vec<u8>, names_base: u32) {
123 let mut names: Vec<u8> = Vec::new();
124 for font in refs {
125 let (kind, value, length) = match font {
126 FontRef::Asset(id) => (FONT_REF_ASSET, *id, 0),
127 FontRef::System(name) => {
128 let at = names_base + names.len() as u32;
129 names.extend_from_slice(name.as_bytes());
130 (FONT_REF_SYSTEM, at, name.len() as u32)
131 }
132 FontRef::Generic(family) => (FONT_REF_GENERIC, u32::from(u16::from(*family)), 0),
133 };
134 out.extend_from_slice(&kind.to_le_bytes());
135 out.extend_from_slice(&value.to_le_bytes());
136 out.extend_from_slice(&length.to_le_bytes());
137 }
138 out.extend_from_slice(&names);
139}
140
141pub(crate) fn decode_record(bytes: &[u8], at: usize) -> Option<FontRef> {
143 let word = |offset: usize| -> Option<u32> {
144 let slice = bytes.get(at + offset..at + offset + 4)?;
145 Some(u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]))
146 };
147 let value = word(FONT_REF_VALUE_AT)?;
148 match word(FONT_REF_KIND_AT)? {
149 FONT_REF_ASSET => Some(FontRef::Asset(value)),
150 FONT_REF_SYSTEM => {
151 let length = word(FONT_REF_LENGTH_AT)? as usize;
152 let name = bytes.get(value as usize..value as usize + length)?;
153 std::str::from_utf8(name)
154 .ok()
155 .map(|name| FontRef::System(name.to_owned()))
156 }
157 FONT_REF_GENERIC => u16::try_from(value)
158 .ok()
159 .and_then(|tag| GenericFamily::try_from(tag).ok())
160 .map(FontRef::Generic),
161 _ => None,
162 }
163}
164
165pub const FONT_STACK_MAGIC: [u8; 4] = *b"CRFS";
173
174pub const FONT_STACK_VERSION: u32 = 1;
177
178pub const MAX_FONT_STACK_FAMILIES: usize = 8;
180
181pub const MAX_FONT_STACK_FACES: usize = 18;
184
185#[derive(Clone, Debug, Default, PartialEq, Eq)]
211pub struct FontStack {
212 families: Vec<Vec<u32>>,
213}
214
215impl FontStack {
216 pub fn new() -> Self {
217 Self::default()
218 }
219
220 pub fn family(mut self, faces: impl IntoIterator<Item = u32>) -> Self {
224 let faces: Vec<u32> = faces.into_iter().take(MAX_FONT_STACK_FACES).collect();
225 if !faces.is_empty() && self.families.len() < MAX_FONT_STACK_FAMILIES {
226 self.families.push(faces);
227 }
228 self
229 }
230
231 pub fn families(&self) -> &[Vec<u32>] {
233 &self.families
234 }
235
236 pub fn to_bytes(&self) -> Vec<u8> {
238 let faces: usize = self.families.iter().map(Vec::len).sum();
239 let mut out = Vec::with_capacity(12 + 4 * (self.families.len() + faces));
240 out.extend_from_slice(&FONT_STACK_MAGIC);
241 out.extend_from_slice(&FONT_STACK_VERSION.to_le_bytes());
242 out.extend_from_slice(&(self.families.len() as u32).to_le_bytes());
243 for family in &self.families {
244 out.extend_from_slice(&(family.len() as u32).to_le_bytes());
245 for face in family {
246 out.extend_from_slice(&face.to_le_bytes());
247 }
248 }
249 out
250 }
251
252 pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
254 let word = |at: usize| -> Option<u32> {
255 let slice = bytes.get(at..at + 4)?;
256 Some(u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]))
257 };
258 if bytes.get(..4)? != FONT_STACK_MAGIC || word(4)? != FONT_STACK_VERSION {
259 return None;
260 }
261 let count = word(8)? as usize;
262 if count > MAX_FONT_STACK_FAMILIES {
263 return None;
264 }
265 let mut at = 12;
266 let mut families = Vec::with_capacity(count);
267 for _ in 0..count {
268 let faces = word(at)? as usize;
269 if faces == 0 || faces > MAX_FONT_STACK_FACES {
270 return None;
271 }
272 at += 4;
273 let mut family = Vec::with_capacity(faces);
274 for _ in 0..faces {
275 family.push(word(at)?);
276 at += 4;
277 }
278 families.push(family);
279 }
280 (at == bytes.len()).then_some(Self { families })
281 }
282
283 pub fn register(&self) -> u32 {
288 crate::asset::register_owned(crate::schema::AssetKind::Font, self.to_bytes())
289 }
290}
291
292#[cfg(test)]
293mod tests {
294 use super::*;
295
296 #[test]
297 fn fr40_a_font_list_round_trips_in_order() {
298 let list = FontRefs::new([
299 FontRef::Asset(7),
300 FontRef::System("JetBrains Mono".to_owned()),
301 FontRef::Generic(GenericFamily::Monospace),
302 ]);
303 assert_eq!(
304 list.refs(),
305 vec![
306 FontRef::Asset(7),
307 FontRef::System("JetBrains Mono".to_owned()),
308 FontRef::Generic(GenericFamily::Monospace),
309 ]
310 );
311 assert_eq!(list.len(), 3);
312 }
313
314 #[test]
315 fn fr40_a_font_list_holds_at_most_eight() {
316 let list = FontRefs::new((0..12).map(FontRef::Asset));
317 assert_eq!(list.len(), MAX_FONT_REFS);
318 assert_eq!(list.refs().last(), Some(&FontRef::Asset(7)));
319 }
320
321 #[test]
322 fn fr40_an_empty_font_list_travels_as_nothing() {
323 assert!(FontRefs::new([]).is_empty());
324 assert!(FontRefs::new([]).as_bytes().is_empty());
325 }
326
327 #[test]
328 fn fr52_a_font_stack_round_trips_in_order() {
329 let stack = FontStack::new().family([1, 2, 3]).family([4]).family([]);
330 assert_eq!(stack.families(), &[vec![1, 2, 3], vec![4]]);
331 let bytes = stack.to_bytes();
332 assert_eq!(&bytes[..4], b"CRFS");
333 assert_eq!(FontStack::from_bytes(&bytes), Some(stack));
334 }
335
336 #[test]
337 fn fr52_font_stack_bytes_are_never_a_font_file() {
338 let fonts: [&[u8]; 6] = [&[0, 1, 0, 0], b"OTTO", b"true", b"ttcf", b"wOFF", b"wOF2"];
339 for font in fonts {
340 assert_ne!(font, &FONT_STACK_MAGIC[..]);
341 assert_eq!(FontStack::from_bytes(font), None);
342 }
343 let mut bytes = FontStack::new().family([1]).to_bytes();
344 bytes.push(0);
345 assert_eq!(
346 FontStack::from_bytes(&bytes),
347 None,
348 "trailing bytes are not a stack"
349 );
350 }
351
352 #[test]
353 fn fr52_a_font_stack_holds_at_most_eight_families() {
354 let stack = (0..12).fold(FontStack::new(), |stack, id| stack.family([id]));
355 assert_eq!(stack.families().len(), MAX_FONT_STACK_FAMILIES);
356 let wide = FontStack::new().family(0..40);
357 assert_eq!(wide.families()[0].len(), MAX_FONT_STACK_FACES);
358 }
359
360 #[test]
361 fn fr52_the_same_font_stack_registers_once() {
362 crate::asset::reset_assets();
363 let stack = FontStack::new().family([1, 2]).family([3]);
364 let first = stack.register();
365 assert_eq!(stack.register(), first);
366 assert_ne!(FontStack::new().family([3]).register(), first);
367 crate::asset::reset_assets();
368 }
369}