Skip to main content

multi_mono_font/
mapping.rs

1//! Glyph mapping.
2//!
3//! A glyph mapping defines the position of characters in a [`MultiMonoFont`] 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//!
12//! # `StrGlyphMapping` encoding
13//!
14//! Strings without a `\0` character can be used to directly map a character to its position in
15//! the mapping string:
16//!
17//! ```
18//! use multi_mono_font::mapping::StrGlyphMapping;
19//!
20//! let mapping = StrGlyphMapping::new("abcdef1234", 0);
21//! assert_eq!(mapping.index('a'), 0);
22//! assert_eq!(mapping.index('b'), 1);
23//! assert_eq!(mapping.index('1'), 6);
24//! assert_eq!(mapping.index('2'), 7);
25//! ```
26//!
27//! This direct mapping is inefficient for mappings that map consecutive ranges of characters to
28//! consecutive index ranges. To define a range of characters a `\0` character followed by the
29//! start and end characters of the inclusive range can be used. This way the mapping in the previous
30//! example can be abbreviated to:
31//!
32//! ```
33//! use multi_mono_font::mapping::StrGlyphMapping;
34//!
35//! let mapping = StrGlyphMapping::new("\0af\014", 0);
36//! assert_eq!(mapping.index('a'), 0);
37//! assert_eq!(mapping.index('b'), 1);
38//! assert_eq!(mapping.index('1'), 6);
39//! assert_eq!(mapping.index('2'), 7);
40//! ```
41//!
42//! [`MultiMonoFont`]: super::MultiMonoFont
43
44use core::ops::RangeInclusive;
45
46/// Glyph mapping stored as a UTF-8 string.
47///
48/// See the [module-level documentation] for more details.
49///
50/// [module-level documentation]: self
51#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
52pub struct StrGlyphMapping<'a> {
53    data: &'a str,
54    replacement_index: usize,
55}
56
57impl<'a> StrGlyphMapping<'a> {
58    /// Creates a new glyph mapping.
59    pub const fn new(data: &'a str, replacement_index: usize) -> Self {
60        Self {
61            data,
62            replacement_index,
63        }
64    }
65
66    /// Returns an iterator over the character ranges.
67    pub fn ranges(&self) -> impl Iterator<Item = (usize, RangeInclusive<char>)> + '_ {
68        let mut chars = self.data.chars();
69        let mut index = 0;
70
71        core::iter::from_fn(move || {
72            let start_index = index;
73
74            let range = match chars.next()? {
75                '\0' => {
76                    let start = chars.next()?;
77                    let end = chars.next()?;
78
79                    index += end as usize - start as usize + 1;
80
81                    start..=end
82                }
83                c => {
84                    index += 1;
85
86                    c..=c
87                }
88            };
89
90            Some((start_index, range))
91        })
92    }
93
94    /// Returns an iterator over the characters in this mapping.
95    pub fn chars(&self) -> impl Iterator<Item = char> + '_ {
96        let mut chars = self.data.chars();
97
98        core::iter::from_fn(move || {
99            let range = match chars.next()? {
100                '\0' => {
101                    let start = chars.next()?;
102                    let end = chars.next()?;
103
104                    start..=end
105                }
106                c => c..=c,
107            };
108
109            Some(range)
110        })
111        .flatten()
112    }
113
114    /// Returns if the mapping contains the given char.
115    pub fn contains(&self, c: char) -> bool {
116        self.chars().any(|v| v == c)
117    }
118
119    pub fn index(&self, c: char) -> usize {
120        // PERF: use ranges instead of chars iter
121        self.chars()
122            .enumerate()
123            .find(|(_, v)| c == *v)
124            .map(|(index, _)| index)
125            .unwrap_or(self.replacement_index)
126    }
127}
128
129macro_rules! impl_mapping {
130    ($( $(#[$meta:meta])* ($enum_variant:ident, $constant:ident, $mapping:expr), )*) => {
131        /// Mapping.
132        ///
133        /// This enum lists all mappings that are included in embedded-graphics. It is used
134        /// to automatically generate font data for all mappings and isn't normally used in
135        /// applications.
136        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
137        pub enum Mapping {
138            $(
139                $(#[$meta])*
140                $enum_variant,
141            )*
142        }
143
144        impl Mapping {
145            /// Returns an iterator over all mappings.
146            pub fn iter() -> impl Iterator<Item = Self> {
147                const ALL: &[Mapping] = &[$(Mapping::$enum_variant),*];
148
149                ALL.iter().copied()
150            }
151
152            /// Returns the MIME identifier for this mapping.
153            pub const fn mime(self) -> &'static str {
154                match self {
155                    $(Mapping::$enum_variant => stringify!($constant)),*
156                }
157            }
158
159            /// Returns a glyph mapping for this mapping.
160            pub const fn glyph_mapping(self) -> &'static StrGlyphMapping<'static> {
161                match self {
162                    $(Self::$enum_variant => &$constant),*
163                }
164            }
165        }
166
167        $(
168            $(#[$meta])*
169            pub const $constant: StrGlyphMapping = StrGlyphMapping::new($mapping, '?' as usize - ' ' as usize);
170        )*
171    };
172}
173
174impl_mapping!(
175    /// ASCII.
176    (Ascii, ASCII, "\0\u{20}\u{7f}"),
177
178    /// ISO/IEC 8859 Part 10: Latin-6, Nordic.
179    (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}"),
180);