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);