1use super::{
3 color::{BackgroundColor, StrokeColor},
4 FrameLine, PixelColor, PixelFrame,
5};
6
7use super::error::ScreenError;
8pub use font8x8::{
9 FontUnicode, UnicodeFonts, BASIC_FONTS, BLOCK_FONTS, BOX_FONTS, GREEK_FONTS, HIRAGANA_FONTS,
10 LATIN_FONTS,
11};
12use std::collections::HashMap;
13use std::fmt;
14
15lazy_static! {
16 pub static ref FONT_HASHMAP: HashMap<char, FontUnicode> = default_hashmap();
19 pub static ref FONT_COLLECTION: FontCollection = FontCollection(default_hashmap());
28}
29
30fn default_hashmap() -> HashMap<char, FontUnicode> {
31 BASIC_FONTS
32 .iter()
33 .chain(LATIN_FONTS.iter())
34 .chain(BLOCK_FONTS.iter())
35 .chain(BOX_FONTS.iter())
36 .chain(GREEK_FONTS.iter())
37 .chain(HIRAGANA_FONTS.iter())
38 .map(|x| (x.0, *x))
39 .collect()
40}
41
42#[derive(Clone, Debug, PartialEq)]
44pub struct FontCollection(HashMap<char, FontUnicode>);
46
47impl FontCollection {
48 pub fn new() -> Self {
52 FontCollection(default_hashmap())
53 }
54
55 pub fn from_hashmap(hashmap: HashMap<char, FontUnicode>) -> Self {
57 FontCollection(hashmap)
58 }
59
60 pub fn get(&self, symbol: char) -> Option<&FontUnicode> {
62 self.0.get(&symbol)
63 }
64
65 pub fn contains_key(&self, symbol: char) -> bool {
67 self.0.contains_key(&symbol)
68 }
69
70 pub fn sanitize_str(&self, s: &str) -> Result<FontString, ScreenError> {
72 let valid = s
73 .chars()
74 .filter(|c| self.0.contains_key(c))
75 .map(|sym| *self.get(sym).unwrap())
76 .collect::<Vec<FontUnicode>>();
77 Ok(FontString(valid))
78 }
79}
80
81impl Default for FontCollection {
82 fn default() -> Self {
83 FontCollection::new()
84 }
85}
86
87#[derive(Clone, Debug, Default, PartialEq)]
90pub struct FontString(Vec<FontUnicode>);
91
92impl FontString {
93 pub fn new() -> Self {
95 FontString(Default::default())
96 }
97
98 pub fn chars(&self) -> Vec<char> {
100 self.0.iter().map(|font| font.char()).collect::<Vec<char>>()
101 }
102
103 pub fn font_frames(&self, stroke: PixelColor, bg: PixelColor) -> Vec<FontFrame> {
105 self.0
106 .iter()
107 .map(|font| FontFrame::new(*font, stroke, bg))
108 .collect::<Vec<FontFrame>>()
109 }
110
111 pub fn pixel_frames(&self, stroke: PixelColor, bg: PixelColor) -> Vec<PixelFrame> {
113 self.font_frames(stroke, bg)
114 .into_iter()
115 .map(|f| f.into())
116 .collect::<Vec<PixelFrame>>()
117 }
118}
119
120impl fmt::Display for FontString {
121 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
122 write!(
123 f,
124 "{}",
125 self.0.iter().map(|font| font.char()).collect::<String>()
126 )
127 }
128}
129
130#[derive(Debug, PartialEq)]
132pub struct FontFrame {
133 font: FontUnicode,
135 stroke: PixelColor,
137 background: PixelColor,
139}
140
141impl FontFrame {
142 pub fn new(font: FontUnicode, stroke: PixelColor, background: PixelColor) -> Self {
144 FontFrame {
145 font,
146 stroke,
147 background,
148 }
149 }
150
151 pub fn pixel_frame(&self) -> PixelFrame {
153 let pixels =
154 font_to_pixel_color_array_with_bg(self.font.byte_array(), self.stroke, self.background);
155 pixels.into()
156 }
157}
158
159impl From<FontFrame> for PixelFrame {
160 fn from(font: FontFrame) -> Self {
161 font.pixel_frame()
162 }
163}
164
165impl BackgroundColor for FontFrame {
166 fn set_background_color(&mut self, color: PixelColor) {
167 self.background = color;
168 }
169 fn get_background_color(&self) -> PixelColor {
170 self.background
171 }
172}
173
174impl StrokeColor for FontFrame {
175 fn set_stroke_color(&mut self, color: PixelColor) {
176 self.stroke = color;
177 }
178 fn get_stroke_color(&self) -> PixelColor {
179 self.stroke
180 }
181}
182
183fn font_to_pixel_color_array_with_bg(
185 symbol: [u8; 8],
186 color: PixelColor,
187 background: PixelColor,
188) -> [PixelColor; 64] {
189 let mut pixels = [background; 64];
190 for (row_idx, encoded_row) in symbol.iter().enumerate() {
191 for col_idx in 0..8 {
192 if (*encoded_row & 1 << col_idx) > 0 {
193 pixels[row_idx * 8 + col_idx] = color;
194 }
195 }
196 }
197 pixels
198}
199
200fn font_to_pixel_color_array(symbol: [u8; 8], color: PixelColor) -> [PixelColor; 64] {
202 font_to_pixel_color_array_with_bg(symbol, color, Default::default())
203}
204
205pub fn font_to_pixel_frame(symbol: [u8; 8], color: PixelColor) -> PixelFrame {
207 let pixels = font_to_pixel_color_array(symbol, color);
208 PixelFrame::new(&pixels)
209}
210
211pub fn font_to_frame(symbol: [u8; 8], color: PixelColor) -> FrameLine {
213 let pixels = font_to_pixel_color_array(symbol, color);
214 FrameLine::from_pixels(&pixels)
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220
221 const BLK: PixelColor = PixelColor::BLACK;
222 const RED: PixelColor = PixelColor::RED;
223 const GRN: PixelColor = PixelColor::GREEN;
224 const BLU: PixelColor = PixelColor::BLUE;
225 const YLW: PixelColor = PixelColor::YELLOW;
226 const BASIC_FONT: [PixelColor; 64] = [
227 BLU, BLU, BLK, BLK, BLK, BLU, BLU, BLK, BLU, BLU, BLU, BLK, BLU, BLU, BLU, BLK, BLU, BLU, BLU, BLU, BLU, BLU, BLU, BLK, BLU, BLU, BLU, BLU, BLU, BLU, BLU, BLK, BLU, BLU, BLK, BLU, BLK, BLU, BLU, BLK, BLU, BLU, BLK, BLK, BLK, BLU, BLU, BLK, BLU, BLU, BLK, BLK, BLK, BLU, BLU, BLK, BLK, BLK, BLK, BLK, BLK, BLK, BLK, BLK, ];
236 const BOX_FONT: [PixelColor; 64] = [
237 BLK, BLK, BLK, GRN, BLK, BLK, BLK, BLK, BLK, BLK, BLK, GRN, BLK, BLK, BLK, BLK, BLK, BLK, BLK, GRN, BLK, BLK, BLK, BLK, BLK, BLK, BLK, GRN, GRN, GRN, GRN, GRN, GRN, GRN, GRN, GRN, GRN, GRN, GRN, GRN, BLK, BLK, BLK, BLK, BLK, BLK, BLK, BLK, BLK, BLK, BLK, BLK, BLK, BLK, BLK, BLK, BLK, BLK, BLK, BLK, BLK, BLK, BLK, BLK, ];
246 const BOX_FONT_BG: [PixelColor; 64] = [
247 YLW, YLW, YLW, BLU, YLW, YLW, YLW, YLW, YLW, YLW, YLW, BLU, YLW, YLW, YLW, YLW, YLW, YLW, YLW, BLU, YLW, YLW, YLW, YLW, YLW, YLW, YLW, BLU, BLU, BLU, BLU, BLU, BLU, BLU, BLU, BLU, BLU, BLU, BLU, BLU, YLW, YLW, YLW, YLW, YLW, YLW, YLW, YLW, YLW, YLW, YLW, YLW, YLW, YLW, YLW, YLW, YLW, YLW, YLW, YLW, YLW, YLW, YLW, YLW, ];
256 const HIRAGANA_FONT: [PixelColor; 64] = [
257 BLK, BLK, BLK, RED, BLK, BLK, BLK, BLK, BLK, RED, RED, RED, RED, RED, RED, BLK, BLK, BLK, BLK, RED, BLK, BLK, BLK, BLK, BLK, BLK, RED, RED, RED, RED, BLK, BLK, BLK, BLK, BLK, BLK, BLK, BLK, RED, BLK, BLK, BLK, BLK, BLK, BLK, BLK, RED, BLK, BLK, BLK, BLK, RED, RED, RED, BLK, BLK, BLK, BLK, BLK, BLK, BLK, BLK, BLK, BLK, ];
266
267 #[test]
268 fn font_collection_sanitizes_text_by_filtering_known_unicode_points() {
269 let font_set = FontCollection::new();
270 let valid_text = font_set.sanitize_str("hola niño @¶øþ¥").unwrap();
271 assert_eq!(format!("{}", valid_text), "hola niño @¶øþ¥");
272 }
273
274 #[test]
275 fn font_collection_sanitizes_text_by_removing_symbols_not_in_set() {
276 let font_set = FontCollection::new();
277 let invalid_text = font_set.sanitize_str("ŧ←→ł").unwrap();
278 assert_eq!(format!("{}", invalid_text), "");
279
280 let font_set = FontCollection::from_hashmap(HashMap::new());
281 let invalid_text = font_set.sanitize_str("hola niño @¶øþ¥").unwrap();
282 assert_eq!(format!("{}", invalid_text), "");
283 }
284
285 #[test]
286 fn font_collection_gets_optional_symbol_by_unicode_key() {
287 let font_set = FontCollection::new();
288 let symbol = font_set.get('ñ');
289 assert!(symbol.is_some());
290 }
291
292 #[test]
293 fn font_collection_searches_for_symbols_by_unicode_key() {
294 let font_set = FontCollection::new();
295 let has_symbol = font_set.contains_key('ñ');
296 assert!(has_symbol);
297 }
298
299 #[test]
300 fn font_string_new_method_starts_emtpy_instance() {
301 let font_string = FontString::new();
302 assert_eq!(font_string.0, Vec::new());
303 }
304
305 #[test]
306 fn font_string_chars_method_returns_vec_of_chars() {
307 let font_set = FontCollection::new();
308 let font_string = font_set.sanitize_str("┷│││┯").unwrap();
309 assert_eq!(font_string.chars(), vec!['┷', '│', '│', '│', '┯']);
310 }
311
312 #[test]
313 fn font_string_implements_display_trait() {
314 let font_set = FontCollection::new();
315 let font_string = font_set.sanitize_str("┷│││┯").unwrap();
316 assert_eq!(format!("{}", font_string), "┷│││┯".to_string());
317 }
318
319 #[test]
320 fn font_string_font_frames_returns_a_vec_of_font_frame() {
321 let font_set = FontCollection::new();
322 let font_string = font_set.sanitize_str("Mち┶").unwrap();
323 let bas_font = font_set.get('M').unwrap();
324 let hir_font = font_set.get('ち').unwrap();
325 let box_font = font_set.get('┶').unwrap();
326 let ft_frames = font_string.font_frames(PixelColor::YELLOW, PixelColor::BLACK);
327 assert_eq!(
328 ft_frames,
329 vec![
330 FontFrame {
331 font: *bas_font,
332 stroke: PixelColor::YELLOW,
333 background: PixelColor::BLACK,
334 },
335 FontFrame {
336 font: *hir_font,
337 stroke: PixelColor::YELLOW,
338 background: PixelColor::BLACK,
339 },
340 FontFrame {
341 font: *box_font,
342 stroke: PixelColor::YELLOW,
343 background: PixelColor::BLACK,
344 },
345 ]
346 );
347 }
348
349 #[test]
350 fn font_string_font_frames_returns_a_vec_of_pixel_frame() {
351 let font_set = FontCollection::new();
352 let font_string = font_set.sanitize_str("MM").unwrap();
353 let px_frames = font_string.pixel_frames(PixelColor::BLUE, PixelColor::BLACK);
354 assert_eq!(
355 px_frames,
356 vec![PixelFrame::from(BASIC_FONT), PixelFrame::from(BASIC_FONT),]
357 );
358 }
359
360 #[test]
361 fn fn_font_to_pixel_color_array_with_bg_creates_new_array() {
362 let font_set = FontCollection::new();
363 let font = font_set.get('┶').unwrap();
364 let px_array = font_to_pixel_color_array_with_bg(
365 font.byte_array(),
366 PixelColor::BLUE,
367 PixelColor::YELLOW,
368 );
369 for (idx, px) in px_array.iter().enumerate() {
370 assert_eq!(*px, BOX_FONT_BG[idx]);
371 }
372 }
373
374 #[test]
375 fn fn_font_to_pixel_color_array_creates_new_array() {
376 let font_set = FontCollection::new();
377 let font = font_set.get('M').unwrap();
378 let px_array = font_to_pixel_color_array(font.byte_array(), PixelColor::BLUE);
379 for (idx, px) in px_array.iter().enumerate() {
380 assert_eq!(*px, BASIC_FONT[idx]);
381 }
382 }
383
384 #[test]
385 fn fn_font_to_pixel_frame_takes_a_byte_array_and_pixel_color() {
386 let font_set = FontCollection::new();
387 let chi_font = font_set.get('ち').unwrap();
388 let px_frame = font_to_pixel_frame(chi_font.byte_array(), PixelColor::RED);
389 assert_eq!(px_frame, PixelFrame::from(HIRAGANA_FONT));
390 }
391
392 #[test]
393 fn fn_font_to_frame_takes_a_byte_array_and_pixel_color() {
394 let font_set = FontCollection::new();
395 let box_font = font_set.get('┶').unwrap();
396 let px_frame_line = font_to_frame(box_font.byte_array(), PixelColor::GREEN);
397 assert_eq!(px_frame_line, PixelFrame::from(BOX_FONT).frame_line());
398 }
399
400 #[test]
401 fn font_frames_are_created_from_ut16_font_a_stroke_and_a_background_color() {
402 let font_set = FontCollection::new();
403 let letter_a = font_set.get('a').unwrap();
404 let font_frame = FontFrame::new(letter_a.clone(), PixelColor::WHITE, PixelColor::BLACK);
405 assert_eq!(
406 font_frame,
407 FontFrame {
408 font: *letter_a,
409 stroke: PixelColor::WHITE,
410 background: PixelColor::BLACK
411 }
412 );
413 }
414
415 #[test]
416 fn font_frames_is_represented_as_a_pixel_frame() {
417 let font_set = FontCollection::new();
418 let hiragana_font = font_set.get('ち').unwrap();
419 let font_frame = FontFrame::new(hiragana_font.clone(), PixelColor::RED, PixelColor::BLACK);
420 let px_frame = font_frame.pixel_frame();
421 assert_eq!(px_frame, PixelFrame::from(HIRAGANA_FONT));
422 }
423
424 #[test]
425 fn pixel_frame_implements_from_font_frame_trait() {
426 let font_set = FontCollection::new();
427 let hiragana_font = font_set.get('ち').unwrap();
428 let font_frame = FontFrame::new(hiragana_font.clone(), PixelColor::RED, PixelColor::BLACK);
429 let px_frame = PixelFrame::from(font_frame);
430 assert_eq!(px_frame, PixelFrame::from(HIRAGANA_FONT));
431 }
432
433 #[test]
434 fn font_frame_sets_background_color() {
435 let font_set = FontCollection::new();
436 let letter_a = font_set.get('a').unwrap();
437 let mut font_frame = FontFrame::new(letter_a.clone(), PixelColor::WHITE, PixelColor::BLACK);
438 font_frame.set_background_color(PixelColor::RED);
439 assert_eq!(
440 font_frame,
441 FontFrame {
442 font: *letter_a,
443 stroke: PixelColor::WHITE,
444 background: PixelColor::RED
445 }
446 );
447 }
448
449 #[test]
450 fn font_frame_gets_background_color() {
451 let font_set = FontCollection::new();
452 let letter_a = font_set.get('a').unwrap();
453 let font_frame = FontFrame::new(letter_a.clone(), PixelColor::WHITE, PixelColor::GREEN);
454 assert_eq!(font_frame.get_background_color(), PixelColor::GREEN);
455 }
456
457 #[test]
458 fn font_frame_sets_stroke_color() {
459 let font_set = FontCollection::new();
460 let letter_a = font_set.get('a').unwrap();
461 let mut font_frame = FontFrame::new(letter_a.clone(), PixelColor::WHITE, PixelColor::BLACK);
462 font_frame.set_stroke_color(PixelColor::YELLOW);
463 assert_eq!(
464 font_frame,
465 FontFrame {
466 font: *letter_a,
467 stroke: PixelColor::YELLOW,
468 background: PixelColor::BLACK
469 }
470 );
471 }
472
473 #[test]
474 fn font_frame_gets_stroke_color() {
475 let font_set = FontCollection::new();
476 let letter_a = font_set.get('a').unwrap();
477 let font_frame = FontFrame::new(letter_a.clone(), PixelColor::BLUE, PixelColor::WHITE);
478 assert_eq!(font_frame.get_stroke_color(), PixelColor::BLUE);
479 }
480}