embedded_graphics/mono_font/
mapping.rs

1//! Glyph mapping.
2//!
3//! A glyph mapping defines the position of characters in a [`MonoFont`] image. This module provides
4//! predefined mappings for common glyph subsets, but custom mappings are also supported.
5//!
6//! # Custom mappings
7//!
8//! Custom mappings can be defined in three different ways:
9//! * The [`StrGlyphMapping`] type can be used to specify a character mapping by encoding the
10//!   mapping as a string.
11//! * The [`GlyphMapping`] trait is implemented for all functions `Fn(char) -> usize`.
12//! * The [`GlyphMapping`] trait can be implemented by a custom type.
13//!
14//! # `StrGlyphMapping` encoding
15//!
16//! Strings without a `\0` character can be used to directly map a character to its position in
17//! the mapping string:
18//!
19//! ```
20//! use embedded_graphics::mono_font::mapping::{GlyphMapping, StrGlyphMapping};
21//!
22//! let mapping = StrGlyphMapping::new("abcdef1234", 0);
23//! assert_eq!(mapping.index('a'), 0);
24//! assert_eq!(mapping.index('b'), 1);
25//! assert_eq!(mapping.index('1'), 6);
26//! assert_eq!(mapping.index('2'), 7);
27//! ```
28//!
29//! This direct mapping is inefficient for mappings that map consecutive ranges of characters to
30//! consecutive index ranges. To define a range of characters a `\0` character followed by the
31//! start and end characters of the inclusive range can be used. This way the mapping in the previous
32//! example can be abbreviated to:
33//!
34//! ```
35//! use embedded_graphics::mono_font::mapping::{GlyphMapping, StrGlyphMapping};
36//!
37//! let mapping = StrGlyphMapping::new("\0af\014", 0);
38//! assert_eq!(mapping.index('a'), 0);
39//! assert_eq!(mapping.index('b'), 1);
40//! assert_eq!(mapping.index('1'), 6);
41//! assert_eq!(mapping.index('2'), 7);
42//! ```
43//!
44//! [`MonoFont`]: super::MonoFont
45
46use core::ops::RangeInclusive;
47
48/// Mapping from characters to glyph indices.
49pub trait GlyphMapping: Sync {
50    /// Maps a character to a glyph index.
51    ///
52    /// If `c` isn't included in the font the index of a suitable replacement glyph is returned.
53    fn index(&self, c: char) -> usize;
54}
55
56impl<F> GlyphMapping for F
57where
58    F: Sync + Fn(char) -> usize,
59{
60    fn index(&self, c: char) -> usize {
61        self(c)
62    }
63}
64
65/// Glyph mapping stored as a UTF-8 string.
66///
67/// See the [module-level documentation] for more details.
68///
69/// [module-level documentation]: self
70#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
71pub struct StrGlyphMapping<'a> {
72    data: &'a str,
73    replacement_index: usize,
74}
75
76impl<'a> StrGlyphMapping<'a> {
77    /// Creates a new glyph mapping.
78    pub const fn new(data: &'a str, replacement_index: usize) -> Self {
79        Self {
80            data,
81            replacement_index,
82        }
83    }
84
85    /// Returns an iterator over the character ranges.
86    pub fn ranges(&self) -> impl Iterator<Item = (usize, RangeInclusive<char>)> + '_ {
87        let mut chars = self.data.chars();
88        let mut index = 0;
89
90        core::iter::from_fn(move || {
91            let start_index = index;
92
93            let range = match chars.next()? {
94                '\0' => {
95                    let start = chars.next()?;
96                    let end = chars.next()?;
97
98                    index += end as usize - start as usize + 1;
99
100                    start..=end
101                }
102                c => {
103                    index += 1;
104
105                    c..=c
106                }
107            };
108
109            Some((start_index, range))
110        })
111    }
112
113    /// Returns an iterator over the characters in this mapping.
114    pub fn chars(&self) -> impl Iterator<Item = char> + '_ {
115        let mut chars = self.data.chars();
116
117        core::iter::from_fn(move || {
118            let range = match chars.next()? {
119                '\0' => {
120                    let start = chars.next()?;
121                    let end = chars.next()?;
122
123                    start..=end
124                }
125                c => c..=c,
126            };
127
128            Some(range)
129        })
130        .flatten()
131    }
132
133    /// Returns if the mapping contains the given char.
134    pub fn contains(&self, c: char) -> bool {
135        self.chars().any(|v| v == c)
136    }
137}
138
139impl GlyphMapping for StrGlyphMapping<'_> {
140    fn index(&self, c: char) -> usize {
141        // PERF: use ranges instead of chars iter
142        self.chars()
143            .enumerate()
144            .find(|(_, v)| c == *v)
145            .map(|(index, _)| index)
146            .unwrap_or(self.replacement_index)
147    }
148}
149
150macro_rules! impl_mapping {
151    ($( $(#[$meta:meta])* ($enum_variant:ident, $constant:ident, $mapping:expr), )*) => {
152        /// Mapping.
153        ///
154        /// This enum lists all mappings that are included in embedded-graphics. It is used
155        /// to automatically generate font data for all mappings and isn't normally used in
156        /// applications.
157        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
158        pub enum Mapping {
159            $(
160                $(#[$meta])*
161                $enum_variant,
162            )*
163        }
164
165        impl Mapping {
166            /// Returns an iterator over all mappings.
167            pub fn iter() -> impl Iterator<Item = Self> {
168                const ALL: &[Mapping] = &[$(Mapping::$enum_variant),*];
169
170                ALL.iter().copied()
171            }
172
173            /// Returns the MIME identifier for this mapping.
174            pub const fn mime(self) -> &'static str {
175                match self {
176                    $(Mapping::$enum_variant => stringify!($constant)),*
177                }
178            }
179
180            /// Returns a glyph mapping for this mapping.
181            pub const fn glyph_mapping(self) -> &'static StrGlyphMapping<'static> {
182                match self {
183                    $(Self::$enum_variant => &$constant),*
184                }
185            }
186        }
187
188        $(
189            $(#[$meta])*
190            pub const $constant: StrGlyphMapping = StrGlyphMapping::new($mapping, '?' as usize - ' ' as usize);
191        )*
192    };
193}
194
195// TODO: Add Iso8859_6 (Latin/Arabic), Iso8859_8 (Latin/Hebrew) and Iso8859_11 (Latin/Thai) when we support RTL and combining characters.
196impl_mapping!(
197    /// ASCII.
198    (Ascii, ASCII, "\0\u{20}\u{7f}"),
199
200    /// ISO/IEC 8859 Part 1: Latin-1, Western European.
201    (Iso8859_1, ISO_8859_1, "\0\u{20}\u{7f}\0\u{a0}\u{ff}"),
202
203    /// ISO/IEC 8859 Part 2: Latin-2, Central European.
204    (Iso8859_2, ISO_8859_2, "\0\u{20}\u{7f}\u{a0}\u{104}\u{2d8}\u{141}\u{a4}\u{13d}\u{15a}\u{a7}\u{a8}\u{160}\u{15e}\u{164}\u{179}\u{ad}\u{17d}\u{17b}\u{b0}\u{105}\u{2db}\u{142}\u{b4}\u{13e}\u{15b}\u{2c7}\u{b8}\u{161}\u{15f}\u{165}\u{17a}\u{2dd}\u{17e}\u{17c}\u{154}\u{c1}\u{c2}\u{102}\u{c4}\u{139}\u{106}\u{c7}\u{10c}\u{c9}\u{118}\u{cb}\u{11a}\u{cd}\u{ce}\u{10e}\u{110}\u{143}\u{147}\u{d3}\u{d4}\u{150}\u{d6}\u{d7}\u{158}\u{16e}\u{da}\u{170}\u{dc}\u{dd}\u{162}\u{df}\u{155}\u{e1}\u{e2}\u{103}\u{e4}\u{13a}\u{107}\u{e7}\u{10d}\u{e9}\u{119}\u{eb}\u{11b}\u{ed}\u{ee}\u{10f}\u{111}\u{144}\u{148}\u{f3}\u{f4}\u{151}\u{f6}\u{f7}\u{159}\u{16f}\u{fa}\u{171}\u{fc}\u{fd}\u{163}\u{2d9}"),
205
206    /// ISO/IEC 8859 Part 3: Latin-3, South European.
207    (Iso8859_3, ISO_8859_3, "\0\u{20}\u{7f}\u{a0}\u{126}\u{2d8}\0\u{a3}\u{a5}\u{124}\u{a7}\u{a8}\u{130}\u{15e}\u{11e}\u{134}\u{ad}\u{ae}\u{17b}\u{b0}\u{127}\0\u{b2}\u{b5}\u{125}\u{b7}\u{b8}\u{131}\u{15f}\u{11f}\u{135}\u{bd}\u{be}\u{17c}\0\u{c0}\u{c4}\u{10a}\u{108}\0\u{c7}\u{d4}\u{120}\u{d6}\u{d7}\u{11c}\0\u{d9}\u{dc}\u{16c}\u{15c}\0\u{df}\u{e4}\u{10b}\u{109}\0\u{e7}\u{f4}\u{121}\u{f6}\u{f7}\u{11d}\0\u{f9}\u{fc}\u{16d}\u{15d}\u{2d9}"),
208
209    /// ISO/IEC 8859 Part 4: Latin-4, North European.
210    (Iso8859_4, ISO_8859_4, "\0\u{20}\u{7f}\u{a0}\u{104}\u{138}\u{156}\u{a4}\u{128}\u{13b}\u{a7}\u{a8}\u{160}\u{112}\u{122}\u{166}\u{ad}\u{17d}\u{af}\u{b0}\u{105}\u{2db}\u{157}\u{b4}\u{129}\u{13c}\u{2c7}\u{b8}\u{161}\u{113}\u{123}\u{167}\u{14a}\u{17e}\u{14b}\u{100}\0\u{c1}\u{c6}\u{12e}\u{10c}\u{c9}\u{118}\u{cb}\u{116}\u{cd}\u{ce}\u{12a}\u{110}\u{145}\u{14c}\u{136}\0\u{d4}\u{d8}\u{172}\0\u{da}\u{dc}\u{168}\u{16a}\u{df}\u{101}\0\u{e1}\u{e6}\u{12f}\u{10d}\u{e9}\u{119}\u{eb}\u{117}\u{ed}\u{ee}\u{12b}\u{111}\u{146}\u{14d}\u{137}\0\u{f4}\u{f8}\u{173}\0\u{fa}\u{fc}\u{169}\u{16b}\u{2d9}"),
211
212    /// ISO/IEC 8859 Part 5: Latin/Cyrillic.
213    (Iso8859_5, ISO_8859_5, "\0\u{20}\u{7f}\u{a0}\0\u{401}\u{40c}\u{ad}\0\u{40e}\u{44f}\u{2116}\0\u{451}\u{45c}\u{a7}\u{45e}\u{45f}"),
214
215    /// ISO/IEC 8859 Part 7: Latin/Greek.
216    (Iso8859_7, ISO_8859_7, "\0\u{20}\u{7f}\u{a0}\u{2018}\u{2019}\u{a3}\u{20ac}\u{20af}\0\u{a6}\u{a9}\u{37a}\0\u{ab}\u{ae}\u{2015}\0\u{b0}\u{b3}\0\u{384}\u{386}\u{b7}\0\u{388}\u{38a}\u{bb}\u{38c}\u{bd}\0\u{38e}\u{3cf}"),
217
218    /// ISO/IEC 8859 Part 9: Latin-5, Turkish.
219    (Iso8859_9, ISO_8859_9, "\0\u{20}\u{7f}\0\u{a0}\u{cf}\u{11e}\0\u{d1}\u{dc}\u{130}\u{15e}\0\u{df}\u{ef}\u{11f}\0\u{f1}\u{fc}\u{131}\u{15f}\u{ff}"),
220
221    /// ISO/IEC 8859 Part 10: Latin-6, Nordic.
222    (Iso8859_10, ISO_8859_10, "\0\u{20}\u{7f}\u{a0}\u{104}\u{112}\u{122}\u{12a}\u{128}\u{136}\u{a7}\u{13b}\u{110}\u{160}\u{166}\u{17d}\u{ad}\u{16a}\u{14a}\u{b0}\u{105}\u{113}\u{123}\u{12b}\u{129}\u{137}\u{b7}\u{13c}\u{111}\u{161}\u{167}\u{17e}\u{2015}\u{16b}\u{14b}\u{100}\0\u{c1}\u{c6}\u{12e}\u{10c}\u{c9}\u{118}\u{cb}\u{116}\0\u{cd}\u{d0}\u{145}\u{14c}\0\u{d3}\u{d6}\u{168}\u{d8}\u{172}\0\u{da}\u{df}\u{101}\0\u{e1}\u{e6}\u{12f}\u{10d}\u{e9}\u{119}\u{eb}\u{117}\0\u{ed}\u{f0}\u{146}\u{14d}\0\u{f3}\u{f6}\u{169}\u{f8}\u{173}\0\u{fa}\u{fe}\u{138}"),
223
224    /// ISO/IEC 8859 Part 13: Latin-7, Baltic Rim.
225    (Iso8859_13, ISO_8859_13, "\0\u{20}\u{7f}\u{a0}\u{201d}\0\u{a2}\u{a4}\u{201e}\u{a6}\u{a7}\u{d8}\u{a9}\u{156}\0\u{ab}\u{ae}\u{c6}\0\u{b0}\u{b3}\u{201c}\0\u{b5}\u{b7}\u{f8}\u{b9}\u{157}\0\u{bb}\u{be}\u{e6}\u{104}\u{12e}\u{100}\u{106}\u{c4}\u{c5}\u{118}\u{112}\u{10c}\u{c9}\u{179}\u{116}\u{122}\u{136}\u{12a}\u{13b}\u{160}\u{143}\u{145}\u{d3}\u{14c}\0\u{d5}\u{d7}\u{172}\u{141}\u{15a}\u{16a}\u{dc}\u{17b}\u{17d}\u{df}\u{105}\u{12f}\u{101}\u{107}\u{e4}\u{e5}\u{119}\u{113}\u{10d}\u{e9}\u{17a}\u{117}\u{123}\u{137}\u{12b}\u{13c}\u{161}\u{144}\u{146}\u{f3}\u{14d}\0\u{f5}\u{f7}\u{173}\u{142}\u{15b}\u{16b}\u{fc}\u{17c}\u{17e}\u{2019}"),
226
227    /// ISO/IEC 8859 Part 14: Latin-8, Celtic.
228    (Iso8859_14, ISO_8859_14, "\0\u{20}\u{7f}\u{a0}\u{1e02}\u{1e03}\u{a3}\u{10a}\u{10b}\u{1e0a}\u{a7}\u{1e80}\u{a9}\u{1e82}\u{1e0b}\u{1ef2}\u{ad}\u{ae}\u{178}\u{1e1e}\u{1e1f}\u{120}\u{121}\u{1e40}\u{1e41}\u{b6}\u{1e56}\u{1e81}\u{1e57}\u{1e83}\u{1e60}\u{1ef3}\u{1e84}\u{1e85}\u{1e61}\0\u{c0}\u{cf}\u{174}\0\u{d1}\u{d6}\u{1e6a}\0\u{d8}\u{dd}\u{176}\0\u{df}\u{ef}\u{175}\0\u{f1}\u{f6}\u{1e6b}\0\u{f8}\u{fd}\u{177}\u{ff}"),
229
230    /// ISO/IEC 8859 Part 15: Latin-9 (revised Latin-1).
231    (Iso8859_15, ISO_8859_15, "\0\u{20}\u{7f}\0\u{a0}\u{a3}\u{20ac}\u{a5}\u{160}\u{a7}\u{161}\0\u{a9}\u{b3}\u{17d}\0\u{b5}\u{b7}\u{17e}\0\u{b9}\u{bb}\u{152}\u{153}\u{178}\0\u{bf}\u{ff}"),
232
233    /// ISO/IEC 8859 Part 16: Latin-10: South-East European.
234    (Iso8859_16, ISO_8859_16, "\0\u{20}\u{7f}\u{a0}\u{104}\u{105}\u{141}\u{20ac}\u{201e}\u{160}\u{a7}\u{161}\u{a9}\u{218}\u{ab}\u{179}\u{ad}\u{17a}\u{17b}\u{b0}\u{b1}\u{10c}\u{142}\u{17d}\u{201d}\u{b6}\u{b7}\u{17e}\u{10d}\u{219}\u{bb}\u{152}\u{153}\u{178}\u{17c}\0\u{c0}\u{c2}\u{102}\u{c4}\u{106}\0\u{c6}\u{cf}\u{110}\u{143}\0\u{d2}\u{d4}\u{150}\u{d6}\u{15a}\u{170}\0\u{d9}\u{dc}\u{118}\u{21a}\0\u{df}\u{e2}\u{103}\u{e4}\u{107}\0\u{e6}\u{ef}\u{111}\u{144}\0\u{f2}\u{f4}\u{151}\u{f6}\u{15b}\u{171}\0\u{f9}\u{fc}\u{119}\u{21b}\u{ff}"),
235
236    /// JIS X 0201: Japanese katakana (halfwidth).
237    (JisX0201, JIS_X0201, "\0\u{20}\u{7f}\0\u{ff60}\u{ff9f}"),
238);
239
240#[cfg(test)]
241mod tests {
242    use super::*;
243
244    #[test]
245    fn empty() {
246        let mapping = StrGlyphMapping::new("", 0);
247
248        let mut iter = mapping.ranges();
249        assert_eq!(iter.next(), None);
250
251        let mut iter = mapping.chars();
252        assert_eq!(iter.next(), None);
253    }
254
255    #[test]
256    fn one_char() {
257        let mapping = StrGlyphMapping::new("a", 0);
258
259        let mut iter = mapping.ranges();
260        assert_eq!(iter.next(), Some((0, 'a'..='a')));
261        assert_eq!(iter.next(), None);
262
263        let mut iter = mapping.chars();
264        assert_eq!(iter.next(), Some('a'));
265        assert_eq!(iter.next(), None);
266
267        assert_eq!(mapping.index('a'), 0);
268    }
269
270    #[test]
271    fn three_chars() {
272        let mapping = StrGlyphMapping::new("abc", 1);
273
274        let mut iter = mapping.ranges();
275        assert_eq!(iter.next(), Some((0, 'a'..='a')));
276        assert_eq!(iter.next(), Some((1, 'b'..='b')));
277        assert_eq!(iter.next(), Some((2, 'c'..='c')));
278        assert_eq!(iter.next(), None);
279
280        let mut iter = mapping.chars();
281        assert_eq!(iter.next(), Some('a'));
282        assert_eq!(iter.next(), Some('b'));
283        assert_eq!(iter.next(), Some('c'));
284        assert_eq!(iter.next(), None);
285
286        assert_eq!(mapping.index('a'), 0);
287        assert_eq!(mapping.index('b'), 1);
288        assert_eq!(mapping.index('c'), 2);
289        assert_eq!(mapping.index('$'), 1);
290    }
291
292    #[test]
293    fn one_range() {
294        let mapping = StrGlyphMapping::new("\x00ac", 2);
295
296        let mut iter = mapping.ranges();
297        assert_eq!(iter.next(), Some((0, 'a'..='c')));
298        assert_eq!(iter.next(), None);
299
300        let mut iter = mapping.chars();
301        assert_eq!(iter.next(), Some('a'));
302        assert_eq!(iter.next(), Some('b'));
303        assert_eq!(iter.next(), Some('c'));
304        assert_eq!(iter.next(), None);
305
306        assert_eq!(mapping.index('a'), 0);
307        assert_eq!(mapping.index('b'), 1);
308        assert_eq!(mapping.index('c'), 2);
309        assert_eq!(mapping.index('$'), 2);
310    }
311
312    #[test]
313    fn incomplete_range() {
314        let mapping = StrGlyphMapping::new("\x00a", 0);
315
316        let mut iter = mapping.ranges();
317        assert_eq!(iter.next(), None);
318
319        let mut iter = mapping.chars();
320        assert_eq!(iter.next(), None);
321    }
322
323    #[test]
324    fn mixed_ranges_and_chars() {
325        let mapping = StrGlyphMapping::new("a\x00bde", 3);
326
327        let mut iter = mapping.ranges();
328        assert_eq!(iter.next(), Some((0, 'a'..='a')));
329        assert_eq!(iter.next(), Some((1, 'b'..='d')));
330        assert_eq!(iter.next(), Some((4, 'e'..='e')));
331        assert_eq!(iter.next(), None);
332
333        let mut iter = mapping.chars();
334        assert_eq!(iter.next(), Some('a'));
335        assert_eq!(iter.next(), Some('b'));
336        assert_eq!(iter.next(), Some('c'));
337        assert_eq!(iter.next(), Some('d'));
338        assert_eq!(iter.next(), Some('e'));
339        assert_eq!(iter.next(), None);
340
341        assert_eq!(mapping.index('a'), 0);
342        assert_eq!(mapping.index('b'), 1);
343        assert_eq!(mapping.index('c'), 2);
344        assert_eq!(mapping.index('d'), 3);
345        assert_eq!(mapping.index('e'), 4);
346        assert_eq!(mapping.index('$'), 3);
347    }
348
349    #[test]
350    fn dyn_str_glyph_mapping() {
351        let mapping = StrGlyphMapping::new("ab", 0);
352        let dyn_mapping: &dyn GlyphMapping = &mapping;
353
354        assert_eq!(dyn_mapping.index('b'), 1);
355    }
356
357    #[test]
358    fn dyn_fn_glyph_mapping() {
359        fn map(c: char) -> usize {
360            match c {
361                'a' => 0,
362                'b' => 1,
363                _ => 2,
364            }
365        }
366
367        let dyn_mapping: &dyn GlyphMapping = &map;
368
369        assert_eq!(dyn_mapping.index('a'), 0);
370        assert_eq!(dyn_mapping.index('b'), 1);
371        assert_eq!(dyn_mapping.index('?'), 2);
372    }
373}