1use core::ops::Range;
4use raw::{
5 tables::{
6 cff::Cff,
7 post::Post,
8 postscript::{Charset, CharsetIter, StringId as Sid},
9 },
10 types::GlyphId,
11 FontRef, TableProvider,
12};
13
14const MAX_GLYPH_NAME_LEN: usize = 63;
18
19#[derive(Clone)]
26pub struct GlyphNames<'a> {
27 inner: Inner<'a>,
28}
29
30#[derive(Clone)]
31enum Inner<'a> {
32 Post(Post<'a>, u32),
34 Cff(Cff<'a>, Charset<'a>),
35 Synthesized(u32),
36}
37
38impl<'a> GlyphNames<'a> {
39 pub fn new(font: &FontRef<'a>) -> Self {
41 let num_glyphs = font
42 .maxp()
43 .map(|maxp| maxp.num_glyphs() as u32)
44 .unwrap_or_default();
45 if let Ok(post) = font.post() {
46 if post.num_names() != 0 {
47 return Self {
48 inner: Inner::Post(post, num_glyphs),
49 };
50 }
51 }
52 if let Some((cff, charset)) = font
53 .cff()
54 .ok()
55 .and_then(|cff| Some((cff.clone(), cff.charset(0).ok()??)))
56 {
57 return Self {
58 inner: Inner::Cff(cff, charset),
59 };
60 }
61 Self {
62 inner: Inner::Synthesized(num_glyphs),
63 }
64 }
65
66 pub fn source(&self) -> GlyphNameSource {
68 match &self.inner {
69 Inner::Post(..) => GlyphNameSource::Post,
70 Inner::Cff(..) => GlyphNameSource::Cff,
71 Inner::Synthesized(..) => GlyphNameSource::Synthesized,
72 }
73 }
74
75 pub fn num_glyphs(&self) -> u32 {
77 match &self.inner {
78 Inner::Post(_, n) | Inner::Synthesized(n) => *n,
79 Inner::Cff(_, charset) => charset.num_glyphs(),
80 }
81 }
82
83 pub fn get(&self, glyph_id: GlyphId) -> Option<GlyphName> {
85 if glyph_id.to_u32() >= self.num_glyphs() {
86 return None;
87 }
88 let name = match &self.inner {
89 Inner::Post(post, _) => GlyphName::from_post(post, glyph_id),
90 Inner::Cff(cff, charset) => charset
91 .string_id(glyph_id)
92 .ok()
93 .and_then(|sid| GlyphName::from_cff_sid(cff, sid)),
94 _ => None,
95 };
96 if !name.as_ref().is_some_and(|s| !s.is_empty()) {
98 return Some(GlyphName::synthesize(glyph_id));
99 }
100 Some(name.unwrap_or_else(|| GlyphName::synthesize(glyph_id)))
101 }
102
103 pub fn iter(&self) -> impl Iterator<Item = (GlyphId, GlyphName)> + 'a + Clone {
106 match &self.inner {
107 Inner::Post(post, n) => Iter::Post(0..*n, post.clone()),
108 Inner::Cff(cff, charset) => Iter::Cff(cff.clone(), charset.iter()),
109 Inner::Synthesized(n) => Iter::Synthesized(0..*n),
110 }
111 }
112}
113
114#[derive(Copy, Clone, PartialEq, Eq, Debug)]
116pub enum GlyphNameSource {
117 Post,
119 Cff,
121 Synthesized,
124}
125
126#[derive(Clone)]
128pub struct GlyphName {
129 name: [u8; MAX_GLYPH_NAME_LEN],
130 len: u8,
131 is_synthesized: bool,
132}
133
134impl GlyphName {
135 pub fn as_str(&self) -> &str {
137 let bytes = &self.name[..self.len as usize];
138 core::str::from_utf8(bytes).unwrap_or_default()
139 }
140
141 pub fn is_synthesized(&self) -> bool {
144 self.is_synthesized
145 }
146
147 fn from_bytes(bytes: &[u8]) -> Self {
148 let mut name = Self::default();
149 name.append(bytes);
150 name
151 }
152
153 fn from_post(post: &Post, glyph_id: GlyphId) -> Option<Self> {
154 glyph_id
155 .try_into()
156 .ok()
157 .and_then(|id| post.glyph_name(id))
158 .map(|s| s.as_bytes())
159 .map(Self::from_bytes)
160 }
161
162 fn from_cff_sid(cff: &Cff, sid: Sid) -> Option<Self> {
163 cff.string(sid)
164 .and_then(|s| core::str::from_utf8(s.bytes()).ok())
165 .map(|s| s.as_bytes())
166 .map(Self::from_bytes)
167 }
168
169 fn synthesize(glyph_id: GlyphId) -> Self {
170 use core::fmt::Write;
171 let mut name = Self {
172 is_synthesized: true,
173 ..Self::default()
174 };
175 let _ = write!(GlyphNameWrite(&mut name), "gid{}", glyph_id.to_u32());
176 name
177 }
178
179 fn append(&mut self, bytes: &[u8]) {
187 let start = self.len as usize;
190 let available = MAX_GLYPH_NAME_LEN - start;
191 let copy_len = available.min(bytes.len());
192 self.name[start..start + copy_len].copy_from_slice(&bytes[..copy_len]);
193 self.len = (start + copy_len) as u8;
194 }
195}
196
197impl Default for GlyphName {
198 fn default() -> Self {
199 Self {
200 name: [0; MAX_GLYPH_NAME_LEN],
201 len: 0,
202 is_synthesized: false,
203 }
204 }
205}
206
207impl core::fmt::Debug for GlyphName {
208 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
209 f.debug_struct("GlyphName")
210 .field("name", &self.as_str())
211 .field("is_synthesized", &self.is_synthesized)
212 .finish()
213 }
214}
215
216impl core::fmt::Display for GlyphName {
217 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
218 write!(f, "{}", self.as_str())
219 }
220}
221
222impl core::ops::Deref for GlyphName {
223 type Target = str;
224
225 fn deref(&self) -> &Self::Target {
226 self.as_str()
227 }
228}
229
230impl PartialEq<&str> for GlyphName {
231 fn eq(&self, other: &&str) -> bool {
232 self.as_str() == *other
233 }
234}
235
236struct GlyphNameWrite<'a>(&'a mut GlyphName);
237
238impl core::fmt::Write for GlyphNameWrite<'_> {
239 fn write_str(&mut self, s: &str) -> core::fmt::Result {
240 self.0.append(s.as_bytes());
241 Ok(())
242 }
243}
244
245#[derive(Clone)]
246enum Iter<'a> {
247 Post(Range<u32>, Post<'a>),
248 Cff(Cff<'a>, CharsetIter<'a>),
249 Synthesized(Range<u32>),
250}
251
252impl Iter<'_> {
253 fn next_name(&mut self) -> Option<Result<(GlyphId, GlyphName), GlyphId>> {
254 match self {
255 Self::Post(range, post) => {
256 let gid = GlyphId::new(range.next()?);
257 Some(
258 GlyphName::from_post(post, gid)
259 .map(|name| (gid, name))
260 .ok_or(gid),
261 )
262 }
263 Self::Cff(cff, iter) => {
264 let (gid, sid) = iter.next()?;
265 Some(
266 GlyphName::from_cff_sid(cff, sid)
267 .map(|name| (gid, name))
268 .ok_or(gid),
269 )
270 }
271 Self::Synthesized(range) => {
272 let gid = GlyphId::new(range.next()?);
273 Some(Ok((gid, GlyphName::synthesize(gid))))
274 }
275 }
276 }
277}
278
279impl Iterator for Iter<'_> {
280 type Item = (GlyphId, GlyphName);
281
282 fn next(&mut self) -> Option<Self::Item> {
283 match self.next_name()? {
284 Ok((gid, name)) if name.is_empty() => Some((gid, GlyphName::synthesize(gid))),
285 Ok(gid_name) => Some(gid_name),
286 Err(gid) => Some((gid, GlyphName::synthesize(gid))),
287 }
288 }
289}
290
291#[cfg(test)]
292mod tests {
293 use super::*;
294 use raw::{FontData, FontRead};
295
296 #[test]
297 fn synthesized_glyph_names() {
298 let count = 58;
299 let names = GlyphNames {
300 inner: Inner::Synthesized(58),
301 };
302 let names_buf = (0..count).map(|i| format!("gid{i}")).collect::<Vec<_>>();
303 let expected_names = names_buf.iter().map(|s| s.as_str()).collect::<Vec<_>>();
304 for (_, name) in names.iter() {
305 assert!(name.is_synthesized())
306 }
307 check_names(&names, &expected_names, GlyphNameSource::Synthesized);
308 }
309
310 #[test]
311 fn synthesize_for_empty_names() {
312 let mut post_data = font_test_data::post::SIMPLE.to_vec();
313 post_data.truncate(post_data.len() - 5);
316 post_data.push(0);
317 let post = Post::read(FontData::new(&post_data)).unwrap();
318 let gid = GlyphId::new(9);
319 assert!(post.glyph_name(gid.try_into().unwrap()).unwrap().is_empty());
320 let names = GlyphNames {
321 inner: Inner::Post(post, 10),
322 };
323 assert_eq!(names.get(gid).unwrap(), "gid9");
324 assert_eq!(names.iter().last().unwrap().1, "gid9");
325 }
326
327 #[test]
328 fn cff_glyph_names() {
329 let font = FontRef::new(font_test_data::NOTO_SERIF_DISPLAY_TRIMMED).unwrap();
330 let names = GlyphNames::new(&font);
331 assert_eq!(names.source(), GlyphNameSource::Cff);
332 let expected_names = [".notdef", "i", "j", "k", "l"];
333 check_names(&names, &expected_names, GlyphNameSource::Cff);
334 }
335
336 #[test]
337 fn post_glyph_names() {
338 let font = FontRef::new(font_test_data::HVAR_WITH_TRUNCATED_ADVANCE_INDEX_MAP).unwrap();
339 let names = GlyphNames::new(&font);
340 let expected_names = [
341 ".notdef",
342 "space",
343 "A",
344 "I",
345 "T",
346 "Aacute",
347 "Agrave",
348 "Iacute",
349 "Igrave",
350 "Amacron",
351 "Imacron",
352 "acutecomb",
353 "gravecomb",
354 "macroncomb",
355 "A.001",
356 "A.002",
357 "A.003",
358 "A.004",
359 "A.005",
360 "A.006",
361 "A.007",
362 "A.008",
363 "A.009",
364 "A.010",
365 ];
366 check_names(&names, &expected_names, GlyphNameSource::Post);
367 }
368
369 #[test]
370 fn post_glyph_names_partial() {
371 let font = FontRef::new(font_test_data::HVAR_WITH_TRUNCATED_ADVANCE_INDEX_MAP).unwrap();
372 let mut names = GlyphNames::new(&font);
373 let Inner::Post(_, len) = &mut names.inner else {
374 panic!("it's a post table!");
375 };
376 *len += 4;
378 let expected_names = [
379 ".notdef",
380 "space",
381 "A",
382 "I",
383 "T",
384 "Aacute",
385 "Agrave",
386 "Iacute",
387 "Igrave",
388 "Amacron",
389 "Imacron",
390 "acutecomb",
391 "gravecomb",
392 "macroncomb",
393 "A.001",
394 "A.002",
395 "A.003",
396 "A.004",
397 "A.005",
398 "A.006",
399 "A.007",
400 "A.008",
401 "A.009",
402 "A.010",
403 "gid24",
405 "gid25",
406 "gid26",
407 "gid27",
408 ];
409 check_names(&names, &expected_names, GlyphNameSource::Post);
410 }
411
412 fn check_names(names: &GlyphNames, expected_names: &[&str], expected_source: GlyphNameSource) {
413 assert_eq!(names.source(), expected_source);
414 let iter_names = names.iter().collect::<Vec<_>>();
415 assert_eq!(iter_names.len(), expected_names.len());
416 for (i, expected) in expected_names.iter().enumerate() {
417 let gid = GlyphId::new(i as u32);
418 let name = names.get(gid).unwrap();
419 assert_eq!(name, expected);
420 assert_eq!(iter_names[i].0, gid);
421 assert_eq!(iter_names[i].1, expected);
422 }
423 }
424}