Skip to main content

retex_engine/
showbox.rs

1//! Displaying node lists in LuaTeX's formats (*TeX: The Program*
2//! §173–§198, §218–§219, §986).
3//!
4//! LuaTeX's box display differs from TeX82's in several places, all
5//! established by running `luatex` on test documents:
6//!
7//! * boxes end with `, direction TLT` (and so on); a shrinking glue set is
8//!   printed as `glue set - 1.0`;
9//! * user kerns print as `\kern3.0`, font kerns as `\kern-1.11113 (font)`,
10//!   italic corrections as `\kern2.0 (italic)` and accent kerns as
11//!   `\kern0.0 (accent)`;
12//! * a discretionary prints `\discretionary (penalty 50)`, followed by its
13//!   pre-break, post-break and replacement lists, each line prefixed by the
14//!   nesting string followed by `.< `, `.> ` or `.= `;
15//! * ligatures print their character and `(ligature …)` with the
16//!   components;
17//! * local paragraph nodes print `\localpar` and four indented lines;
18//!   direction nodes print `\begindir TRT` / `\enddir TRT`; boundaries
19//!   print `\boundary=7`, `\noboundary`, `\wordboundary=0`,
20//!   `\protrusionboundary=5`;
21//! * the short display (used by overfull/underfull box warnings and
22//!   `\tracingparagraphs`) prints a space for any glue that is not zero,
23//!   and a discretionary's pre- and post-break texts.
24
25use crate::Engine;
26use crate::maincontrol::{IGNORE_DEPTH, Mode, ModeKind};
27use crate::nodes::{NodeData, NodeRef, Whatsit, glyph_sub, id, kern_sub};
28use crate::primitives_gen as pg;
29
30/// The names of LuaTeX's four directions.
31pub const DIR_NAMES: [&str; 4] = ["TLT", "TRT", "LTL", "RTT"];
32
33impl Engine {
34    /// Prints the identifier of font `f` (`\tenrm`).
35    pub fn print_font_id(&mut self, f: u32) {
36        let cs = self.font_id_cs(f);
37        let name = self.hash.name(cs).to_vec();
38        self.print_esc_bytes(&name);
39    }
40
41    /// Prints a glyph as `\font c` (`print_font_and_char`).
42    pub fn print_font_and_char(&mut self, p: NodeRef) {
43        let (f, c) = match self.nodes.glyph(p) {
44            Some(g) => (g.font, g.chr),
45            None => return,
46        };
47        self.print_font_id(f);
48        self.printer.print_char(' ' as u32);
49        self.print_glyph_char(p, c);
50    }
51
52    /// Prints the character of glyph `p` (code `c`), through the
53    /// `glyph_info` callback when one is set.
54    fn print_glyph_char(&mut self, p: NodeRef, c: u32) {
55        match self.run_glyph_info(p) {
56            None => self.printer.print_char(c),
57            Some(Some(text)) => self.printer.print_callback_bytes(&text),
58            // LuaTeX prints the character code in hexadecimal.
59            Some(None) => self.printer.print_hex(i64::from(c)),
60        }
61    }
62
63    /// Prints the highlights of list `p` (`short_display`).
64    pub fn short_display(&mut self, mut p: NodeRef) {
65        while !p.is_null() {
66            let n = self.nodes.get(p);
67            match &n.data {
68                NodeData::Glyph(g) => {
69                    let (f, c, comps, sub) = (g.font, g.chr, g.components, n.subtype);
70                    if sub & glyph_sub::LIGATURE != 0 && !comps.is_null() {
71                        self.short_display(comps);
72                    } else {
73                        if i64::from(f) != self.ts.font_in_short_display {
74                            self.print_font_id(f);
75                            self.printer.print_char(' ' as u32);
76                            self.ts.font_in_short_display = i64::from(f);
77                        }
78                        self.print_glyph_char(p, c);
79                    }
80                }
81                NodeData::Box(_)
82                | NodeData::Ins { .. }
83                | NodeData::Whatsit(_)
84                | NodeData::Mark { .. }
85                | NodeData::Adjust { .. }
86                | NodeData::Unset { .. } => self.printer.print_str("[]"),
87                NodeData::Rule { .. } => self.printer.print_char('|' as u32),
88                NodeData::Glue { spec, .. } => {
89                    if spec.width != 0 || spec.stretch != 0 || spec.shrink != 0 {
90                        self.printer.print_char(' ' as u32);
91                    }
92                }
93                NodeData::Math { .. } => self.printer.print_char('$' as u32),
94                NodeData::Disc { pre, post, .. } => {
95                    let (pre, post) = (*pre, *post);
96                    self.short_display(pre);
97                    self.short_display(post);
98                }
99                _ => {}
100            }
101            p = self.nodes.next(p);
102        }
103    }
104
105    fn print_rule_dimen(&mut self, d: i32) {
106        if d == crate::typeset::NULL_FLAG {
107            self.printer.print_char('*' as u32);
108        } else {
109            self.printer.print_scaled(d);
110        }
111    }
112
113    /// Displays a sublist with `suffix` appended to the nesting string
114    /// (`node_list_display`).
115    fn node_list_display(&mut self, p: NodeRef, suffix: &[u8]) {
116        let n = self.ts.show_prefix.len();
117        self.ts.show_prefix.extend_from_slice(suffix);
118        self.show_node_list(p);
119        self.ts.show_prefix.truncate(n);
120    }
121
122    /// Prints the nesting string (`print_current_string`).
123    fn print_current_string(&mut self) {
124        let s = std::mem::take(&mut self.ts.show_prefix);
125        for &b in &s {
126            self.printer.print_char(u32::from(b));
127        }
128        self.ts.show_prefix = s;
129    }
130
131    /// Displays a node list (`show_node_list`).
132    pub fn show_node_list(&mut self, mut p: NodeRef) {
133        if self.ts.show_prefix.len() as i64 > i64::from(self.depth_threshold) {
134            if !p.is_null() {
135                self.printer.print_str(" []");
136            }
137            return;
138        }
139        let mut n = 0;
140        while !p.is_null() {
141            self.printer.print_ln();
142            self.print_current_string();
143            n += 1;
144            if n > self.breadth_max {
145                self.printer.print_str("etc.");
146                return;
147            }
148            self.display_node(p);
149            p = self.nodes.next(p);
150        }
151    }
152
153    /// Kept for callers of the old name.
154    pub fn display_node_list(&mut self, p: NodeRef) {
155        self.show_node_list(p);
156    }
157
158    /// Displays one node (`Display node p`).
159    fn display_node(&mut self, p: NodeRef) {
160        let node = self.nodes.get(p);
161        let (nid, sub) = (node.id, node.subtype);
162        match &node.data {
163            NodeData::Glyph(g) => {
164                let comps = g.components;
165                let f = g.font;
166                self.print_font_and_char(p);
167                if sub & glyph_sub::LIGATURE != 0 {
168                    self.printer.print_str(" (ligature ");
169                    if sub & glyph_sub::LEFT != 0 {
170                        self.printer.print_char('|' as u32);
171                    }
172                    self.ts.font_in_short_display = i64::from(f);
173                    self.short_display(comps);
174                    if sub & glyph_sub::RIGHT != 0 {
175                        self.printer.print_char('|' as u32);
176                    }
177                    self.printer.print_char(')' as u32);
178                }
179            }
180            NodeData::Box(b) => {
181                let b = (**b).clone();
182                self.print_esc_str(if nid == id::HLIST { "hbox(" } else { "vbox(" });
183                self.printer.print_scaled(b.height);
184                self.printer.print_char('+' as u32);
185                self.printer.print_scaled(b.depth);
186                self.printer.print_str(")x");
187                self.printer.print_scaled(b.width);
188                if b.glue_sign != 0 && b.glue_set != 0.0 {
189                    self.printer.print_str(", glue set ");
190                    if b.glue_sign == 2 {
191                        self.printer.print_str("- ");
192                    }
193                    let g = b.glue_set;
194                    if g.abs() > 20000.0 {
195                        if g > 0.0 {
196                            self.printer.print_char('>' as u32);
197                        } else {
198                            self.printer.print_str("< -");
199                        }
200                        self.print_glue(20000 * retex_arith::UNITY, b.glue_order, None);
201                    } else {
202                        self.print_glue(
203                            (f64::from(retex_arith::UNITY) * g).round() as i32,
204                            b.glue_order,
205                            None,
206                        );
207                    }
208                }
209                if b.shift != 0 {
210                    self.printer.print_str(", shifted ");
211                    self.printer.print_scaled(b.shift);
212                }
213                self.printer.print_str(", direction ");
214                self.printer
215                    .print_str(DIR_NAMES[(b.dir.rem_euclid(4)) as usize]);
216                self.node_list_display(b.head, b".");
217            }
218            NodeData::Unset {
219                b,
220                span_count,
221                stretch,
222                shrink,
223                stretch_order,
224                shrink_order,
225            } => {
226                let (h, d, w, head) = (b.height, b.depth, b.width, b.head);
227                let (sc, st, sh, sto, sho) = (
228                    *span_count,
229                    *stretch,
230                    *shrink,
231                    *stretch_order,
232                    *shrink_order,
233                );
234                self.print_esc_str("unsetbox(");
235                self.printer.print_scaled(h);
236                self.printer.print_char('+' as u32);
237                self.printer.print_scaled(d);
238                self.printer.print_str(")x");
239                self.printer.print_scaled(w);
240                if sc != 0 {
241                    self.printer.print_str(" (");
242                    self.printer.print_int(i64::from(sc) + 1);
243                    self.printer.print_str(" columns)");
244                }
245                if st != 0 {
246                    self.printer.print_str(", stretch ");
247                    self.print_glue(st, sto, None);
248                }
249                if sh != 0 {
250                    self.printer.print_str(", shrink ");
251                    self.print_glue(sh, sho, None);
252                }
253                self.node_list_display(head, b".");
254            }
255            NodeData::Rule {
256                width,
257                depth,
258                height,
259                ..
260            } => {
261                let (w, d, h) = (*width, *depth, *height);
262                self.print_esc_str(match sub {
263                    crate::image::IMAGE_RULE => "image(",
264                    crate::image::BOX_RESOURCE_RULE => "box(",
265                    _ => "rule(",
266                });
267                self.print_rule_dimen(h);
268                self.printer.print_char('+' as u32);
269                self.print_rule_dimen(d);
270                self.printer.print_str(")x");
271                self.print_rule_dimen(w);
272            }
273            NodeData::Ins {
274                cost,
275                depth,
276                height,
277                spec,
278                head,
279            } => {
280                let (cost, depth, height, spec, head) = (*cost, *depth, *height, *spec, *head);
281                self.print_esc_str("insert");
282                self.printer.print_int(i64::from(sub));
283                self.printer.print_str(", natural size ");
284                self.printer.print_scaled(height);
285                self.printer.print_str("; split(");
286                self.print_spec(&spec, None);
287                self.printer.print_char(',' as u32);
288                self.printer.print_scaled(depth);
289                self.printer.print_str("); float cost ");
290                self.printer.print_int(i64::from(cost));
291                self.node_list_display(head, b".");
292            }
293            NodeData::Whatsit(w) => {
294                let w = (**w).clone();
295                self.display_whatsit(&w, sub);
296            }
297            NodeData::Glue { spec, leader } => {
298                let (spec, leader) = (*spec, *leader);
299                if sub >= crate::nodes::glue_sub::A_LEADERS {
300                    self.print_esc_str("");
301                    match sub {
302                        crate::nodes::glue_sub::C_LEADERS => self.printer.print_char('c' as u32),
303                        crate::nodes::glue_sub::X_LEADERS => self.printer.print_char('x' as u32),
304                        crate::nodes::glue_sub::G_LEADERS => self.printer.print_char('g' as u32),
305                        _ => {}
306                    }
307                    self.printer.print_str("leaders ");
308                    self.print_spec(&spec, None);
309                    self.node_list_display(leader, b".");
310                } else {
311                    self.print_esc_str("glue");
312                    if sub != 0 {
313                        self.printer.print_char('(' as u32);
314                        if sub < crate::nodes::glue_sub::COND_MATH_GLUE {
315                            self.print_skip_param(sub);
316                        } else if sub == crate::nodes::glue_sub::COND_MATH_GLUE {
317                            self.print_esc_str("nonscript");
318                        } else {
319                            self.print_esc_str("mskip");
320                        }
321                        self.printer.print_char(')' as u32);
322                    }
323                    if sub != crate::nodes::glue_sub::COND_MATH_GLUE {
324                        self.printer.print_char(' ' as u32);
325                        if sub < crate::nodes::glue_sub::COND_MATH_GLUE {
326                            self.print_spec(&spec, None);
327                        } else {
328                            self.print_spec(&spec, Some("mu"));
329                        }
330                    }
331                }
332            }
333            NodeData::Kern { kern, .. } => {
334                let k = *kern;
335                if sub == crate::nodes::glue_sub::MU_GLUE {
336                    self.print_esc_str("mkern");
337                    self.printer.print_scaled(k);
338                    self.printer.print_str("mu");
339                } else {
340                    self.print_esc_str("kern");
341                    self.printer.print_scaled(k);
342                    match sub {
343                        kern_sub::FONT => self.printer.print_str(" (font)"),
344                        kern_sub::ACCENT => self.printer.print_str(" (accent)"),
345                        kern_sub::ITALIC => self.printer.print_str(" (italic)"),
346                        _ => {}
347                    }
348                }
349            }
350            NodeData::MarginKern { width, .. } => {
351                let w = *width;
352                self.print_esc_str("kern");
353                self.printer.print_scaled(w);
354                if sub == 0 {
355                    self.printer.print_str(" (left margin)");
356                } else {
357                    self.printer.print_str(" (right margin)");
358                }
359            }
360            NodeData::Math { surround, glue } => {
361                let w = *surround;
362                let g = *glue;
363                self.print_esc_str("math");
364                self.printer.print_str(if sub == 0 { "on" } else { "off" });
365                if g.width != 0 || g.stretch != 0 || g.shrink != 0 {
366                    self.printer.print_str(", glued ");
367                    self.print_spec(&g, None);
368                } else if w != 0 {
369                    self.printer.print_str(", surrounded ");
370                    self.printer.print_scaled(w);
371                }
372            }
373            NodeData::Penalty { penalty } => {
374                let v = *penalty;
375                self.print_esc_str("penalty ");
376                self.printer.print_int(i64::from(v));
377            }
378            NodeData::Disc {
379                pre,
380                post,
381                replace,
382                penalty,
383            } => {
384                let (pre, post, replace, pen) = (*pre, *post, *replace, *penalty);
385                self.print_esc_str("discretionary");
386                self.printer.print_str(" (penalty ");
387                self.printer.print_int(i64::from(pen));
388                self.printer.print_char(')' as u32);
389                self.node_list_display(pre, b".< ");
390                self.node_list_display(post, b".> ");
391                self.node_list_display(replace, b".= ");
392            }
393            NodeData::Mark { class, mark } => {
394                let (class, mark) = (*class, mark.clone());
395                self.print_esc_str("mark");
396                if class != 0 {
397                    self.printer.print_char('s' as u32);
398                    self.printer.print_int(i64::from(class));
399                }
400                self.printer.print_char('{' as u32);
401                let lim = self.printer.max_print_line.saturating_sub(10) as i64;
402                self.show_token_list(&mark, 0, mark.len(), lim);
403                self.printer.print_char('}' as u32);
404            }
405            NodeData::Adjust { head } => {
406                let head = *head;
407                self.print_esc_str("vadjust");
408                if sub != 0 {
409                    self.printer.print_str(" pre ");
410                }
411                self.node_list_display(head, b".");
412            }
413            NodeData::Boundary { value } => {
414                let v = *value;
415                match sub {
416                    0 => self.print_esc_str("noboundary"),
417                    1 => {
418                        self.print_esc_str("boundary=");
419                        self.printer.print_int(i64::from(v));
420                    }
421                    2 => {
422                        self.print_esc_str("protrusionboundary=");
423                        self.printer.print_int(i64::from(v));
424                    }
425                    _ => {
426                        self.print_esc_str("wordboundary=");
427                        self.printer.print_int(i64::from(v));
428                    }
429                }
430            }
431            NodeData::LocalPar {
432                pen_inter,
433                pen_broken,
434                box_left,
435                box_right,
436                ..
437            } => {
438                let (pi, pb, bl, br) = (*pen_inter, *pen_broken, *box_left, *box_right);
439                self.print_esc_str("localpar");
440                let base = self.ts.show_prefix.len();
441                self.ts.show_prefix.push(b'.');
442                self.printer.print_ln();
443                self.print_current_string();
444                self.print_esc_str("localinterlinepenalty=");
445                self.printer.print_int(i64::from(pi));
446                self.printer.print_ln();
447                self.print_current_string();
448                self.print_esc_str("localbrokenpenalty=");
449                self.printer.print_int(i64::from(pb));
450                for (name, b) in [("localleftbox", bl), ("localrightbox", br)] {
451                    self.printer.print_ln();
452                    self.print_current_string();
453                    self.print_esc_str(name);
454                    if b.is_null() {
455                        self.printer.print_str("=null");
456                    } else {
457                        self.node_list_display(b, b".");
458                    }
459                }
460                self.ts.show_prefix.truncate(base);
461            }
462            NodeData::Dir { dir, .. } => {
463                let d = *dir;
464                self.print_esc_str(if sub == 0 { "begindir " } else { "enddir " });
465                self.printer
466                    .print_str(DIR_NAMES[(i32::from(d)).rem_euclid(4) as usize]);
467            }
468            NodeData::Temp | NodeData::Other(_) => {
469                self.printer.print_str("Unknown node type!");
470            }
471            _ => {
472                if !self.display_math_node(p) {
473                    self.printer.print_str("Unknown node type!");
474                }
475            }
476        }
477    }
478
479    /// Displays a whatsit (TeX82 §1356 with LuaTeX's additions).
480    fn display_whatsit(&mut self, w: &Whatsit, sub: u16) {
481        match w {
482            Whatsit::Open { stream, name } => {
483                self.print_esc_str("openout");
484                self.print_write_stream(*stream as i32);
485                self.printer.print_char('=' as u32);
486                self.printer.print_bytes(name);
487            }
488            Whatsit::Write { stream, data } => {
489                self.print_esc_str("write");
490                self.print_write_stream(*stream);
491                self.printer.print_char('{' as u32);
492                let lim = self.printer.max_print_line.saturating_sub(10) as i64;
493                self.show_token_list(data, 0, data.len(), lim);
494                self.printer.print_char('}' as u32);
495            }
496            Whatsit::Close { stream } => {
497                self.print_esc_str("closeout");
498                self.print_write_stream(*stream as i32);
499            }
500            Whatsit::Special { data } => {
501                self.print_esc_str("special");
502                self.printer.print_char('{' as u32);
503                self.printer.print_bytes(data);
504                self.printer.print_char('}' as u32);
505            }
506            Whatsit::LateLua { data, .. } => {
507                self.print_esc_str("latelua0");
508                self.print_late_data(data);
509            }
510            Whatsit::PdfLiteral { data, mode, late } => {
511                self.print_esc_str(if *late {
512                    "pdflateliteral "
513                } else {
514                    "pdfliteral "
515                });
516                self.printer.print_str(match mode {
517                    0 => "origin",
518                    1 => "page",
519                    2 => "direct",
520                    3 => "raw",
521                    4 => "text",
522                    _ => "font",
523                });
524                self.print_late_data(data);
525            }
526            Whatsit::SavePos => self.print_esc_str("savepos"),
527            Whatsit::PdfColorstack { stack, cmd, data } => {
528                self.print_esc_str("pdfcolorstack ");
529                self.printer.print_int(i64::from(*stack));
530                self.printer.print_str(match cmd {
531                    0 => " set ",
532                    1 => " push ",
533                    2 => " pop",
534                    _ => " current",
535                });
536                if *cmd < 2 {
537                    self.printer.print_char('{' as u32);
538                    self.printer.print_bytes(data);
539                    self.printer.print_char('}' as u32);
540                }
541            }
542            Whatsit::PdfSetmatrix { data } => {
543                self.print_esc_str("pdfsetmatrix");
544                self.printer.print_char('{' as u32);
545                self.printer.print_bytes(data);
546                self.printer.print_char('}' as u32);
547            }
548            Whatsit::PdfSave => self.print_esc_str("pdfsave"),
549            Whatsit::PdfRestore => self.print_esc_str("pdfrestore"),
550            Whatsit::Other { fields, data } => self.display_other_whatsit(sub, fields, data),
551            Whatsit::PdfAnnot { dims, data, .. } => {
552                self.print_esc_str("pdfannot");
553                self.print_pdf_dims(*dims);
554                self.printer.print_char('{' as u32);
555                self.printer.print_bytes(data);
556                self.printer.print_char('}' as u32);
557            }
558            Whatsit::PdfStartLink {
559                dims, attr, action, ..
560            } => {
561                self.print_esc_str("pdfstartlink");
562                self.print_pdf_dims(*dims);
563                if !attr.is_empty() {
564                    self.printer.print_str(" attr{");
565                    self.printer.print_bytes(attr);
566                    self.printer.print_char('}' as u32);
567                }
568                self.printer.print_str(" action ");
569                self.print_pdf_action(action);
570            }
571            Whatsit::PdfEndLink => self.print_esc_str("pdfendlink"),
572            Whatsit::PdfDest { id, view } => {
573                use crate::pdfext::{DestId, DestKind};
574                self.print_esc_str("pdfdest ");
575                match id {
576                    DestId::Num(n) => {
577                        self.printer.print_str("num");
578                        self.printer.print_int(i64::from(*n));
579                    }
580                    DestId::Name(n) => {
581                        self.printer.print_str("name{");
582                        self.printer.print_bytes(n);
583                        self.printer.print_char('}' as u32);
584                    }
585                }
586                self.printer.print_str(match view.kind {
587                    DestKind::Xyz => " xyz",
588                    DestKind::Fit => " fit",
589                    DestKind::FitH => " fith",
590                    DestKind::FitV => " fitv",
591                    DestKind::FitB => " fitb",
592                    DestKind::FitBH => " fitbh",
593                    DestKind::FitBV => " fitbv",
594                    DestKind::FitR => " fitr",
595                });
596                if view.kind == DestKind::Xyz && view.zoom != 0 {
597                    self.printer.print_str(" zoom");
598                    self.printer.print_int(i64::from(view.zoom));
599                }
600                if view.kind == DestKind::FitR {
601                    self.print_pdf_dims(view.dims);
602                }
603            }
604            Whatsit::PdfRefobj { objnum } => self.show_refobj(i64::from(*objnum)),
605        }
606    }
607
608    /// Shows a `\pdfrefobj` node. Observed: LuaTeX shows the object's
609    /// definition while it is not written (`\pdfrefobj attr <attr> stream
610    /// file <data>`, each part only when present), else nothing after
611    /// `\pdfrefobj`.
612    fn show_refobj(&mut self, objnum: i64) {
613        self.print_esc_str("pdfrefobj");
614        let Some(obj) = i32::try_from(objnum)
615            .ok()
616            .and_then(|n| self.pdf.objects.get(&n))
617            .filter(|o| o.defined && !o.written)
618            .cloned()
619        else {
620            return;
621        };
622        if !obj.attr.is_empty() {
623            self.printer.print_str(" attr ");
624            self.printer.print_bytes(&obj.attr);
625        }
626        if obj.stream {
627            self.printer.print_str(" stream");
628        }
629        if obj.file {
630            self.printer.print_str(" file");
631        }
632        self.printer.print_char(' ' as u32);
633        self.printer.print_bytes(&obj.data);
634    }
635
636    /// Prints `(height+depth)xwidth` of a PDF whatsit, `*` for running
637    /// dimensions.
638    fn print_pdf_dims(&mut self, d: [i32; 3]) {
639        self.printer.print_char('(' as u32);
640        self.print_rule_dimen(d[1]);
641        self.printer.print_char('+' as u32);
642        self.print_rule_dimen(d[2]);
643        self.printer.print_str(")x");
644        self.print_rule_dimen(d[0]);
645    }
646
647    /// Prints an action as LuaTeX shows it: `goto name{x}`, `goto num3`,
648    /// `page3{/Fit}`, `file{f} goto name{x}`, `user{…}`, `thread num3`.
649    fn print_pdf_action(&mut self, a: &crate::pdfext::PdfAction) {
650        use crate::pdfext::{ActionTarget, PdfAction};
651        let (file, target, goto) = match a {
652            PdfAction::User(u) => {
653                self.printer.print_str("user{");
654                self.printer.print_bytes(u);
655                self.printer.print_char('}' as u32);
656                return;
657            }
658            PdfAction::Goto { file, target, .. } => (file, target, true),
659            PdfAction::Thread { file, target } => (file, target, false),
660        };
661        if let Some(f) = file {
662            self.printer.print_str("file{");
663            self.printer.print_bytes(f);
664            self.printer.print_str("} ");
665        }
666        match target {
667            ActionTarget::Page(p, spec) => {
668                self.printer.print_str("page");
669                self.printer.print_int(i64::from(*p));
670                self.printer.print_char('{' as u32);
671                self.printer.print_bytes(spec);
672                self.printer.print_char('}' as u32);
673            }
674            ActionTarget::Name(n) => {
675                self.printer
676                    .print_str(if goto { "goto name{" } else { "thread name{" });
677                self.printer.print_bytes(n);
678                self.printer.print_char('}' as u32);
679            }
680            ActionTarget::Num(n) => {
681                self.printer
682                    .print_str(if goto { "goto num" } else { "thread num" });
683                self.printer.print_int(i64::from(*n));
684            }
685        }
686    }
687
688    /// Displays the PDF whatsits kept in generic form (`\pdfextension
689    /// dest`, `startlink`, `endlink`, `refobj`, `annot`).
690    fn display_other_whatsit(&mut self, sub: u16, fields: &[i64], data: &[u8]) {
691        use crate::nodes::whatsit_sub as ws;
692        let split = |d: &[u8]| -> (Vec<u8>, Vec<u8>) {
693            match d.iter().position(|&b| b == 0) {
694                Some(i) => (d[..i].to_vec(), d[i + 1..].to_vec()),
695                None => (d.to_vec(), Vec::new()),
696            }
697        };
698        let dims = |e: &mut Engine, f: &[i64]| {
699            if f.len() >= 3 {
700                e.printer.print_char('(' as u32);
701                e.print_rule_dimen(f[1] as i32);
702                e.printer.print_char('+' as u32);
703                e.print_rule_dimen(f[2] as i32);
704                e.printer.print_str(")x");
705                e.print_rule_dimen(f[0] as i32);
706            }
707        };
708        match sub {
709            ws::PDF_END_LINK => self.print_esc_str("pdfendlink"),
710            ws::PDF_REFOBJ => self.show_refobj(fields.first().copied().unwrap_or(0)),
711            ws::PDF_DEST => {
712                let (name, kind) = split(data);
713                self.print_esc_str("pdfdest ");
714                if !name.is_empty() && name.iter().all(u8::is_ascii_digit) {
715                    self.printer.print_str("num");
716                    self.printer.print_bytes(&name);
717                } else {
718                    self.printer.print_str("name{");
719                    self.printer.print_bytes(&name);
720                    self.printer.print_char('}' as u32);
721                }
722                self.printer.print_char(' ' as u32);
723                self.printer.print_bytes(&kind);
724            }
725            ws::PDF_START_LINK => {
726                let (attr, action) = split(data);
727                self.print_esc_str("pdfstartlink");
728                dims(self, fields);
729                if !attr.is_empty() {
730                    self.printer.print_str(" attr{");
731                    self.printer.print_bytes(&attr);
732                    self.printer.print_char('}' as u32);
733                }
734                self.printer.print_str(" action ");
735                let a = String::from_utf8_lossy(&action).into_owned();
736                if let Some(rest) = a.strip_prefix("/S /GoTo ") {
737                    self.printer.print_str("goto ");
738                    if let Some(n) = rest.strip_prefix("/Num ") {
739                        self.printer.print_str("num");
740                        self.printer.print_str(n.trim());
741                    } else if let Some(n) = rest.strip_prefix("/Name (") {
742                        self.printer.print_str("name{");
743                        self.printer.print_str(n.trim_end_matches(')'));
744                        self.printer.print_char('}' as u32);
745                    } else {
746                        self.printer.print_str(rest);
747                    }
748                } else {
749                    self.printer.print_str("user{");
750                    self.printer.print_bytes(&action);
751                    self.printer.print_char('}' as u32);
752                }
753            }
754            ws::PDF_ANNOT => {
755                self.print_esc_str("pdfannot");
756                dims(self, fields);
757                self.printer.print_char('{' as u32);
758                self.printer.print_bytes(data);
759                self.printer.print_char('}' as u32);
760            }
761            _ => self.printer.print_str("whatsit?"),
762        }
763    }
764
765    fn print_late_data(&mut self, data: &crate::nodes::LateData) {
766        match data {
767            crate::nodes::LateData::Tokens(t) => {
768                self.printer.print_char('{' as u32);
769                let lim = self.printer.max_print_line.saturating_sub(10) as i64;
770                self.show_token_list(t, 0, t.len(), lim);
771                self.printer.print_char('}' as u32);
772            }
773            crate::nodes::LateData::Bytes(b) => {
774                self.printer.print_char('{' as u32);
775                self.printer.print_bytes(b);
776                self.printer.print_char('}' as u32);
777            }
778        }
779    }
780
781    fn print_write_stream(&mut self, s: i32) {
782        if (0..16).contains(&s) {
783            self.printer.print_int(i64::from(s));
784        } else if s == 16 {
785            self.printer.print_char('*' as u32);
786        } else {
787            self.printer.print_char('-' as u32);
788        }
789    }
790
791    /// Prints the name of a glue parameter given a glue subtype
792    /// (`print_skip_param`).
793    pub fn print_skip_param(&mut self, sub: u16) {
794        use crate::nodes::glue_sub as g;
795        let name = match sub {
796            g::LINE_SKIP => "lineskip",
797            g::BASELINE_SKIP => "baselineskip",
798            g::PAR_SKIP => "parskip",
799            g::ABOVE_DISPLAY => "abovedisplayskip",
800            g::BELOW_DISPLAY => "belowdisplayskip",
801            g::ABOVE_DISPLAY_SHORT => "abovedisplayshortskip",
802            g::BELOW_DISPLAY_SHORT => "belowdisplayshortskip",
803            g::LEFT_SKIP => "leftskip",
804            g::RIGHT_SKIP => "rightskip",
805            g::TOP_SKIP => "topskip",
806            g::SPLIT_TOP_SKIP => "splittopskip",
807            g::TAB_SKIP => "tabskip",
808            g::SPACE_SKIP => "spaceskip",
809            g::XSPACE_SKIP => "xspaceskip",
810            g::PAR_FILL_SKIP => "parfillskip",
811            g::MATH_SKIP => "mathskip",
812            g::THIN_MU_SKIP => "thinmuskip",
813            g::MED_MU_SKIP => "medmuskip",
814            g::THICK_MU_SKIP => "thickmuskip",
815            _ => {
816                self.printer.print_str("[unknown glue parameter!]");
817                return;
818            }
819        };
820        self.print_esc_str(name);
821    }
822
823    /// Displays the semantic nest (`show_activities`).
824    pub fn show_activities(&mut self) {
825        self.printer.print_nl("");
826        self.printer.print_ln();
827        for i in (0..self.nest.len()).rev() {
828            let (mode, line, head, aux_pd, sf, pg, lang) = {
829                let l = &self.nest[i];
830                (
831                    l.mode,
832                    l.mode_line,
833                    l.head,
834                    l.prev_depth,
835                    l.space_factor,
836                    l.prev_graf,
837                    l.clang,
838                )
839            };
840            self.printer.print_nl("### ");
841            self.print_mode(mode);
842            self.printer.print_str(" entered at line ");
843            self.printer.print_int(i64::from(line.abs()));
844            if line < 0 {
845                self.printer.print_str(" (\\output routine)");
846            }
847            if i == 0 {
848                self.show_page_contents();
849                if !self.nodes.next(self.page.contrib_head).is_null() {
850                    self.printer.print_nl("### recent contributions:");
851                }
852            }
853            let first = self.nodes.next(head);
854            self.show_box(first);
855            match mode.abs_kind() {
856                ModeKind::Vertical => {
857                    self.printer.print_nl("prevdepth ");
858                    if aux_pd <= IGNORE_DEPTH {
859                        self.printer.print_str("ignored");
860                    } else {
861                        self.printer.print_scaled(aux_pd);
862                    }
863                    if pg != 0 {
864                        self.printer.print_str(", prevgraf ");
865                        self.printer.print_int(i64::from(pg));
866                        self.printer
867                            .print_str(if pg != 1 { " lines" } else { " line" });
868                    }
869                }
870                ModeKind::Horizontal => {
871                    self.printer.print_nl("spacefactor ");
872                    self.printer.print_int(i64::from(sf));
873                    if mode == Mode::Horizontal && lang > 0 {
874                        self.printer.print_str(", current language ");
875                        self.printer.print_int(i64::from(lang));
876                    }
877                }
878                ModeKind::Math => {
879                    let inc = self.nest[i].incompleat_noad;
880                    if !inc.is_null() {
881                        self.printer.print_str("this will be denominator of:");
882                        self.show_box(inc);
883                    }
884                }
885                ModeKind::None => {}
886            }
887        }
888    }
889
890    /// Shows the current page in `\showlists` (§986).
891    pub fn show_page_contents(&mut self) {
892        let head = self.page.page_head;
893        if head == self.page.page_tail {
894            return;
895        }
896        self.printer.print_nl("### current page:");
897        if self.ts.output_active {
898            self.printer.print_str(" (held over for next output)");
899        }
900        let first = self.nodes.next(head);
901        self.show_box(first);
902        if self.page.contents != crate::typeset::PageContents::Empty {
903            self.printer.print_nl("total height ");
904            self.print_totals();
905            self.printer.print_nl(" goal height ");
906            self.printer.print_scaled(self.page.so_far[0]);
907            let ins: Vec<crate::typeset::PageIns> = self.page.ins.clone();
908            for r in ins {
909                self.printer.print_ln();
910                self.print_esc_str("insert");
911                self.printer.print_int(i64::from(r.n));
912                self.printer.print_str(" adds ");
913                let c = self.eqtb.count[r.n as usize].val;
914                let t = if c == 1000 {
915                    r.height
916                } else {
917                    retex_arith::x_over_n(r.height, 1000)
918                        .map(|d| d.quotient)
919                        .unwrap_or(0)
920                        .wrapping_mul(c)
921                };
922                self.printer.print_scaled(t);
923                if r.split_up {
924                    let mut q = head;
925                    let mut t = 0;
926                    loop {
927                        q = self.nodes.next(q);
928                        if q.is_null() {
929                            break;
930                        }
931                        if self.nodes.id(q) == id::INS && u32::from(self.nodes.subtype(q)) == r.n {
932                            t += 1;
933                        }
934                        if q == r.broken_ins {
935                            break;
936                        }
937                    }
938                    self.printer.print_str(", #");
939                    self.printer.print_int(t);
940                    self.printer.print_str(" might split");
941                }
942            }
943        }
944    }
945
946    /// Prints the page totals (`print_totals`).
947    pub fn print_totals(&mut self) {
948        let s = self.page.so_far;
949        self.printer.print_scaled(s[1]);
950        let names = ["", "fi", "fil", "fill", "filll"];
951        // Stretch of orders normal, fi, fil, fill, filll;
952        // shrink in so_far[6].
953        for (o, nm) in names.iter().enumerate() {
954            let v = self.page.stretch(o as u8);
955            if v != 0 {
956                self.printer.print_str(" plus ");
957                self.printer.print_scaled(v);
958                self.printer.print_str(nm);
959            }
960        }
961        if s[6] != 0 {
962            self.printer.print_str(" minus ");
963            self.printer.print_scaled(s[6]);
964        }
965    }
966
967    /// The `\tracingonline`-dependent part of box display used by
968    /// `\showbox` (`show_box`).
969    pub fn show_box_param_limits(&mut self) {
970        self.depth_threshold = self.eqtb.int(pg::assign_int::SHOWBOXDEPTH);
971        self.breadth_max = self.eqtb.int(pg::assign_int::SHOWBOXBREADTH);
972        if self.breadth_max <= 0 {
973            self.breadth_max = 5;
974        }
975    }
976}