Skip to main content

skrifa/
glyph_name.rs

1//! Support for accessing glyph names.
2
3use core::ops::Range;
4use raw::{
5    ps::{
6        cff::charset::{Charset, Iter as CharsetIter},
7        Sid,
8    },
9    tables::{
10        cff::Cff,
11        post::{self, Post},
12    },
13    types::GlyphId,
14    FontRef, TableProvider,
15};
16
17/// "Names must be no longer than 63 characters; some older implementations
18/// can assume a length limit of 31 characters."
19/// See <https://learn.microsoft.com/en-us/typography/opentype/spec/post#version-20>
20const MAX_GLYPH_NAME_LEN: usize = 63;
21
22/// Mapping from glyph identifiers to names.
23///
24/// This sources glyph names from the `post` and `CFF` tables in that order.
25/// If glyph names are not available in either, then they are synthesized
26/// as `gidDDD` where `DDD` is the glyph identifier in decimal. Use the
27/// [`source`](Self::source) to determine which source was chosen.
28#[derive(Clone)]
29pub struct GlyphNames<'a> {
30    inner: Inner<'a>,
31}
32
33#[derive(Clone)]
34enum Inner<'a> {
35    // Second field is num_glyphs
36    Post(Post<'a>, u32),
37    Cff(Cff<'a>, Charset<'a>),
38    Synthesized(u32),
39}
40
41impl<'a> GlyphNames<'a> {
42    /// Creates a new object for accessing glyph names from the given font.
43    pub fn new(font: &FontRef<'a>) -> Self {
44        let num_glyphs = font
45            .selected_maxp()
46            .map(|maxp| maxp.num_glyphs())
47            .unwrap_or_default();
48        if let Ok(post) = font.post() {
49            if post.num_names() != 0 {
50                return Self {
51                    inner: Inner::Post(post, num_glyphs),
52                };
53            }
54        }
55        if let Some((cff, charset)) = font
56            .cff()
57            .ok()
58            .and_then(|cff| Some((cff.clone(), cff.charset(0)?)))
59        {
60            return Self {
61                inner: Inner::Cff(cff, charset),
62            };
63        }
64        Self {
65            inner: Inner::Synthesized(num_glyphs),
66        }
67    }
68
69    /// Returns the chosen source for glyph names.
70    pub fn source(&self) -> GlyphNameSource {
71        match &self.inner {
72            Inner::Post(..) => GlyphNameSource::Post,
73            Inner::Cff(..) => GlyphNameSource::Cff,
74            Inner::Synthesized(..) => GlyphNameSource::Synthesized,
75        }
76    }
77
78    /// Returns the number of glyphs in the font.
79    pub fn num_glyphs(&self) -> u32 {
80        match &self.inner {
81            Inner::Post(_, n) | Inner::Synthesized(n) => *n,
82            Inner::Cff(_, charset) => charset.num_glyphs(),
83        }
84    }
85
86    /// Returns the name for the given glyph identifier.
87    pub fn get(&self, glyph_id: GlyphId) -> Option<GlyphName> {
88        if glyph_id.to_u32() >= self.num_glyphs() {
89            return None;
90        }
91        let name = match &self.inner {
92            Inner::Post(post, _) => GlyphName::from_post(post, glyph_id),
93            Inner::Cff(cff, charset) => charset
94                .string_id(glyph_id)
95                .and_then(|sid| GlyphName::from_cff_sid(cff, sid)),
96            _ => None,
97        };
98        // If name is empty string, synthesize it
99        if name.as_ref().is_none_or(|s| s.is_empty()) {
100            return Some(GlyphName::synthesize(glyph_id));
101        }
102        Some(name.unwrap_or_else(|| GlyphName::synthesize(glyph_id)))
103    }
104
105    /// Returns an iterator yielding the identifier and name for all glyphs in
106    /// the font.
107    pub fn iter(&self) -> impl Iterator<Item = (GlyphId, GlyphName)> + 'a + Clone {
108        match &self.inner {
109            Inner::Post(post, n) => Iter::Post(0..*n, post.glyph_names()),
110            Inner::Cff(cff, charset) => Iter::Cff(cff.clone(), charset.iter()),
111            Inner::Synthesized(n) => Iter::Synthesized(0..*n),
112        }
113    }
114}
115
116/// Specifies the chosen source for glyph names.
117#[derive(Copy, Clone, PartialEq, Eq, Debug)]
118pub enum GlyphNameSource {
119    /// Glyph names are sourced from the `post` table.
120    Post,
121    /// Glyph names are sourced from the `CFF` table.
122    Cff,
123    /// Glyph names are synthesized in the format `gidDDD` where `DDD` is
124    /// the glyph identifier in decimal.
125    Synthesized,
126}
127
128/// The name of a glyph.
129#[derive(Clone)]
130pub struct GlyphName {
131    name: [u8; MAX_GLYPH_NAME_LEN],
132    len: u8,
133    is_synthesized: bool,
134}
135
136impl GlyphName {
137    /// Returns the underlying name as a string.
138    pub fn as_str(&self) -> &str {
139        let bytes = &self.name[..self.len as usize];
140        core::str::from_utf8(bytes).unwrap_or_default()
141    }
142
143    /// Returns true if the glyph name was synthesized, i.e. not found in any
144    /// source.
145    pub fn is_synthesized(&self) -> bool {
146        self.is_synthesized
147    }
148
149    fn from_bytes(bytes: &[u8]) -> Self {
150        let mut name = Self::default();
151        name.append(bytes);
152        name
153    }
154
155    fn from_post(post: &Post, glyph_id: GlyphId) -> Option<Self> {
156        glyph_id
157            .try_into()
158            .ok()
159            .and_then(|id| post.glyph_name(id))
160            .map(|s| s.as_bytes())
161            .map(Self::from_bytes)
162    }
163
164    fn from_cff_sid(cff: &Cff, sid: Sid) -> Option<Self> {
165        cff.string(sid)
166            .and_then(|s| core::str::from_utf8(s).ok())
167            .map(|s| s.as_bytes())
168            .map(Self::from_bytes)
169    }
170
171    fn synthesize(glyph_id: GlyphId) -> Self {
172        use core::fmt::Write;
173        let mut name = Self {
174            is_synthesized: true,
175            ..Self::default()
176        };
177        let _ = write!(GlyphNameWrite(&mut name), "gid{}", glyph_id.to_u32());
178        name
179    }
180
181    /// Appends the given bytes to `self` while keeping the maximum length
182    /// at 63 bytes.
183    ///
184    /// This exists primarily to support the [`core::fmt::Write`] impl
185    /// (which is used for generating synthesized glyph names) because
186    /// we have no guarantee of how many times `write_str` might be called
187    /// for a given format.
188    fn append(&mut self, bytes: &[u8]) {
189        // We simply truncate when length exceeds the max since glyph names
190        // are expected to be <= 63 chars
191        let start = self.len as usize;
192        let available = MAX_GLYPH_NAME_LEN - start;
193        let copy_len = available.min(bytes.len());
194        self.name[start..start + copy_len].copy_from_slice(&bytes[..copy_len]);
195        self.len = (start + copy_len) as u8;
196    }
197}
198
199impl Default for GlyphName {
200    fn default() -> Self {
201        Self {
202            name: [0; MAX_GLYPH_NAME_LEN],
203            len: 0,
204            is_synthesized: false,
205        }
206    }
207}
208
209impl core::fmt::Debug for GlyphName {
210    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
211        f.debug_struct("GlyphName")
212            .field("name", &self.as_str())
213            .field("is_synthesized", &self.is_synthesized)
214            .finish()
215    }
216}
217
218impl core::fmt::Display for GlyphName {
219    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
220        write!(f, "{}", self.as_str())
221    }
222}
223
224impl core::ops::Deref for GlyphName {
225    type Target = str;
226
227    fn deref(&self) -> &Self::Target {
228        self.as_str()
229    }
230}
231
232impl PartialEq<&str> for GlyphName {
233    fn eq(&self, other: &&str) -> bool {
234        self.as_str() == *other
235    }
236}
237
238struct GlyphNameWrite<'a>(&'a mut GlyphName);
239
240impl core::fmt::Write for GlyphNameWrite<'_> {
241    fn write_str(&mut self, s: &str) -> core::fmt::Result {
242        self.0.append(s.as_bytes());
243        Ok(())
244    }
245}
246
247#[derive(Clone)]
248enum Iter<'a> {
249    Post(Range<u32>, post::GlyphNames<'a>),
250    Cff(Cff<'a>, CharsetIter<'a>),
251    Synthesized(Range<u32>),
252}
253
254impl Iter<'_> {
255    fn next_name(&mut self) -> Option<Result<(GlyphId, GlyphName), GlyphId>> {
256        match self {
257            Self::Post(range, iter) => {
258                let gid = GlyphId::new(range.next()?);
259                Some(
260                    iter.next()
261                        .map(|(_, name)| (gid, GlyphName::from_bytes(name.as_bytes())))
262                        .ok_or(gid),
263                )
264            }
265            Self::Cff(cff, iter) => {
266                let (gid, sid) = iter.next()?;
267                Some(
268                    GlyphName::from_cff_sid(cff, sid)
269                        .map(|name| (gid, name))
270                        .ok_or(gid),
271                )
272            }
273            Self::Synthesized(range) => {
274                let gid = GlyphId::new(range.next()?);
275                Some(Ok((gid, GlyphName::synthesize(gid))))
276            }
277        }
278    }
279}
280
281impl Iterator for Iter<'_> {
282    type Item = (GlyphId, GlyphName);
283
284    fn next(&mut self) -> Option<Self::Item> {
285        match self.next_name()? {
286            Ok((gid, name)) if name.is_empty() => Some((gid, GlyphName::synthesize(gid))),
287            Ok(gid_name) => Some(gid_name),
288            Err(gid) => Some((gid, GlyphName::synthesize(gid))),
289        }
290    }
291}
292
293#[cfg(test)]
294mod tests {
295    use super::*;
296    use raw::{FontData, FontRead};
297
298    #[test]
299    fn synthesized_glyph_names() {
300        let count = 58;
301        let names = GlyphNames {
302            inner: Inner::Synthesized(58),
303        };
304        let names_buf = (0..count).map(|i| format!("gid{i}")).collect::<Vec<_>>();
305        let expected_names = names_buf.iter().map(|s| s.as_str()).collect::<Vec<_>>();
306        for (_, name) in names.iter() {
307            assert!(name.is_synthesized())
308        }
309        check_names(&names, &expected_names, GlyphNameSource::Synthesized);
310    }
311
312    #[test]
313    fn synthesize_for_empty_names() {
314        let mut post_data = font_test_data::post::SIMPLE.to_vec();
315        // last name in this post data is "hola" so pop 5 bytes and then
316        // push a 0 to simulate an empty name
317        post_data.truncate(post_data.len() - 5);
318        post_data.push(0);
319        let post = Post::read(FontData::new(&post_data)).unwrap();
320        let gid = GlyphId::new(9);
321        assert!(post.glyph_name(gid.try_into().unwrap()).unwrap().is_empty());
322        let names = GlyphNames {
323            inner: Inner::Post(post, 10),
324        };
325        assert_eq!(names.get(gid).unwrap(), "gid9");
326        assert_eq!(names.iter().last().unwrap().1, "gid9");
327    }
328
329    #[test]
330    fn cff_glyph_names() {
331        let font = FontRef::new(font_test_data::NOTO_SERIF_DISPLAY_TRIMMED).unwrap();
332        let names = GlyphNames::new(&font);
333        assert_eq!(names.source(), GlyphNameSource::Cff);
334        let expected_names = [".notdef", "i", "j", "k", "l"];
335        check_names(&names, &expected_names, GlyphNameSource::Cff);
336    }
337
338    #[test]
339    fn post_glyph_names() {
340        let font = FontRef::new(font_test_data::HVAR_WITH_TRUNCATED_ADVANCE_INDEX_MAP).unwrap();
341        let names = GlyphNames::new(&font);
342        let expected_names = [
343            ".notdef",
344            "space",
345            "A",
346            "I",
347            "T",
348            "Aacute",
349            "Agrave",
350            "Iacute",
351            "Igrave",
352            "Amacron",
353            "Imacron",
354            "acutecomb",
355            "gravecomb",
356            "macroncomb",
357            "A.001",
358            "A.002",
359            "A.003",
360            "A.004",
361            "A.005",
362            "A.006",
363            "A.007",
364            "A.008",
365            "A.009",
366            "A.010",
367        ];
368        check_names(&names, &expected_names, GlyphNameSource::Post);
369    }
370
371    #[test]
372    fn post_glyph_names_partial() {
373        let font = FontRef::new(font_test_data::HVAR_WITH_TRUNCATED_ADVANCE_INDEX_MAP).unwrap();
374        let mut names = GlyphNames::new(&font);
375        let Inner::Post(_, len) = &mut names.inner else {
376            panic!("it's a post table!");
377        };
378        // Increase count by 4 so we synthesize the remaining names
379        *len += 4;
380        let expected_names = [
381            ".notdef",
382            "space",
383            "A",
384            "I",
385            "T",
386            "Aacute",
387            "Agrave",
388            "Iacute",
389            "Igrave",
390            "Amacron",
391            "Imacron",
392            "acutecomb",
393            "gravecomb",
394            "macroncomb",
395            "A.001",
396            "A.002",
397            "A.003",
398            "A.004",
399            "A.005",
400            "A.006",
401            "A.007",
402            "A.008",
403            "A.009",
404            "A.010",
405            // synthesized names...
406            "gid24",
407            "gid25",
408            "gid26",
409            "gid27",
410        ];
411        check_names(&names, &expected_names, GlyphNameSource::Post);
412    }
413
414    fn check_names(names: &GlyphNames, expected_names: &[&str], expected_source: GlyphNameSource) {
415        assert_eq!(names.source(), expected_source);
416        let iter_names = names.iter().collect::<Vec<_>>();
417        assert_eq!(iter_names.len(), expected_names.len());
418        for (i, expected) in expected_names.iter().enumerate() {
419            let gid = GlyphId::new(i as u32);
420            let name = names.get(gid).unwrap();
421            assert_eq!(name, expected);
422            assert_eq!(iter_names[i].0, gid);
423            assert_eq!(iter_names[i].1, expected);
424        }
425    }
426}