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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
//! CFF version 1.
include!("../../../generated/generated_cff.rs");
use crate::ps::{
cff::{charset::Charset, dict},
error::Error,
string::Sid,
};
/// 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: Index<'a>,
top_dicts: Index<'a>,
strings: Index<'a>,
global_subrs: Index<'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) -> Index<'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<&'a [u8]> {
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) -> Index<'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
/// [`Sid`] type.
///
/// See "String INDEX" at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf#page=17>
pub fn strings(&self) -> Index<'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: Sid) -> Option<&'a [u8]> {
match id.resolve_standard() {
Ok(name) => Some(name),
Err(ix) => self.strings.get(ix).ok(),
}
}
/// 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) -> Index<'a> {
self.global_subrs.clone()
}
/// Returns the character set associated with the top dict at the given
/// index.
///
/// See "Charsets" at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf#page=21>
pub fn charset(&self, top_dict_index: usize) -> Result<Option<Charset<'a>>, Error> {
let top_dict = self.top_dicts().get(top_dict_index)?;
let offset_data = self.offset_data();
let mut charset_offset: Option<usize> = None;
let mut num_glyphs: Option<u32> = None;
for entry in dict::entries(top_dict, None) {
match entry {
Ok(dict::Entry::Charset(offset)) => {
charset_offset = Some(offset);
}
Ok(dict::Entry::CharstringsOffset(offset)) => {
num_glyphs = Some(
Index::read(
offset_data
.split_off(offset)
.ok_or(ReadError::OutOfBounds)?,
)?
.count() as u32,
);
}
// The ROS operator signifies a CID-keyed font and the charset
// maps to CIDs rather than SIDs which we don't parse for
// glyph names.
// <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf#page=28>
Ok(dict::Entry::Ros { .. }) => {
return Ok(None);
}
_ => {}
}
}
if let Some((charset_offset, num_glyphs)) = charset_offset.zip(num_glyphs) {
Ok(Some(Charset::new(offset_data, charset_offset, num_glyphs)?))
} else {
Ok(None)
}
}
}
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 = Index::read(data)?;
data = data
.split_off(names.size_in_bytes()?)
.ok_or(ReadError::OutOfBounds)?;
let top_dicts = Index::read(data)?;
data = data
.split_off(top_dicts.size_in_bytes()?)
.ok_or(ReadError::OutOfBounds)?;
let strings = Index::read(data)?;
data = data
.split_off(strings.size_in_bytes()?)
.ok_or(ReadError::OutOfBounds)?;
let global_subrs = Index::read(data)?;
Ok(Self {
header,
names,
top_dicts,
strings,
global_subrs,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{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 = cff.names().get(0).unwrap();
assert_eq!(name, b"NotoSerifDisplay-Regular");
assert_eq!(cff.strings().count(), 5);
// Version
assert_eq!(cff.string(Sid::new(391)).unwrap(), b"2.9");
// Notice
assert_eq!(
cff.string(Sid::new(392)).unwrap(),
b"Noto is a trademark of Google LLC."
);
// Copyright
assert_eq!(
cff.string(Sid::new(393)).unwrap(),
b"Copyright 2022 The Noto Project Authors https:github.comnotofontslatin-greek-cyrillic"
);
// FullName
assert_eq!(
cff.string(Sid::new(394)).unwrap(),
b"Noto Serif Display Regular"
);
// FamilyName
assert_eq!(cff.string(Sid::new(395)).unwrap(), b"Noto Serif Display");
}
#[test]
fn glyph_names() {
test_glyph_names(
font_test_data::NOTO_SERIF_DISPLAY_TRIMMED,
&[".notdef", "i", "j", "k", "l"],
);
}
#[test]
fn icons_glyph_names() {
test_glyph_names(font_test_data::MATERIAL_ICONS_SUBSET, &[".notdef", "_10k"]);
}
fn test_glyph_names(font_data: &[u8], expected_names: &[&str]) {
let font = FontRef::new(font_data).unwrap();
let cff = font.cff().unwrap();
let charset = cff.charset(0).unwrap().unwrap();
let sid_to_string = |sid| std::str::from_utf8(cff.string(sid).unwrap()).unwrap();
let names_by_lookup = (0..charset.num_glyphs())
.map(|gid| sid_to_string(charset.string_id(GlyphId::new(gid)).unwrap()))
.collect::<Vec<_>>();
assert_eq!(names_by_lookup, expected_names);
let names_by_iter = charset
.iter()
.map(|(_gid, sid)| sid_to_string(sid))
.collect::<Vec<_>>();
assert_eq!(names_by_iter, expected_names);
}
}