1include!("../../generated/generated_cff.rs");
4
5use super::postscript::{dict, Charset, Error, Index1, Latin1String, StringId};
6
7#[derive(Clone)]
9pub struct Cff<'a> {
10 header: CffHeader<'a>,
11 names: Index1<'a>,
12 top_dicts: Index1<'a>,
13 strings: Index1<'a>,
14 global_subrs: Index1<'a>,
15}
16
17impl<'a> Cff<'a> {
18 pub fn offset_data(&self) -> FontData<'a> {
19 self.header.offset_data()
20 }
21
22 pub fn header(&self) -> CffHeader<'a> {
23 self.header.clone()
24 }
25
26 pub fn names(&self) -> Index1<'a> {
32 self.names.clone()
33 }
34
35 pub fn name(&self, index: usize) -> Option<Latin1String<'a>> {
38 Some(Latin1String::new(self.names.get(index).ok()?))
39 }
40
41 pub fn top_dicts(&self) -> Index1<'a> {
48 self.top_dicts.clone()
49 }
50
51 pub fn strings(&self) -> Index1<'a> {
59 self.strings.clone()
60 }
61
62 pub fn string(&self, id: StringId) -> Option<Latin1String<'a>> {
67 match id.standard_string() {
68 Ok(name) => Some(name),
69 Err(ix) => self.strings.get(ix).ok().map(Latin1String::new),
70 }
71 }
72
73 pub fn global_subrs(&self) -> Index1<'a> {
80 self.global_subrs.clone()
81 }
82
83 pub fn charset(&self, top_dict_index: usize) -> Result<Option<Charset<'a>>, Error> {
88 let top_dict = self.top_dicts().get(top_dict_index)?;
89 let offset_data = self.offset_data();
90 let mut charset_offset: Option<usize> = None;
91 let mut num_glyphs: Option<u32> = None;
92 for entry in dict::entries(top_dict, None) {
93 match entry {
94 Ok(dict::Entry::Charset(offset)) => {
95 charset_offset = Some(offset);
96 }
97 Ok(dict::Entry::CharstringsOffset(offset)) => {
98 num_glyphs = Some(
99 Index1::read(
100 offset_data
101 .split_off(offset)
102 .ok_or(ReadError::OutOfBounds)?,
103 )?
104 .count() as u32,
105 );
106 }
107 _ => {}
108 }
109 }
110 if let Some((charset_offset, num_glyphs)) = charset_offset.zip(num_glyphs) {
111 Ok(Some(Charset::new(offset_data, charset_offset, num_glyphs)?))
112 } else {
113 Ok(None)
114 }
115 }
116}
117
118impl TopLevelTable for Cff<'_> {
119 const TAG: Tag = Tag::new(b"CFF ");
120}
121
122impl<'a> FontRead<'a> for Cff<'a> {
123 fn read(data: FontData<'a>) -> Result<Self, ReadError> {
124 let header = CffHeader::read(data)?;
125 let mut data = FontData::new(header.trailing_data());
126 let names = Index1::read(data)?;
127 data = data
128 .split_off(names.size_in_bytes()?)
129 .ok_or(ReadError::OutOfBounds)?;
130 let top_dicts = Index1::read(data)?;
131 data = data
132 .split_off(top_dicts.size_in_bytes()?)
133 .ok_or(ReadError::OutOfBounds)?;
134 let strings = Index1::read(data)?;
135 data = data
136 .split_off(strings.size_in_bytes()?)
137 .ok_or(ReadError::OutOfBounds)?;
138 let global_subrs = Index1::read(data)?;
139 Ok(Self {
140 header,
141 names,
142 top_dicts,
143 strings,
144 global_subrs,
145 })
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152 use crate::{tables::postscript::StringId, FontRef, TableProvider};
153
154 #[test]
155 fn read_noto_serif_display_cff() {
156 let font = FontRef::new(font_test_data::NOTO_SERIF_DISPLAY_TRIMMED).unwrap();
157 let cff = font.cff().unwrap();
158 assert_eq!(cff.header().major(), 1);
159 assert_eq!(cff.header().minor(), 0);
160 assert_eq!(cff.top_dicts().count(), 1);
161 assert_eq!(cff.names().count(), 1);
162 assert_eq!(cff.global_subrs.count(), 17);
163 let name = Latin1String::new(cff.names().get(0).unwrap());
164 assert_eq!(name, "NotoSerifDisplay-Regular");
165 assert_eq!(cff.strings().count(), 5);
166 assert_eq!(cff.string(StringId::new(391)).unwrap(), "2.9");
168 assert_eq!(
170 cff.string(StringId::new(392)).unwrap(),
171 "Noto is a trademark of Google LLC."
172 );
173 assert_eq!(
175 cff.string(StringId::new(393)).unwrap(),
176 "Copyright 2022 The Noto Project Authors https:github.comnotofontslatin-greek-cyrillic"
177 );
178 assert_eq!(
180 cff.string(StringId::new(394)).unwrap(),
181 "Noto Serif Display Regular"
182 );
183 assert_eq!(
185 cff.string(StringId::new(395)).unwrap(),
186 "Noto Serif Display"
187 );
188 }
189
190 #[test]
191 fn glyph_names() {
192 test_glyph_names(
193 font_test_data::NOTO_SERIF_DISPLAY_TRIMMED,
194 &[".notdef", "i", "j", "k", "l"],
195 );
196 }
197
198 #[test]
199 fn icons_glyph_names() {
200 test_glyph_names(font_test_data::MATERIAL_ICONS_SUBSET, &[".notdef", "_10k"]);
201 }
202
203 fn test_glyph_names(font_data: &[u8], expected_names: &[&str]) {
204 let font = FontRef::new(font_data).unwrap();
205 let cff = font.cff().unwrap();
206 let charset = cff.charset(0).unwrap().unwrap();
207 let sid_to_string = |sid| std::str::from_utf8(cff.string(sid).unwrap().bytes()).unwrap();
208 let names_by_lookup = (0..charset.num_glyphs())
209 .map(|gid| sid_to_string(charset.string_id(GlyphId::new(gid)).unwrap()))
210 .collect::<Vec<_>>();
211 assert_eq!(names_by_lookup, expected_names);
212 let names_by_iter = charset
213 .iter()
214 .map(|(_gid, sid)| sid_to_string(sid))
215 .collect::<Vec<_>>();
216 assert_eq!(names_by_iter, expected_names);
217 }
218}