basalt-tui 0.7.0

Basalt TUI application for Obsidian notes.
Documentation
//! Text stylizing.
//!
//! The [`stylize`] function allows converting regular A-z letters and 0โ€“9 numbers into stylized
//! variants. The text is converted according to the [`FontStyle`] enum.
//!
//! # Examples
//!
//! ```
//! use basalt_tui::stylized_text::{FontStyle, stylize};
//!
//! assert_eq!(stylize("My Heading", FontStyle::FrakturBold), "๐•ธ๐–ž ๐•ณ๐–Š๐–†๐–‰๐–Ž๐–“๐–Œ");
//! ```

/// Enum representing different font styles.
///
/// - BlackBoardBold (๐”น๐•๐•’๐•”๐•œ๐”น๐• ๐•’๐•ฃ๐••๐”น๐• ๐•๐••)
/// - FrakturBold (๐•ฑ๐–—๐–†๐–๐–™๐–š๐–—๐•ญ๐–”๐–‘๐–‰)
/// - Script (๐“ข๐“ฌ๐“ป๐“ฒ๐“น๐“ฝ)
#[derive(Debug, Clone, Copy)]
pub enum FontStyle {
    /// Blackboard Bold (Double-struck) style (e.g., ๐•‹๐•š๐•ฅ๐•๐•–).
    BlackBoardBold,
    /// Bold Fraktur style. (e.g., ๐•ฟ๐–Ž๐–™๐–‘๐–Š)
    FrakturBold,
    /// Script style. (e.g., ๐“ฃ๐“ฒ๐“ฝ๐“ต๐“ฎ)
    Script,
}

/// Stylizes the given input string using the specified [`FontStyle`].
///
/// Each character in the input is mapped to its corresponding stylized Unicode character based on
/// the provided style. Characters that do not have a stylized equivalent are returned unchanged.
///
/// # Examples
///
/// ```
/// use basalt_tui::stylized_text::{FontStyle, stylize};
///
/// assert_eq!(stylize("Black Board Bold", FontStyle::BlackBoardBold), "๐”น๐•๐•’๐•”๐•œ ๐”น๐• ๐•’๐•ฃ๐•• ๐”น๐• ๐•๐••");
/// assert_eq!(stylize("Fraktur Bold", FontStyle::FrakturBold), "๐•ฑ๐–—๐–†๐–๐–™๐–š๐–— ๐•ญ๐–”๐–‘๐–‰");
/// assert_eq!(stylize("Script", FontStyle::Script), "๐“ข๐“ฌ๐“ป๐“ฒ๐“น๐“ฝ");
/// ```
pub fn stylize(input: &str, style: FontStyle) -> String {
    input.chars().map(|c| stylize_char(c, style)).collect()
}

/// Returns the stylized Unicode character for a given `char` and [`FontStyle`].
///
/// Letters between A-z and number 0-9 are stylized. Characters outside the stylized range (e.g.,
/// punctuation) are returned as-is.
///
/// To find the corresponding stylized character, we add the remainder to the unicode character
/// range, which is achieved by subtracting the start position from the input `char`.
fn stylize_char(c: char, style: FontStyle) -> char {
    match style {
        FontStyle::BlackBoardBold => match c {
            'C' => char::from_u32(0x2102),
            'H' => char::from_u32(0x210D),
            'N' => char::from_u32(0x2115),
            'P' => char::from_u32(0x2119),
            'Q' => char::from_u32(0x211A),
            'R' => char::from_u32(0x211D),
            'Z' => char::from_u32(0x2124),
            'A'..='Z' => char::from_u32(0x1D538 + (c as u32 - 'A' as u32)),
            'a'..='z' => char::from_u32(0x1D552 + (c as u32 - 'a' as u32)),
            '0'..='9' => char::from_u32(0x1D7D8 + (c as u32 - '0' as u32)),
            _ => None,
        },
        FontStyle::FrakturBold => match c {
            'A'..='Z' => char::from_u32(0x1D56C + (c as u32 - 'A' as u32)),
            'a'..='z' => char::from_u32(0x1D586 + (c as u32 - 'a' as u32)),
            '0'..='9' => char::from_u32(0x1D7CE + (c as u32 - '0' as u32)),
            _ => None,
        },
        FontStyle::Script => match c {
            'A'..='Z' => char::from_u32(0x1D4D0 + (c as u32 - 'A' as u32)),
            'a'..='z' => char::from_u32(0x1D4EA + (c as u32 - 'a' as u32)),
            '0'..='9' => char::from_u32(0x1D7CE + (c as u32 - '0' as u32)),
            _ => None,
        },
    }
    .unwrap_or(c)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_stylize() {
        let text = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        let tests = [
            (
                FontStyle::Script,
                "๐“๐“‘๐“’๐““๐“”๐“•๐“–๐“—๐“˜๐“™๐“š๐“›๐“œ๐“๐“ž๐“Ÿ๐“ ๐“ก๐“ข๐“ฃ๐“ค๐“ฅ๐“ฆ๐“ง๐“จ๐“ฉ๐“ช๐“ซ๐“ฌ๐“ญ๐“ฎ๐“ฏ๐“ฐ๐“ฑ๐“ฒ๐“ณ๐“ด๐“ต๐“ถ๐“ท๐“ธ๐“น๐“บ๐“ป๐“ผ๐“ฝ๐“พ๐“ฟ๐”€๐”๐”‚๐”ƒ๐ŸŽ๐Ÿ๐Ÿ๐Ÿ‘๐Ÿ’๐Ÿ“๐Ÿ”๐Ÿ•๐Ÿ–๐Ÿ—",
            ),
            (
                FontStyle::FrakturBold,
                "๐•ฌ๐•ญ๐•ฎ๐•ฏ๐•ฐ๐•ฑ๐•ฒ๐•ณ๐•ด๐•ต๐•ถ๐•ท๐•ธ๐•น๐•บ๐•ป๐•ผ๐•ฝ๐•พ๐•ฟ๐–€๐–๐–‚๐–ƒ๐–„๐–…๐–†๐–‡๐–ˆ๐–‰๐–Š๐–‹๐–Œ๐–๐–Ž๐–๐–๐–‘๐–’๐–“๐–”๐–•๐––๐–—๐–˜๐–™๐–š๐–›๐–œ๐–๐–ž๐–Ÿ๐ŸŽ๐Ÿ๐Ÿ๐Ÿ‘๐Ÿ’๐Ÿ“๐Ÿ”๐Ÿ•๐Ÿ–๐Ÿ—",
            ),
            (
                FontStyle::BlackBoardBold,
                "๐”ธ๐”นโ„‚๐”ป๐”ผ๐”ฝ๐”พโ„๐•€๐•๐•‚๐•ƒ๐•„โ„•๐•†โ„™โ„šโ„๐•Š๐•‹๐•Œ๐•๐•Ž๐•๐•โ„ค๐•’๐•“๐•”๐••๐•–๐•—๐•˜๐•™๐•š๐•›๐•œ๐•๐•ž๐•Ÿ๐• ๐•ก๐•ข๐•ฃ๐•ค๐•ฅ๐•ฆ๐•ง๐•จ๐•ฉ๐•ช๐•ซ๐Ÿ˜๐Ÿ™๐Ÿš๐Ÿ›๐Ÿœ๐Ÿ๐Ÿž๐ŸŸ๐Ÿ ๐Ÿก",
            ),
        ];

        tests
            .iter()
            .for_each(|test| assert_eq!(stylize(text, test.0), test.1));
    }
}