mathra 1.1.1

Equation converter: math LaTeX to a tree of meaning and accessible SVG, with locale rules (English and pt-BR).
Documentation
//! Letters with a variant (italic, bold, double-struck…) through Unicode's math blocks (U+1D400).

use crate::tree::Variant;

/// L-LET-1: Latin and lowercase Greek letters in math italic (block U+1D400).
pub fn math_italic(c: char) -> char {
    character(c, Variant::Italic)
}

pub fn character(c: char, variant: Variant) -> char {
    use Variant::*;
    // historical exceptions: letters that already existed in the Letterlike Symbols block
    let exception = match (variant, c) {
        (Italic, 'h') => Some('β„Ž'),
        (Italic, 'ı') => Some('𝚀'),
        (Italic, 'Θ·') => Some('πš₯'),
        (Calligraphic, 'B') => Some('ℬ'),
        (Calligraphic, 'E') => Some('β„°'),
        (Calligraphic, 'F') => Some('β„±'),
        (Calligraphic, 'H') => Some('β„‹'),
        (Calligraphic, 'I') => Some('ℐ'),
        (Calligraphic, 'L') => Some('β„’'),
        (Calligraphic, 'M') => Some('β„³'),
        (Calligraphic, 'R') => Some('β„›'),
        (Calligraphic, 'e') => Some('β„―'),
        (Calligraphic, 'g') => Some('β„Š'),
        (Calligraphic, 'o') => Some('β„΄'),
        (Fraktur, 'C') => Some('β„­'),
        (Fraktur, 'H') => Some('β„Œ'),
        (Fraktur, 'I') => Some('β„‘'),
        (Fraktur, 'R') => Some('β„œ'),
        (Fraktur, 'Z') => Some('ℨ'),
        (DoubleStruck, 'C') => Some('β„‚'),
        (DoubleStruck, 'H') => Some('ℍ'),
        (DoubleStruck, 'N') => Some('β„•'),
        (DoubleStruck, 'P') => Some('β„™'),
        (DoubleStruck, 'Q') => Some('β„š'),
        (DoubleStruck, 'R') => Some('ℝ'),
        (DoubleStruck, 'Z') => Some('β„€'),
        _ => None,
    };
    if let Some(e) = exception {
        return e;
    }
    let (uppercase, lowercase, digit, greek_upper, greek_lower) = match variant {
        Upright => return c,
        Italic => (0x1D434, 0x1D44E, None, None, Some(0x1D6FC)),
        Bold => (0x1D400, 0x1D41A, Some(0x1D7CE), Some(0x1D6A8), Some(0x1D6C2)),
        BoldItalic => (0x1D468, 0x1D482, Some(0x1D7CE), Some(0x1D71C), Some(0x1D736)),
        Calligraphic => (0x1D49C, 0x1D4B6, None, None, None),
        Fraktur => (0x1D504, 0x1D51E, None, None, None),
        DoubleStruck => (0x1D538, 0x1D552, Some(0x1D7D8), None, None),
        SansSerif => (0x1D5A0, 0x1D5BA, Some(0x1D7E2), None, None),
        Monospace => (0x1D670, 0x1D68A, Some(0x1D7F6), None, None),
    };
    let code = match c {
        'A'..='Z' => Some(uppercase + (c as u32 - 'A' as u32)),
        'a'..='z' => Some(lowercase + (c as u32 - 'a' as u32)),
        '0'..='9' => digit.map(|d| d + (c as u32 - '0' as u32)),
        'Ξ‘'..='Ξ©' => greek_upper.map(|g| g + (c as u32 - 'Ξ‘' as u32)),
        'Ξ±'..='Ο‰' => greek_lower.map(|g| g + (c as u32 - 'Ξ±' as u32)),
        _ => None,
    };
    code.and_then(char::from_u32).unwrap_or(c)
}