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