1include!("../../../generated/generated_cff.rs");
4
5use crate::ps::{
6 cff::{charset::Charset, dict},
7 error::Error,
8 string::Sid,
9};
10
11#[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 pub fn names(&self) -> Index<'a> {
36 self.names.clone()
37 }
38
39 pub fn name(&self, index: usize) -> Option<&'a [u8]> {
42 self.names.get(index).ok()
43 }
44
45 pub fn top_dicts(&self) -> Index<'a> {
52 self.top_dicts.clone()
53 }
54
55 pub fn strings(&self) -> Index<'a> {
63 self.strings.clone()
64 }
65
66 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 pub fn global_subrs(&self) -> Index<'a> {
84 self.global_subrs.clone()
85 }
86
87 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 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 ReadArgs for Cff<'_> {
134 type Args = ();
135}
136
137impl<'a> FontRead<'a> for Cff<'a> {
138 fn read_with_args(data: FontData<'a>, _: ()) -> Result<Self, ReadError> {
139 let header = CffHeader::read(data)?;
140 let mut data = FontData::new(header.trailing_data());
141 let names = Index::read(data)?;
142 data = data
143 .split_off(names.size_in_bytes()?)
144 .ok_or(ReadError::OutOfBounds)?;
145 let top_dicts = Index::read(data)?;
146 data = data
147 .split_off(top_dicts.size_in_bytes()?)
148 .ok_or(ReadError::OutOfBounds)?;
149 let strings = Index::read(data)?;
150 data = data
151 .split_off(strings.size_in_bytes()?)
152 .ok_or(ReadError::OutOfBounds)?;
153 let global_subrs = Index::read(data)?;
154 Ok(Self {
155 header,
156 names,
157 top_dicts,
158 strings,
159 global_subrs,
160 })
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167 use crate::{FontRef, TableProvider};
168
169 #[test]
170 fn read_noto_serif_display_cff() {
171 let font = FontRef::new(font_test_data::NOTO_SERIF_DISPLAY_TRIMMED).unwrap();
172 let cff = font.cff().unwrap();
173 assert_eq!(cff.header().major(), 1);
174 assert_eq!(cff.header().minor(), 0);
175 assert_eq!(cff.top_dicts().count(), 1);
176 assert_eq!(cff.names().count(), 1);
177 assert_eq!(cff.global_subrs.count(), 17);
178 let name = cff.names().get(0).unwrap();
179 assert_eq!(name, b"NotoSerifDisplay-Regular");
180 assert_eq!(cff.strings().count(), 5);
181 assert_eq!(cff.string(Sid::new(391)).unwrap(), b"2.9");
183 assert_eq!(
185 cff.string(Sid::new(392)).unwrap(),
186 b"Noto is a trademark of Google LLC."
187 );
188 assert_eq!(
190 cff.string(Sid::new(393)).unwrap(),
191 b"Copyright 2022 The Noto Project Authors https:github.comnotofontslatin-greek-cyrillic"
192 );
193 assert_eq!(
195 cff.string(Sid::new(394)).unwrap(),
196 b"Noto Serif Display Regular"
197 );
198 assert_eq!(cff.string(Sid::new(395)).unwrap(), b"Noto Serif Display");
200 }
201
202 #[test]
203 fn glyph_names() {
204 test_glyph_names(
205 font_test_data::NOTO_SERIF_DISPLAY_TRIMMED,
206 &[".notdef", "i", "j", "k", "l"],
207 );
208 }
209
210 #[test]
211 fn icons_glyph_names() {
212 test_glyph_names(font_test_data::MATERIAL_ICONS_SUBSET, &[".notdef", "_10k"]);
213 }
214
215 fn test_glyph_names(font_data: &[u8], expected_names: &[&str]) {
216 let font = FontRef::new(font_data).unwrap();
217 let cff = font.cff().unwrap();
218 let charset = cff.charset(0).unwrap().unwrap();
219 let sid_to_string = |sid| std::str::from_utf8(cff.string(sid).unwrap()).unwrap();
220 let names_by_lookup = (0..charset.num_glyphs())
221 .map(|gid| sid_to_string(charset.string_id(GlyphId::new(gid)).unwrap()))
222 .collect::<Vec<_>>();
223 assert_eq!(names_by_lookup, expected_names);
224 let names_by_iter = charset
225 .iter()
226 .map(|(_gid, sid)| sid_to_string(sid))
227 .collect::<Vec<_>>();
228 assert_eq!(names_by_iter, expected_names);
229 }
230}