Skip to main content

mp4_atom/moov/trak/mdia/minf/stbl/stsd/
ftab.rs

1use crate::*;
2
3/// Font table for tx3g
4///
5/// 3GPP TS 26.245 or ETSI TS 126 245 Section 5.16
6/// See https://www.etsi.org/deliver/etsi_ts/126200_126299/126245/18.00.00_60/ts_126245v180000p.pdf
7
8#[derive(Debug, Clone, PartialEq, Eq, Default)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10pub struct FontEntry {
11    pub font_id: u16,
12    pub font: String,
13}
14
15#[derive(Debug, Clone, PartialEq, Eq, Default)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17pub struct Ftab {
18    pub font_entries: Vec<FontEntry>,
19}
20
21impl Atom for Ftab {
22    const KIND: FourCC = FourCC::new(b"ftab");
23
24    fn decode_body<B: Buf>(buf: &mut B) -> Result<Self> {
25        let entry_count = u16::decode(buf)?;
26        let mut font_entries = Vec::with_capacity(std::cmp::min(8, entry_count as usize));
27        for _ in 0..entry_count {
28            let font_id = u16::decode(buf)?;
29            let font = decode_length_prefixed_bom_string::<u8>(buf)?;
30            let font_entry = FontEntry { font_id, font };
31            font_entries.push(font_entry);
32        }
33        Ok(Ftab { font_entries })
34    }
35
36    fn encode_body<B: BufMut>(&self, buf: &mut B) -> Result<()> {
37        let entry_count: u16 = self
38            .font_entries
39            .len()
40            .try_into()
41            .map_err(|_| Error::TooLarge(Self::KIND))?;
42        entry_count.encode(buf)?;
43        for font_entry in &self.font_entries {
44            font_entry.font_id.encode(buf)?;
45            // We always encode as UTF-8, so there won't be round tripping of UTF-16. That is OK.
46            encode_length_prefixed_string::<u8>(buf, &font_entry.font, Self::KIND)?;
47        }
48
49        Ok(())
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    #[test]
58    fn test_ftab_utf16_font_names_decode_as_utf8_on_encode() {
59        let utf16le = [b"\xFF\xFE".as_slice(), b"S\0e\0r\0i\0f\0"].concat();
60        let utf16be = [b"\xFE\xFF".as_slice(), b"\0S\0a\0n\0s"].concat();
61
62        let mut body = vec![0, 2];
63        for (id, name) in [(1u8, &utf16le), (2, &utf16be)] {
64            body.extend([0, id, name.len() as u8]);
65            body.extend(name);
66        }
67
68        let ftab = Ftab::decode_body(&mut body.as_slice()).unwrap();
69        assert_eq!(
70            ftab.font_entries,
71            vec![
72                FontEntry {
73                    font_id: 1,
74                    font: "Serif".into()
75                },
76                FontEntry {
77                    font_id: 2,
78                    font: "Sans".into()
79                },
80            ]
81        );
82
83        // UTF-16 isn't preserved: names are re-encoded as UTF-8.
84        let mut encoded = Vec::new();
85        ftab.encode_body(&mut encoded).unwrap();
86        assert_eq!(encoded, b"\0\x02\0\x01\x05Serif\0\x02\x04Sans");
87    }
88
89    #[test]
90    fn test_ftab_font_name_exceeds_buffer() {
91        let err = Ftab::decode_body(&mut [0, 1, 0, 1, 9, b'a'].as_slice()).unwrap_err();
92        assert!(matches!(err, Error::OutOfBounds));
93    }
94}