figleter/
lib.rs

1//! you can visit [`figlet`] and [`figfont`] to find more details.
2//! you can visit [`fongdb`] to find more font.
3//!
4//! # Examples
5//!
6//! download [`small.flf`] and place it to the `resources` folder.
7//!
8//! convert string literal using standard or specified font:
9//!
10//! ```
11//! use figleter::FIGfont;
12//!
13//! let standard_font = FIGfont::standard().unwrap();
14//! let figure = standard_font.convert("FIGlet");
15//! assert!(figure.is_some());
16//!
17//! let small_font = FIGfont::from_file("resources/small.flf").unwrap();
18//! let figure = small_font.convert("FIGlet");
19//! assert!(figure.is_some());
20//! ```
21//! [`figlet`]: http://www.figlet.org
22//! [`figfont`]: http://www.jave.de/figlet/figfont.html
23//! [`fongdb`]: http://www.figlet.org/fontdb.cgi
24//! [`small.flf`]: http://www.figlet.org/fonts/small.flf
25
26use std::collections::HashMap;
27use std::{fmt, fs};
28
29#[derive(Debug)]
30struct Layout {
31    layout: i32,
32}
33
34impl Layout {
35    fn new(full_layout: Option<i32>, old_layout: i32) -> Layout {
36        if let Some(full_layout) = full_layout {
37            Self {
38                layout: full_layout,
39            }
40        } else {
41            Self { layout: old_layout }
42        }
43    }
44    fn has_rule_1(&self) -> bool {
45        return self.layout & 1 != 0;
46    }
47    fn has_rule_2(&self) -> bool {
48        return self.layout & 2 != 0;
49    }
50    fn has_rule_3(&self) -> bool {
51        return self.layout & 4 != 0;
52    }
53    fn has_rule_4(&self) -> bool {
54        return self.layout & 8 != 0;
55    }
56    // fn has_rule_5(&self) -> bool {
57    //     return self.layout & 16 != 0;
58    // }
59    // fn has_rule_6(&self) -> bool {
60    //     return self.layout & 32 != 0;
61    // }
62}
63
64/// FIGlet font, which will hold the mapping from u32 code to FIGcharacter
65#[derive(Debug)]
66pub struct FIGfont {
67    pub header_line: HeaderLine,
68    pub comments: String,
69    pub fonts: HashMap<u32, FIGcharacter>,
70}
71
72impl FIGfont {
73    fn read_font_file(filename: &str) -> Result<String, String> {
74        fs::read_to_string(filename).map_err(|e| format!("{e:?}"))
75    }
76
77    fn read_header_line(header_line: &str) -> Result<HeaderLine, String> {
78        HeaderLine::try_from(header_line)
79    }
80
81    fn read_comments(lines: &[&str], comment_count: i32) -> Result<String, String> {
82        let length = lines.len() as i32;
83        if length < comment_count + 1 {
84            Err("can't get comments from font".to_string())
85        } else {
86            let comment = lines[1..(1 + comment_count) as usize].join("\n");
87            Ok(comment)
88        }
89    }
90
91    fn extract_one_line(
92        lines: &[&str],
93        index: usize,
94        height: usize,
95        is_last_index: bool,
96    ) -> Result<String, String> {
97        let line = lines
98            .get(index)
99            .ok_or(format!("can't get line at specified index:{index}"))?;
100
101        let mut width = line.len() - 1;
102        if is_last_index && height != 1 {
103            width -= 1;
104        }
105
106        Ok(line[..width].to_string())
107    }
108
109    fn extract_one_font(
110        lines: &[&str],
111        code: u32,
112        start_index: usize,
113        height: usize,
114    ) -> Result<FIGcharacter, String> {
115        let mut characters = vec![];
116        for i in 0..height {
117            let index = start_index + i as usize;
118            let is_last_index = i == height - 1;
119            let one_line_character =
120                FIGfont::extract_one_line(lines, index, height, is_last_index)?;
121            characters.push(one_line_character);
122        }
123        let width = characters[0].len() as u32;
124        let height = height as u32;
125
126        Ok(FIGcharacter {
127            code,
128            characters,
129            width,
130            height,
131        })
132    }
133
134    // 32-126, 196, 214, 220, 228, 246, 252, 223
135    fn read_required_font(
136        lines: &[&str],
137        headerline: &HeaderLine,
138        map: &mut HashMap<u32, FIGcharacter>,
139    ) -> Result<(), String> {
140        let offset = (1 + headerline.comment_lines) as usize;
141        let height = headerline.height as usize;
142        let size = lines.len();
143
144        for i in 0..=94 {
145            let code = (i + 32) as u32;
146            let start_index = offset + i * height;
147            if start_index >= size {
148                break;
149            }
150
151            let font = FIGfont::extract_one_font(lines, code, start_index, height)?;
152            map.insert(code, font);
153        }
154
155        let offset = offset + 95 * height;
156        let required_deutsch_characters_codes: [u32; 7] = [196, 214, 220, 228, 246, 252, 223];
157        for (i, code) in required_deutsch_characters_codes.iter().enumerate() {
158            let start_index = offset + i * height;
159            if start_index >= size {
160                break;
161            }
162
163            let font = FIGfont::extract_one_font(lines, *code, start_index, height)?;
164            map.insert(*code, font);
165        }
166
167        Ok(())
168    }
169
170    fn extract_codetag_font_code(lines: &[&str], index: usize) -> Result<u32, String> {
171        let line = lines
172            .get(index)
173            .ok_or_else(|| "get codetag line error".to_string())?;
174
175        let infos: Vec<&str> = line.trim().split(' ').collect();
176        if infos.is_empty() {
177            return Err("extract code for codetag font error".to_string());
178        }
179
180        let code = infos[0].trim();
181
182        let code = if let Some(s) = code.strip_prefix("0x") {
183            u32::from_str_radix(s, 16)
184        } else if let Some(s) = code.strip_prefix("0X") {
185            u32::from_str_radix(s, 16)
186        } else if let Some(s) = code.strip_prefix('0') {
187            u32::from_str_radix(s, 8)
188        } else {
189            code.parse()
190        };
191
192        code.map_err(|e| format!("{e:?}"))
193    }
194
195    fn read_codetag_font(
196        lines: &[&str],
197        headerline: &HeaderLine,
198        map: &mut HashMap<u32, FIGcharacter>,
199    ) -> Result<(), String> {
200        let offset = (1 + headerline.comment_lines + 102 * headerline.height) as usize;
201        let codetag_height = (headerline.height + 1) as usize;
202        let codetag_lines = lines.len() - offset;
203
204        if codetag_lines % codetag_height != 0 {
205            return Err("codetag font is illegal.".to_string());
206        }
207
208        let size = codetag_lines / codetag_height;
209
210        for i in 0..size {
211            let start_index = offset + i * codetag_height;
212            if start_index >= lines.len() {
213                break;
214            }
215
216            let code = FIGfont::extract_codetag_font_code(lines, start_index)?;
217            let font = FIGfont::extract_one_font(
218                lines,
219                code,
220                start_index + 1,
221                headerline.height as usize,
222            )?;
223            map.insert(code, font);
224        }
225
226        Ok(())
227    }
228
229    fn read_fonts(
230        lines: &[&str],
231        headerline: &HeaderLine,
232    ) -> Result<HashMap<u32, FIGcharacter>, String> {
233        let mut map = HashMap::new();
234        FIGfont::read_required_font(lines, headerline, &mut map)?;
235        FIGfont::read_codetag_font(lines, headerline, &mut map)?;
236        Ok(map)
237    }
238
239    /// generate FIGlet font from string literal
240    pub fn from_content(contents: &str) -> Result<FIGfont, String> {
241        let lines: Vec<&str> = contents.lines().collect();
242
243        if lines.is_empty() {
244            return Err("can not generate FIGlet font from empty string".to_string());
245        }
246
247        let header_line = FIGfont::read_header_line(lines.first().unwrap())?;
248        let comments = FIGfont::read_comments(&lines, header_line.comment_lines)?;
249        let fonts = FIGfont::read_fonts(&lines, &header_line)?;
250
251        Ok(FIGfont {
252            header_line,
253            comments,
254            fonts,
255        })
256    }
257
258    /// generate FIGlet font from specified file
259    pub fn from_file(fontname: &str) -> Result<FIGfont, String> {
260        let contents = FIGfont::read_font_file(fontname)?;
261        FIGfont::from_content(&contents)
262    }
263
264    /// the standard FIGlet font, which you can find [`fontdb`]
265    ///
266    /// [`fontdb`]: http://www.figlet.org/fontdb.cgi
267    pub fn standard() -> Result<FIGfont, String> {
268        let contents = std::include_str!("standard.flf");
269        FIGfont::from_content(contents)
270    }
271
272    /// convert string literal to FIGure
273    pub fn convert(&self, message: &str) -> Option<FIGure> {
274        if message.is_empty() {
275            return None;
276        }
277
278        let mut characters: Vec<&FIGcharacter> = vec![];
279        for ch in message.chars() {
280            let code = ch as u32;
281            if let Some(character) = self.fonts.get(&code) {
282                characters.push(character);
283            }
284        }
285
286        if characters.is_empty() {
287            return None;
288        }
289
290        Some(FIGure {
291            characters,
292            height: self.header_line.height as u32,
293            layout: Layout::new(self.header_line.full_layout, self.header_line.old_layout),
294            hardblank: self.header_line.hardblank,
295        })
296    }
297}
298
299/// the first line in FIGlet font, which you can find the documentation [`headerline`]
300///
301/// [`headerline`]: http://www.jave.de/figlet/figfont.html#headerline
302#[derive(Debug)]
303pub struct HeaderLine {
304    pub header_line: String,
305
306    // required
307    pub signature: String,
308    pub hardblank: char,
309    pub height: i32,
310    pub baseline: i32,
311    pub max_length: i32,
312    pub old_layout: i32, // Legal values -1 to 63
313    pub comment_lines: i32,
314
315    // optional
316    pub print_direction: Option<i32>,
317    pub full_layout: Option<i32>, // Legal values 0 to 32767
318    pub codetag_count: Option<i32>,
319}
320
321impl HeaderLine {
322    fn extract_signature_with_hardblank(
323        signature_with_hardblank: &str,
324    ) -> Result<(String, char), String> {
325        if signature_with_hardblank.len() < 6 {
326            Err("can't get signature with hardblank from first line of font".to_string())
327        } else {
328            let hardblank_index = signature_with_hardblank.len() - 1;
329            let signature = &signature_with_hardblank[..hardblank_index];
330            let hardblank = signature_with_hardblank[hardblank_index..]
331                .chars()
332                .next()
333                .unwrap();
334
335            Ok((String::from(signature), hardblank))
336        }
337    }
338
339    fn extract_required_info(infos: &[&str], index: usize, field: &str) -> Result<i32, String> {
340        let val = match infos.get(index) {
341            Some(val) => Ok(val),
342            None => Err(format!(
343                "can't get field:{field} index:{index} from {}",
344                infos.join(",")
345            )),
346        }?;
347
348        val.parse()
349            .map_err(|_| format!("can't parse required field:{field} of {val} to i32"))
350    }
351
352    fn extract_optional_info(infos: &[&str], index: usize, _field: &str) -> Option<i32> {
353        if let Some(val) = infos.get(index) {
354            val.parse().ok()
355        } else {
356            None
357        }
358    }
359}
360
361impl TryFrom<&str> for HeaderLine {
362    type Error = String;
363
364    fn try_from(header_line: &str) -> Result<Self, Self::Error> {
365        let infos: Vec<&str> = header_line.trim().split(' ').collect();
366
367        if infos.len() < 6 {
368            return Err("headerline is illegal".to_string());
369        }
370
371        let signature_with_hardblank =
372            HeaderLine::extract_signature_with_hardblank(infos.first().unwrap())?;
373
374        let height = HeaderLine::extract_required_info(&infos, 1, "height")?;
375        let baseline = HeaderLine::extract_required_info(&infos, 2, "baseline")?;
376        let max_length = HeaderLine::extract_required_info(&infos, 3, "max length")?;
377        let old_layout = HeaderLine::extract_required_info(&infos, 4, "old layout")?;
378        let comment_lines = HeaderLine::extract_required_info(&infos, 5, "comment lines")?;
379
380        let print_direction = HeaderLine::extract_optional_info(&infos, 6, "print direction");
381        let full_layout = HeaderLine::extract_optional_info(&infos, 7, "full layout");
382        let codetag_count = HeaderLine::extract_optional_info(&infos, 8, "codetag count");
383
384        Ok(HeaderLine {
385            header_line: String::from(header_line),
386            signature: signature_with_hardblank.0,
387            hardblank: signature_with_hardblank.1,
388            height,
389            baseline,
390            max_length,
391            old_layout,
392            comment_lines,
393            print_direction,
394            full_layout,
395            codetag_count,
396        })
397    }
398}
399
400/// the matched ascii art of one character
401#[derive(Debug)]
402pub struct FIGcharacter {
403    pub code: u32,
404    pub characters: Vec<String>,
405    pub width: u32,
406    pub height: u32,
407}
408
409impl fmt::Display for FIGcharacter {
410    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
411        write!(f, "{}", self.characters.join("\n"))
412    }
413}
414
415/// the matched ascii arts of string literal
416#[derive(Debug)]
417pub struct FIGure<'a> {
418    pub characters: Vec<&'a FIGcharacter>,
419    pub height: u32,
420    layout: Layout,
421    hardblank: char,
422}
423
424impl<'a> FIGure<'a> {
425    fn is_not_empty(&self) -> bool {
426        !self.characters.is_empty() && self.height > 0
427    }
428}
429
430fn first_non_space(a: impl Iterator<Item = char>, len: usize) -> (char, usize) {
431    for (idx, c) in a.enumerate() {
432        if !c.is_whitespace() {
433            return (c, idx);
434        }
435    }
436    (' ', len)
437}
438
439fn rule_2_char(c: char) -> bool {
440    return c == '|'
441        || c == '/'
442        || c == '\\'
443        || c == '['
444        || c == ']'
445        || c == '{'
446        || c == '}'
447        || c == '('
448        || c == ')'
449        || c == '<'
450        || c == '>';
451}
452
453fn rule_3_klass(c: char) -> u32 {
454    match c {
455        '|' => 1,
456        '/' => 2,
457        '\\' => 2,
458        '[' => 3,
459        ']' => 3,
460        '{' => 4,
461        '}' => 4,
462        '(' => 5,
463        ')' => 5,
464        '<' => 6,
465        '>' => 6,
466        _ => 0,
467    }
468}
469
470fn rule_4_chars(a: char, b: char) -> bool {
471    match a {
472        '(' => b == ')',
473        ')' => b == '(',
474        '[' => b == ']',
475        ']' => b == '[',
476        '{' => b == '}',
477        '}' => b == '{',
478        _ => false,
479    }
480}
481
482fn compute_kerning(a: &String, b: &String, layout: &Layout) -> (usize, usize) {
483    let (a_char, a_k) = first_non_space(a.chars().rev(), a.len());
484    let (b_char, b_k) = first_non_space(b.chars(), b.len());
485    if layout.has_rule_1() && a_char == b_char {
486        return (a_k + 1, b_k);
487    }
488    if layout.has_rule_2() && a_char == '_' && rule_2_char(b_char) {
489        return (a_k, b_k + 1);
490    }
491    if layout.has_rule_2() && b_char == '_' && rule_2_char(a_char) {
492        return (a_k, b_k + 1);
493    }
494    let a_3_klass = rule_3_klass(a_char);
495    let b_3_klass = rule_3_klass(b_char);
496    if layout.has_rule_3() && a_3_klass != b_3_klass && a_3_klass > 0 && b_3_klass > 0 {
497        return (a_k + 1, b_k);
498    }
499    if layout.has_rule_4() && rule_4_chars(a_char, b_char) {
500        return (a_k + 1, b_k);
501    }
502
503    return (a_k + 1, b_k - 1);
504}
505
506fn merge_string(a: &String, b: &String, left_kernel: usize, right_kernel: usize) -> String {
507    if a.len() <= left_kernel {
508        b.to_owned()
509    } else {
510        let (char_a, a_k) = first_non_space(a.chars().rev(), a.len());
511        let (char_b, b_k) = first_non_space(b.chars(), b.len());
512        let (middle_c, a_off, b_off) = if a_k < left_kernel
513            && rule_3_klass(char_a) >= rule_3_klass(char_b)
514            && !rule_4_chars(char_a, char_b)
515        {
516            (None, a_k, left_kernel + right_kernel - a_k)
517        } else if b_k < right_kernel
518            && rule_3_klass(char_a) <= rule_3_klass(char_b)
519            && !rule_4_chars(char_a, char_b)
520        {
521            (None, left_kernel + right_kernel - b_k, b_k)
522        } else if left_kernel == 0 {
523            (None, 0, right_kernel)
524        } else {
525            let a_c = a.chars().nth(a.len() - left_kernel).unwrap();
526            let b_c = b.chars().nth(right_kernel).unwrap();
527            if a_c == ' ' || rule_3_klass(a_c) < rule_3_klass(b_c) && rule_3_klass(a_c) > 0 {
528                (None, left_kernel, right_kernel)
529            } else if rule_4_chars(a_c, b_c) {
530                (Some("|"), left_kernel, right_kernel + 1)
531            } else {
532                (None, left_kernel - 1, right_kernel + 1)
533            }
534        };
535        match middle_c {
536            None => a[..a.len() - a_off].to_string() + &b[b_off..],
537            Some(middle_c) => a[..a.len() - a_off].to_string() + middle_c + &b[b_off..],
538        }
539    }
540}
541
542impl<'a> fmt::Display for FIGure<'a> {
543    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
544        if self.is_not_empty() {
545            let mut kerning: Vec<(usize, usize)> = vec![(0, 0)];
546            for i in 1..self.characters.len() {
547                kerning.push(
548                    self.characters[i - 1]
549                        .characters
550                        .iter()
551                        .zip(self.characters[i].characters.iter())
552                        .map(|(lc, rc)| {
553                            let k = compute_kerning(lc, rc, &self.layout);
554                            k
555                        })
556                        .reduce(|acc, c| if acc.0 + acc.1 < c.0 + c.1 { acc } else { c })
557                        .unwrap_or((0, 0)),
558                );
559            }
560            let mut rs: Vec<String> = vec![];
561            rs.resize(self.height as usize, String::new());
562            for (character, (left_kerning, right_kerning)) in
563                self.characters.iter().zip(kerning.iter())
564            {
565                rs = rs
566                    .into_iter()
567                    .zip(character.characters.iter())
568                    .map(|(r, c)| merge_string(&r, &c, *left_kerning, *right_kerning))
569                    .collect();
570            }
571
572            write!(f, "{}", rs.join("\n").replace(self.hardblank, " "))
573        } else {
574            write!(f, "")
575        }
576    }
577}
578
579#[cfg(test)]
580mod tests {
581    use super::*;
582
583    #[test]
584    fn test_new_headerline() {
585        let line = "flf2a$ 6 5 20 15 3 0 143 229";
586        let headerline = HeaderLine::try_from(line);
587        assert!(headerline.is_ok());
588        let headerline = headerline.unwrap();
589
590        assert_eq!(line, headerline.header_line);
591        assert_eq!("flf2a", headerline.signature);
592        assert_eq!('$', headerline.hardblank);
593        assert_eq!(6, headerline.height);
594        assert_eq!(5, headerline.baseline);
595        assert_eq!(20, headerline.max_length);
596        assert_eq!(15, headerline.old_layout);
597        assert_eq!(3, headerline.comment_lines);
598        assert_eq!(Some(0), headerline.print_direction);
599        assert_eq!(Some(143), headerline.full_layout);
600        assert_eq!(Some(229), headerline.codetag_count);
601    }
602
603    #[test]
604    fn test_new_figfont() {
605        let font = FIGfont::standard();
606        assert!(font.is_ok());
607        let font = font.unwrap();
608
609        let headerline = font.header_line;
610        assert_eq!("flf2a$ 6 5 16 15 11 0 24463", headerline.header_line);
611        assert_eq!("flf2a", headerline.signature);
612        assert_eq!('$', headerline.hardblank);
613        assert_eq!(6, headerline.height);
614        assert_eq!(5, headerline.baseline);
615        assert_eq!(16, headerline.max_length);
616        assert_eq!(15, headerline.old_layout);
617        assert_eq!(11, headerline.comment_lines);
618        assert_eq!(Some(0), headerline.print_direction);
619        assert_eq!(Some(24463), headerline.full_layout);
620        assert_eq!(None, headerline.codetag_count);
621
622        assert_eq!(
623            "Standard by Glenn Chappell & Ian Chai 3/93 -- based on Frank's .sig
624Includes ISO Latin-1
625figlet release 2.1 -- 12 Aug 1994
626Modified for figlet 2.2 by John Cowan <cowan@ccil.org>
627  to add Latin-{2,3,4,5} support (Unicode U+0100-017F).
628Permission is hereby given to modify this font, as long as the
629modifier's name is placed on a comment line.
630
631Modified by Paul Burton <solution@earthlink.net> 12/96 to include new parameter
632supported by FIGlet and FIGWin.  May also be slightly modified for better use
633of new full-width/kern/smush alternatives, but default output is NOT changed.",
634            font.comments
635        );
636
637        let one_font = font.fonts.get(&('F' as u32));
638        assert!(one_font.is_some());
639
640        let one_font = one_font.unwrap();
641        assert_eq!(70, one_font.code);
642        assert_eq!(8, one_font.width);
643        assert_eq!(6, one_font.height);
644
645        assert_eq!(6, one_font.characters.len());
646        assert_eq!("  _____ ", one_font.characters.get(0).unwrap());
647        assert_eq!(" |  ___|", one_font.characters.get(1).unwrap());
648        assert_eq!(" | |_   ", one_font.characters.get(2).unwrap());
649        assert_eq!(" |  _|  ", one_font.characters.get(3).unwrap());
650        assert_eq!(" |_|    ", one_font.characters.get(4).unwrap());
651        assert_eq!("        ", one_font.characters.get(5).unwrap());
652    }
653
654    #[test]
655    fn test_convert() {
656        let standard_font = FIGfont::standard();
657        assert!(standard_font.is_ok());
658        let standard_font = standard_font.unwrap();
659
660        let figure = standard_font.convert("FIGlet");
661        assert!(figure.is_some());
662
663        let figure = figure.unwrap();
664        assert_eq!(6, figure.height);
665        assert_eq!(6, figure.characters.len());
666
667        let f = figure.characters.get(0).unwrap();
668        assert_eq!(figure.height, f.height);
669        assert_eq!(8, f.width);
670        assert_eq!("  _____ ", f.characters.get(0).unwrap());
671        assert_eq!(" |  ___|", f.characters.get(1).unwrap());
672        assert_eq!(" | |_   ", f.characters.get(2).unwrap());
673        assert_eq!(" |  _|  ", f.characters.get(3).unwrap());
674        assert_eq!(" |_|    ", f.characters.get(4).unwrap());
675        assert_eq!("        ", f.characters.get(5).unwrap());
676
677        let i = figure.characters.get(1).unwrap();
678        assert_eq!(figure.height, i.height);
679        assert_eq!(6, i.width);
680        assert_eq!("  ___ ", i.characters.get(0).unwrap());
681        assert_eq!(" |_ _|", i.characters.get(1).unwrap());
682        assert_eq!("  | | ", i.characters.get(2).unwrap());
683        assert_eq!("  | | ", i.characters.get(3).unwrap());
684        assert_eq!(" |___|", i.characters.get(4).unwrap());
685        assert_eq!("      ", i.characters.get(5).unwrap());
686
687        let g = figure.characters.get(2).unwrap();
688        assert_eq!(figure.height, g.height);
689        assert_eq!(8, g.width);
690        assert_eq!(r"   ____ ", g.characters.get(0).unwrap());
691        assert_eq!(r"  / ___|", g.characters.get(1).unwrap());
692        assert_eq!(r" | |  _ ", g.characters.get(2).unwrap());
693        assert_eq!(r" | |_| |", g.characters.get(3).unwrap());
694        assert_eq!(r"  \____|", g.characters.get(4).unwrap());
695        assert_eq!(r"        ", g.characters.get(5).unwrap());
696
697        let l = figure.characters.get(3).unwrap();
698        assert_eq!(figure.height, l.height);
699        assert_eq!(4, l.width);
700        assert_eq!("  _ ", l.characters.get(0).unwrap());
701        assert_eq!(" | |", l.characters.get(1).unwrap());
702        assert_eq!(" | |", l.characters.get(2).unwrap());
703        assert_eq!(" | |", l.characters.get(3).unwrap());
704        assert_eq!(" |_|", l.characters.get(4).unwrap());
705        assert_eq!("    ", l.characters.get(5).unwrap());
706
707        let e = figure.characters.get(4).unwrap();
708        assert_eq!(figure.height, e.height);
709        assert_eq!(7, e.width);
710        assert_eq!(r"       ", e.characters.get(0).unwrap());
711        assert_eq!(r"   ___ ", e.characters.get(1).unwrap());
712        assert_eq!(r"  / _ \", e.characters.get(2).unwrap());
713        assert_eq!(r" |  __/", e.characters.get(3).unwrap());
714        assert_eq!(r"  \___|", e.characters.get(4).unwrap());
715        assert_eq!(r"       ", e.characters.get(5).unwrap());
716
717        let t = figure.characters.get(5).unwrap();
718        assert_eq!(figure.height, t.height);
719        assert_eq!(6, t.width);
720        assert_eq!(r"  _   ", t.characters.get(0).unwrap());
721        assert_eq!(r" | |_ ", t.characters.get(1).unwrap());
722        assert_eq!(r" | __|", t.characters.get(2).unwrap());
723        assert_eq!(r" | |_ ", t.characters.get(3).unwrap());
724        assert_eq!(r"  \__|", t.characters.get(4).unwrap());
725        assert_eq!(r"      ", t.characters.get(5).unwrap());
726    }
727    #[test]
728    fn test_convert_with_kerning() {
729        let standard_font = FIGfont::standard();
730        assert!(standard_font.is_ok());
731        let standard_font = standard_font.unwrap();
732
733        let figure = standard_font.convert("FIGlet");
734        assert!(figure.is_some());
735        assert_eq!(
736            format!("{}", figure.unwrap()),
737            r"  _____ ___ ____ _      _   
738 |  ___|_ _/ ___| | ___| |_ 
739 | |_   | | |  _| |/ _ \ __|
740 |  _|  | | |_| | |  __/ |_ 
741 |_|   |___\____|_|\___|\__|
742                            "
743        );
744        let figure = standard_font.convert("oVo Tr oTo");
745        assert!(figure.is_some());
746        assert_eq!(
747            format!("{}", figure.unwrap()),
748            r"     __     __      _____           _____     
749   __\ \   / /__   |_   _| __    __|_   _|__  
750  / _ \ \ / / _ \    | || '__|  / _ \| |/ _ \ 
751 | (_) \ V / (_) |   | || |    | (_) | | (_) |
752  \___/ \_/ \___/    |_||_|     \___/|_|\___/ 
753                                              "
754        );
755    }
756}