Skip to main content

latex_rust/layout/
style.rs

1//! TeX math style (Appendix G).
2
3/// Math style, including cramped variants.
4///
5/// Display and text are script-level 0; `\scriptstyle` is 1; `\scriptscriptstyle`
6/// is 2. Cramped variants are used under radicals and in denominators.
7///
8/// # Examples
9///
10/// ```
11/// use latex_rust::MathStyle;
12///
13/// assert_eq!(MathStyle::Display.numerator().gold(), "text");
14/// assert!(MathStyle::Display.cramp().is_cramped());
15/// ```
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub enum MathStyle {
18    /// Display math (`$$`, `\[`).
19    Display,
20    /// Cramped display (e.g. under a radical in display).
21    DisplayCramped,
22    /// Text / inline math (`$`, `\(`).
23    Text,
24    /// Cramped text.
25    TextCramped,
26    /// First-level script.
27    Script,
28    /// Cramped script.
29    ScriptCramped,
30    /// Second-level script.
31    ScriptScript,
32    /// Cramped scriptscript.
33    ScriptScriptCramped,
34}
35
36impl MathStyle {
37    /// True for the two display styles.
38    #[must_use]
39    pub fn is_display(self) -> bool {
40        matches!(self, Self::Display | Self::DisplayCramped)
41    }
42
43    /// True when cramped.
44    #[must_use]
45    pub fn is_cramped(self) -> bool {
46        matches!(
47            self,
48            Self::DisplayCramped
49                | Self::TextCramped
50                | Self::ScriptCramped
51                | Self::ScriptScriptCramped
52        )
53    }
54
55    /// Script nest level: 0 text/display, 1 script, 2 scriptscript.
56    #[must_use]
57    pub fn script_level(self) -> u8 {
58        match self {
59            Self::Display | Self::DisplayCramped | Self::Text | Self::TextCramped => 0,
60            Self::Script | Self::ScriptCramped => 1,
61            Self::ScriptScript | Self::ScriptScriptCramped => 2,
62        }
63    }
64
65    /// Cramped form of this style.
66    #[must_use]
67    pub fn cramp(self) -> Self {
68        match self {
69            Self::Display => Self::DisplayCramped,
70            Self::Text => Self::TextCramped,
71            Self::Script => Self::ScriptCramped,
72            Self::ScriptScript => Self::ScriptScriptCramped,
73            other => other,
74        }
75    }
76
77    /// Style for a superscript or subscript of this style.
78    #[must_use]
79    pub fn into_script(self) -> Self {
80        let cramped = self.is_cramped();
81        match self.script_level() {
82            0 => {
83                if cramped {
84                    Self::ScriptCramped
85                } else {
86                    Self::Script
87                }
88            }
89            _ => {
90                if cramped {
91                    Self::ScriptScriptCramped
92                } else {
93                    Self::ScriptScript
94                }
95            }
96        }
97    }
98
99    /// Numerator style (TeX: display → text, otherwise one script tighter; not cramped).
100    #[must_use]
101    pub fn numerator(self) -> Self {
102        if self.is_display() {
103            Self::Text
104        } else {
105            self.into_script()
106        }
107    }
108
109    /// Denominator style (numerator style, cramped).
110    #[must_use]
111    pub fn denominator(self) -> Self {
112        self.numerator().cramp()
113    }
114
115    /// Gold name.
116    #[must_use]
117    pub fn gold(self) -> &'static str {
118        match self {
119            Self::Display => "display",
120            Self::DisplayCramped => "display-cramped",
121            Self::Text => "text",
122            Self::TextCramped => "text-cramped",
123            Self::Script => "script",
124            Self::ScriptCramped => "script-cramped",
125            Self::ScriptScript => "scriptscript",
126            Self::ScriptScriptCramped => "scriptscript-cramped",
127        }
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::MathStyle;
134
135    #[test]
136    fn style_machine_gold() {
137        assert_eq!(MathStyle::Display.numerator().gold(), "text");
138        assert_eq!(MathStyle::Display.denominator().gold(), "text-cramped");
139        assert_eq!(MathStyle::Text.numerator().gold(), "script");
140        assert_eq!(MathStyle::Text.denominator().gold(), "script-cramped");
141        assert_eq!(MathStyle::Script.numerator().gold(), "scriptscript");
142        assert_eq!(
143            MathStyle::Script.denominator().gold(),
144            "scriptscript-cramped"
145        );
146        assert_eq!(MathStyle::Display.into_script().gold(), "script");
147        assert_eq!(
148            MathStyle::DisplayCramped.into_script().gold(),
149            "script-cramped"
150        );
151        assert_eq!(MathStyle::Text.cramp().gold(), "text-cramped");
152        assert_eq!(MathStyle::Script.into_script().gold(), "scriptscript");
153        assert!(MathStyle::Display.cramp().is_cramped());
154        assert_eq!(MathStyle::Display.script_level(), 0);
155        assert_eq!(MathStyle::Script.script_level(), 1);
156        assert_eq!(MathStyle::ScriptScript.script_level(), 2);
157    }
158}