Skip to main content

fea_rs/common/
glyph_map.rs

1use write_fonts::tables::post::Post;
2
3use crate::compile::error::GlyphOrderError;
4
5use super::{GlyphId16, GlyphIdent};
6use fontdrasil::types::GlyphName;
7use std::{
8    borrow::Cow,
9    collections::{BTreeMap, HashMap},
10};
11
12/// A glyph map for mapping from raw glyph identifiers to numeral `GlyphId16`s.
13///
14/// This is used to map from names or CIDS encountered in a FEA file to the actual
15/// GlyphId16s that will be used in the final font.
16///
17/// Currently, the only way to construct this type is by calling `collect()`
18/// on an iterator of some type that impls `Into<GlyphIdent>` (such as `&str`,
19/// `GlyphName`, or `u16` (if using CIDs)).
20///
21/// ```
22/// # use fea_rs::GlyphMap;
23/// let myglyphs = GlyphMap::new(["a", "b", "gee", "whiz"]).unwrap();
24/// ```
25#[derive(Clone, Debug, Default, PartialEq)]
26#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
27pub struct GlyphMap {
28    names: HashMap<GlyphName, GlyphId16>,
29    cids: HashMap<u16, GlyphId16>,
30}
31
32impl GlyphMap {
33    /// Construct a new GlyphMap from an iterator of idents.
34    ///
35    /// Idents
36    pub fn new<T, I>(iter: I) -> Result<Self, GlyphOrderError>
37    where
38        T: Into<GlyphIdent>,
39        I: IntoIterator<Item = T>,
40    {
41        let mut names = HashMap::new();
42        let mut cids = HashMap::new();
43        for (idx, item) in iter.into_iter().enumerate() {
44            let idx = u16::try_from(idx)
45                .map(GlyphId16::new)
46                .map_err(|_| GlyphOrderError::TooManyGlyphs { found: idx as _ })?;
47            match item.into() {
48                GlyphIdent::Cid(cid) => cids.insert(cid, idx),
49                GlyphIdent::Name(name) => names.insert(name, idx),
50            };
51        }
52        Ok(GlyphMap { names, cids })
53    }
54    /// The total number of glyphs
55    pub fn len(&self) -> usize {
56        self.names.len() + self.cids.len()
57    }
58
59    /// Returns `true` if this map contains no glyphs
60    pub fn is_empty(&self) -> bool {
61        self.names.is_empty() && self.cids.is_empty()
62    }
63
64    /// Generates a reverse map of ids -> raw identifers (names or CIDs)
65    //  maybe just for testing?
66    pub fn reverse_map(&self) -> BTreeMap<GlyphId16, GlyphIdent> {
67        self.names
68            .iter()
69            .map(|(name, id)| (*id, GlyphIdent::Name(name.clone())))
70            .chain(
71                self.cids
72                    .iter()
73                    .map(|(cid, id)| (*id, GlyphIdent::Cid(*cid))),
74            )
75            .collect()
76    }
77
78    /// Iterate the idents in this map, in GID order.
79    ///
80    /// This is really only intended to be used to create new glyphmaps for testing.
81    pub fn iter(&self) -> impl Iterator<Item = GlyphIdent> + '_ {
82        self.reverse_map().into_values()
83    }
84
85    /// Return `true` if the map contains the provided `GlyphIdent`.
86    pub fn contains<Q: ?Sized + sealed::AsGlyphIdent>(&self, key: &Q) -> bool {
87        if let Some(name) = key.named() {
88            self.names.contains_key(name)
89        } else if let Some(cid) = key.cid() {
90            self.cids.contains_key(cid)
91        } else {
92            unreachable!()
93        }
94    }
95
96    /// Return the `GlyphId16` for the provided `GlyphIdent`
97    pub fn get<Q: ?Sized + sealed::AsGlyphIdent>(&self, key: &Q) -> Option<GlyphId16> {
98        if let Some(name) = key.named() {
99            self.names.get(name).copied()
100        } else if let Some(cid) = key.cid() {
101            self.cids.get(cid).copied()
102        } else {
103            unreachable!()
104        }
105    }
106
107    /// Generate a post table from this glyph map
108    pub fn make_post_table(&self) -> Post {
109        let reverse = self.reverse_map();
110        let rev_vec = reverse
111            .values()
112            .map(|val| match val {
113                GlyphIdent::Name(s) => Cow::Borrowed(s.as_str()),
114                GlyphIdent::Cid(cid) => Cow::Owned(format!("cid{:05}", *cid)),
115            })
116            .collect::<Vec<_>>();
117
118        Post::new_v2(rev_vec.iter().map(Cow::as_ref))
119    }
120}
121
122mod sealed {
123    use super::super::GlyphIdent;
124    use fontdrasil::types::GlyphName;
125    use smol_str::SmolStr;
126
127    /// Something that is either a Cid or a glyph name.
128    ///
129    /// This is only implemented internally.
130    ///
131    /// Invariant: an implementor must return `Some` from exactly one of these
132    /// two methods.
133    pub trait AsGlyphIdent {
134        fn named(&self) -> Option<&str> {
135            None
136        }
137
138        fn cid(&self) -> Option<&u16> {
139            None
140        }
141    }
142
143    impl AsGlyphIdent for str {
144        fn named(&self) -> Option<&str> {
145            Some(self)
146        }
147    }
148
149    impl AsGlyphIdent for SmolStr {
150        fn named(&self) -> Option<&str> {
151            Some(self.as_str())
152        }
153    }
154
155    impl AsGlyphIdent for GlyphName {
156        fn named(&self) -> Option<&str> {
157            Some(self.as_str())
158        }
159    }
160
161    impl AsGlyphIdent for u16 {
162        fn cid(&self) -> Option<&u16> {
163            Some(self)
164        }
165    }
166
167    impl AsGlyphIdent for GlyphIdent {
168        fn named(&self) -> Option<&str> {
169            if let GlyphIdent::Name(name) = self {
170                Some(name.as_str())
171            } else {
172                None
173            }
174        }
175
176        fn cid(&self) -> Option<&u16> {
177            if let GlyphIdent::Cid(cid) = self {
178                Some(cid)
179            } else {
180                None
181            }
182        }
183    }
184}