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}