Skip to main content

retex_engine/
mathdisplay.rs

1//! Displaying noads (*TeX: The Program* §690–§698) in LuaTeX's format.
2//!
3//! LuaTeX keeps TeX82's layout: a noad prints its type (`\mathord`,
4//! `\mathop\limits`, …) followed by its fields, each on a line prefixed by
5//! the recursion history extended with `.` (nucleus), `^` (superscript),
6//! `_` (subscript), `\` (numerator), `/` (denominator or radical degree).
7//! Observed differences from TeX82:
8//!
9//! * styles include the cramped ones (`\crampedtextstyle`);
10//! * radicals print their primitive (`\radical`, `\Uroot`,
11//!   `\Uoverdelimiter`, …), math accents print `\Umathaccent` followed by
12//!   ` bottom`, ` both`, ` fixed ` as applicable;
13//! * `\middle` noads print `\middle`.
14//!
15//! LuaTeX does not initialize some fields of delimiter nodes, so it
16//! sometimes prints garbage such as `height=0.00143` or `[ exact ]` before a
17//! delimiter code; retex prints the clean form.
18
19use crate::Engine;
20use crate::nodes::{
21    DEFAULT_CODE, NodeData, NodeRef, accent_sub, fence_sub, id, noad_sub, radical_sub,
22};
23
24/// The names of the eight math styles.
25pub const STYLE_NAMES: [&str; 8] = [
26    "displaystyle",
27    "crampeddisplaystyle",
28    "textstyle",
29    "crampedtextstyle",
30    "scriptstyle",
31    "crampedscriptstyle",
32    "scriptscriptstyle",
33    "crampedscriptscriptstyle",
34];
35
36impl Engine {
37    /// Prints a style (`print_style`).
38    pub fn print_style(&mut self, s: u32) {
39        match STYLE_NAMES.get(s as usize) {
40            Some(n) => self.print_esc_str(n),
41            None => self.printer.print_str("Unknown style!"),
42        }
43    }
44
45    /// Prints family and character of a `math_char` kernel
46    /// (`print_fam_and_char`).
47    pub fn print_fam_and_char(&mut self, p: NodeRef) {
48        let (fam, chr) = match self.nodes.data(p) {
49            NodeData::MathChar { fam, chr } => (*fam, *chr),
50            _ => (0, 0),
51        };
52        self.print_esc_str("fam");
53        self.printer.print_int(i64::from(fam));
54        self.printer.print_char(' ' as u32);
55        self.printer.print_char(chr);
56    }
57
58    /// Prints a delimiter as a hexadecimal value (`print_delimiter`).
59    pub fn print_delimiter(&mut self, p: NodeRef) {
60        let (sf, sc, lf, lc) = self.delim_fields(p);
61        let mut a = i64::from(sf) * 256 + i64::from(sc);
62        a = a * 0x1000 + i64::from(lf) * 256 + i64::from(lc);
63        if a < 0 {
64            self.printer.print_int(a);
65        } else {
66            self.printer.print_hex(a);
67        }
68    }
69
70    /// The fields (small family, small character, large family, large
71    /// character) of a delimiter node; zeros for null.
72    pub fn delim_fields(&self, p: NodeRef) -> (u32, u32, u32, u32) {
73        if p.is_null() {
74            return (0, 0, 0, 0);
75        }
76        match self.nodes.data(p) {
77            NodeData::Delim {
78                small_fam,
79                small_char,
80                large_fam,
81                large_char,
82            } => (*small_fam, *small_char, *large_fam, *large_char),
83            _ => (0, 0, 0, 0),
84        }
85    }
86
87    /// Displays a noad field (`print_subsidiary_data`).
88    fn print_subsidiary_data(&mut self, p: NodeRef, c: u8) {
89        if self.ts.show_prefix.len() as i64 >= i64::from(self.depth_threshold) {
90            if !p.is_null() {
91                self.printer.print_str(" []");
92            }
93            return;
94        }
95        if p.is_null() {
96            return;
97        }
98        self.ts.show_prefix.push(c);
99        match self.nodes.data(p) {
100            NodeData::MathChar { .. } => {
101                self.printer.print_ln();
102                self.print_current_prefix();
103                self.print_fam_and_char(p);
104            }
105            NodeData::SubList { head } => {
106                let head = *head;
107                if self.nodes.id(p) == id::SUB_MLIST && head.is_null() {
108                    self.printer.print_ln();
109                    self.print_current_prefix();
110                    self.printer.print_str("{}");
111                } else {
112                    self.show_node_list(head);
113                }
114            }
115            _ => {}
116        }
117        self.ts.show_prefix.pop();
118    }
119
120    fn print_current_prefix(&mut self) {
121        let s = std::mem::take(&mut self.ts.show_prefix);
122        for &b in &s {
123            self.printer.print_char(u32::from(b));
124        }
125        self.ts.show_prefix = s;
126    }
127
128    fn show_list_with(&mut self, p: NodeRef, c: u8) {
129        self.ts.show_prefix.push(c);
130        self.show_node_list(p);
131        self.ts.show_prefix.pop();
132    }
133
134    /// Displays a math node or noad; returns false if `p` is not one.
135    pub fn display_math_node(&mut self, p: NodeRef) -> bool {
136        let nid = self.nodes.id(p);
137        let sub = self.nodes.subtype(p);
138        match self.nodes.data(p).clone() {
139            NodeData::Style { style } => self.print_style(u32::from(style)),
140            NodeData::Choice(c) => {
141                self.print_esc_str("mathchoice");
142                for (l, ch) in c.iter().zip([b'D', b'T', b'S', b's']) {
143                    self.show_list_with(*l, ch);
144                }
145            }
146            NodeData::Noad(n) => {
147                match nid {
148                    id::RADICAL => {
149                        let name = match sub {
150                            radical_sub::UROOT => "Uroot",
151                            radical_sub::UUNDERDELIMITER => "Uunderdelimiter",
152                            radical_sub::UOVERDELIMITER => "Uoverdelimiter",
153                            radical_sub::UDELIMITERUNDER => "Udelimiterunder",
154                            radical_sub::UDELIMITEROVER => "Udelimiterover",
155                            radical_sub::UHEXTENSIBLE => "Uhextensible",
156                            _ => "radical",
157                        };
158                        self.print_esc_str(name);
159                        self.print_delimiter(n.left);
160                        if n.width != 0 {
161                            self.printer.print_str("width=");
162                            self.printer.print_scaled(n.width);
163                            self.printer.print_char(' ' as u32);
164                        }
165                        if !n.degree.is_null() {
166                            self.print_subsidiary_data(n.degree, b'/');
167                        }
168                    }
169                    id::ACCENT => {
170                        self.print_esc_str("Umathaccent");
171                        let (top, bot) = (!n.accent.is_null(), !n.bot_accent.is_null());
172                        if top && bot {
173                            self.printer.print_str(" both");
174                        } else if bot {
175                            self.printer.print_str(" bottom");
176                        }
177                        if n.options & crate::nodes::noad_opt::OVERLAY != 0 {
178                            self.printer.print_str(" overlay");
179                        }
180                        if n.fraction != 0 {
181                            self.printer.print_str(" fraction=");
182                            self.printer.print_int(i64::from(n.fraction));
183                            self.printer.print_char(' ' as u32);
184                        }
185                        if top {
186                            if sub == accent_sub::FIXED_TOP || sub == accent_sub::FIXED_BOTH {
187                                self.printer.print_str(" fixed ");
188                            }
189                            self.print_fam_and_char(n.accent);
190                        }
191                        if bot {
192                            if sub == accent_sub::FIXED_BOTTOM || sub == accent_sub::FIXED_BOTH {
193                                self.printer.print_str(" fixed ");
194                            }
195                            self.print_fam_and_char(n.bot_accent);
196                        }
197                    }
198                    _ => {
199                        let name = match sub {
200                            noad_sub::ORD => "mathord",
201                            noad_sub::OP_DISPLAY_LIMITS
202                            | noad_sub::OP_LIMITS
203                            | noad_sub::OP_NO_LIMITS => "mathop",
204                            noad_sub::BIN => "mathbin",
205                            noad_sub::REL => "mathrel",
206                            noad_sub::OPEN => "mathopen",
207                            noad_sub::CLOSE => "mathclose",
208                            noad_sub::PUNCT => "mathpunct",
209                            noad_sub::INNER => "mathinner",
210                            noad_sub::UNDER => "underline",
211                            noad_sub::OVER => "overline",
212                            _ => "vcenter",
213                        };
214                        self.print_esc_str(name);
215                        if sub == noad_sub::OP_LIMITS {
216                            self.print_esc_str("limits");
217                        } else if sub == noad_sub::OP_NO_LIMITS {
218                            self.print_esc_str("nolimits");
219                        }
220                    }
221                }
222                self.print_subsidiary_data(n.nucleus, b'.');
223                self.print_subsidiary_data(n.sup, b'^');
224                self.print_subsidiary_data(n.sub, b'_');
225            }
226            NodeData::Fence(f) => {
227                self.print_esc_str(match sub {
228                    fence_sub::LEFT => "left",
229                    fence_sub::MIDDLE | fence_sub::NO => "middle",
230                    _ => "right",
231                });
232                self.print_delimiter(f.delim);
233            }
234            NodeData::Fraction(f) => {
235                self.print_esc_str("fraction, thickness ");
236                if f.thickness == DEFAULT_CODE {
237                    self.printer.print_str("= default");
238                } else {
239                    self.printer.print_scaled(f.thickness);
240                }
241                if self.delim_fields(f.left) != (0, 0, 0, 0) {
242                    self.printer.print_str(", left-delimiter ");
243                    self.print_delimiter(f.left);
244                }
245                if self.delim_fields(f.right) != (0, 0, 0, 0) {
246                    self.printer.print_str(", right-delimiter ");
247                    self.print_delimiter(f.right);
248                }
249                self.print_subsidiary_data(f.num, b'\\');
250                self.print_subsidiary_data(f.denom, b'/');
251            }
252            _ => return false,
253        }
254        true
255    }
256}