Skip to main content

retex_engine/
display.rs

1//! Displaying boxes, glue, equivalents and the semantic nest (*TeX: The
2//! Program* §173–§198, §218–§219, §252; e-TeX `show_save_groups`).
3
4use crate::Engine;
5use crate::cmd::Cmd;
6use crate::eqtb::{Loc, MU_GLUE_OFFSET, NUM_FAMILIES, Val};
7use crate::glue::Glue;
8use crate::maincontrol::Mode;
9use crate::nodes::NodeRef;
10
11impl Engine {
12    /// Prints a glue component with its order (`print_glue`). LuaTeX's
13    /// orders are `fi`, `fil`, `fill`, `filll`.
14    pub fn print_glue(&mut self, d: i32, order: u8, s: Option<&str>) {
15        self.printer.print_scaled(d);
16        if order > 4 {
17            self.printer.print_str("foul");
18        } else if order > 0 {
19            self.printer.print_str("fi");
20            for _ in 1..order {
21                self.printer.print_char('l' as u32);
22            }
23        } else if let Some(s) = s {
24            self.printer.print_str(s);
25        }
26    }
27
28    /// Prints a glue specification (`print_spec`).
29    pub fn print_spec(&mut self, g: &Glue, s: Option<&str>) {
30        self.printer.print_scaled(g.width);
31        if let Some(s) = s {
32            self.printer.print_str(s);
33        }
34        if g.stretch != 0 {
35            self.printer.print_str(" plus ");
36            self.print_glue(g.stretch, g.stretch_order, s);
37        }
38        if g.shrink != 0 {
39            self.printer.print_str(" minus ");
40            self.print_glue(g.shrink, g.shrink_order, s);
41        }
42    }
43
44    /// Displays an equivalent for tracing (`show_eqtb`).
45    pub fn show_eqtb(&mut self, loc: Loc) {
46        match loc {
47            Loc::Cs(cs) => {
48                self.sprint_cs(cs);
49                self.printer.print_char('=' as u32);
50                let m = self.eqtb.meaning(cs).clone();
51                if m.cmd.is_call() && m.mac.as_ref().is_some_and(|mac| mac.protected) {
52                    self.print_esc_str("protected");
53                    if m.cmd == Cmd::Call {
54                        self.printer.print_char(' ' as u32);
55                    }
56                }
57                self.print_cmd_chr(m.cmd, m.chr);
58                if m.cmd >= Cmd::Call
59                    && let Some(mac) = m.mac
60                {
61                    self.printer.print_char(':' as u32);
62                    let t = mac.tokens.clone();
63                    self.show_token_list(&t, 0, t.len(), 32);
64                }
65            }
66            Loc::IntPar(i) => {
67                self.print_cmd_chr(Cmd::AssignInt, i);
68                self.printer.print_char('=' as u32);
69                self.printer.print_int(self.eqtb.int(i).into());
70            }
71            Loc::Count(n) => {
72                self.print_esc_str("count");
73                self.printer.print_int(n.into());
74                self.printer.print_char('=' as u32);
75                self.printer
76                    .print_int(self.eqtb.count[n as usize].val.into());
77            }
78            Loc::Attr(n) => {
79                self.print_esc_str("attribute");
80                self.printer.print_int(n.into());
81                self.printer.print_char('=' as u32);
82                self.printer
83                    .print_int(self.eqtb.attr[n as usize].val.into());
84            }
85            Loc::DimenPar(i) => {
86                self.print_cmd_chr(Cmd::AssignDimen, i);
87                self.printer.print_char('=' as u32);
88                self.printer.print_scaled(self.eqtb.dimen(i));
89                self.printer.print_str("pt");
90            }
91            Loc::Dimen(n) => {
92                self.print_esc_str("dimen");
93                self.printer.print_int(n.into());
94                self.printer.print_char('=' as u32);
95                self.printer.print_scaled(self.eqtb.dimen[n as usize].val);
96                self.printer.print_str("pt");
97            }
98            Loc::GluePar(i) => {
99                let mu = i >= MU_GLUE_OFFSET;
100                if mu {
101                    self.print_cmd_chr(Cmd::AssignMuGlue, i - MU_GLUE_OFFSET);
102                } else {
103                    self.print_cmd_chr(Cmd::AssignGlue, i);
104                }
105                self.printer.print_char('=' as u32);
106                let g = self.eqtb.glue(i);
107                self.print_spec(&g, Some(if mu { "mu" } else { "pt" }));
108            }
109            Loc::Skip(n) | Loc::MuSkip(n) => {
110                let mu = matches!(loc, Loc::MuSkip(_));
111                self.print_esc_str(if mu { "muskip" } else { "skip" });
112                self.printer.print_int(n.into());
113                self.printer.print_char('=' as u32);
114                let g = if mu {
115                    self.eqtb.muskip[n as usize].val
116                } else {
117                    self.eqtb.skip[n as usize].val
118                };
119                self.print_spec(&g, Some(if mu { "mu" } else { "pt" }));
120            }
121            Loc::ToksPar(_) | Loc::Toks(_) => {
122                if let Loc::Toks(n) = loc {
123                    self.print_esc_str("toks");
124                    self.printer.print_int(n.into());
125                } else if let Loc::ToksPar(i) = loc {
126                    self.print_cmd_chr(Cmd::AssignToks, i);
127                }
128                self.printer.print_char('=' as u32);
129                if let Val::Toks(t) = self.eqtb.get(loc) {
130                    self.show_token_list(&t, 0, t.len(), 32);
131                }
132            }
133            Loc::Box(n) => {
134                self.print_esc_str("box");
135                self.printer.print_int(n.into());
136                self.printer.print_char('=' as u32);
137                let b = self.eqtb.boxes[n as usize].val;
138                if b.is_null() {
139                    self.printer.print_str("void");
140                } else {
141                    self.depth_threshold = 0;
142                    self.breadth_max = 1;
143                    self.show_box1(b);
144                }
145            }
146            Loc::CurFont => {
147                self.printer.print_str("current font");
148                self.printer.print_char('=' as u32);
149                let cs = self.font_id_cs(self.eqtb.cur_font.val);
150                let name = self.hash.name(cs).to_vec();
151                self.print_esc_bytes(&name);
152            }
153            Loc::MathFont(i) => {
154                let size = i / NUM_FAMILIES as u32;
155                let fam = i % NUM_FAMILIES as u32;
156                self.print_cmd_chr(Cmd::DefFamily, size);
157                self.printer.print_int(fam.into());
158                self.printer.print_char('=' as u32);
159                let cs = self.font_id_cs(self.eqtb.math_font[i as usize].val);
160                let name = self.hash.name(cs).to_vec();
161                self.print_esc_bytes(&name);
162            }
163            Loc::ParShape => {
164                self.print_esc_str("parshape");
165                self.printer.print_char('=' as u32);
166                let n = self.eqtb.par_shape.val.as_ref().map_or(0, |s| s.len() / 2);
167                self.printer.print_int(n as i64);
168            }
169            Loc::EtexShape(i) => {
170                self.print_cmd_chr(Cmd::SetEtexShape, i);
171                self.printer.print_char('=' as u32);
172                let n = self.eqtb.etex_shape[i as usize]
173                    .val
174                    .as_ref()
175                    .map_or(0, |s| s.len());
176                self.printer.print_int(n as i64);
177            }
178            Loc::Cat { table, ch } => {
179                self.print_esc_str("catcode");
180                self.printer.print_int(ch.into());
181                self.printer.print_char('=' as u32);
182                self.printer
183                    .print_int(self.eqtb.catcode_in(table, ch).into());
184            }
185            Loc::Code(kind, ch) => {
186                use crate::eqtb::CodeKind as K;
187                let name = match kind {
188                    K::Lc => "lccode",
189                    K::Uc => "uccode",
190                    K::Sf => "sfcode",
191                    K::Math => "mathcode",
192                    K::Del => "delcode",
193                };
194                self.print_esc_str(name);
195                self.printer.print_int(ch.into());
196                self.printer.print_char('=' as u32);
197                self.printer.print_int(self.eqtb.code_table(kind).get(ch));
198            }
199            Loc::MathParam(i) => {
200                self.print_cmd_chr(Cmd::SetMathParam, i / 8);
201                self.printer.print_int((i % 8).into());
202                self.printer.print_char('=' as u32);
203                if let Val::Int(v) = self.eqtb.get(loc) {
204                    self.printer.print_scaled(v as i32);
205                    self.printer.print_str("pt");
206                }
207            }
208            Loc::LocalCount(i) => {
209                self.print_esc_str(if i == 0 {
210                    "nolocalwhatsits"
211                } else {
212                    "nolocaldirs"
213                });
214                self.printer.print_char('=' as u32);
215                self.printer
216                    .print_int(self.eqtb.local_count[i as usize].val.into());
217            }
218            Loc::DirPar(i) => {
219                // Observed: `{changing \textdir TLT}`.
220                const NAMES: [&str; 6] = [
221                    "pagedir", "bodydir", "pardir", "textdir", "mathdir", "linedir",
222                ];
223                self.print_esc_str(NAMES.get(i as usize).copied().unwrap_or("textdir"));
224                self.printer.print_char(' ' as u32);
225                let d = self.eqtb.dir_par[i as usize].val;
226                self.printer
227                    .print_str(crate::showbox::DIR_NAMES[d.rem_euclid(4) as usize]);
228            }
229            Loc::LocalBox(i) => {
230                self.print_esc_str(if i == 0 {
231                    "localleftbox"
232                } else {
233                    "localrightbox"
234                });
235                self.printer.print_char('=' as u32);
236                let b = self.eqtb.local_box[i as usize].val;
237                if b.is_null() {
238                    self.printer.print_str("void");
239                } else {
240                    self.depth_threshold = 0;
241                    self.breadth_max = 1;
242                    self.show_box1(b);
243                }
244            }
245        }
246    }
247
248    /// Displays a box (`show_box`), honouring `\showboxdepth` and
249    /// `\showboxbreadth`.
250    pub fn show_box(&mut self, p: NodeRef) {
251        self.show_box_param_limits();
252        self.show_node_list(p);
253        self.printer.print_ln();
254    }
255
256    /// Displays a node list with the current limits (`show_node_list`).
257    pub fn show_box1(&mut self, p: NodeRef) {
258        self.show_node_list(p);
259    }
260
261    /// Shows the active groups (`\showgroups`, e-TeX's `show_save_groups`):
262    /// for each group, how it started, walking the semantic nest along.
263    ///
264    /// Observed in LuaTeX: the context shown before a box is its
265    /// specification's dimension taken as a shift (`\moveright10.0pt\hbox
266    /// to10.0pt{`; `\setbox` and leaders never show), and an insert group
267    /// shows the line where it started as the insert number.
268    pub fn show_save_groups(&mut self) {
269        use crate::save::{GroupCode, SaveEntry};
270        let (v_level, v_group, v_boundary) = (
271            self.save.cur_level,
272            self.save.cur_group,
273            self.save.cur_boundary,
274        );
275        // The auxiliary value `k` entries below boundary `b` (e-TeX's
276        // `saved(-1-k)`, the line being part of the boundary entry).
277        let saved = |e: &Engine, b: usize, k: usize| -> i64 {
278            match b.checked_sub(k).and_then(|i| e.save.stack.get(i)) {
279                Some(SaveEntry::Value(v)) => *v,
280                _ => 0,
281            }
282        };
283        let mode_of = |e: &Engine, p: usize| e.nest.get(p).map(|l| l.mode);
284        let mut p = self.nest.len() - 1;
285        let mut a: i32 = 1;
286        self.printer.print_nl("");
287        self.printer.print_ln();
288        self.save.cur_level = self.save.cur_level.saturating_sub(1);
289        loop {
290            self.printer.print_nl("### ");
291            self.print_group(true);
292            let group = self.save.cur_group;
293            if group == GroupCode::BottomLevel {
294                break;
295            }
296            let b = self.save.cur_boundary;
297            let mut m;
298            loop {
299                m = mode_of(self, p).unwrap_or(Mode::Vertical);
300                if p > 0 {
301                    p -= 1;
302                } else {
303                    m = Mode::Vertical;
304                }
305                if m != Mode::Horizontal {
306                    break;
307                }
308            }
309            self.printer.print_str(" (");
310            // What follows the group's description: a box name (with
311            // context and specification), a brace, or nothing.
312            enum Tail {
313                Box(&'static str, bool),
314                Brace,
315                Close,
316            }
317            let tail = match group {
318                GroupCode::Simple => {
319                    p += 1;
320                    Tail::Brace
321                }
322                GroupCode::Hbox | GroupCode::AdjustedHbox => Tail::Box("hbox", true),
323                GroupCode::Vbox => Tail::Box("vbox", true),
324                GroupCode::Vtop => Tail::Box("vtop", true),
325                GroupCode::Align => {
326                    if a == 0 {
327                        a = 1;
328                        Tail::Box(
329                            if m == Mode::InternalVertical {
330                                "halign"
331                            } else {
332                                "valign"
333                            },
334                            false,
335                        )
336                    } else {
337                        if a == 1 {
338                            self.printer.print_str("align entry");
339                        } else {
340                            self.print_esc_str("cr");
341                        }
342                        if p as i32 >= a {
343                            p = (p as i32 - a) as usize;
344                        }
345                        a = 0;
346                        Tail::Close
347                    }
348                }
349                GroupCode::NoAlign => {
350                    p += 1;
351                    a = -1;
352                    self.print_esc_str("noalign");
353                    Tail::Brace
354                }
355                GroupCode::Output => {
356                    self.print_esc_str("output");
357                    Tail::Close
358                }
359                GroupCode::Math => Tail::Brace,
360                GroupCode::Disc | GroupCode::MathChoice => {
361                    self.print_esc_str(if group == GroupCode::Disc {
362                        "discretionary"
363                    } else {
364                        "mathchoice"
365                    });
366                    let n = saved(self, b, 1);
367                    for i in 1..=3 {
368                        if i <= n {
369                            self.printer.print_str("{}");
370                        }
371                    }
372                    Tail::Brace
373                }
374                GroupCode::Insert => {
375                    let n = match self.save.stack.get(b) {
376                        Some(SaveEntry::Boundary { line, .. }) => i64::from(*line),
377                        _ => 0,
378                    };
379                    if n == 255 {
380                        self.print_esc_str("vadjust");
381                    } else {
382                        self.print_esc_str("insert");
383                        self.printer.print_int(n);
384                    }
385                    Tail::Brace
386                }
387                GroupCode::Vcenter => Tail::Box("vcenter", false),
388                GroupCode::SemiSimple => {
389                    p += 1;
390                    self.print_esc_str("begingroup");
391                    Tail::Close
392                }
393                GroupCode::MathShift => {
394                    if m == Mode::DisplayMath {
395                        self.printer.print_char('$' as u32);
396                    } else if mode_of(self, p) == Some(Mode::DisplayMath) {
397                        self.print_cmd_chr(crate::cmd::Cmd::EqNo, saved(self, b, 1) as u32);
398                        self.printer.print_str(")");
399                        self.save_groups_next();
400                        continue;
401                    }
402                    self.printer.print_char('$' as u32);
403                    Tail::Close
404                }
405                GroupCode::MathLeft => {
406                    let left = self
407                        .nest
408                        .get(p + 1)
409                        .map(|l| l.delim_ptr)
410                        .is_some_and(|d| !d.is_null() && self.nodes.subtype(d) == 1);
411                    self.print_esc_str(if left { "left" } else { "middle" });
412                    Tail::Close
413                }
414                _ => Tail::Close,
415            };
416            if let Tail::Box(s, context) = tail {
417                // Boxes keep the index of their attributes below the
418                // boundary (see `scan_spec`), the other groups do not.
419                let off = usize::from(matches!(
420                    group,
421                    GroupCode::Hbox | GroupCode::AdjustedHbox | GroupCode::Vbox | GroupCode::Vtop
422                ));
423                if context {
424                    // The box context: LuaTeX shows the dimension.
425                    let i = saved(self, b, 2 + off);
426                    match crate::build::ctx_from_i64(i) {
427                        crate::maincontrol::BoxContext::Shift(0) => {}
428                        crate::maincontrol::BoxContext::Shift(d) => {
429                            let vertical = mode_of(self, p).is_some_and(|m| {
430                                matches!(m, Mode::Vertical | Mode::InternalVertical)
431                            });
432                            let cmd = if vertical {
433                                crate::cmd::Cmd::Hmove
434                            } else {
435                                crate::cmd::Cmd::Vmove
436                            };
437                            self.print_cmd_chr(cmd, u32::from(d < 0));
438                            self.printer.print_scaled(d.abs());
439                            self.printer.print_str("pt");
440                        }
441                        crate::maincontrol::BoxContext::SetBox(n) => {
442                            self.print_esc_str("setbox");
443                            self.printer.print_int(i64::from(n));
444                            self.printer.print_char('=' as u32);
445                        }
446                        crate::maincontrol::BoxContext::GlobalSetBox(n) => {
447                            self.print_esc_str("global");
448                            self.print_esc_str("setbox");
449                            self.printer.print_int(i64::from(n));
450                            self.printer.print_char('=' as u32);
451                        }
452                        crate::maincontrol::BoxContext::Shipout => self.print_esc_str("shipout"),
453                        crate::maincontrol::BoxContext::Leaders(sub) => {
454                            self.print_cmd_chr(crate::cmd::Cmd::LeaderShip, u32::from(sub))
455                        }
456                        crate::maincontrol::BoxContext::Lua => {}
457                    }
458                }
459                self.print_esc_str(s);
460                // The specification (saved(-3), saved(-2); LuaTeX keeps
461                // the direction after them).
462                let (code, d) = (saved(self, b, 3 + off), saved(self, b, 2 + off));
463                if d != 0 {
464                    self.printer.print_char(' ' as u32);
465                    self.printer
466                        .print_str(if code == 0 { "to" } else { "spread" });
467                    self.printer.print_scaled(d as i32);
468                    self.printer.print_str("pt");
469                }
470                self.printer.print_char('{' as u32);
471            } else if let Tail::Brace = tail {
472                self.printer.print_char('{' as u32);
473            }
474            self.printer.print_char(')' as u32);
475            self.save_groups_next();
476        }
477        self.save.cur_level = v_level;
478        self.save.cur_group = v_group;
479        self.save.cur_boundary = v_boundary;
480    }
481
482    /// Steps `show_save_groups` out to the enclosing group.
483    fn save_groups_next(&mut self) {
484        self.save.cur_level = self.save.cur_level.saturating_sub(1);
485        if let Some(crate::save::SaveEntry::Boundary {
486            group, boundary, ..
487        }) = self.save.stack.get(self.save.cur_boundary)
488        {
489            self.save.cur_group = *group;
490            self.save.cur_boundary = *boundary;
491        } else {
492            self.save.cur_group = crate::save::GroupCode::BottomLevel;
493        }
494    }
495}