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 { &TIGHT_SPACING_TABLE } else { &SPACING_TABLE };
73    table[left.index()][right.index()] as f64
74}
75
76/// Convert mu to em. 1mu = 1/18 em (at the current style's quad width).
77pub fn mu_to_em(mu: f64, quad: f64) -> f64 {
78    mu * quad / 18.0
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn test_ord_bin_spacing() {
87        assert_eq!(atom_spacing(MathClass::Ord, MathClass::Bin, false), 4.0);
88    }
89
90    #[test]
91    fn test_ord_rel_spacing() {
92        assert_eq!(atom_spacing(MathClass::Ord, MathClass::Rel, false), 5.0);
93    }
94
95    #[test]
96    fn test_ord_ord_no_spacing() {
97        assert_eq!(atom_spacing(MathClass::Ord, MathClass::Ord, false), 0.0);
98    }
99
100    #[test]
101    fn test_tight_eliminates_most_spacing() {
102        assert_eq!(atom_spacing(MathClass::Ord, MathClass::Bin, true), 0.0);
103        assert_eq!(atom_spacing(MathClass::Ord, MathClass::Rel, true), 0.0);
104    }
105
106    #[test]
107    fn test_tight_keeps_op_spacing() {
108        assert_eq!(atom_spacing(MathClass::Ord, MathClass::Op, true), 3.0);
109        assert_eq!(atom_spacing(MathClass::Op, MathClass::Ord, true), 3.0);
110    }
111
112    #[test]
113    fn test_mu_to_em() {
114        let quad = 1.0;
115        assert!((mu_to_em(3.0, quad) - 3.0 / 18.0).abs() < 1e-10);
116        assert!((mu_to_em(4.0, quad) - 4.0 / 18.0).abs() < 1e-10);
117        assert!((mu_to_em(5.0, quad) - 5.0 / 18.0).abs() < 1e-10);
118    }
119}