1use crate::schema::GenericFamily;
14use std::rc::Rc;
15
16pub const FONT_REF_LEN: usize = 12;
18pub const FONT_REF_KIND_AT: usize = 0;
20pub const FONT_REF_VALUE_AT: usize = 4;
21pub const FONT_REF_LENGTH_AT: usize = 8;
22
23pub const FONT_REF_ASSET: u32 = 1;
25pub const FONT_REF_SYSTEM: u32 = 2;
28pub const FONT_REF_GENERIC: u32 = 3;
30
31pub const MAX_FONT_REFS: usize = 8;
34
35#[derive(Clone, Debug, PartialEq, Eq)]
37pub enum FontRef {
38 Asset(u32),
40 System(String),
42 Generic(GenericFamily),
44}
45
46#[derive(Clone, Default, PartialEq)]
51pub struct FontRefs {
52 bytes: Option<Rc<[u8]>>,
53}
54
55impl std::fmt::Debug for FontRefs {
56 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 formatter.debug_list().entries(self.refs()).finish()
58 }
59}
60
61impl FontRefs {
62 pub fn new(refs: impl IntoIterator<Item = FontRef>) -> Self {
64 let refs: Vec<FontRef> = refs.into_iter().take(MAX_FONT_REFS).collect();
65 if refs.is_empty() {
66 return Self::default();
67 }
68 let mut bytes = Vec::with_capacity(4 + refs.len() * FONT_REF_LEN);
69 bytes.extend_from_slice(&(refs.len() as u32).to_le_bytes());
70 let names_at = 4 + refs.len() * FONT_REF_LEN;
71 write_records(&refs, &mut bytes, names_at as u32);
72 Self {
73 bytes: Some(bytes.into()),
74 }
75 }
76
77 pub fn from_bytes(bytes: impl Into<Rc<[u8]>>) -> Self {
79 let bytes = bytes.into();
80 Self {
81 bytes: (!bytes.is_empty()).then_some(bytes),
82 }
83 }
84
85 pub fn as_bytes(&self) -> &[u8] {
88 self.bytes.as_deref().unwrap_or(&[])
89 }
90
91 pub fn is_empty(&self) -> bool {
92 self.bytes.is_none()
93 }
94
95 pub fn len(&self) -> usize {
97 let bytes = self.as_bytes();
98 if bytes.len() < 4 {
99 return 0;
100 }
101 u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize
102 }
103
104 pub fn refs(&self) -> Vec<FontRef> {
106 let bytes = self.as_bytes();
107 (0..self.len())
108 .filter_map(|index| decode_record(bytes, 4 + index * FONT_REF_LEN))
109 .collect()
110 }
111
112 pub(crate) fn write_relocated(&self, out: &mut Vec<u8>, base: u32) {
115 write_records(&self.refs(), out, base);
116 }
117}
118
119fn write_records(refs: &[FontRef], out: &mut Vec<u8>, names_base: u32) {
122 let mut names: Vec<u8> = Vec::new();
123 for font in refs {
124 let (kind, value, length) = match font {
125 FontRef::Asset(id) => (FONT_REF_ASSET, *id, 0),
126 FontRef::System(name) => {
127 let at = names_base + names.len() as u32;
128 names.extend_from_slice(name.as_bytes());
129 (FONT_REF_SYSTEM, at, name.len() as u32)
130 }
131 FontRef::Generic(family) => (FONT_REF_GENERIC, u32::from(u16::from(*family)), 0),
132 };
133 out.extend_from_slice(&kind.to_le_bytes());
134 out.extend_from_slice(&value.to_le_bytes());
135 out.extend_from_slice(&length.to_le_bytes());
136 }
137 out.extend_from_slice(&names);
138}
139
140pub(crate) fn decode_record(bytes: &[u8], at: usize) -> Option<FontRef> {
142 let word = |offset: usize| -> Option<u32> {
143 let slice = bytes.get(at + offset..at + offset + 4)?;
144 Some(u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]))
145 };
146 let value = word(FONT_REF_VALUE_AT)?;
147 match word(FONT_REF_KIND_AT)? {
148 FONT_REF_ASSET => Some(FontRef::Asset(value)),
149 FONT_REF_SYSTEM => {
150 let length = word(FONT_REF_LENGTH_AT)? as usize;
151 let name = bytes.get(value as usize..value as usize + length)?;
152 std::str::from_utf8(name)
153 .ok()
154 .map(|name| FontRef::System(name.to_owned()))
155 }
156 FONT_REF_GENERIC => u16::try_from(value)
157 .ok()
158 .and_then(|tag| GenericFamily::try_from(tag).ok())
159 .map(FontRef::Generic),
160 _ => None,
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167
168 #[test]
169 fn fr40_a_font_list_round_trips_in_order() {
170 let list = FontRefs::new([
171 FontRef::Asset(7),
172 FontRef::System("JetBrains Mono".to_owned()),
173 FontRef::Generic(GenericFamily::Monospace),
174 ]);
175 assert_eq!(
176 list.refs(),
177 vec![
178 FontRef::Asset(7),
179 FontRef::System("JetBrains Mono".to_owned()),
180 FontRef::Generic(GenericFamily::Monospace),
181 ]
182 );
183 assert_eq!(list.len(), 3);
184 }
185
186 #[test]
187 fn fr40_a_font_list_holds_at_most_eight() {
188 let list = FontRefs::new((0..12).map(FontRef::Asset));
189 assert_eq!(list.len(), MAX_FONT_REFS);
190 assert_eq!(list.refs().last(), Some(&FontRef::Asset(7)));
191 }
192
193 #[test]
194 fn fr40_an_empty_font_list_travels_as_nothing() {
195 assert!(FontRefs::new([]).is_empty());
196 assert!(FontRefs::new([]).as_bytes().is_empty());
197 }
198}