Skip to main content

ratex_layout/
spacing.rs

1/// TeX math atom classes, determining inter-element spacing.
2///
3/// From TeXbook pp. 170-171. Also matches KaTeX's DomEnum.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5pub enum MathClass {
6    Ord,   // ordinary
7    Op,    // large operator
8    Bin,   // binary operation
9    Rel,   // relation
10    Open,  // opening delimiter
11    Close, // closing delimiter
12    Punct, // punctuation
13    Inner, // inner (fractions, etc.)
14}
15
16/// Spacing between adjacent math atoms, in mu (1mu = 1/18 em).
17///
18/// From TeXbook p. 170, Table 18.
19/// 0 = no space, 3 = thin space, 4 = medium space, 5 = thick space.
20/// Negative values should never appear here.
21///
22/// Rows = left atom class, Columns = right atom class.
23/// Order: Ord, Op, Bin, Rel, Open, Close, Punct, Inner
24#[rustfmt::skip]
25const SPACING_TABLE: [[i8; 8]; 8] = [
26//       Ord  Op  Bin  Rel Open Close Punct Inner
27/*Ord*/  [ 0,  3,  4,   5,  0,   0,   0,    3],
28/*Op*/   [ 3,  4,  0,   5,  0,   0,   0,    3],
29/*Bin*/  [ 4,  4,  0,   0,  4,   0,   0,    4],
30/*Rel*/  [ 5,  5,  0,   0,  5,   0,   0,    5],
31/*Open*/ [ 0,  0,  0,   0,  0,   0,   0,    0],
32/*Close*/[ 0,  3,  4,   5,  0,   0,   0,    3],
33/*Punct*/[ 3,  3,  0,   5,  3,   3,   3,    3],
34/*Inner*/[ 3,  3,  4,   5,  3,   0,   3,    3],
35];
36
37/// Same table but for tight (script/scriptscript) styles.
38/// In tight mode, only thin spaces (3mu) between Op-Ord and Op-Op are kept.
39#[rustfmt::skip]
40const TIGHT_SPACING_TABLE: [[i8; 8]; 8] = [
41//       Ord  Op  Bin  Rel Open Close Punct Inner
42/*Ord*/  [ 0,  3,  0,   0,  0,   0,   0,    0],
43/*Op*/   [ 3,  3,  0,   0,  0,   0,   0,    0],
44/*Bin*/  [ 0,  0,  0,   0,  0,   0,   0,    0],
45/*Rel*/  [ 0,  0,  0,   0,  0,   0,   0,    0],
46/*Open*/ [ 0,  0,  0,   0,  0,   0,   0,    0],
47/*Close*/[ 0,  3,  0,   0,  0,   0,   0,    0],
48/*Punct*/[ 0,  0,  0,   0,  0,   0,   0,    0],
49/*Inner*/[ 0,  3,  0,   0,  0,   0,   0,    0],
50];
51
52impl MathClass {
53    fn index(self) -> usize {
54        match self {
55            Self::Ord => 0,
56            Self::Op => 1,
57            Self::Bin => 2,
58            Self::Rel => 3,
59            Self::Open => 4,
60            Self::Close => 5,
61            Self::Punct => 6,
62            Self::Inner => 7,
63        }
64    }
65}
66
67/// Get spacing (in mu) between two adjacent math atoms.
68///
69/// `tight` should be true for script and scriptscript styles.
70/// Returns the spacing in mu units (1mu = 1/18 em).
71pub fn atom_spacing(left: MathClass, right: MathClass, tight: bool) -> f64 {
72    let table = if tight {
73        &TIGHT_SPACING_TABLE
74    } else {
75        &SPACING_TABLE
76    };
77    table[left.index()][right.index()] as f64
78}
79
80/// Convert mu to em. 1mu = 1/18 em (at the current style's quad width).
81pub fn mu_to_em(mu: f64, quad: f64) -> f64 {
82    mu * quad / 18.0
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn test_ord_bin_spacing() {
91        assert_eq!(atom_spacing(MathClass::Ord, MathClass::Bin, false), 4.0);
92    }
93
94    #[test]
95    fn test_ord_rel_spacing() {
96        assert_eq!(atom_spacing(MathClass::Ord, MathClass::Rel, false), 5.0);
97    }
98
99    #[test]
100    fn test_ord_ord_no_spacing() {
101        assert_eq!(atom_spacing(MathClass::Ord, MathClass::Ord, false), 0.0);
102    }
103
104    #[test]
105    fn test_tight_eliminates_most_spacing() {
106        assert_eq!(atom_spacing(MathClass::Ord, MathClass::Bin, true), 0.0);
107        assert_eq!(atom_spacing(MathClass::Ord, MathClass::Rel, true), 0.0);
108    }
109
110    #[test]
111    fn test_tight_keeps_op_spacing() {
112        assert_eq!(atom_spacing(MathClass::Ord, MathClass::Op, true), 3.0);
113        assert_eq!(atom_spacing(MathClass::Op, MathClass::Ord, true), 3.0);
114    }
115
116    #[test]
117    fn test_mu_to_em() {
118        let quad = 1.0;
119        assert!((mu_to_em(3.0, quad) - 3.0 / 18.0).abs() < 1e-10);
120        assert!((mu_to_em(4.0, quad) - 4.0 / 18.0).abs() < 1e-10);
121        assert!((mu_to_em(5.0, quad) - 5.0 / 18.0).abs() < 1e-10);
122    }
123}