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 | |
46 | use core::ops::RangeInclusive; |
47 | |
48 | /// Mapping from characters to glyph indices. |
49 | pub 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 | |
56 | impl<F> GlyphMapping for F |
57 | where |
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)] |
71 | pub struct StrGlyphMapping<'a> { |
72 | data: &'a str, |
73 | replacement_index: usize, |
74 | } |
75 | |
76 | impl<'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 | |
139 | impl 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 | |
150 | macro_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. |
196 | impl_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)] |
241 | mod 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 = ↦ |
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 | } |
374 | |