Skip to main content

compose_rust/
fonts.rs

1//! The font a piece of text with no role is set in.
2//!
3//! Text with a role takes its font from the theme, and nothing on this side can change
4//! that for one node. Text with no role, which is what a page laid out by CSS draws, names
5//! its own: a list of candidates tried in order, the way a CSS `font-family` list is. The
6//! text is set in the first one the Renderer can resolve, and a letter it has no glyph for
7//! comes from the next candidate that has one.
8//!
9//! The list travels as fixed-length records followed by the bytes of the family names they
10//! point at. The same records sit in a `Text`'s `Font` property, in a run of a `Text`'s
11//! spans, and in a measure request, so the Renderer resolves all three with one function
12//! and the text it measures is set in exactly the font it draws.
13
14use crate::schema::GenericFamily;
15use std::rc::Rc;
16
17/// Bytes of one candidate: what kind it is, then two words whose meaning the kind gives.
18pub const FONT_REF_LEN: usize = 12;
19/// Where each word of a candidate starts.
20pub const FONT_REF_KIND_AT: usize = 0;
21pub const FONT_REF_VALUE_AT: usize = 4;
22pub const FONT_REF_LENGTH_AT: usize = 8;
23
24/// A registered font asset. `value` is its id.
25pub const FONT_REF_ASSET: u32 = 1;
26/// An installed family, by name. `value` and `length` are where the UTF-8 name sits,
27/// counted from the start of the buffer the record is in.
28pub const FONT_REF_SYSTEM: u32 = 2;
29/// A generic family. `value` is its `GenericFamily` tag.
30pub const FONT_REF_GENERIC: u32 = 3;
31
32/// The most candidates one list may hold. A CSS list longer than this is a page that
33/// names fonts it does not expect to find, and the ninth would never be reached.
34pub const MAX_FONT_REFS: usize = 8;
35
36/// One candidate.
37#[derive(Clone, Debug, PartialEq, Eq)]
38pub enum FontRef {
39    /// A font the application registered as an asset, by id: CSS `@font-face`.
40    Asset(u32),
41    /// A family installed on the machine, by name.
42    System(String),
43    /// A family CSS names without naming a font.
44    Generic(GenericFamily),
45}
46
47/// A list of candidates, ready to travel.
48///
49/// Shared rather than copied, like a span list: the same list on every frame compares
50/// equal and costs no record.
51#[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    /// The list, in the order it is tried. Anything past [`MAX_FONT_REFS`] is left out.
64    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    /// Adopts an already encoded list.
79    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    /// The property blob: a count, the records, then the names, each record's name
87    /// counted from the start of the blob.
88    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    /// How many candidates the list holds.
97    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    /// The candidates, decoded again. A record that does not decode is left out.
106    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    /// The records without the count, with every name counted from `base` instead of from
114    /// the start of the blob: what a span or a measure request carries.
115    pub(crate) fn write_relocated(&self, out: &mut Vec<u8>, base: u32) {
116        write_records(&self.refs(), out, base);
117    }
118}
119
120/// Writes `refs` as records followed by their names, with each name's position counted
121/// as `names_base` plus its distance from the end of the records.
122fn 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
141/// One record at `at`, with its name read from the same buffer.
142pub(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
165/// What the bytes of a font stack start with.
166///
167/// A stack is registered as a font asset, because to everything that names a font it is
168/// one: a theme slot or a font list names it by id the same way it names a face. These four
169/// bytes are what tell the Renderer it is not a font file. No font format starts with them
170/// (TrueType starts with `00 01 00 00` or `true`, OpenType with `OTTO`, a collection with
171/// `ttcf`), so a face can never be read as a stack or a stack as a face.
172pub const FONT_STACK_MAGIC: [u8; 4] = *b"CRFS";
173
174/// The layout of a stack after the magic. Version 1 is a family count, then for each family
175/// a face count and that many face ids, every number a little endian `u32`.
176pub const FONT_STACK_VERSION: u32 = 1;
177
178/// The most families one stack may hold, for the same reason a font list stops at eight.
179pub const MAX_FONT_STACK_FAMILIES: usize = 8;
180
181/// The most faces one family of a stack may hold: one for each of the nine standard weights,
182/// upright and italic.
183pub const MAX_FONT_STACK_FACES: usize = 18;
184
185/// Fonts the application shipped, tried a glyph at a time in the order given.
186///
187/// A family is the faces of one typeface, a file for each weight or style. Text in a stack
188/// is set in the first family, and each letter that family has no glyph for comes from the
189/// next family that has one, the way CSS reads `font-family: Inter, Pretendard`. Letters no
190/// family has come from the machine's own fonts. Within a family the face is picked by the
191/// weight and style the text asks for, which each face's file says for itself.
192///
193/// A stack is registered once and named by id, so a theme can make it the font of every
194/// role and stay `Copy`:
195///
196/// ```ignore
197/// static INTER_REGULAR: &[u8] = include_bytes!("../assets/fonts/Inter-Regular.otf");
198/// static INTER_BOLD: &[u8] = include_bytes!("../assets/fonts/Inter-Bold.otf");
199/// static PRETENDARD_REGULAR: &[u8] = include_bytes!("../assets/fonts/Pretendard-Regular.otf");
200///
201/// let latin = [asset(AssetKind::Font, INTER_REGULAR), asset(AssetKind::Font, INTER_BOLD)];
202/// let korean = [asset(AssetKind::Font, PRETENDARD_REGULAR)];
203/// let stack = FontStack::new().family(latin).family(korean).register();
204/// let theme = Theme::adaptive(DesignSystem::Material3).with_default_font(stack);
205/// ```
206///
207/// The faces have to be registered before the stack that names them, which the order above
208/// gives for free. A stack naming an id that is not a registered face is reported by the
209/// Renderer, and that face is left out.
210#[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    /// Adds one family, tried after every family added before it. An empty family is left
221    /// out, and so is anything past [`MAX_FONT_STACK_FAMILIES`] families or
222    /// [`MAX_FONT_STACK_FACES`] faces.
223    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    /// The families, in the order they are tried.
232    pub fn families(&self) -> &[Vec<u32>] {
233        &self.families
234    }
235
236    /// The asset bytes: the magic, the version, then the families.
237    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    /// Reads bytes [`FontStack::to_bytes`] wrote, or `None` for anything else.
253    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    /// Registers the stack and returns the id a theme or a font list names it by.
284    ///
285    /// The same stack registered twice is one registration, as with any asset, so this can
286    /// run in a component body.
287    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}