glyph.rs

4.2 kB · rust · 143 lines

1use super::bitmaps::{DIGITS, EXTRAS, SPECIALS, UPPERS};2use serde::{Deserialize, Serialize};34/// One character's pixel bitmap.5#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]6pub struct Glyph {7    /// The character the glyph draws.8    pub char: char,9    /// The bitmap rows of '0' and '1' characters.10    pub rows: Vec<String>,11}1213impl Glyph {14    /// Builds a glyph from its character and rows.15    pub fn new(char: char, rows: Vec<String>) -> Glyph {16        Glyph { char, rows }17    }18    /// Returns the cell width of the first row, or 0 for an empty glyph.19    pub fn width(&self) -> usize {20        self.rows.first().map_or(0, |row| row.chars().count())21    }22    /// Returns the number of rows.23    pub fn height(&self) -> usize {24        self.rows.len()25    }26}2728/// The characters that dip below the five-row baseline.29pub(crate) const DESCENDERS: &[char] = &[30    '@', '$', '\u{00a9}', '\u{00ae}', '(', ')', '[', ']', '{', '}',31];3233/// Returns whether the character dips below the baseline.34pub(crate) fn descends(c: char) -> bool {35    DESCENDERS.contains(&c)36}3738/// Cuts blank edge columns from a bitmap, collapsing an all-blank one to a single '0' column; a row shorter than the cut keeps what it has.39pub fn trim(rows: &[String]) -> Vec<String> {40    let grid: Vec<Vec<char>> = rows.iter().map(|row| row.chars().collect()).collect();41    let width = grid.iter().map(Vec::len).max().unwrap_or(0);42    if width == 0 {43        return rows.to_vec();44    }45    let lit = |col: usize| grid.iter().any(|row| row.get(col) == Some(&'1'));46    let (Some(start), Some(end)) = (47        (0..width).find(|&col| lit(col)),48        (0..width).rev().find(|&col| lit(col)),49    ) else {50        return grid.iter().map(|_| "0".to_string()).collect();51    };52    grid.iter()53        .map(|row| row.iter().skip(start).take(end + 1 - start).collect())54        .collect()55}5657/// Blanks the four corner cells of an uppercase bitmap into its rounded lowercase form.58pub fn lower(rows: &[&str]) -> Vec<String> {59    let mut grid: Vec<Vec<char>> = rows.iter().map(|row| row.chars().collect()).collect();60    let last = grid.len().saturating_sub(1);61    for y in [0, last] {62        if let Some(row) = grid.get_mut(y) {63            if let Some(first) = row.first_mut() {64                *first = '0';65            }66            if let Some(end) = row.last_mut() {67                *end = '0';68            }69        }70    }71    grid.into_iter()72        .map(|row| row.into_iter().collect())73        .collect()74}7576fn rows_of(rows: &[&str]) -> Vec<String> {77    rows.iter().map(|row| row.to_string()).collect()78}7980/// Builds the twenty-six uppercase glyphs.81pub fn uppers() -> Vec<Glyph> {82    UPPERS83        .iter()84        .map(|&(c, rows)| Glyph::new(c, rows_of(rows)))85        .collect()86}8788/// Builds the twenty-six lowercase glyphs by rounding the uppers' corners.89pub fn lowers() -> Vec<Glyph> {90    UPPERS91        .iter()92        .map(|&(c, rows)| {93            let lowered = c.to_ascii_lowercase();94            Glyph::new(lowered, lower(rows))95        })96        .collect()97}9899/// Builds the ten digit glyphs.100pub fn digits() -> Vec<Glyph> {101    DIGITS102        .iter()103        .map(|&(c, rows)| Glyph::new(c, rows_of(rows)))104        .collect()105}106107/// Builds the punctuation, symbol and arrow glyphs.108pub fn extras() -> Vec<Glyph> {109    EXTRAS110        .iter()111        .map(|&(c, rows)| Glyph::new(c, rows_of(rows)))112        .collect()113}114115/// Builds the four seven-row glyphs: dollar, at, copyright and registered.116pub fn specials() -> Vec<Glyph> {117    SPECIALS118        .iter()119        .map(|&(c, rows)| Glyph::new(c, rows_of(rows)))120        .collect()121}122123/// Builds every glyph in font order: uppers, lowers, digits, extras, specials.124pub fn all() -> Vec<Glyph> {125    let mut glyphs = uppers();126    glyphs.extend(lowers());127    glyphs.extend(digits());128    glyphs.extend(extras());129    glyphs.extend(specials());130    glyphs131}132133#[cfg(test)]134mod tests {135    use super::*;136137    #[test]138    fn trim_cuts_blank_edge_columns_and_collapses_an_empty_bitmap() {139        let rows = vec!["00100".to_string(), "00100".to_string()];140        assert_eq!(trim(&rows), vec!["1".to_string(), "1".to_string()]);141        assert_eq!(trim(&vec!["00000".to_string(); 5]), vec!["0"; 5]);142    }143}