skrifa/
glyph_name.rs

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