read_fonts/tables/
meta.rs

1//! The [meta (Metadata)](https://docs.microsoft.com/en-us/typography/opentype/spec/meta) table
2
3include!("../../generated/generated_meta.rs");
4
5pub const DLNG: Tag = Tag::new(b"dlng");
6pub const SLNG: Tag = Tag::new(b"slng");
7
8/// Data stored in the 'meta' table.
9pub enum Metadata<'a> {
10    /// Used for the 'dlng' and 'slng' metadata
11    ScriptLangTags(VarLenArray<'a, ScriptLangTag<'a>>),
12    /// Other metadata, which may exist in certain apple fonts
13    Other(&'a [u8]),
14}
15
16impl ReadArgs for Metadata<'_> {
17    type Args = (Tag, u32);
18}
19
20impl<'a> FontReadWithArgs<'a> for Metadata<'a> {
21    fn read_with_args(data: FontData<'a>, args: &Self::Args) -> Result<Self, ReadError> {
22        let (tag, len) = *args;
23        let data = data.slice(0..len as usize).ok_or(ReadError::OutOfBounds)?;
24        if [DLNG, SLNG].contains(&tag) {
25            VarLenArray::read(data).map(Metadata::ScriptLangTags)
26        } else {
27            Ok(Metadata::Other(data.as_bytes()))
28        }
29    }
30}
31
32#[derive(Clone, Debug)]
33pub struct ScriptLangTag<'a>(&'a str);
34
35impl<'a> ScriptLangTag<'a> {
36    pub fn as_str(&self) -> &'a str {
37        self.0
38    }
39}
40
41impl AsRef<str> for ScriptLangTag<'_> {
42    fn as_ref(&self) -> &str {
43        self.0
44    }
45}
46
47#[cfg(feature = "std")]
48impl From<ScriptLangTag<'_>> for String {
49    fn from(value: ScriptLangTag<'_>) -> Self {
50        value.0.into()
51    }
52}
53
54impl VarSize for ScriptLangTag<'_> {
55    type Size = u32;
56
57    fn read_len_at(data: FontData, pos: usize) -> Option<usize> {
58        let bytes = data.split_off(pos)?.as_bytes();
59        if bytes.is_empty() {
60            return None;
61        }
62        let end = data
63            .as_bytes()
64            .iter()
65            .position(|b| *b == b',')
66            .map(|pos| pos + 1) // include comma
67            .unwrap_or(bytes.len());
68        Some(end)
69    }
70}
71
72impl<'a> FontRead<'a> for ScriptLangTag<'a> {
73    fn read(data: FontData<'a>) -> Result<Self, ReadError> {
74        std::str::from_utf8(data.as_bytes())
75            .map_err(|_| ReadError::MalformedData("LangScriptTag must be utf8"))
76            .map(|s| ScriptLangTag(s.trim_matches([' ', ','])))
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83    use font_test_data::meta as test_data;
84
85    impl PartialEq<&str> for ScriptLangTag<'_> {
86        fn eq(&self, other: &&str) -> bool {
87            self.as_ref() == *other
88        }
89    }
90
91    fn expect_script_lang_tags(table: Metadata, expected: &[&str]) -> bool {
92        let Metadata::ScriptLangTags(langs) = table else {
93            panic!("wrong metadata");
94        };
95        let result = langs.iter().map(|x| x.unwrap()).collect::<Vec<_>>();
96        result == expected
97    }
98
99    #[test]
100    fn parse_simple() {
101        let table = Meta::read(test_data::SIMPLE_META_TABLE.into()).unwrap();
102        let rec1 = table.data_maps()[0];
103        let rec2 = table.data_maps()[1];
104
105        assert_eq!(rec1.tag(), Tag::new(b"dlng"));
106        assert_eq!(rec2.tag(), Tag::new(b"slng"));
107        assert!(expect_script_lang_tags(
108            rec1.data(table.offset_data()).unwrap(),
109            &["en-latn", "latn"]
110        ));
111        assert!(expect_script_lang_tags(
112            rec2.data(table.offset_data()).unwrap(),
113            &["latn"]
114        ));
115    }
116}