Skip to main content

retex_engine/
codes.rs

1//! Character codes: IniTeX defaults and LuaTeX's packed math and
2//! delimiter codes.
3//!
4//! LuaTeX reports math codes (`\mathcode`, `\Umathcodenum`) as
5//! `class·2²¹ + family·2²⁴ + character`; a math-active code has class 8.
6//! Delimiter codes pack small and large variants; −1 means "not a
7//! delimiter".
8
9/// Default `\lccode`: ASCII letters map to lowercase.
10pub fn initex_lccode(c: u32) -> i64 {
11    match c {
12        0x41..=0x5A => i64::from(c + 32),
13        0x61..=0x7A => i64::from(c),
14        _ => 0,
15    }
16}
17
18/// Default `\uccode`: ASCII letters map to uppercase.
19pub fn initex_uccode(c: u32) -> i64 {
20    match c {
21        0x41..=0x5A => i64::from(c),
22        0x61..=0x7A => i64::from(c - 32),
23        _ => 0,
24    }
25}
26
27/// Default `\sfcode`: 999 for ASCII uppercase letters, 1000 otherwise.
28pub fn initex_sfcode(c: u32) -> i64 {
29    if (0x41..=0x5A).contains(&c) {
30        999
31    } else {
32        1000
33    }
34}
35
36/// Packs a LuaTeX math code.
37pub const fn math_code(class: u32, fam: u32, chr: u32) -> i64 {
38    ((class as i64) << 21) + ((fam as i64) << 24) + chr as i64
39}
40
41/// The math class of a packed math code.
42pub const fn math_class(code: i64) -> u32 {
43    ((code >> 21) & 7) as u32
44}
45
46/// The family of a packed math code.
47pub const fn math_fam(code: i64) -> u32 {
48    ((code >> 24) & 0xFF) as u32
49}
50
51/// The character of a packed math code.
52pub const fn math_char(code: i64) -> u32 {
53    (code & 0x1F_FFFF) as u32
54}
55
56/// Flag on a `\mathchardef` value (`math_given`) that came from a number
57/// of `"8000` or more: the rest is a packed math code (see
58/// [`crate::scan`]'s `scan_math_given`).
59pub const MATH_GIVEN_PACKED: u32 = 1 << 31;
60
61/// The number `\the` reports for a `math_given` value: TeX's
62/// class·`"1000` + family·`"100` + character, without masking.
63pub const fn math_given_report(g: u32) -> i32 {
64    if g & MATH_GIVEN_PACKED == 0 {
65        return g as i32;
66    }
67    let code = (g & !MATH_GIVEN_PACKED) as i64;
68    (math_class(code) * 0x1000 + math_fam(code) * 0x100 + math_char(code)) as i32
69}
70
71/// The internal math code meaning "math active" (TeX's `"8000`). LuaTeX
72/// reports it as `8·2²¹` (which is also the packed code of family 1,
73/// character 0), but keeps the two apart internally.
74pub const MATH_ACTIVE: i64 = 1 << 40;
75
76/// The value `\the\mathcode` and `\Umathcodenum` report for an internal
77/// math code.
78pub const fn math_code_report(code: i64) -> i32 {
79    if code == MATH_ACTIVE {
80        8 << 21
81    } else {
82        code as i32
83    }
84}
85
86/// Default `\mathcode`: digits are variable family 0, letters variable
87/// family 1, everything else class 0 family 0.
88pub fn initex_mathcode(c: u32) -> i64 {
89    match c {
90        0x30..=0x39 => math_code(7, 0, c),
91        0x41..=0x5A | 0x61..=0x7A => math_code(7, 1, c),
92        _ => i64::from(c),
93    }
94}
95
96/// Default `\delcode`: 0 for `.`, −1 otherwise.
97pub fn initex_delcode(c: u32) -> i64 {
98    if c == u32::from(b'.') { 0 } else { -1 }
99}
100
101/// Converts a TeX 24-bit delimiter code (`"uvvxyy`… as `\delcode` takes
102/// it) to the internal form; −1 stays −1.
103pub fn delcode_from_classic(v: i32) -> i64 {
104    if v < 0 {
105        return -1;
106    }
107    let v = v as i64;
108    let small_fam = (v >> 20) & 0xF;
109    let small_chr = (v >> 12) & 0xFF;
110    let large_fam = (v >> 8) & 0xF;
111    let large_chr = v & 0xFF;
112    // Internal: small (fam, char) and large (fam, char), 8+21 bits each.
113    (small_fam << 53) | (small_chr << 32) | (large_fam << 21) | large_chr | (1 << 62)
114}
115
116/// The value `\the\delcode` reports for an internal delimiter code.
117pub fn delcode_report(v: i64, _cmd: crate::cmd::Cmd) -> i32 {
118    if v < 0 {
119        return -1;
120    }
121    if v & (1 << 62) == 0 {
122        return v as i32;
123    }
124    let small_fam = (v >> 53) & 0xF;
125    let small_chr = (v >> 32) & 0xFF;
126    let large_fam = (v >> 21) & 0xF;
127    let large_chr = v & 0xFF;
128    ((small_fam << 20) | (small_chr << 12) | (large_fam << 8) | large_chr) as i32
129}
130
131/// Whether `\Umath…` parameter `p` is a spacing (muglue) parameter.
132pub fn math_param_is_glue(p: u32) -> bool {
133    (49..49 + 64).contains(&p)
134}
135
136/// Value of an unset `\Umath…` parameter (LuaTeX's `undefined_math_parameter`).
137pub const UNDEFINED_MATH_PARAM: i32 = 0x3FFF_FFFF;
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    #[test]
144    fn packing_matches_luatex() {
145        // Values observed from LuaTeX: \the\mathcode`a = 31457377 etc.
146        assert_eq!(initex_mathcode('a' as u32), 31457377);
147        assert_eq!(initex_mathcode('1' as u32), 14680113);
148        assert_eq!(math_code(1, 2, 0x3B1), 35652529);
149        assert_eq!(math_code_report(MATH_ACTIVE), 16777216);
150    }
151}