lib.rs
4.8 kB · rust · 160 lines
1#![doc = include_str!("../README.md")]2#![deny(missing_docs)]34/// The stroke-order writing animations of text.5pub mod animate;6/// The built font files the crate ships: json, ttf, woff, woff2.7pub mod assets;8/// The glyph well this crate pours.9pub mod data;10/// The raw bitmap tables of the font.11pub mod glyphs;12/// The glyph builders for uppers, lowers, digits, extras and specials.13pub mod letters;14mod models;15/// The Unicode names of the font's characters.16pub mod names;17/// The stroke orders that write each character.18pub mod paths;19/// The hand-penned stroke tables, one per glyph.20pub mod pens;21/// The 0/1 grid a text renders to.22pub mod raster;23/// The string, list and JSON forms of glyphs.24pub mod serializer;25/// The descenders and the trimming of bitmaps.26pub mod shape;2728use std::collections::BTreeMap;29use std::sync::OnceLock;3031pub use animate::{animate, cycle, merge, Anim, FPS, HOLD};32pub use letters::{all, digits, extras, lowers, specials, uppers};33pub use models::Glyph;34pub use names::name_of;35pub use paths::{draft, floor, path, strokes};36pub use raster::raster;37pub use serializer::{to_json, to_lists, to_strings};38pub use shape::{descends, trim, DESCENDERS};3940fn book() -> &'static BTreeMap<char, Glyph> {41 static BOOK: OnceLock<BTreeMap<char, Glyph>> = OnceLock::new();42 BOOK.get_or_init(|| all().into_iter().map(|g| (g.char, g)).collect())43}4445fn order() -> &'static Vec<char> {46 static ORDER: OnceLock<Vec<char>> = OnceLock::new();47 ORDER.get_or_init(|| all().iter().map(|g| g.char).collect())48}4950/// Returns an owned copy of the character's glyph, or None outside the font.51///52/// ```53/// let a = mrlyfont::glyph('a').unwrap();54/// assert_eq!(a.rows[0], "01110");55/// assert_eq!(mrlyfont::glyph('\u{6f22}'), None);56/// ```57pub fn glyph(c: char) -> Option<Glyph> {58 book().get(&c).cloned()59}6061/// Returns a borrowed glyph from the static book, or None outside the font.62pub fn look(c: char) -> Option<&'static Glyph> {63 book().get(&c)64}6566/// Returns every character in the font, in font order.67pub fn supported() -> Vec<char> {68 order().clone()69}7071/// Returns the whole font as a map from character to bitmap rows.72pub fn map() -> BTreeMap<char, Vec<String>> {73 all().into_iter().map(|g| (g.char, g.rows)).collect()74}7576#[cfg(test)]77mod tests {78 use super::*;79 const WORDMARK: &str = "MRLYPROD";8081 #[test]82 fn the_book_never_reorders_the_font() {83 let straight: Vec<char> = all().iter().map(|g| g.char).collect();84 assert_eq!(85 supported(),86 straight,87 "the font app seeds its order from supported()"88 );89 assert_eq!(supported().first(), Some(&'A'), "uppers still lead");90 }9192 #[test]93 fn the_cached_glyph_is_the_built_glyph() {94 for want in all() {95 assert_eq!(96 glyph(want.char).as_ref(),97 Some(&want),98 "{} drifted",99 want.char100 );101 }102 assert_eq!(glyph('\u{6f22}'), None);103 }104 #[test]105 fn mrlyprod_union_is_x() {106 let mut union = vec![vec!['0'; 5]; 5];107 for c in WORDMARK.chars() {108 let g = glyph(c).unwrap();109 for (y, row) in g.rows.iter().enumerate() {110 for (x, ch) in row.chars().enumerate() {111 if ch == '1' {112 union[y][x] = '1';113 }114 }115 }116 }117 let folded: Vec<String> = union118 .into_iter()119 .map(|row| row.into_iter().collect())120 .collect();121 assert_eq!(folded, glyph('X').unwrap().rows);122 }123 #[test]124 fn lowercase_rounds_corners() {125 let a = glyph('a').unwrap();126 assert_eq!(a.rows, vec!["01110", "10001", "11111", "10001", "00000"]);127 assert_eq!(glyph('A').unwrap().rows[0], "11111");128 }129 #[test]130 fn trim_collapses_blank() {131 let space = glyph(' ').unwrap();132 assert_eq!(trim(&space.rows), vec!["0"; 5]);133 }134 #[test]135 fn trim_drops_edge_columns() {136 let rows = vec!["00100".to_string(), "00100".to_string()];137 assert_eq!(trim(&rows), vec!["1".to_string(), "1".to_string()]);138 }139 #[test]140 fn count_matches_layout() {141 assert_eq!(supported().len(), 108);142 assert_eq!(uppers().len(), 26);143 assert_eq!(lowers().len(), 26);144 assert_eq!(digits().len(), 10);145 assert_eq!(extras().len(), 42);146 assert_eq!(specials().len(), 4);147 }148 #[test]149 fn descenders_flagged() {150 assert!(descends('$'));151 assert!(descends('('));152 assert!(!descends('A'));153 }154 #[test]155 fn json_is_multiline_and_named() {156 let json = to_json(&all());157 assert!(json.contains("\"name\": \"LATIN CAPITAL LETTER A\""));158 assert!(json.contains("\"rows\": [\n"));159 }160}