mod.rs

4.1 kB · rust · 141 lines

1#![doc = include_str!("README.md")]23/// The stroke-order writing animations of text.4pub mod animate;5/// The raw bitmap tables of the font.6pub mod glyphs;7/// The glyph builders for uppers, lowers, digits, extras and specials.8pub mod letters;9mod models;10/// The Unicode names of the font's characters.11pub mod names;12/// The stroke orders that write each character.13pub mod paths;14/// The hand-penned stroke tables, one per glyph.15pub mod pens;16/// The 0/1 grid a text renders to.17pub mod raster;18/// The descenders and the trimming of bitmaps.19pub mod shape;2021use std::collections::BTreeMap;22use std::sync::OnceLock;2324pub use animate::{animate, cycle, merge, Anim, FPS, HOLD};25pub use letters::all;26pub use models::Glyph;27pub use names::name_of;28pub use paths::{draft, floor, path, strokes};29pub use raster::raster;30pub use shape::trim;3132fn book() -> &'static BTreeMap<char, Glyph> {33    static BOOK: OnceLock<BTreeMap<char, Glyph>> = OnceLock::new();34    BOOK.get_or_init(|| all().into_iter().map(|g| (g.char, g)).collect())35}3637fn order() -> &'static Vec<char> {38    static ORDER: OnceLock<Vec<char>> = OnceLock::new();39    ORDER.get_or_init(|| all().iter().map(|g| g.char).collect())40}4142/// Returns an owned copy of the character's glyph, or None outside the font.43///44/// ```45/// let a = mrlyrs::font::glyph('a').unwrap();46/// assert_eq!(a.rows[0], "01110");47/// assert_eq!(mrlyrs::font::glyph('\u{6f22}'), None);48/// ```49pub fn glyph(c: char) -> Option<Glyph> {50    book().get(&c).cloned()51}5253/// Returns every character in the font, in font order.54pub fn supported() -> Vec<char> {55    order().clone()56}5758/// Returns the whole font as a map from character to bitmap rows.59pub fn map() -> BTreeMap<char, Vec<String>> {60    all().into_iter().map(|g| (g.char, g.rows)).collect()61}6263#[cfg(test)]64mod tests {65    use super::*;66    const WORDMARK: &str = "MRLYPROD";6768    #[test]69    fn the_book_never_reorders_the_font() {70        let straight: Vec<char> = all().iter().map(|g| g.char).collect();71        assert_eq!(72            supported(),73            straight,74            "the font app seeds its order from supported()"75        );76        assert_eq!(supported().first(), Some(&'A'), "uppers still lead");77    }7879    #[test]80    fn the_cached_glyph_is_the_built_glyph() {81        for want in all() {82            assert_eq!(83                glyph(want.char).as_ref(),84                Some(&want),85                "{} drifted",86                want.char87            );88        }89        assert_eq!(glyph('\u{6f22}'), None);90    }91    #[test]92    fn mrlyprod_union_is_x() {93        let mut union = vec![vec!['0'; 5]; 5];94        for c in WORDMARK.chars() {95            let g = glyph(c).unwrap();96            for (y, row) in g.rows.iter().enumerate() {97                for (x, ch) in row.chars().enumerate() {98                    if ch == '1' {99                        union[y][x] = '1';100                    }101                }102            }103        }104        let folded: Vec<String> = union105            .into_iter()106            .map(|row| row.into_iter().collect())107            .collect();108        assert_eq!(folded, glyph('X').unwrap().rows);109    }110    #[test]111    fn lowercase_rounds_corners() {112        let a = glyph('a').unwrap();113        assert_eq!(a.rows, vec!["01110", "10001", "11111", "10001", "00000"]);114        assert_eq!(glyph('A').unwrap().rows[0], "11111");115    }116    #[test]117    fn trim_collapses_blank() {118        let space = glyph(' ').unwrap();119        assert_eq!(trim(&space.rows), vec!["0"; 5]);120    }121    #[test]122    fn trim_drops_edge_columns() {123        let rows = vec!["00100".to_string(), "00100".to_string()];124        assert_eq!(trim(&rows), vec!["1".to_string(), "1".to_string()]);125    }126    #[test]127    fn count_matches_layout() {128        assert_eq!(supported().len(), 108);129        assert_eq!(letters::uppers().len(), 26);130        assert_eq!(letters::lowers().len(), 26);131        assert_eq!(letters::digits().len(), 10);132        assert_eq!(letters::extras().len(), 42);133        assert_eq!(letters::specials().len(), 4);134    }135    #[test]136    fn descenders_flagged() {137        assert!(shape::descends('$'));138        assert!(shape::descends('('));139        assert!(!shape::descends('A'));140    }141}