read_fonts/tables/
meta.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
//! The [meta (Metadata)](https://docs.microsoft.com/en-us/typography/opentype/spec/meta) table

include!("../../generated/generated_meta.rs");

pub const DLNG: Tag = Tag::new(b"dlng");
pub const SLNG: Tag = Tag::new(b"slng");

/// Data stored in the 'meta' table.
pub enum Metadata<'a> {
    /// Used for the 'dlng' and 'slng' metadata
    ScriptLangTags(VarLenArray<'a, ScriptLangTag<'a>>),
    /// Other metadata, which may exist in certain apple fonts
    Other(&'a [u8]),
}

impl ReadArgs for Metadata<'_> {
    type Args = (Tag, u32);
}

impl<'a> FontReadWithArgs<'a> for Metadata<'a> {
    fn read_with_args(data: FontData<'a>, args: &Self::Args) -> Result<Self, ReadError> {
        let (tag, len) = *args;
        let data = data.slice(0..len as usize).ok_or(ReadError::OutOfBounds)?;
        if [DLNG, SLNG].contains(&tag) {
            VarLenArray::read(data).map(Metadata::ScriptLangTags)
        } else {
            Ok(Metadata::Other(data.as_bytes()))
        }
    }
}

#[derive(Clone, Debug)]
pub struct ScriptLangTag<'a>(&'a str);

impl<'a> ScriptLangTag<'a> {
    pub fn as_str(&self) -> &'a str {
        self.0
    }
}

impl AsRef<str> for ScriptLangTag<'_> {
    fn as_ref(&self) -> &str {
        self.0
    }
}

#[cfg(feature = "std")]
impl From<ScriptLangTag<'_>> for String {
    fn from(value: ScriptLangTag<'_>) -> Self {
        value.0.into()
    }
}

impl VarSize for ScriptLangTag<'_> {
    type Size = u32;

    fn read_len_at(data: FontData, pos: usize) -> Option<usize> {
        let bytes = data.split_off(pos)?.as_bytes();
        if bytes.is_empty() {
            return None;
        }
        let end = data
            .as_bytes()
            .iter()
            .position(|b| *b == b',')
            .map(|pos| pos + 1) // include comma
            .unwrap_or(bytes.len());
        Some(end)
    }
}

impl<'a> FontRead<'a> for ScriptLangTag<'a> {
    fn read(data: FontData<'a>) -> Result<Self, ReadError> {
        std::str::from_utf8(data.as_bytes())
            .map_err(|_| ReadError::MalformedData("LangScriptTag must be utf8"))
            .map(|s| ScriptLangTag(s.trim_matches([' ', ','])))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use font_test_data::meta as test_data;

    impl PartialEq<&str> for ScriptLangTag<'_> {
        fn eq(&self, other: &&str) -> bool {
            self.as_ref() == *other
        }
    }

    fn expect_script_lang_tags(table: Metadata, expected: &[&str]) -> bool {
        let Metadata::ScriptLangTags(langs) = table else {
            panic!("wrong metadata");
        };
        let result = langs.iter().map(|x| x.unwrap()).collect::<Vec<_>>();
        result == expected
    }

    #[test]
    fn parse_simple() {
        let table = Meta::read(test_data::SIMPLE_META_TABLE.into()).unwrap();
        let rec1 = table.data_maps()[0];
        let rec2 = table.data_maps()[1];

        assert_eq!(rec1.tag(), Tag::new(b"dlng"));
        assert_eq!(rec2.tag(), Tag::new(b"slng"));
        assert!(expect_script_lang_tags(
            rec1.data(table.offset_data()).unwrap(),
            &["en-latn", "latn"]
        ));
        assert!(expect_script_lang_tags(
            rec2.data(table.offset_data()).unwrap(),
            &["latn"]
        ));
    }
}