1use super::super::{Font, Kind};
4use crate::{
5 ps::{
6 cff::{charset, CffFontRef},
7 type1::Type1Font,
8 },
9 tables::{cff::Cff, post},
10 types::GlyphId16,
11 TableProvider,
12};
13use alloc::{boxed::Box, vec::Vec};
14use core::{fmt, iter::Peekable, ops::Deref};
15use types::GlyphId;
16
17const MAX_GLYPH_NAME_LEN: usize = 63;
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub enum GlyphNameSource {
25 Post,
27 Cff,
29 Type1,
31 Synthesized,
33}
34
35#[derive(Clone)]
41pub struct GlyphName {
42 name: [u8; MAX_GLYPH_NAME_LEN],
43 len: u8,
44 source: GlyphNameSource,
45}
46
47impl GlyphName {
48 pub fn as_str(&self) -> &str {
50 core::str::from_utf8(&self.name[..self.len as usize]).unwrap_or_default()
51 }
52
53 pub fn source(&self) -> GlyphNameSource {
55 self.source
56 }
57
58 pub fn is_synthesized(&self) -> bool {
60 self.source == GlyphNameSource::Synthesized
61 }
62
63 fn from_bytes(bytes: &[u8], source: GlyphNameSource) -> Option<Self> {
64 let value = core::str::from_utf8(bytes).ok()?;
65 if value.is_empty() {
66 return None;
67 }
68 let mut name = Self {
69 name: [0; MAX_GLYPH_NAME_LEN],
70 len: 0,
71 source,
72 };
73 name.append(value);
74 Some(name)
75 }
76
77 fn synthesized(glyph: GlyphId) -> Self {
78 use fmt::Write;
79 let mut name = Self {
80 name: [0; MAX_GLYPH_NAME_LEN],
81 len: 0,
82 source: GlyphNameSource::Synthesized,
83 };
84 let _ = write!(GlyphNameWrite(&mut name), "gid{}", glyph.to_u32());
85 name
86 }
87
88 fn append(&mut self, value: &str) {
90 let start = self.len as usize;
91 let mut count = (MAX_GLYPH_NAME_LEN - start).min(value.len());
92 while !value.is_char_boundary(count) {
93 count -= 1;
94 }
95 self.name[start..start + count].copy_from_slice(&value.as_bytes()[..count]);
96 self.len = (start + count) as u8;
97 }
98}
99
100impl fmt::Debug for GlyphName {
101 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102 f.debug_struct("GlyphName")
103 .field("name", &self.as_str())
104 .field("source", &self.source)
105 .finish()
106 }
107}
108
109impl fmt::Display for GlyphName {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 f.write_str(self.as_str())
112 }
113}
114
115impl Deref for GlyphName {
116 type Target = str;
117
118 fn deref(&self) -> &Self::Target {
119 self.as_str()
120 }
121}
122
123impl PartialEq<&str> for GlyphName {
124 fn eq(&self, other: &&str) -> bool {
125 self.as_str() == *other
126 }
127}
128
129struct GlyphNameWrite<'a>(&'a mut GlyphName);
130
131impl fmt::Write for GlyphNameWrite<'_> {
132 fn write_str(&mut self, value: &str) -> fmt::Result {
133 self.0.append(value);
134 Ok(())
135 }
136}
137
138enum NameSource<'a> {
139 Post(post::Post<'a>),
140 SfntCff(Cff<'a>, charset::Charset<'a>),
141 StandaloneCff(CffFontRef<'a>, charset::Charset<'a>),
142 Type1(&'a Type1Font),
143 Synthesized,
144}
145
146fn source(font: &Font) -> NameSource<'_> {
147 if let Kind::Type1(type1) = font.kind() {
148 return NameSource::Type1(type1);
149 }
150 if let Kind::Cff(cff, _) = font.kind() {
151 if cff.is_cid() {
152 return NameSource::Synthesized;
153 }
154 return cff.charset().map_or(NameSource::Synthesized, |charset| {
155 NameSource::StandaloneCff(cff, charset)
156 });
157 }
158 if let Ok(post) = font.tables().post() {
159 if post.num_names() != 0 {
160 return NameSource::Post(post);
161 }
162 }
163 if let Ok(cff) = font.tables().cff() {
164 if font.cff().is_some_and(|parsed| parsed.is_cid()) {
165 return NameSource::Synthesized;
166 }
167 if let Some(charset) = cff.charset(0) {
168 return NameSource::SfntCff(cff, charset);
169 }
170 }
171 NameSource::Synthesized
172}
173
174pub(crate) fn glyph_name(font: &Font, glyph: GlyphId) -> Option<GlyphName> {
175 if glyph.to_u32() >= font.num_glyphs() {
176 return None;
177 }
178 let name = match source(font) {
179 NameSource::Post(post) => GlyphId16::try_from(glyph)
180 .ok()
181 .and_then(|id| post.glyph_name(id))
182 .and_then(|value| GlyphName::from_bytes(value.as_bytes(), GlyphNameSource::Post)),
183 NameSource::SfntCff(cff, charset) => charset
184 .string_id(glyph)
185 .and_then(|id| cff.string(id))
186 .and_then(|value| GlyphName::from_bytes(value, GlyphNameSource::Cff)),
187 NameSource::StandaloneCff(cff, charset) => charset
188 .string_id(glyph)
189 .and_then(|id| cff.string(id))
190 .and_then(|value| GlyphName::from_bytes(value, GlyphNameSource::Cff)),
191 NameSource::Type1(type1) => type1
192 .glyph_name(glyph)
193 .and_then(|value| GlyphName::from_bytes(value.as_bytes(), GlyphNameSource::Type1)),
194 NameSource::Synthesized => None,
195 };
196 Some(name.unwrap_or_else(|| GlyphName::synthesized(glyph)))
197}
198
199pub(crate) struct GlyphNameIndex {
201 names: Box<[u8]>,
203 entries: Box<[(u32, u32, u32)]>,
206}
207
208impl GlyphNameIndex {
209 pub(crate) fn new(font: &Font) -> Self {
210 let mut names = Vec::new();
211 let mut entries = Vec::new();
212 let mut iter = name_iter(font);
215 while let Some((glyph, name)) = iter.next_stored() {
216 let Some((name, _)) = name else {
217 continue;
218 };
219 let start = names.len() as u32;
220 names.extend_from_slice(name.as_bytes());
221 entries.push((start, names.len() as u32, glyph.to_u32()));
222 }
223 let name = |entry: &(u32, u32, u32)| &names[entry.0 as usize..entry.1 as usize];
224 entries.sort_unstable_by(|a, b| name(a).cmp(name(b)).then(a.2.cmp(&b.2)));
225 Self {
226 names: names.into_boxed_slice(),
227 entries: entries.into_boxed_slice(),
228 }
229 }
230
231 pub(crate) fn get(&self, name: &str) -> Option<GlyphId> {
232 let key = name.as_bytes();
233 let index = self.entries.partition_point(|entry| self.name(entry) < key);
234 let entry = self.entries.get(index)?;
235 (self.name(entry) == key).then(|| GlyphId::new(entry.2))
236 }
237
238 fn name(&self, entry: &(u32, u32, u32)) -> &[u8] {
239 &self.names[entry.0 as usize..entry.1 as usize]
240 }
241}
242
243enum NameIterSource<'a> {
244 Post(Peekable<post::GlyphNames<'a>>),
246 SfntCff(Cff<'a>, charset::Iter<'a>),
247 StandaloneCff(CffFontRef<'a>, charset::Iter<'a>),
248 Type1(&'a Type1Font),
249 Synthesized,
250}
251
252struct NameIter<'a> {
253 source: NameIterSource<'a>,
254 next: u32,
255 count: u32,
256}
257
258pub(crate) fn glyph_names(font: &Font) -> impl ExactSizeIterator<Item = (GlyphId, GlyphName)> + '_ {
259 name_iter(font)
260}
261
262fn name_iter(font: &Font) -> NameIter<'_> {
263 let source = match source(font) {
264 NameSource::Post(post) => NameIterSource::Post(post.glyph_names().peekable()),
265 NameSource::SfntCff(cff, charset) => NameIterSource::SfntCff(cff, charset.iter()),
266 NameSource::StandaloneCff(cff, charset) => {
267 NameIterSource::StandaloneCff(cff, charset.iter())
268 }
269 NameSource::Type1(type1) => NameIterSource::Type1(type1),
270 NameSource::Synthesized => NameIterSource::Synthesized,
271 };
272 NameIter {
273 source,
274 next: 0,
275 count: font.num_glyphs(),
276 }
277}
278
279impl<'a> NameIter<'a> {
280 fn next_stored(&mut self) -> Option<(GlyphId, Option<(&'a str, GlyphNameSource)>)> {
283 if self.next >= self.count {
284 return None;
285 }
286 let glyph = GlyphId::new(self.next);
287 self.next += 1;
288 let cff_string = |cff_string: Option<&'a [u8]>| {
289 cff_string
290 .and_then(|value| core::str::from_utf8(value).ok())
291 .map(|value| (value, GlyphNameSource::Cff))
292 };
293 let name = match &mut self.source {
294 NameIterSource::Post(names) => names
295 .next_if(|(id, _)| *id == glyph)
296 .map(|(_, value)| (value, GlyphNameSource::Post)),
297 NameIterSource::SfntCff(cff, names) => names
298 .next()
299 .and_then(|(id, sid)| cff_string((id == glyph).then(|| cff.string(sid)).flatten())),
300 NameIterSource::StandaloneCff(cff, names) => names
301 .next()
302 .and_then(|(id, sid)| cff_string((id == glyph).then(|| cff.string(sid)).flatten())),
303 NameIterSource::Type1(type1) => type1
304 .glyph_name(glyph)
305 .map(|value| (value, GlyphNameSource::Type1)),
306 NameIterSource::Synthesized => None,
307 };
308 Some((glyph, name.filter(|(value, _)| !value.is_empty())))
309 }
310}
311
312impl Iterator for NameIter<'_> {
313 type Item = (GlyphId, GlyphName);
314
315 fn size_hint(&self) -> (usize, Option<usize>) {
316 let remaining = (self.count - self.next) as usize;
317 (remaining, Some(remaining))
318 }
319
320 fn next(&mut self) -> Option<Self::Item> {
321 let (glyph, name) = self.next_stored()?;
322 let name = name.and_then(|(value, source)| GlyphName::from_bytes(value.as_bytes(), source));
323 Some((glyph, name.unwrap_or_else(|| GlyphName::synthesized(glyph))))
324 }
325}
326
327impl ExactSizeIterator for NameIter<'_> {}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332 use crate::{model::Blob, FontRef};
333 use alloc::sync::Arc;
334 use types::Tag;
335
336 #[test]
337 fn post_names_agree_between_lookup_and_iteration() {
338 let font = Font::new(font_test_data::HVAR_WITH_TRUNCATED_ADVANCE_INDEX_MAP, 0).unwrap();
339 let mut iter = font.glyph_names();
340 let count = font.num_glyphs() as usize;
341 assert_eq!(iter.size_hint(), (count, Some(count)));
342 assert!(iter.next().is_some());
343 assert_eq!(iter.by_ref().count(), count - 1);
344
345 let names: Vec<_> = font.glyph_names().collect();
346 assert_eq!(names.len(), font.num_glyphs() as usize);
347 assert_eq!(names[0].1, ".notdef");
348 assert_eq!(names[1].1, "space");
349 assert_eq!(names[2].1, "A");
350 for (glyph, name) in names {
351 let looked_up = font.glyph_name(glyph).unwrap();
352 assert_eq!(name.as_str(), looked_up.as_str());
353 assert_eq!(name.source(), looked_up.source());
354 }
355 assert!(font.glyph_name(GlyphId::new(font.num_glyphs())).is_none());
356 }
357
358 #[test]
359 fn a_partial_post_table_synthesizes_only_missing_names() {
360 let source: Arc<dyn Fn(Tag) -> Option<Blob> + Send + Sync> = Arc::new(|tag| {
361 if tag == Tag::new(b"post") {
362 return Some(Blob::from(font_test_data::post::SIMPLE.to_vec()));
363 }
364 let font = FontRef::new(font_test_data::HVAR_WITH_TRUNCATED_ADVANCE_INDEX_MAP).ok()?;
365 font.table_data(tag)
366 .map(|data| Blob::from(data.as_bytes().to_vec()))
367 });
368 let font = Font::new(source, 0).unwrap();
369 let names: Vec<_> = font.glyph_names().collect();
370 assert!(font.num_glyphs() > 10);
371 assert_eq!(names[9].1, "hola");
372 assert_eq!(names[9].1.source(), GlyphNameSource::Post);
373 assert_eq!(names[10].1, "gid10");
374 assert_eq!(names[10].1.source(), GlyphNameSource::Synthesized);
375 assert_eq!(
376 font.glyph_name(GlyphId::new(9)).unwrap().source(),
377 GlyphNameSource::Post
378 );
379 assert_eq!(
380 font.glyph_name(GlyphId::new(10)).unwrap().source(),
381 GlyphNameSource::Synthesized
382 );
383 }
384
385 #[test]
388 fn post_names_survive_a_bad_entry() {
389 let long = "a".repeat(70);
390 let mut post = alloc::vec![0, 2, 0, 0];
391 post.extend_from_slice(&[0; 28]);
392 post.extend_from_slice(&4u16.to_be_bytes());
394 for index in [0u16, 258, 258 + 5, 258 + 1] {
395 post.extend_from_slice(&index.to_be_bytes());
396 }
397 for name in ["ok", long.as_str()] {
398 post.push(name.len() as u8);
399 post.extend_from_slice(name.as_bytes());
400 }
401 let source: Arc<dyn Fn(Tag) -> Option<Blob> + Send + Sync> = Arc::new(move |tag| {
402 if tag == Tag::new(b"post") {
403 return Some(Blob::from(post.clone()));
404 }
405 let font = FontRef::new(font_test_data::HVAR_WITH_TRUNCATED_ADVANCE_INDEX_MAP).ok()?;
406 font.table_data(tag)
407 .map(|data| Blob::from(data.as_bytes().to_vec()))
408 });
409 let font = Font::new(source, 0).unwrap();
410 let names: Vec<_> = font.glyph_names().collect();
411 assert_eq!(names[1].1, "ok");
412 assert_eq!(names[2].1, "gid2");
413 assert_eq!(names[3].1.as_str(), "a".repeat(63));
414 assert_eq!(names[3].1.source(), GlyphNameSource::Post);
415 for (glyph, name) in names {
416 let looked_up = font.glyph_name(glyph).unwrap();
417 assert_eq!(name.as_str(), looked_up.as_str(), "glyph {glyph}");
418 assert_eq!(name.source(), looked_up.source());
419 }
420 }
421
422 #[test]
425 fn a_long_name_is_cut_between_characters() {
426 let name = GlyphName::from_bytes("é".repeat(35).as_bytes(), GlyphNameSource::Cff).unwrap();
427 assert_eq!(name.as_str(), "é".repeat(31));
428 }
429
430 #[test]
433 fn glyph_by_name_finds_the_first_glyph_with_each_name() {
434 let sfnt = FontRef::new(font_test_data::NOTO_SANS_JP_CFF).unwrap();
435 let cid_cff = sfnt.cff().unwrap().offset_data().as_bytes();
436 let fonts = [
437 Font::new(font_test_data::HVAR_WITH_TRUNCATED_ADVANCE_INDEX_MAP, 0).unwrap(),
438 Font::new(font_test_data::NOTO_SERIF_DISPLAY_TRIMMED, 0).unwrap(),
439 Font::new(font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFA, 0).unwrap(),
440 Font::new(cid_cff, 0).unwrap(),
441 ];
442 for font in &fonts {
443 let names: Vec<_> = font.glyph_names().collect();
444 for (glyph, name) in &names {
445 let first = names
446 .iter()
447 .find(|(_, other)| !other.is_synthesized() && other.as_str() == name.as_str())
448 .map(|(glyph, _)| *glyph);
449 assert_eq!(font.glyph_by_name(name.as_str()), first, "glyph {glyph}");
450 }
451 assert_eq!(font.glyph_by_name("no.such.glyph"), None);
452 assert_eq!(font.glyph_by_name(""), None);
453 }
454 assert_eq!(fonts[0].glyph_by_name("A"), Some(GlyphId::new(2)));
455 assert_eq!(fonts[3].glyph_by_name("gid1"), None);
456 }
457
458 #[test]
459 fn cff_names_agree_between_lookup_and_iteration() {
460 let font = Font::new(font_test_data::NOTO_SERIF_DISPLAY_TRIMMED, 0).unwrap();
461 let names: Vec<_> = font.glyph_names().collect();
462 for (gid, expected) in [".notdef", "i", "j", "k", "l"].iter().enumerate() {
463 let (glyph, name) = &names[gid];
464 assert_eq!(*glyph, GlyphId::new(gid as u32));
465 assert_eq!(name, expected);
466 assert_eq!(name.source(), GlyphNameSource::Cff);
467 assert_eq!(font.glyph_name(*glyph).unwrap().as_str(), *expected);
468 }
469 }
470
471 #[test]
472 fn standalone_cid_cff_synthesizes_gid_names() {
473 let sfnt = FontRef::new(font_test_data::NOTO_SANS_JP_CFF).unwrap();
474 let cff = sfnt.cff().unwrap().offset_data().as_bytes();
475 let font = Font::new(cff, 0).unwrap();
476 let Kind::Cff(raw, _) = font.kind() else {
477 panic!("expected standalone CFF font");
478 };
479 assert!(raw.is_cid());
480 let names: Vec<_> = font.glyph_names().collect();
481 assert_eq!(names.len(), font.num_glyphs() as usize);
482 for (glyph, name) in names {
483 assert_eq!(name.as_str(), format!("gid{}", glyph.to_u32()));
484 assert!(name.is_synthesized());
485 assert_eq!(font.glyph_name(glyph).unwrap().as_str(), name.as_str());
486 }
487 assert!(font.glyph_name(GlyphId::new(font.num_glyphs())).is_none());
488 }
489
490 #[test]
491 fn sfnt_cid_cff_without_post_names_synthesizes_gid_names() {
492 let font = Font::new(font_test_data::NOTO_SANS_JP_CFF, 0).unwrap();
493 assert!(font.cff().unwrap().is_cid());
494 let names: Vec<_> = font.glyph_names().take(3).collect();
495 for (glyph, name) in names {
496 assert_eq!(name.as_str(), format!("gid{}", glyph.to_u32()));
497 assert!(name.is_synthesized());
498 }
499 }
500
501 #[test]
502 fn type1_names_agree_between_lookup_and_iteration() {
503 let font = Font::new(font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFA, 0).unwrap();
504 let Kind::Type1(type1) = font.kind() else {
505 panic!("expected Type 1 font");
506 };
507 let names: Vec<_> = font.glyph_names().collect();
508 assert_eq!(names.len(), font.num_glyphs() as usize);
509 for (glyph, name) in names {
510 assert_eq!(name.as_str(), type1.glyph_name(glyph).unwrap());
511 assert_eq!(name.source(), GlyphNameSource::Type1);
512 assert_eq!(font.glyph_name(glyph).unwrap().as_str(), name.as_str());
513 }
514 }
515
516 #[test]
519 fn long_names_are_found_in_full() {
520 let prefix = "a".repeat(63);
521 let (x, y) = (prefix.clone() + "xxxxxxx", prefix.clone() + "yyyyyyy");
522 let mut post = alloc::vec![0, 2, 0, 0];
523 post.extend_from_slice(&[0; 28]);
524 post.extend_from_slice(&3u16.to_be_bytes());
525 for index in [0u16, 258, 259] {
526 post.extend_from_slice(&index.to_be_bytes());
527 }
528 for name in [&x, &y] {
529 post.push(name.len() as u8);
530 post.extend_from_slice(name.as_bytes());
531 }
532 let source: Arc<dyn Fn(Tag) -> Option<Blob> + Send + Sync> = Arc::new(move |tag| {
533 if tag == Tag::new(b"post") {
534 return Some(Blob::from(post.clone()));
535 }
536 let font = FontRef::new(font_test_data::HVAR_WITH_TRUNCATED_ADVANCE_INDEX_MAP).ok()?;
537 font.table_data(tag)
538 .map(|data| Blob::from(data.as_bytes().to_vec()))
539 });
540 let font = Font::new(source, 0).unwrap();
541 assert_eq!(font.glyph_by_name(&x), Some(GlyphId::new(1)));
542 assert_eq!(font.glyph_by_name(&y), Some(GlyphId::new(2)));
543 assert_eq!(font.glyph_by_name(&prefix), None);
544 assert_eq!(font.glyph_name(GlyphId::new(1)).unwrap(), prefix.as_str());
545 }
546
547 #[test]
548 fn missing_name_tables_synthesize_each_name() {
549 let source: Arc<dyn Fn(Tag) -> Option<Blob> + Send + Sync> = Arc::new(|tag| {
550 if tag == Tag::new(b"post") || tag == Tag::new(b"CFF ") {
551 return None;
552 }
553 let font = FontRef::new(font_test_data::TINOS_SUBSET).ok()?;
554 font.table_data(tag)
555 .map(|data| Blob::from(data.as_bytes().to_vec()))
556 });
557 let font = Font::new(source, 0).unwrap();
558 let names: Vec<_> = font.glyph_names().collect();
559 assert_eq!(names.len(), font.num_glyphs() as usize);
560 for (glyph, name) in names {
561 assert_eq!(name.as_str(), format!("gid{}", glyph.to_u32()));
562 assert!(name.is_synthesized());
563 assert_eq!(font.glyph_name(glyph).unwrap().source(), name.source());
564 }
565 }
566}