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