//! Support for accessing glyph names. use core::ops::Range; use raw::{ tables::{ cff::Cff, post::Post, postscript::{Charset, CharsetIter, StringId as Sid}, }, types::GlyphId, FontRef, TableProvider, }; /// "Names must be no longer than 63 characters; some older implementations /// can assume a length limit of 31 characters." /// See const MAX_GLYPH_NAME_LEN: usize = 63; /// Mapping from glyph identifiers to names. /// /// This sources glyph names from the `post` and `CFF` tables in that order. /// If glyph names are not available in either, then they are synthesized /// as `gidDDD` where `DDD` is the glyph identifier in decimal. Use the /// [`source`](Self::source) to determine which source was chosen. #[derive(Clone)] pub struct GlyphNames<'a> { inner: Inner<'a>, } #[derive(Clone)] enum Inner<'a> { // Second field is num_glyphs Post(Post<'a>, u32), Cff(Cff<'a>, Charset<'a>), Synthesized(u32), } impl<'a> GlyphNames<'a> { /// Creates a new object for accessing glyph names from the given font. pub fn new(font: &FontRef<'a>) -> Self { let num_glyphs = font .maxp() .map(|maxp| maxp.num_glyphs() as u32) .unwrap_or_default(); if let Ok(post) = font.post() { if post.num_names() != 0 { return Self { inner: Inner::Post(post, num_glyphs), }; } } if let Some((cff, charset)) = font .cff() .ok() .and_then(|cff| Some((cff.clone(), cff.charset(0).ok()??))) { return Self { inner: Inner::Cff(cff, charset), }; } Self { inner: Inner::Synthesized(num_glyphs), } } /// Returns the chosen source for glyph names. pub fn source(&self) -> GlyphNameSource { match &self.inner { Inner::Post(..) => GlyphNameSource::Post, Inner::Cff(..) => GlyphNameSource::Cff, Inner::Synthesized(..) => GlyphNameSource::Synthesized, } } /// Returns the number of glyphs in the font. pub fn num_glyphs(&self) -> u32 { match &self.inner { Inner::Post(_, n) | Inner::Synthesized(n) => *n, Inner::Cff(_, charset) => charset.num_glyphs(), } } /// Returns the name for the given glyph identifier. pub fn get(&self, glyph_id: GlyphId) -> Option { if glyph_id.to_u32() >= self.num_glyphs() { return None; } let name = match &self.inner { Inner::Post(post, _) => GlyphName::from_post(post, glyph_id), Inner::Cff(cff, charset) => charset .string_id(glyph_id) .ok() .and_then(|sid| GlyphName::from_cff_sid(cff, sid)), _ => None, }; // If name is empty string, synthesize it if !name.as_ref().is_some_and(|s| !s.is_empty()) { return Some(GlyphName::synthesize(glyph_id)); } Some(name.unwrap_or_else(|| GlyphName::synthesize(glyph_id))) } /// Returns an iterator yielding the identifier and name for all glyphs in /// the font. pub fn iter(&self) -> impl Iterator + 'a + Clone { match &self.inner { Inner::Post(post, n) => Iter::Post(0..*n, post.clone()), Inner::Cff(cff, charset) => Iter::Cff(cff.clone(), charset.iter()), Inner::Synthesized(n) => Iter::Synthesized(0..*n), } } } /// Specifies the chosen source for glyph names. #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum GlyphNameSource { /// Glyph names are sourced from the `post` table. Post, /// Glyph names are sourced from the `CFF` table. Cff, /// Glyph names are synthesized in the format `gidDDD` where `DDD` is /// the glyph identifier in decimal. Synthesized, } /// The name of a glyph. #[derive(Clone)] pub struct GlyphName { name: [u8; MAX_GLYPH_NAME_LEN], len: u8, is_synthesized: bool, } impl GlyphName { /// Returns the underlying name as a string. pub fn as_str(&self) -> &str { let bytes = &self.name[..self.len as usize]; core::str::from_utf8(bytes).unwrap_or_default() } /// Returns true if the glyph name was synthesized, i.e. not found in any /// source. pub fn is_synthesized(&self) -> bool { self.is_synthesized } fn from_bytes(bytes: &[u8]) -> Self { let mut name = Self::default(); name.append(bytes); name } fn from_post(post: &Post, glyph_id: GlyphId) -> Option { glyph_id .try_into() .ok() .and_then(|id| post.glyph_name(id)) .map(|s| s.as_bytes()) .map(Self::from_bytes) } fn from_cff_sid(cff: &Cff, sid: Sid) -> Option { cff.string(sid) .and_then(|s| core::str::from_utf8(s.bytes()).ok()) .map(|s| s.as_bytes()) .map(Self::from_bytes) } fn synthesize(glyph_id: GlyphId) -> Self { use core::fmt::Write; let mut name = Self { is_synthesized: true, ..Self::default() }; let _ = write!(GlyphNameWrite(&mut name), "gid{}", glyph_id.to_u32()); name } /// Appends the given bytes to `self` while keeping the maximum length /// at 63 bytes. /// /// This exists primarily to support the [`core::fmt::Write`] impl /// (which is used for generating synthesized glyph names) because /// we have no guarantee of how many times `write_str` might be called /// for a given format. fn append(&mut self, bytes: &[u8]) { // We simply truncate when length exceeds the max since glyph names // are expected to be <= 63 chars let start = self.len as usize; let available = MAX_GLYPH_NAME_LEN - start; let copy_len = available.min(bytes.len()); self.name[start..start + copy_len].copy_from_slice(&bytes[..copy_len]); self.len = (start + copy_len) as u8; } } impl Default for GlyphName { fn default() -> Self { Self { name: [0; MAX_GLYPH_NAME_LEN], len: 0, is_synthesized: false, } } } impl core::fmt::Debug for GlyphName { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("GlyphName") .field("name", &self.as_str()) .field("is_synthesized", &self.is_synthesized) .finish() } } impl core::fmt::Display for GlyphName { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{}", self.as_str()) } } impl core::ops::Deref for GlyphName { type Target = str; fn deref(&self) -> &Self::Target { self.as_str() } } impl PartialEq<&str> for GlyphName { fn eq(&self, other: &&str) -> bool { self.as_str() == *other } } struct GlyphNameWrite<'a>(&'a mut GlyphName); impl core::fmt::Write for GlyphNameWrite<'_> { fn write_str(&mut self, s: &str) -> core::fmt::Result { self.0.append(s.as_bytes()); Ok(()) } } #[derive(Clone)] enum Iter<'a> { Post(Range, Post<'a>), Cff(Cff<'a>, CharsetIter<'a>), Synthesized(Range), } impl Iter<'_> { fn next_name(&mut self) -> Option> { match self { Self::Post(range, post) => { let gid = GlyphId::new(range.next()?); Some( GlyphName::from_post(post, gid) .map(|name| (gid, name)) .ok_or(gid), ) } Self::Cff(cff, iter) => { let (gid, sid) = iter.next()?; Some( GlyphName::from_cff_sid(cff, sid) .map(|name| (gid, name)) .ok_or(gid), ) } Self::Synthesized(range) => { let gid = GlyphId::new(range.next()?); Some(Ok((gid, GlyphName::synthesize(gid)))) } } } } impl Iterator for Iter<'_> { type Item = (GlyphId, GlyphName); fn next(&mut self) -> Option { match self.next_name()? { Ok((gid, name)) if name.is_empty() => Some((gid, GlyphName::synthesize(gid))), Ok(gid_name) => Some(gid_name), Err(gid) => Some((gid, GlyphName::synthesize(gid))), } } } #[cfg(test)] mod tests { use super::*; use raw::{FontData, FontRead}; #[test] fn synthesized_glyph_names() { let count = 58; let names = GlyphNames { inner: Inner::Synthesized(58), }; let names_buf = (0..count).map(|i| format!("gid{i}")).collect::>(); let expected_names = names_buf.iter().map(|s| s.as_str()).collect::>(); for (_, name) in names.iter() { assert!(name.is_synthesized()) } check_names(&names, &expected_names, GlyphNameSource::Synthesized); } #[test] fn synthesize_for_empty_names() { let mut post_data = font_test_data::post::SIMPLE.to_vec(); // last name in this post data is "hola" so pop 5 bytes and then // push a 0 to simulate an empty name post_data.truncate(post_data.len() - 5); post_data.push(0); let post = Post::read(FontData::new(&post_data)).unwrap(); let gid = GlyphId::new(9); assert!(post.glyph_name(gid.try_into().unwrap()).unwrap().is_empty()); let names = GlyphNames { inner: Inner::Post(post, 10), }; assert_eq!(names.get(gid).unwrap(), "gid9"); assert_eq!(names.iter().last().unwrap().1, "gid9"); } #[test] fn cff_glyph_names() { let font = FontRef::new(font_test_data::NOTO_SERIF_DISPLAY_TRIMMED).unwrap(); let names = GlyphNames::new(&font); assert_eq!(names.source(), GlyphNameSource::Cff); let expected_names = [".notdef", "i", "j", "k", "l"]; check_names(&names, &expected_names, GlyphNameSource::Cff); } #[test] fn post_glyph_names() { let font = FontRef::new(font_test_data::HVAR_WITH_TRUNCATED_ADVANCE_INDEX_MAP).unwrap(); let names = GlyphNames::new(&font); let expected_names = [ ".notdef", "space", "A", "I", "T", "Aacute", "Agrave", "Iacute", "Igrave", "Amacron", "Imacron", "acutecomb", "gravecomb", "macroncomb", "A.001", "A.002", "A.003", "A.004", "A.005", "A.006", "A.007", "A.008", "A.009", "A.010", ]; check_names(&names, &expected_names, GlyphNameSource::Post); } #[test] fn post_glyph_names_partial() { let font = FontRef::new(font_test_data::HVAR_WITH_TRUNCATED_ADVANCE_INDEX_MAP).unwrap(); let mut names = GlyphNames::new(&font); let Inner::Post(_, len) = &mut names.inner else { panic!("it's a post table!"); }; // Increase count by 4 so we synthesize the remaining names *len += 4; let expected_names = [ ".notdef", "space", "A", "I", "T", "Aacute", "Agrave", "Iacute", "Igrave", "Amacron", "Imacron", "acutecomb", "gravecomb", "macroncomb", "A.001", "A.002", "A.003", "A.004", "A.005", "A.006", "A.007", "A.008", "A.009", "A.010", // synthesized names... "gid24", "gid25", "gid26", "gid27", ]; check_names(&names, &expected_names, GlyphNameSource::Post); } fn check_names(names: &GlyphNames, expected_names: &[&str], expected_source: GlyphNameSource) { assert_eq!(names.source(), expected_source); let iter_names = names.iter().collect::>(); assert_eq!(iter_names.len(), expected_names.len()); for (i, expected) in expected_names.iter().enumerate() { let gid = GlyphId::new(i as u32); let name = names.get(gid).unwrap(); assert_eq!(name, expected); assert_eq!(iter_names[i].0, gid); assert_eq!(iter_names[i].1, expected); } } }