Skip to main content

read_fonts/ps/cff/
v1.rs

1//! CFF version 1.
2
3include!("../../../generated/generated_cff.rs");
4
5use crate::ps::{
6    cff::{charset::Charset, dict},
7    error::Error,
8    string::Sid,
9};
10
11/// The [Compact Font Format](https://learn.microsoft.com/en-us/typography/opentype/spec/cff) table.
12#[derive(Clone)]
13pub struct Cff<'a> {
14    header: CffHeader<'a>,
15    names: Index<'a>,
16    top_dicts: Index<'a>,
17    strings: Index<'a>,
18    global_subrs: Index<'a>,
19}
20
21impl<'a> Cff<'a> {
22    pub fn offset_data(&self) -> FontData<'a> {
23        self.header.offset_data()
24    }
25
26    pub fn header(&self) -> CffHeader<'a> {
27        self.header.clone()
28    }
29
30    /// Returns the name index.
31    ///
32    /// This contains the PostScript names of all fonts in the font set.
33    ///
34    /// See "Name INDEX" at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf#page=13>
35    pub fn names(&self) -> Index<'a> {
36        self.names.clone()
37    }
38
39    /// Returns the PostScript name for the font in the font set at the
40    /// given index.
41    pub fn name(&self, index: usize) -> Option<&'a [u8]> {
42        self.names.get(index).ok()
43    }
44
45    /// Returns the top dict index.
46    ///
47    /// This contains the top-level DICTs of all fonts in the font set. The
48    /// objects here correspond to those in the name index.
49    ///
50    /// See "Top DICT INDEX" at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf#page=14>
51    pub fn top_dicts(&self) -> Index<'a> {
52        self.top_dicts.clone()
53    }
54
55    /// Returns the string index.
56    ///
57    /// This contains all of the strings used by fonts within the font set.
58    /// They are referenced by string identifiers represented by the
59    /// [`Sid`] type.
60    ///
61    /// See "String INDEX" at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf#page=17>
62    pub fn strings(&self) -> Index<'a> {
63        self.strings.clone()
64    }
65
66    /// Returns the associated string for the given identifier.
67    ///
68    /// If the identifier does not represent a standard string, the result is
69    /// looked up in the string index.
70    pub fn string(&self, id: Sid) -> Option<&'a [u8]> {
71        match id.resolve_standard() {
72            Ok(name) => Some(name),
73            Err(ix) => self.strings.get(ix).ok(),
74        }
75    }
76
77    /// Returns the global subroutine index.
78    ///
79    /// This contains sub-programs that are referenced by one or more
80    /// charstrings in the font set.
81    ///
82    /// See "Local/Global Subrs INDEXes" at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf#page=25>
83    pub fn global_subrs(&self) -> Index<'a> {
84        self.global_subrs.clone()
85    }
86
87    /// Returns the character set associated with the top dict at the given
88    /// index.
89    ///
90    /// See "Charsets" at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf#page=21>
91    pub fn charset(&self, top_dict_index: usize) -> Result<Option<Charset<'a>>, Error> {
92        let top_dict = self.top_dicts().get(top_dict_index)?;
93        let offset_data = self.offset_data();
94        let mut charset_offset: Option<usize> = None;
95        let mut num_glyphs: Option<u32> = None;
96        for entry in dict::entries(top_dict, None) {
97            match entry {
98                Ok(dict::Entry::Charset(offset)) => {
99                    charset_offset = Some(offset);
100                }
101                Ok(dict::Entry::CharstringsOffset(offset)) => {
102                    num_glyphs = Some(
103                        Index::read(
104                            offset_data
105                                .split_off(offset)
106                                .ok_or(ReadError::OutOfBounds)?,
107                        )?
108                        .count() as u32,
109                    );
110                }
111                // The ROS operator signifies a CID-keyed font and the charset
112                // maps to CIDs rather than SIDs which we don't parse for
113                // glyph names.
114                // <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf#page=28>
115                Ok(dict::Entry::Ros { .. }) => {
116                    return Ok(None);
117                }
118                _ => {}
119            }
120        }
121        if let Some((charset_offset, num_glyphs)) = charset_offset.zip(num_glyphs) {
122            Ok(Some(Charset::new(offset_data, charset_offset, num_glyphs)?))
123        } else {
124            Ok(None)
125        }
126    }
127}
128
129impl TopLevelTable for Cff<'_> {
130    const TAG: Tag = Tag::new(b"CFF ");
131}
132
133impl<'a> FontRead<'a> for Cff<'a> {
134    fn read(data: FontData<'a>) -> Result<Self, ReadError> {
135        let header = CffHeader::read(data)?;
136        let mut data = FontData::new(header.trailing_data());
137        let names = Index::read(data)?;
138        data = data
139            .split_off(names.size_in_bytes()?)
140            .ok_or(ReadError::OutOfBounds)?;
141        let top_dicts = Index::read(data)?;
142        data = data
143            .split_off(top_dicts.size_in_bytes()?)
144            .ok_or(ReadError::OutOfBounds)?;
145        let strings = Index::read(data)?;
146        data = data
147            .split_off(strings.size_in_bytes()?)
148            .ok_or(ReadError::OutOfBounds)?;
149        let global_subrs = Index::read(data)?;
150        Ok(Self {
151            header,
152            names,
153            top_dicts,
154            strings,
155            global_subrs,
156        })
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163    use crate::{FontRef, TableProvider};
164
165    #[test]
166    fn read_noto_serif_display_cff() {
167        let font = FontRef::new(font_test_data::NOTO_SERIF_DISPLAY_TRIMMED).unwrap();
168        let cff = font.cff().unwrap();
169        assert_eq!(cff.header().major(), 1);
170        assert_eq!(cff.header().minor(), 0);
171        assert_eq!(cff.top_dicts().count(), 1);
172        assert_eq!(cff.names().count(), 1);
173        assert_eq!(cff.global_subrs.count(), 17);
174        let name = cff.names().get(0).unwrap();
175        assert_eq!(name, b"NotoSerifDisplay-Regular");
176        assert_eq!(cff.strings().count(), 5);
177        // Version
178        assert_eq!(cff.string(Sid::new(391)).unwrap(), b"2.9");
179        // Notice
180        assert_eq!(
181            cff.string(Sid::new(392)).unwrap(),
182            b"Noto is a trademark of Google LLC."
183        );
184        // Copyright
185        assert_eq!(
186            cff.string(Sid::new(393)).unwrap(),
187            b"Copyright 2022 The Noto Project Authors https:github.comnotofontslatin-greek-cyrillic"
188        );
189        // FullName
190        assert_eq!(
191            cff.string(Sid::new(394)).unwrap(),
192            b"Noto Serif Display Regular"
193        );
194        // FamilyName
195        assert_eq!(cff.string(Sid::new(395)).unwrap(), b"Noto Serif Display");
196    }
197
198    #[test]
199    fn glyph_names() {
200        test_glyph_names(
201            font_test_data::NOTO_SERIF_DISPLAY_TRIMMED,
202            &[".notdef", "i", "j", "k", "l"],
203        );
204    }
205
206    #[test]
207    fn icons_glyph_names() {
208        test_glyph_names(font_test_data::MATERIAL_ICONS_SUBSET, &[".notdef", "_10k"]);
209    }
210
211    fn test_glyph_names(font_data: &[u8], expected_names: &[&str]) {
212        let font = FontRef::new(font_data).unwrap();
213        let cff = font.cff().unwrap();
214        let charset = cff.charset(0).unwrap().unwrap();
215        let sid_to_string = |sid| std::str::from_utf8(cff.string(sid).unwrap()).unwrap();
216        let names_by_lookup = (0..charset.num_glyphs())
217            .map(|gid| sid_to_string(charset.string_id(GlyphId::new(gid)).unwrap()))
218            .collect::<Vec<_>>();
219        assert_eq!(names_by_lookup, expected_names);
220        let names_by_iter = charset
221            .iter()
222            .map(|(_gid, sid)| sid_to_string(sid))
223            .collect::<Vec<_>>();
224        assert_eq!(names_by_iter, expected_names);
225    }
226}