fea_rs/common/
glyph_map.rs

1use write_fonts::tables::post::Post;
2
3use super::{GlyphId16, GlyphIdent};
4use fontdrasil::types::GlyphName;
5use std::{
6    borrow::Cow,
7    collections::{BTreeMap, HashMap},
8    convert::TryInto,
9    iter::FromIterator,
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::from_iter(["a", "b", "gee", "whiz"]);
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    /// The total number of glyphs
34    pub fn len(&self) -> usize {
35        self.names.len() + self.cids.len()
36    }
37
38    /// Returns `true` if this map contains no glyphs
39    pub fn is_empty(&self) -> bool {
40        self.names.is_empty() && self.cids.is_empty()
41    }
42
43    /// Generates a reverse map of ids -> raw identifers (names or CIDs)
44    //  maybe just for testing?
45    pub fn reverse_map(&self) -> BTreeMap<GlyphId16, GlyphIdent> {
46        self.names
47            .iter()
48            .map(|(name, id)| (*id, GlyphIdent::Name(name.clone())))
49            .chain(
50                self.cids
51                    .iter()
52                    .map(|(cid, id)| (*id, GlyphIdent::Cid(*cid))),
53            )
54            .collect()
55    }
56
57    /// Iterate the idents in this map, in GID order.
58    ///
59    /// This is really only intended to be used to create new glyphmaps for testing.
60    pub fn iter(&self) -> impl Iterator<Item = GlyphIdent> + '_ {
61        self.reverse_map().into_values()
62    }
63
64    /// Return `true` if the map contains the provided `GlyphIdent`.
65    pub fn contains<Q: ?Sized + sealed::AsGlyphIdent>(&self, key: &Q) -> bool {
66        if let Some(name) = key.named() {
67            self.names.contains_key(name)
68        } else if let Some(cid) = key.cid() {
69            self.cids.contains_key(cid)
70        } else {
71            unreachable!()
72        }
73    }
74
75    /// Return the `GlyphId16` for the provided `GlyphIdent`
76    pub fn get<Q: ?Sized + sealed::AsGlyphIdent>(&self, key: &Q) -> Option<GlyphId16> {
77        if let Some(name) = key.named() {
78            self.names.get(name).copied()
79        } else if let Some(cid) = key.cid() {
80            self.cids.get(cid).copied()
81        } else {
82            unreachable!()
83        }
84    }
85
86    /// Generate a post table from this glyph map
87    pub fn make_post_table(&self) -> Post {
88        let reverse = self.reverse_map();
89        let rev_vec = reverse
90            .values()
91            .map(|val| match val {
92                GlyphIdent::Name(s) => Cow::Borrowed(s.as_str()),
93                GlyphIdent::Cid(cid) => Cow::Owned(format!("cid{:05}", *cid)),
94            })
95            .collect::<Vec<_>>();
96
97        Post::new_v2(rev_vec.iter().map(Cow::as_ref))
98    }
99}
100
101impl<T: Into<GlyphIdent>> FromIterator<T> for GlyphMap {
102    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
103        let mut names = HashMap::new();
104        let mut cids = HashMap::new();
105        for (idx, item) in iter.into_iter().enumerate() {
106            let idx = GlyphId16::new(idx.try_into().unwrap());
107            match item.into() {
108                GlyphIdent::Cid(cid) => cids.insert(cid, idx),
109                GlyphIdent::Name(name) => names.insert(name, idx),
110            };
111        }
112        GlyphMap { names, cids }
113    }
114}
115
116mod sealed {
117    use super::super::GlyphIdent;
118    use fontdrasil::types::GlyphName;
119    use smol_str::SmolStr;
120
121    /// Something that is either a Cid or a glyph name.
122    ///
123    /// This is only implemented internally.
124    ///
125    /// Invariant: an implementor must return `Some` from exactly one of these
126    /// two methods.
127    pub trait AsGlyphIdent {
128        fn named(&self) -> Option<&str> {
129            None
130        }
131
132        fn cid(&self) -> Option<&u16> {
133            None
134        }
135    }
136
137    impl AsGlyphIdent for str {
138        fn named(&self) -> Option<&str> {
139            Some(self)
140        }
141    }
142
143    impl AsGlyphIdent for SmolStr {
144        fn named(&self) -> Option<&str> {
145            Some(self.as_str())
146        }
147    }
148
149    impl AsGlyphIdent for GlyphName {
150        fn named(&self) -> Option<&str> {
151            Some(self.as_str())
152        }
153    }
154
155    impl AsGlyphIdent for u16 {
156        fn cid(&self) -> Option<&u16> {
157            Some(self)
158        }
159    }
160
161    impl AsGlyphIdent for GlyphIdent {
162        fn named(&self) -> Option<&str> {
163            if let GlyphIdent::Name(name) = self {
164                Some(name.as_str())
165            } else {
166                None
167            }
168        }
169
170        fn cid(&self) -> Option<&u16> {
171            if let GlyphIdent::Cid(cid) = self {
172                Some(cid)
173            } else {
174                None
175            }
176        }
177    }
178}