retex-engine 1.0.0-rc.2

The TeX typesetting engine for retex
Documentation
//! Character codes: IniTeX defaults and LuaTeX's packed math and
//! delimiter codes.
//!
//! LuaTeX reports math codes (`\mathcode`, `\Umathcodenum`) as
//! `class·2²¹ + family·2²⁴ + character`; a math-active code has class 8.
//! Delimiter codes pack small and large variants; −1 means "not a
//! delimiter".

/// Default `\lccode`: ASCII letters map to lowercase.
pub fn initex_lccode(c: u32) -> i64 {
    match c {
        0x41..=0x5A => i64::from(c + 32),
        0x61..=0x7A => i64::from(c),
        _ => 0,
    }
}

/// Default `\uccode`: ASCII letters map to uppercase.
pub fn initex_uccode(c: u32) -> i64 {
    match c {
        0x41..=0x5A => i64::from(c),
        0x61..=0x7A => i64::from(c - 32),
        _ => 0,
    }
}

/// Default `\sfcode`: 999 for ASCII uppercase letters, 1000 otherwise.
pub fn initex_sfcode(c: u32) -> i64 {
    if (0x41..=0x5A).contains(&c) {
        999
    } else {
        1000
    }
}

/// Packs a LuaTeX math code.
pub const fn math_code(class: u32, fam: u32, chr: u32) -> i64 {
    ((class as i64) << 21) + ((fam as i64) << 24) + chr as i64
}

/// The math class of a packed math code.
pub const fn math_class(code: i64) -> u32 {
    ((code >> 21) & 7) as u32
}

/// The family of a packed math code.
pub const fn math_fam(code: i64) -> u32 {
    ((code >> 24) & 0xFF) as u32
}

/// The character of a packed math code.
pub const fn math_char(code: i64) -> u32 {
    (code & 0x1F_FFFF) as u32
}

/// Flag on a `\mathchardef` value (`math_given`) that came from a number
/// of `"8000` or more: the rest is a packed math code (see
/// [`crate::scan`]'s `scan_math_given`).
pub const MATH_GIVEN_PACKED: u32 = 1 << 31;

/// The number `\the` reports for a `math_given` value: TeX's
/// class·`"1000` + family·`"100` + character, without masking.
pub const fn math_given_report(g: u32) -> i32 {
    if g & MATH_GIVEN_PACKED == 0 {
        return g as i32;
    }
    let code = (g & !MATH_GIVEN_PACKED) as i64;
    (math_class(code) * 0x1000 + math_fam(code) * 0x100 + math_char(code)) as i32
}

/// The internal math code meaning "math active" (TeX's `"8000`). LuaTeX
/// reports it as `8·2²¹` (which is also the packed code of family 1,
/// character 0), but keeps the two apart internally.
pub const MATH_ACTIVE: i64 = 1 << 40;

/// The value `\the\mathcode` and `\Umathcodenum` report for an internal
/// math code.
pub const fn math_code_report(code: i64) -> i32 {
    if code == MATH_ACTIVE {
        8 << 21
    } else {
        code as i32
    }
}

/// Default `\mathcode`: digits are variable family 0, letters variable
/// family 1, everything else class 0 family 0.
pub fn initex_mathcode(c: u32) -> i64 {
    match c {
        0x30..=0x39 => math_code(7, 0, c),
        0x41..=0x5A | 0x61..=0x7A => math_code(7, 1, c),
        _ => i64::from(c),
    }
}

/// Default `\delcode`: 0 for `.`, −1 otherwise.
pub fn initex_delcode(c: u32) -> i64 {
    if c == u32::from(b'.') { 0 } else { -1 }
}

/// Converts a TeX 24-bit delimiter code (`"uvvxyy`… as `\delcode` takes
/// it) to the internal form; −1 stays −1.
pub fn delcode_from_classic(v: i32) -> i64 {
    if v < 0 {
        return -1;
    }
    let v = v as i64;
    let small_fam = (v >> 20) & 0xF;
    let small_chr = (v >> 12) & 0xFF;
    let large_fam = (v >> 8) & 0xF;
    let large_chr = v & 0xFF;
    // Internal: small (fam, char) and large (fam, char), 8+21 bits each.
    (small_fam << 53) | (small_chr << 32) | (large_fam << 21) | large_chr | (1 << 62)
}

/// The value `\the\delcode` reports for an internal delimiter code.
pub fn delcode_report(v: i64, _cmd: crate::cmd::Cmd) -> i32 {
    if v < 0 {
        return -1;
    }
    if v & (1 << 62) == 0 {
        return v as i32;
    }
    let small_fam = (v >> 53) & 0xF;
    let small_chr = (v >> 32) & 0xFF;
    let large_fam = (v >> 21) & 0xF;
    let large_chr = v & 0xFF;
    ((small_fam << 20) | (small_chr << 12) | (large_fam << 8) | large_chr) as i32
}

/// Whether `\Umath…` parameter `p` is a spacing (muglue) parameter.
pub fn math_param_is_glue(p: u32) -> bool {
    (49..49 + 64).contains(&p)
}

/// Value of an unset `\Umath…` parameter (LuaTeX's `undefined_math_parameter`).
pub const UNDEFINED_MATH_PARAM: i32 = 0x3FFF_FFFF;

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

    #[test]
    fn packing_matches_luatex() {
        // Values observed from LuaTeX: \the\mathcode`a = 31457377 etc.
        assert_eq!(initex_mathcode('a' as u32), 31457377);
        assert_eq!(initex_mathcode('1' as u32), 14680113);
        assert_eq!(math_code(1, 2, 0x3B1), 35652529);
        assert_eq!(math_code_report(MATH_ACTIVE), 16777216);
    }
}