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