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.print_mark_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.print_mark_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.print_mark_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::PdfLinkState { state } => {
552                self.print_esc_str("pdflinkstate ");
553                self.printer.print_int(i64::from(*state));
554            }
555            Whatsit::UserDefined { user_id, value } => {
556                // Observed: `\whatsitID=` and the value: a number, a
557                // quoted string, a token list in braces, a node list in
558                // brackets, a Lua reference number, `<>` for attributes.
559                self.print_esc_str("whatsit");
560                self.printer.print_int(i64::from(*user_id));
561                self.printer.print_char('=' as u32);
562                match value {
563                    crate::nodes::UserValue::Int(n) | crate::nodes::UserValue::Lua(n) => {
564                        self.printer.print_int(i64::from(*n))
565                    }
566                    crate::nodes::UserValue::Str(s) => {
567                        self.printer.print_char('"' as u32);
568                        self.printer.print_bytes(s);
569                        self.printer.print_char('"' as u32);
570                    }
571                    crate::nodes::UserValue::Tokens(t) => {
572                        self.printer.print_char('{' as u32);
573                        let t = t.clone();
574                        self.show_token_list(&t, 0, t.len(), i64::MAX);
575                        self.printer.print_char('}' as u32);
576                    }
577                    crate::nodes::UserValue::Nodes(p) => {
578                        self.printer.print_char('[' as u32);
579                        let p = *p;
580                        self.show_node_list(p);
581                        self.printer.print_char(']' as u32);
582                    }
583                    crate::nodes::UserValue::Attr => self.printer.print_str("<>"),
584                }
585            }
586            Whatsit::PdfAnnot { dims, data, .. } => {
587                self.print_esc_str("pdfannot");
588                self.print_pdf_dims(*dims);
589                self.printer.print_char('{' as u32);
590                self.print_mark_bytes(data);
591                self.printer.print_char('}' as u32);
592            }
593            Whatsit::PdfStartLink {
594                dims, attr, action, ..
595            } => {
596                self.print_esc_str("pdfstartlink");
597                self.print_pdf_dims(*dims);
598                if !attr.is_empty() {
599                    self.printer.print_str(" attr{");
600                    self.print_mark_bytes(attr);
601                    self.printer.print_char('}' as u32);
602                }
603                self.printer.print_str(" action ");
604                self.print_pdf_action(action);
605            }
606            Whatsit::PdfEndLink => self.print_esc_str("pdfendlink"),
607            // Observed: a structure destination shows as an ordinary one.
608            Whatsit::PdfDest { id, view, .. } => {
609                use crate::pdfext::{DestId, DestKind};
610                self.print_esc_str("pdfdest ");
611                match id {
612                    DestId::Num(n) => {
613                        self.printer.print_str("num");
614                        self.printer.print_int(i64::from(*n));
615                    }
616                    DestId::Name(n) => {
617                        self.printer.print_str("name{");
618                        self.print_mark_bytes(n);
619                        self.printer.print_char('}' as u32);
620                    }
621                }
622                self.printer.print_str(match view.kind {
623                    DestKind::Xyz => " xyz",
624                    DestKind::Fit => " fit",
625                    DestKind::FitH => " fith",
626                    DestKind::FitV => " fitv",
627                    DestKind::FitB => " fitb",
628                    DestKind::FitBH => " fitbh",
629                    DestKind::FitBV => " fitbv",
630                    DestKind::FitR => " fitr",
631                });
632                if view.kind == DestKind::Xyz && view.zoom != 0 {
633                    self.printer.print_str(" zoom");
634                    self.printer.print_int(i64::from(view.zoom));
635                }
636                if view.kind == DestKind::FitR {
637                    self.print_pdf_dims(view.dims);
638                }
639            }
640            Whatsit::PdfRefobj { objnum } => self.show_refobj(i64::from(*objnum)),
641        }
642    }
643
644    /// Shows a `\pdfrefobj` node. Observed: LuaTeX shows the object's
645    /// definition while it is not written (`\pdfrefobj attr <attr> stream
646    /// file <data>`, each part only when present), else nothing after
647    /// `\pdfrefobj`.
648    fn show_refobj(&mut self, objnum: i64) {
649        self.print_esc_str("pdfrefobj");
650        let Some(obj) = i32::try_from(objnum)
651            .ok()
652            .and_then(|n| self.pdf.objects.get(&n))
653            .filter(|o| o.defined && !o.written)
654            .cloned()
655        else {
656            return;
657        };
658        if !obj.attr.is_empty() {
659            self.printer.print_str(" attr ");
660            self.printer.print_bytes(&obj.attr);
661        }
662        if obj.stream {
663            self.printer.print_str(" stream");
664        }
665        if obj.file {
666            self.printer.print_str(" file");
667        }
668        self.printer.print_char(' ' as u32);
669        self.printer.print_bytes(&obj.data);
670    }
671
672    /// Prints `(height+depth)xwidth` of a PDF whatsit, `*` for running
673    /// dimensions.
674    fn print_pdf_dims(&mut self, d: [i32; 3]) {
675        self.printer.print_char('(' as u32);
676        self.print_rule_dimen(d[1]);
677        self.printer.print_char('+' as u32);
678        self.print_rule_dimen(d[2]);
679        self.printer.print_str(")x");
680        self.print_rule_dimen(d[0]);
681    }
682
683    /// Prints an action as LuaTeX shows it: `goto name{x}`, `goto num3`,
684    /// `page3{/Fit}`, `file{f} goto name{x}`, `user{…}`, `thread num3`.
685    fn print_pdf_action(&mut self, a: &crate::pdfext::PdfAction) {
686        use crate::pdfext::{ActionTarget, PdfAction};
687        let (file, target, goto) = match a {
688            PdfAction::User(u) => {
689                self.printer.print_str("user{");
690                self.print_mark_bytes(u);
691                self.printer.print_char('}' as u32);
692                return;
693            }
694            PdfAction::Goto {
695                file,
696                target,
697                structure,
698                ..
699            } => {
700                // Observed: `struct name{…} goto name{…}`.
701                match structure {
702                    Some(ActionTarget::Name(n)) => {
703                        self.printer.print_str("struct name{");
704                        self.print_mark_bytes(n);
705                        self.printer.print_str("} ");
706                    }
707                    Some(ActionTarget::Num(n)) => {
708                        self.printer.print_str("struct num");
709                        self.printer.print_int(i64::from(*n));
710                        self.printer.print_char(' ' as u32);
711                    }
712                    _ => {}
713                }
714                (file, target, true)
715            }
716            PdfAction::Thread { file, target } => (file, target, false),
717        };
718        if let Some(f) = file {
719            self.printer.print_str("file{");
720            self.print_mark_bytes(f);
721            self.printer.print_str("} ");
722        }
723        match target {
724            ActionTarget::Page(p, spec) => {
725                self.printer.print_str("page");
726                self.printer.print_int(i64::from(*p));
727                self.printer.print_char('{' as u32);
728                self.print_mark_bytes(spec);
729                self.printer.print_char('}' as u32);
730            }
731            ActionTarget::Name(n) => {
732                self.printer
733                    .print_str(if goto { "goto name{" } else { "thread name{" });
734                self.print_mark_bytes(n);
735                self.printer.print_char('}' as u32);
736            }
737            ActionTarget::Num(n) => {
738                self.printer
739                    .print_str(if goto { "goto num" } else { "thread num" });
740                self.printer.print_int(i64::from(*n));
741            }
742        }
743    }
744
745    /// Prints `data`, a token list in LuaTeX, as `print_mark` does: at
746    /// most `max_print_line - 10` characters, then `\ETC.` (observed for
747    /// the data of specials, literals, annotations, links and
748    /// destinations).
749    fn print_mark_bytes(&mut self, data: &[u8]) {
750        let limit = self.printer.max_print_line.saturating_sub(10);
751        let mut count = 0;
752        let mut cut = None;
753        for (i, &b) in data.iter().enumerate() {
754            // A byte that does not continue a UTF-8 sequence starts a
755            // character.
756            if b & 0xC0 != 0x80 {
757                if count == limit {
758                    cut = Some(i);
759                    break;
760                }
761                count += 1;
762            }
763        }
764        match cut {
765            None => self.printer.print_bytes(data),
766            Some(i) => {
767                self.printer.print_bytes(&data[..i]);
768                self.print_esc_str("ETC.");
769            }
770        }
771    }
772
773    /// Displays the PDF whatsits kept in generic form (`\pdfextension
774    /// dest`, `startlink`, `endlink`, `refobj`, `annot`).
775    fn display_other_whatsit(&mut self, sub: u16, fields: &[i64], data: &[u8]) {
776        use crate::nodes::whatsit_sub as ws;
777        let split = |d: &[u8]| -> (Vec<u8>, Vec<u8>) {
778            match d.iter().position(|&b| b == 0) {
779                Some(i) => (d[..i].to_vec(), d[i + 1..].to_vec()),
780                None => (d.to_vec(), Vec::new()),
781            }
782        };
783        let dims = |e: &mut Engine, f: &[i64]| {
784            if f.len() >= 3 {
785                e.printer.print_char('(' as u32);
786                e.print_rule_dimen(f[1] as i32);
787                e.printer.print_char('+' as u32);
788                e.print_rule_dimen(f[2] as i32);
789                e.printer.print_str(")x");
790                e.print_rule_dimen(f[0] as i32);
791            }
792        };
793        match sub {
794            ws::PDF_END_LINK => self.print_esc_str("pdfendlink"),
795            ws::PDF_REFOBJ => self.show_refobj(fields.first().copied().unwrap_or(0)),
796            ws::PDF_DEST => {
797                let (name, kind) = split(data);
798                self.print_esc_str("pdfdest ");
799                if !name.is_empty() && name.iter().all(u8::is_ascii_digit) {
800                    self.printer.print_str("num");
801                    self.printer.print_bytes(&name);
802                } else {
803                    self.printer.print_str("name{");
804                    self.print_mark_bytes(&name);
805                    self.printer.print_char('}' as u32);
806                }
807                self.printer.print_char(' ' as u32);
808                self.printer.print_bytes(&kind);
809            }
810            ws::PDF_START_LINK => {
811                let (attr, action) = split(data);
812                self.print_esc_str("pdfstartlink");
813                dims(self, fields);
814                if !attr.is_empty() {
815                    self.printer.print_str(" attr{");
816                    self.print_mark_bytes(&attr);
817                    self.printer.print_char('}' as u32);
818                }
819                self.printer.print_str(" action ");
820                let a = String::from_utf8_lossy(&action).into_owned();
821                if let Some(rest) = a.strip_prefix("/S /GoTo ") {
822                    self.printer.print_str("goto ");
823                    if let Some(n) = rest.strip_prefix("/Num ") {
824                        self.printer.print_str("num");
825                        self.printer.print_str(n.trim());
826                    } else if let Some(n) = rest.strip_prefix("/Name (") {
827                        self.printer.print_str("name{");
828                        self.printer.print_str(n.trim_end_matches(')'));
829                        self.printer.print_char('}' as u32);
830                    } else {
831                        self.printer.print_str(rest);
832                    }
833                } else {
834                    self.printer.print_str("user{");
835                    self.print_mark_bytes(&action);
836                    self.printer.print_char('}' as u32);
837                }
838            }
839            ws::PDF_ANNOT => {
840                self.print_esc_str("pdfannot");
841                dims(self, fields);
842                self.printer.print_char('{' as u32);
843                self.print_mark_bytes(data);
844                self.printer.print_char('}' as u32);
845            }
846            _ => self.printer.print_str("whatsit?"),
847        }
848    }
849
850    fn print_late_data(&mut self, data: &crate::nodes::LateData) {
851        match data {
852            crate::nodes::LateData::Tokens(t) => {
853                self.printer.print_char('{' as u32);
854                let lim = self.printer.max_print_line.saturating_sub(10) as i64;
855                self.show_token_list(t, 0, t.len(), lim);
856                self.printer.print_char('}' as u32);
857            }
858            crate::nodes::LateData::Bytes(b) => {
859                self.printer.print_char('{' as u32);
860                self.print_mark_bytes(b);
861                self.printer.print_char('}' as u32);
862            }
863            crate::nodes::LateData::Lua { reference, .. } => {
864                self.printer.print_str(" <lua data reference ");
865                self.printer.print_int(i64::from(*reference));
866                self.printer.print_char('>' as u32);
867            }
868        }
869    }
870
871    fn print_write_stream(&mut self, s: i32) {
872        if (0..16).contains(&s) {
873            self.printer.print_int(i64::from(s));
874        } else if s == 16 {
875            self.printer.print_char('*' as u32);
876        } else {
877            self.printer.print_char('-' as u32);
878        }
879    }
880
881    /// Prints the name of a glue parameter given a glue subtype
882    /// (`print_skip_param`).
883    pub fn print_skip_param(&mut self, sub: u16) {
884        use crate::nodes::glue_sub as g;
885        let name = match sub {
886            g::LINE_SKIP => "lineskip",
887            g::BASELINE_SKIP => "baselineskip",
888            g::PAR_SKIP => "parskip",
889            g::ABOVE_DISPLAY => "abovedisplayskip",
890            g::BELOW_DISPLAY => "belowdisplayskip",
891            g::ABOVE_DISPLAY_SHORT => "abovedisplayshortskip",
892            g::BELOW_DISPLAY_SHORT => "belowdisplayshortskip",
893            g::LEFT_SKIP => "leftskip",
894            g::RIGHT_SKIP => "rightskip",
895            g::TOP_SKIP => "topskip",
896            g::SPLIT_TOP_SKIP => "splittopskip",
897            g::TAB_SKIP => "tabskip",
898            g::SPACE_SKIP => "spaceskip",
899            g::XSPACE_SKIP => "xspaceskip",
900            g::PAR_FILL_SKIP => "parfillskip",
901            g::MATH_SKIP => "mathskip",
902            g::THIN_MU_SKIP => "thinmuskip",
903            g::MED_MU_SKIP => "medmuskip",
904            g::THICK_MU_SKIP => "thickmuskip",
905            _ => {
906                self.printer.print_str("[unknown glue parameter!]");
907                return;
908            }
909        };
910        self.print_esc_str(name);
911    }
912
913    /// Displays the semantic nest (`show_activities`).
914    pub fn show_activities(&mut self) {
915        self.printer.print_nl("");
916        self.printer.print_ln();
917        for i in (0..self.nest.len()).rev() {
918            let (mode, line, head, aux_pd, sf, pg, lang) = {
919                let l = &self.nest[i];
920                (
921                    l.mode,
922                    l.mode_line,
923                    l.head,
924                    l.prev_depth,
925                    l.space_factor,
926                    l.prev_graf,
927                    l.clang,
928                )
929            };
930            self.printer.print_nl("### ");
931            self.print_mode(mode);
932            self.printer.print_str(" entered at line ");
933            self.printer.print_int(i64::from(line.abs()));
934            if line < 0 {
935                self.printer.print_str(" (\\output routine)");
936            }
937            if i == 0 {
938                self.show_page_contents();
939                if !self.nodes.next(self.page.contrib_head).is_null() {
940                    self.printer.print_nl("### recent contributions:");
941                }
942            }
943            let first = self.nodes.next(head);
944            self.show_box(first);
945            match mode.abs_kind() {
946                ModeKind::Vertical => {
947                    self.printer.print_nl("prevdepth ");
948                    if aux_pd <= IGNORE_DEPTH {
949                        self.printer.print_str("ignored");
950                    } else {
951                        self.printer.print_scaled(aux_pd);
952                    }
953                    if pg != 0 {
954                        self.printer.print_str(", prevgraf ");
955                        self.printer.print_int(i64::from(pg));
956                        self.printer
957                            .print_str(if pg != 1 { " lines" } else { " line" });
958                    }
959                }
960                ModeKind::Horizontal => {
961                    self.printer.print_nl("spacefactor ");
962                    self.printer.print_int(i64::from(sf));
963                    if mode == Mode::Horizontal && lang > 0 {
964                        self.printer.print_str(", current language ");
965                        self.printer.print_int(i64::from(lang));
966                    }
967                }
968                ModeKind::Math => {
969                    let inc = self.nest[i].incompleat_noad;
970                    if !inc.is_null() {
971                        self.printer.print_str("this will be denominator of:");
972                        self.show_box(inc);
973                    }
974                }
975                ModeKind::None => {}
976            }
977        }
978    }
979
980    /// Shows the current page in `\showlists` (§986).
981    pub fn show_page_contents(&mut self) {
982        let head = self.page.page_head;
983        if head == self.page.page_tail {
984            return;
985        }
986        self.printer.print_nl("### current page:");
987        if self.ts.output_active {
988            self.printer.print_str(" (held over for next output)");
989        }
990        let first = self.nodes.next(head);
991        self.show_box(first);
992        if self.page.contents != crate::typeset::PageContents::Empty {
993            self.printer.print_nl("total height ");
994            self.print_totals();
995            self.printer.print_nl(" goal height ");
996            self.printer.print_scaled(self.page.so_far[0]);
997            let ins: Vec<crate::typeset::PageIns> = self.page.ins.clone();
998            for r in ins {
999                self.printer.print_ln();
1000                self.print_esc_str("insert");
1001                self.printer.print_int(i64::from(r.n));
1002                self.printer.print_str(" adds ");
1003                let c = self.eqtb.count[r.n as usize].val;
1004                let t = if c == 1000 {
1005                    r.height
1006                } else {
1007                    retex_arith::x_over_n(r.height, 1000)
1008                        .map(|d| d.quotient)
1009                        .unwrap_or(0)
1010                        .wrapping_mul(c)
1011                };
1012                self.printer.print_scaled(t);
1013                if r.split_up {
1014                    let mut q = head;
1015                    let mut t = 0;
1016                    loop {
1017                        q = self.nodes.next(q);
1018                        if q.is_null() {
1019                            break;
1020                        }
1021                        if self.nodes.id(q) == id::INS && u32::from(self.nodes.subtype(q)) == r.n {
1022                            t += 1;
1023                        }
1024                        if q == r.broken_ins {
1025                            break;
1026                        }
1027                    }
1028                    self.printer.print_str(", #");
1029                    self.printer.print_int(t);
1030                    self.printer.print_str(" might split");
1031                }
1032            }
1033        }
1034    }
1035
1036    /// Prints the page totals (`print_totals`).
1037    pub fn print_totals(&mut self) {
1038        let s = self.page.so_far;
1039        self.printer.print_scaled(s[1]);
1040        let names = ["", "fi", "fil", "fill", "filll"];
1041        // Stretch of orders normal, fi, fil, fill, filll;
1042        // shrink in so_far[6].
1043        for (o, nm) in names.iter().enumerate() {
1044            let v = self.page.stretch(o as u8);
1045            if v != 0 {
1046                self.printer.print_str(" plus ");
1047                self.printer.print_scaled(v);
1048                self.printer.print_str(nm);
1049            }
1050        }
1051        if s[6] != 0 {
1052            self.printer.print_str(" minus ");
1053            self.printer.print_scaled(s[6]);
1054        }
1055    }
1056
1057    /// The `\tracingonline`-dependent part of box display used by
1058    /// `\showbox` (`show_box`).
1059    pub fn show_box_param_limits(&mut self) {
1060        self.depth_threshold = self.eqtb.int(pg::assign_int::SHOWBOXDEPTH);
1061        self.breadth_max = self.eqtb.int(pg::assign_int::SHOWBOXBREADTH);
1062        if self.breadth_max <= 0 {
1063            self.breadth_max = 5;
1064        }
1065    }
1066}