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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
//! The [CFF](https://learn.microsoft.com/en-us/typography/opentype/spec/cff) table

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

use super::postscript::{Index1, Latin1String, StringId};

/// The [Compact Font Format](https://learn.microsoft.com/en-us/typography/opentype/spec/cff) table.
#[derive(Clone)]
pub struct Cff<'a> {
    header: CffHeader<'a>,
    names: Index1<'a>,
    top_dicts: Index1<'a>,
    strings: Index1<'a>,
    global_subrs: Index1<'a>,
}

impl<'a> Cff<'a> {
    pub fn offset_data(&self) -> FontData<'a> {
        self.header.offset_data()
    }

    pub fn header(&self) -> CffHeader<'a> {
        self.header.clone()
    }

    /// Returns the name index.
    ///
    /// This contains the PostScript names of all fonts in the font set.
    ///
    /// See "Name INDEX" at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf#page=13>
    pub fn names(&self) -> Index1<'a> {
        self.names.clone()
    }

    /// Returns the PostScript name for the font in the font set at the
    /// given index.
    pub fn name(&self, index: usize) -> Option<Latin1String<'a>> {
        Some(Latin1String::new(self.names.get(index).ok()?))
    }

    /// Returns the top dict index.
    ///
    /// This contains the top-level DICTs of all fonts in the font set. The
    /// objects here correspond to those in the name index.
    ///
    /// See "Top DICT INDEX" at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf#page=14>
    pub fn top_dicts(&self) -> Index1<'a> {
        self.top_dicts.clone()
    }

    /// Returns the string index.
    ///
    /// This contains all of the strings used by fonts within the font set.
    /// They are referenced by string identifiers represented by the
    /// [`StringId`] type.
    ///
    /// See "String INDEX" at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf#page=17>
    pub fn strings(&self) -> Index1<'a> {
        self.strings.clone()
    }

    /// Returns the associated string for the given identifier.
    ///
    /// If the identifier does not represent a standard string, the result is
    /// looked up in the string index.
    pub fn string(&self, id: StringId) -> Option<Latin1String<'a>> {
        match id.standard_string() {
            Ok(name) => Some(name),
            Err(ix) => self.strings.get(ix).ok().map(Latin1String::new),
        }
    }

    /// Returns the global subroutine index.
    ///
    /// This contains sub-programs that are referenced by one or more
    /// charstrings in the font set.
    ///
    /// See "Local/Global Subrs INDEXes" at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf#page=25>
    pub fn global_subrs(&self) -> Index1<'a> {
        self.global_subrs.clone()
    }
}

impl TopLevelTable for Cff<'_> {
    const TAG: Tag = Tag::new(b"CFF ");
}

impl<'a> FontRead<'a> for Cff<'a> {
    fn read(data: FontData<'a>) -> Result<Self, ReadError> {
        let header = CffHeader::read(data)?;
        let mut data = FontData::new(header.trailing_data());
        let names = Index1::read(data)?;
        data = data
            .split_off(names.size_in_bytes()?)
            .ok_or(ReadError::OutOfBounds)?;
        let top_dicts = Index1::read(data)?;
        data = data
            .split_off(top_dicts.size_in_bytes()?)
            .ok_or(ReadError::OutOfBounds)?;
        let strings = Index1::read(data)?;
        data = data
            .split_off(strings.size_in_bytes()?)
            .ok_or(ReadError::OutOfBounds)?;
        let global_subrs = Index1::read(data)?;
        Ok(Self {
            header,
            names,
            top_dicts,
            strings,
            global_subrs,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{tables::postscript::StringId, FontRef, TableProvider};

    #[test]
    fn read_noto_serif_display_cff() {
        let font = FontRef::new(font_test_data::NOTO_SERIF_DISPLAY_TRIMMED).unwrap();
        let cff = font.cff().unwrap();
        assert_eq!(cff.header().major(), 1);
        assert_eq!(cff.header().minor(), 0);
        assert_eq!(cff.top_dicts().count(), 1);
        assert_eq!(cff.names().count(), 1);
        assert_eq!(cff.global_subrs.count(), 17);
        let name = Latin1String::new(cff.names().get(0).unwrap());
        assert_eq!(name, "NotoSerifDisplay-Regular");
        assert_eq!(cff.strings().count(), 5);
        // Version
        assert_eq!(cff.string(StringId::new(391)).unwrap(), "2.9");
        // Notice
        assert_eq!(
            cff.string(StringId::new(392)).unwrap(),
            "Noto is a trademark of Google LLC."
        );
        // Copyright
        assert_eq!(
            cff.string(StringId::new(393)).unwrap(),
            "Copyright 2022 The Noto Project Authors https:github.comnotofontslatin-greek-cyrillic"
        );
        // FullName
        assert_eq!(
            cff.string(StringId::new(394)).unwrap(),
            "Noto Serif Display Regular"
        );
        // FamilyName
        assert_eq!(
            cff.string(StringId::new(395)).unwrap(),
            "Noto Serif Display"
        );
    }
}