Skip to main content

retex_engine/
build.rs

1//! Building boxes and lists (*TeX: The Program* §1055–§1135, with e-TeX's
2//! and LuaTeX's changes).
3//!
4//! LuaTeX differences implemented here (established with black-box tests):
5//!
6//! * a paragraph starts with a local paragraph node (`\localpar`), then
7//!   the indentation box;
8//! * `\-` takes its pre-break text from the language's `\prehyphenchar`
9//!   and its penalty from `\exhyphenpenalty`; `\discretionary` takes
10//!   `\hyphenpenalty` (both at the time the node is made) and accepts an
11//!   optional `penalty <int>`;
12//! * `\hpack`, `\vpack` and `\tpack` build boxes like `\hbox`, `\vbox` and
13//!   `\vtop` but without node processing;
14//! * the boundary commands append boundary nodes, and start a paragraph in
15//!   vertical mode.
16
17use crate::Engine;
18use crate::cmd::Cmd;
19use crate::eqtb::{Loc, Val};
20use crate::glue::{FIL, FILL, Glue};
21use crate::input::TokKind;
22use crate::maincontrol::{BoxContext, IGNORE_DEPTH, Mode, ModeKind};
23use crate::nodes::{BoxData, NodeData, NodeRef, glue_sub, id, kern_sub};
24use crate::primitives_gen::{assign_dimen as dp, assign_glue as gp, assign_int as ip};
25use crate::save::{GroupCode, SaveEntry};
26use crate::scan::ValLevel;
27use crate::token::Token;
28use crate::typeset::NULL_FLAG;
29
30/// Modifiers (`chr` codes) of list-building commands, LuaTeX numbering.
31#[allow(missing_docs)]
32pub mod chr {
33    pub const BOX: u32 = 0;
34    pub const COPY: u32 = 1;
35    pub const LAST_BOX: u32 = 2;
36    pub const VSPLIT: u32 = 3;
37    pub const TPACK: u32 = 4;
38    pub const VPACK: u32 = 5;
39    pub const HPACK: u32 = 6;
40    pub const VTOP: u32 = 7;
41    pub const VBOX: u32 = 8;
42    pub const HBOX: u32 = 141;
43    pub const SHIPOUT: u32 = 98;
44    pub const FIL: u32 = 0;
45    pub const FILL: u32 = 1;
46    pub const SS: u32 = 2;
47    pub const FIL_NEG: u32 = 3;
48    pub const SKIP: u32 = 4;
49    pub const MSKIP: u32 = 5;
50    pub const NOINDENT: u32 = 0;
51    pub const INDENT: u32 = 1;
52    pub const QUITVMODE: u32 = 2;
53}
54
55/// The specification of a box being built (`to`/`spread`).
56#[derive(Clone, Copy, Debug, PartialEq, Eq)]
57pub enum PackSpec {
58    /// `to <dimen>`.
59    Exactly(i32),
60    /// `spread <dimen>` (or natural size with 0).
61    Additional(i32),
62}
63
64impl PackSpec {
65    /// The natural size (`natural`).
66    pub const NATURAL: PackSpec = PackSpec::Additional(0);
67}
68
69const BOX_FLAG: i64 = 1 << 40;
70const GLOBAL_BOX_FLAG: i64 = BOX_FLAG + 0x1_0000;
71const SHIP_OUT_FLAG: i64 = BOX_FLAG + 0x2_0000;
72const LEADER_FLAG: i64 = BOX_FLAG + 0x3_0000;
73
74/// Encodes a box context for the save stack.
75pub fn ctx_to_i64(c: BoxContext) -> i64 {
76    match c {
77        BoxContext::Shift(s) => i64::from(s),
78        BoxContext::SetBox(n) => BOX_FLAG + i64::from(n),
79        BoxContext::GlobalSetBox(n) => GLOBAL_BOX_FLAG + i64::from(n),
80        BoxContext::Shipout => SHIP_OUT_FLAG,
81        BoxContext::Leaders(s) => LEADER_FLAG + i64::from(s),
82    }
83}
84
85/// Decodes a box context from the save stack.
86pub fn ctx_from_i64(v: i64) -> BoxContext {
87    if v < BOX_FLAG {
88        BoxContext::Shift(v as i32)
89    } else if v < GLOBAL_BOX_FLAG {
90        BoxContext::SetBox((v - BOX_FLAG) as u32)
91    } else if v < SHIP_OUT_FLAG {
92        BoxContext::GlobalSetBox((v - GLOBAL_BOX_FLAG) as u32)
93    } else if v == SHIP_OUT_FLAG {
94        BoxContext::Shipout
95    } else {
96        BoxContext::Leaders((v - LEADER_FLAG) as u16)
97    }
98}
99
100/// `norm_min`.
101pub fn norm_min(h: i32) -> u8 {
102    h.clamp(1, 63) as u8
103}
104
105impl Engine {
106    /// Pushes an auxiliary value (TeX's `saved(0):=v; incr(save_ptr)`).
107    pub fn push_saved(&mut self, v: i64) {
108        self.save.stack.push(SaveEntry::Value(v));
109    }
110
111    /// Pops an auxiliary value.
112    pub fn pop_saved(&mut self) -> i64 {
113        match self.save.stack.pop() {
114            Some(SaveEntry::Value(v)) => v,
115            other => {
116                if let Some(e) = other {
117                    self.save.stack.push(e);
118                }
119                self.pop_saved_below()
120            }
121        }
122    }
123
124    /// The topmost auxiliary value of the current group when other entries
125    /// lie above it: a definition made while the value was waiting (the
126    /// `\csname` of `\hbox\csname\relax}` defines a control sequence
127    /// while the box specification is scanned) saves the old meaning
128    /// there. As in TeX, that entry stays and is restored when the
129    /// enclosing group ends.
130    #[cold]
131    fn pop_saved_below(&mut self) -> i64 {
132        let found = self
133            .save
134            .stack
135            .iter()
136            .rev()
137            .take_while(|e| !matches!(e, SaveEntry::Boundary { .. }))
138            .position(|e| matches!(e, SaveEntry::Value(_)));
139        match found {
140            Some(k) => {
141                let i = self.save.stack.len() - 1 - k;
142                match self.save.stack.remove(i) {
143                    SaveEntry::Value(v) => v,
144                    _ => 0,
145                }
146            }
147            None => {
148                debug_assert!(false, "save stack out of sync");
149                0
150            }
151        }
152    }
153
154    /// The auxiliary value `k` entries below the top (`saved(-1-k)`).
155    pub fn peek_saved(&self, k: usize) -> i64 {
156        let n = self.save.stack.len();
157        match self.save.stack.get(n.wrapping_sub(1 + k)) {
158            Some(SaveEntry::Value(v)) => *v,
159            _ => 0,
160        }
161    }
162
163    /// Replaces the auxiliary value `k` entries below the top.
164    pub fn set_peek_saved(&mut self, k: usize, v: i64) {
165        let n = self.save.stack.len();
166        if let Some(SaveEntry::Value(x)) = self.save.stack.get_mut(n.wrapping_sub(1 + k)) {
167            *x = v;
168        }
169    }
170
171    /// A glue node for glue parameter `sub` (`new_param_glue`).
172    pub fn new_param_glue(&mut self, sub: u16) -> NodeRef {
173        let g = self.eqtb.glue(glue_param_of_subtype(sub));
174        self.new_node(
175            id::GLUE,
176            sub,
177            NodeData::Glue {
178                spec: g,
179                leader: NodeRef::NULL,
180            },
181        )
182    }
183
184    /// A glue node with the given specification.
185    pub fn new_glue_node(&mut self, spec: Glue, sub: u16) -> NodeRef {
186        self.new_node(
187            id::GLUE,
188            sub,
189            NodeData::Glue {
190                spec,
191                leader: NodeRef::NULL,
192            },
193        )
194    }
195
196    /// A kern node.
197    pub fn new_kern_node(&mut self, w: i32, sub: u16) -> NodeRef {
198        self.new_node(
199            id::KERN,
200            sub,
201            NodeData::Kern {
202                kern: w,
203                expansion_factor: 0,
204            },
205        )
206    }
207
208    /// A penalty node.
209    pub fn new_penalty_node(&mut self, m: i32) -> NodeRef {
210        self.new_node(id::PENALTY, 0, NodeData::Penalty { penalty: m })
211    }
212
213    /// An empty box (`new_null_box`) with the current attributes.
214    pub fn new_null_box_node(&mut self, hlist: bool) -> NodeRef {
215        let p = self.nodes.new_null_box(hlist);
216        self.give_new_attr(p);
217        p
218    }
219
220    /// Commands that build lists and boxes (the cases of `main_control`
221    /// not handled in [`crate::maincontrol`]).
222    pub fn typeset_command(&mut self) {
223        use ModeKind as K;
224        let kind = self.mode.abs_kind();
225        let cmd = self.cur_cmd;
226        match (kind, cmd) {
227            // Things that start a paragraph.
228            (
229                K::Vertical,
230                Cmd::Letter
231                | Cmd::OtherChar
232                | Cmd::CharNum
233                | Cmd::CharGiven
234                | Cmd::MathShift
235                | Cmd::MathShiftCs
236                | Cmd::UnHbox
237                | Cmd::Vrule
238                | Cmd::Accent
239                | Cmd::Discretionary
240                | Cmd::Hskip
241                | Cmd::Valign
242                | Cmd::ExSpace
243                | Cmd::Boundary
244                | Cmd::CharGhost
245                | Cmd::MathCharNum
246                | Cmd::MathGiven
247                | Cmd::XmathGiven,
248            ) => {
249                self.back_input();
250                self.new_graf(true);
251            }
252            (K::Horizontal, Cmd::MathCharNum | Cmd::MathGiven | Cmd::XmathGiven) => {
253                self.math_char_in_text();
254            }
255            (K::Vertical, Cmd::StartPar) => {
256                let c = self.cur_chr;
257                self.new_graf(c != chr::NOINDENT);
258            }
259            (K::Horizontal | K::Math, Cmd::StartPar) => self.indent_in_hmode(),
260            (K::Vertical, Cmd::Hrule) | (K::Horizontal | K::Math, Cmd::Vrule) => {
261                let r = self.scan_rule_spec();
262                self.tail_append(r);
263                if kind == K::Vertical {
264                    self.cur_list_mut().prev_depth = IGNORE_DEPTH;
265                } else if kind == K::Horizontal {
266                    self.cur_list_mut().space_factor = 1000;
267                }
268            }
269            (K::Vertical, Cmd::Nohrule) | (K::Horizontal | K::Math, Cmd::Novrule) => {
270                let r = self.scan_rule_spec();
271                self.nodes.set_subtype(r, 3);
272                self.tail_append(r);
273                if kind == K::Vertical {
274                    self.cur_list_mut().prev_depth = IGNORE_DEPTH;
275                } else if kind == K::Horizontal {
276                    self.cur_list_mut().space_factor = 1000;
277                }
278            }
279            (K::Vertical, Cmd::Vskip)
280            | (K::Horizontal | K::Math, Cmd::Hskip)
281            | (K::Math, Cmd::Mskip) => self.append_glue(),
282            (_, Cmd::Kern) | (K::Math, Cmd::Mkern) => self.append_kern(),
283            (K::Vertical, Cmd::Hmove) | (K::Horizontal | K::Math, Cmd::Vmove) => {
284                let t = self.cur_chr;
285                self.scan_normal_dimen();
286                let v = self.cur_val;
287                self.scan_box(BoxContext::Shift(if t == 0 { v } else { -v }));
288            }
289            (_, Cmd::LeaderShip) => {
290                let c = self.cur_chr;
291                if c == chr::SHIPOUT {
292                    self.scan_box(BoxContext::Shipout);
293                } else if c >= glue_sub::A_LEADERS as u32 {
294                    self.scan_box(BoxContext::Leaders(c as u16));
295                } else {
296                    // Other LuaTeX shipping variants are not supported.
297                    self.scan_box(BoxContext::Shipout);
298                }
299            }
300            (_, Cmd::MakeBox) => self.begin_box(BoxContext::Shift(0)),
301            (K::Vertical, Cmd::ParEnd) => {
302                self.normal_paragraph();
303                if self.mode.is_privileged() {
304                    self.build_page();
305                }
306            }
307            (K::Horizontal, Cmd::ParEnd) => {
308                if self.input.align_state < 0 {
309                    self.off_save();
310                }
311                self.end_graf();
312                if self.mode == Mode::Vertical {
313                    self.build_page();
314                }
315            }
316            (K::Horizontal, Cmd::Stop | Cmd::Vskip | Cmd::Hrule | Cmd::UnVbox | Cmd::Halign) => {
317                self.head_for_vmode()
318            }
319            (_, Cmd::Insert) | (K::Horizontal | K::Math, Cmd::Vadjust) => {
320                self.begin_insert_or_adjust()
321            }
322            (K::Vertical, Cmd::Vadjust) => self.report_illegal_case(),
323            (_, Cmd::Mark) => self.make_mark(),
324            (_, Cmd::BreakPenalty) => self.append_penalty(),
325            (_, Cmd::RemoveItem) => self.delete_last(),
326            (K::Vertical, Cmd::UnVbox) | (K::Horizontal | K::Math, Cmd::UnHbox) => self.unpackage(),
327            (K::Horizontal, Cmd::ItalCorr) => self.append_italic_correction(),
328            (K::Math, Cmd::ItalCorr) => {
329                let k = self.new_kern_node(0, kern_sub::FONT);
330                self.tail_append(k);
331            }
332            (K::Vertical, Cmd::ItalCorr) => self.report_illegal_case(),
333            (K::Horizontal | K::Math, Cmd::Discretionary) => self.append_discretionary(),
334            (K::Horizontal, Cmd::Accent) => self.make_accent(),
335            (K::Horizontal, Cmd::Boundary) => self.append_boundary(),
336            (K::Horizontal, Cmd::CharGhost) => self.append_ghost(),
337            (_, Cmd::CarRet | Cmd::TabMark) => self.align_error(),
338            (_, Cmd::NoAlign) => self.no_align_error(),
339            (_, Cmd::Omit) => self.omit_error(),
340            (K::Vertical, Cmd::Halign) | (K::Horizontal, Cmd::Valign) => self.init_align(),
341            (K::Math, Cmd::Halign) => {
342                if self.privileged() {
343                    if self.save.cur_group == GroupCode::MathShift {
344                        self.init_align();
345                    } else {
346                        self.off_save();
347                    }
348                }
349            }
350            (K::Vertical | K::Horizontal, Cmd::Endv) => self.do_endv(),
351            (_, Cmd::EndCsName) => self.cs_error(),
352            (_, Cmd::AssignLocalBox) => self.begin_local_box(),
353            (K::Horizontal, Cmd::MathShift | Cmd::MathShiftCs) => self.init_math(),
354            (K::Math, _) => self.math_command(),
355            (
356                K::Vertical | K::Horizontal,
357                Cmd::SupMark
358                | Cmd::SubMark
359                | Cmd::MathComp
360                | Cmd::DelimNum
361                | Cmd::LeftRight
362                | Cmd::Above
363                | Cmd::Radical
364                | Cmd::MathStyle
365                | Cmd::MathChoice
366                | Cmd::Vcenter
367                | Cmd::NonScript
368                | Cmd::Mkern
369                | Cmd::LimitSwitch
370                | Cmd::Mskip
371                | Cmd::MathAccent
372                | Cmd::SuperSubScript
373                | Cmd::NoSuperSubScript,
374            ) => self.insert_dollar_sign(),
375            (K::Vertical | K::Horizontal, Cmd::EqNo) => self.report_illegal_case(),
376            (_, Cmd::Option) => {
377                // \mathoption: scanned and ignored.
378                self.scan_int();
379                self.scan_optional_equals();
380                self.scan_int();
381            }
382            (_, Cmd::Node) => {
383                self.scan_int();
384            }
385            _ => {
386                let (c, r) = (self.cur_cmd, self.cur_chr);
387                let s = self.print_to_string(|e| e.print_cmd_chr(c, r));
388                self.not_implemented(&String::from_utf8_lossy(&s));
389            }
390        }
391    }
392
393    /// `\hskip`, `\vskip`, `\hfil`, … (`append_glue`).
394    pub fn append_glue(&mut self) {
395        let s = self.cur_chr;
396        let g = match s {
397            chr::FIL => Glue {
398                stretch: retex_arith::UNITY,
399                stretch_order: FIL,
400                ..Glue::ZERO
401            },
402            chr::FILL => Glue {
403                stretch: retex_arith::UNITY,
404                stretch_order: FILL,
405                ..Glue::ZERO
406            },
407            chr::SS => Glue {
408                stretch: retex_arith::UNITY,
409                stretch_order: FIL,
410                shrink: retex_arith::UNITY,
411                shrink_order: FIL,
412                ..Glue::ZERO
413            },
414            chr::FIL_NEG => Glue {
415                stretch: -retex_arith::UNITY,
416                stretch_order: FIL,
417                ..Glue::ZERO
418            },
419            chr::SKIP => {
420                self.scan_glue(ValLevel::Glue);
421                self.cur_glue
422            }
423            _ => {
424                self.scan_glue(ValLevel::Mu);
425                self.cur_glue
426            }
427        };
428        let sub = if s == chr::MSKIP {
429            glue_sub::MU_GLUE
430        } else {
431            glue_sub::USER
432        };
433        let p = self.new_glue_node(g, sub);
434        self.tail_append(p);
435    }
436
437    /// `\kern`, `\mkern` (`append_kern`).
438    pub fn append_kern(&mut self) {
439        let s = self.cur_chr as u16;
440        let mu = s == glue_sub::MU_GLUE;
441        self.scan_dimen(mu, false, false);
442        let v = self.cur_val;
443        let p = self.new_kern_node(
444            v,
445            if mu {
446                glue_sub::MU_GLUE
447            } else {
448                kern_sub::USER
449            },
450        );
451        self.tail_append(p);
452    }
453
454    /// Scans `\hrule`/`\vrule` dimensions (`scan_rule_spec`).
455    pub fn scan_rule_spec(&mut self) -> NodeRef {
456        let vertical_rule = matches!(self.cur_cmd, Cmd::Vrule | Cmd::Novrule);
457        let (mut w, mut h, mut d) = if vertical_rule {
458            (26214, NULL_FLAG, NULL_FLAG)
459        } else {
460            (NULL_FLAG, 26214, 0)
461        };
462        let (mut left, mut right) = (0, 0);
463        // The rule is made before its dimensions are scanned: it has the
464        // input line of `\hrule`/`\vrule` (observed in SyncTeX files).
465        let sync = self.nodes.sync.as_ref().map(|s| s.cur);
466        loop {
467            if self.scan_keyword("width") {
468                self.scan_normal_dimen();
469                w = self.cur_val;
470                continue;
471            }
472            if self.scan_keyword("height") {
473                self.scan_normal_dimen();
474                h = self.cur_val;
475                continue;
476            }
477            if self.scan_keyword("depth") {
478                self.scan_normal_dimen();
479                d = self.cur_val;
480                continue;
481            }
482            if self.scan_keyword("left") {
483                self.scan_normal_dimen();
484                left = self.cur_val;
485                continue;
486            }
487            if self.scan_keyword("right") {
488                self.scan_normal_dimen();
489                right = self.cur_val;
490                continue;
491            }
492            break;
493        }
494        let p = self.new_node(
495            id::RULE,
496            0,
497            NodeData::Rule {
498                width: w,
499                depth: d,
500                height: h,
501                dir: 0,
502                index: 0,
503                left,
504                right,
505            },
506        );
507        if let Some(f) = sync {
508            self.nodes.set_sync_fields(p, f);
509        }
510        p
511    }
512
513    /// Scans a box specification and pushes it with a new group
514    /// (`scan_spec`); `ctx` is the box context to save, if any.
515    pub fn scan_spec(&mut self, c: GroupCode, ctx: Option<i64>) {
516        // LuaTeX accepts `attr <n> <v>` and one direction before
517        // to/spread: `dir <TLT…>` or `bdir <number>` (observed: a number
518        // outside 0..3 means TLT; a second direction is not a keyword).
519        let mut dir: i64 = -1;
520        let mut attrs: Vec<(u32, i32)> = Vec::new();
521        loop {
522            if self.scan_keyword("attr") {
523                self.scan_register_num();
524                let n = self.cur_val as u32;
525                self.scan_optional_equals();
526                self.scan_int();
527                attrs.retain(|(k, _)| *k != n);
528                attrs.push((n, self.cur_val));
529                continue;
530            }
531            if dir < 0 && self.scan_keyword("dir") {
532                dir = i64::from(self.scan_direction_spec());
533                continue;
534            }
535            if dir < 0 && self.scan_keyword("bdir") {
536                self.scan_int();
537                dir = if (0..=3).contains(&self.cur_val) {
538                    i64::from(self.cur_val)
539                } else {
540                    0
541                };
542                continue;
543            }
544            break;
545        }
546        let is_box = matches!(
547            c,
548            GroupCode::Hbox | GroupCode::AdjustedHbox | GroupCode::Vbox | GroupCode::Vtop
549        );
550        if dir < 0 && is_box {
551            dir = i64::from(self.default_box_dir());
552        }
553        let spec = if self.scan_keyword("to") {
554            self.scan_normal_dimen();
555            PackSpec::Exactly(self.cur_val)
556        } else if self.scan_keyword("spread") {
557            self.scan_normal_dimen();
558            PackSpec::Additional(self.cur_val)
559        } else {
560            PackSpec::NATURAL
561        };
562        if let Some(v) = ctx {
563            self.push_saved(v);
564        }
565        let (code, v) = match spec {
566            PackSpec::Exactly(v) => (0, v),
567            PackSpec::Additional(v) => (1, v),
568        };
569        self.push_saved(code);
570        self.push_saved(i64::from(v));
571        self.push_saved(dir);
572        if is_box {
573            // Observed: a box gets the attributes current where it begins,
574            // with those given by `attr`.
575            let mut a = self.cur_attr();
576            if !attrs.is_empty() {
577                let mut v: Vec<(u32, i32)> = a.as_deref().unwrap_or(&[]).to_vec();
578                for (n, x) in attrs {
579                    v.retain(|(k, _)| *k != n);
580                    if x != crate::eqtb::UNUSED_ATTRIBUTE {
581                        v.push((n, x));
582                    }
583                }
584                v.sort_unstable();
585                a = if v.is_empty() {
586                    None
587                } else {
588                    Some(std::rc::Rc::from(v))
589                };
590            }
591            self.push_saved(self.ts.box_attrs.len() as i64);
592            self.ts.box_attrs.push(a);
593        }
594        self.new_save_level(c);
595        self.begin_box_dirs(c, dir as i32);
596        self.scan_left_brace();
597    }
598
599    /// Pops the specification pushed by `scan_spec` (for `\vcenter`).
600    pub fn pop_spec_pub(&mut self) -> (PackSpec, i64) {
601        self.pop_spec()
602    }
603
604    /// Pops the specification pushed by `scan_spec`: (spec, dir).
605    fn pop_spec(&mut self) -> (PackSpec, i64) {
606        let dir = self.pop_saved();
607        let v = self.pop_saved() as i32;
608        let code = self.pop_saved();
609        (
610            if code == 0 {
611                PackSpec::Exactly(v)
612            } else {
613                PackSpec::Additional(v)
614            },
615            dir,
616        )
617    }
618
619    /// Scans a box and builds it in context `ctx` (`scan_box`).
620    pub fn scan_box(&mut self, ctx: BoxContext) {
621        self.get_nonblank_nonrelax_noncall();
622        if self.cur_cmd == Cmd::MakeBox {
623            self.begin_box(ctx);
624        } else if matches!(ctx, BoxContext::Leaders(_))
625            && matches!(
626                self.cur_cmd,
627                Cmd::Hrule | Cmd::Vrule | Cmd::Nohrule | Cmd::Novrule
628            )
629        {
630            let r = self.scan_rule_spec();
631            self.box_end(ctx, r);
632        } else {
633            self.print_err("A <box> was supposed to be here");
634            self.help(&[
635                "I was expecting to see \\hbox or \\vbox or \\copy or \\box or",
636                "something like that. So you might find something missing in",
637                "your output. But keep trying; you can fix this later.",
638            ]);
639            self.back_error();
640        }
641    }
642
643    /// Starts building a box of the kind given by `cur_chr`, or fetches a
644    /// box register (`begin_box`).
645    pub fn begin_box(&mut self, ctx: BoxContext) {
646        let cur_box: NodeRef;
647        match self.cur_chr {
648            chr::BOX => {
649                self.scan_register_num();
650                let n = self.cur_val as usize;
651                cur_box = self.eqtb.boxes[n].val;
652                self.eqtb.boxes[n].val = NodeRef::NULL;
653            }
654            chr::COPY => {
655                self.scan_register_num();
656                let b = self.eqtb.boxes[self.cur_val as usize].val;
657                cur_box = self.copy_node_list(b);
658            }
659            chr::LAST_BOX => {
660                cur_box = self.take_last_box();
661            }
662            chr::VSPLIT => {
663                self.scan_register_num();
664                let n = self.cur_val as u32;
665                if !self.scan_keyword("to") {
666                    self.print_err("Missing `to' inserted");
667                    self.help(&[
668                        "I'm working on `\\vsplit<box number> to <dimen>';",
669                        "will look for the <dimen> next.",
670                    ]);
671                    self.error();
672                }
673                self.scan_normal_dimen();
674                let h = self.cur_val;
675                cur_box = self.vsplit(n, h);
676            }
677            c => {
678                // Initiate the construction of an hbox or vbox.
679                let k = match c {
680                    chr::HBOX | chr::HPACK => ModeKind::Horizontal,
681                    _ => ModeKind::Vertical,
682                };
683                let ctxv = ctx_to_i64(ctx);
684                // The packing variant is remembered with the context.
685                self.push_saved(i64::from(c));
686                if k == ModeKind::Horizontal {
687                    if matches!(ctx, BoxContext::Shift(_))
688                        && self.mode.abs_kind() == ModeKind::Vertical
689                    {
690                        self.scan_spec(GroupCode::AdjustedHbox, Some(ctxv));
691                    } else {
692                        self.scan_spec(GroupCode::Hbox, Some(ctxv));
693                    }
694                } else {
695                    if c == chr::VBOX || c == chr::VPACK {
696                        self.scan_spec(GroupCode::Vbox, Some(ctxv));
697                    } else {
698                        self.scan_spec(GroupCode::Vtop, Some(ctxv));
699                    }
700                    self.normal_paragraph();
701                }
702                if k == ModeKind::Vertical {
703                    self.push_nest(Mode::InternalVertical);
704                    self.cur_list_mut().prev_depth = IGNORE_DEPTH;
705                    let t = self
706                        .eqtb
707                        .toks_param(crate::primitives_gen::assign_toks::EVERYVBOX)
708                        .clone();
709                    if !t.is_empty() {
710                        self.begin_token_list(t, TokKind::EveryVboxText);
711                    }
712                } else {
713                    self.push_nest(Mode::RestrictedHorizontal);
714                    self.cur_list_mut().space_factor = 1000;
715                    let t = self
716                        .eqtb
717                        .toks_param(crate::primitives_gen::assign_toks::EVERYHBOX)
718                        .clone();
719                    if !t.is_empty() {
720                        self.begin_token_list(t, TokKind::EveryHboxText);
721                    }
722                }
723                return;
724            }
725        }
726        self.box_end(ctx, cur_box);
727    }
728
729    /// Copies a node list (`copy_node_list`).
730    pub fn copy_node_list(&mut self, p: NodeRef) -> NodeRef {
731        if p.is_null() {
732            return p;
733        }
734        self.nodes.copy_node(p)
735    }
736
737    /// `\lastbox`: removes the last box from the current list.
738    fn take_last_box(&mut self) -> NodeRef {
739        if self.mode.abs_kind() == ModeKind::Math {
740            self.you_cant();
741            self.help(&["Sorry; this \\lastbox will be void."]);
742            self.error();
743            return NodeRef::NULL;
744        }
745        let (head, tail) = (self.cur_list().head, self.cur_list().tail);
746        if self.mode == Mode::Vertical && head == tail {
747            self.you_cant();
748            self.help(&[
749                "Sorry...I usually can't take things from the current page.",
750                "This \\lastbox will therefore be void.",
751            ]);
752            self.error();
753            return NodeRef::NULL;
754        }
755        if tail == head {
756            return NodeRef::NULL;
757        }
758        let t = self.nodes.id(tail);
759        if t != id::HLIST && t != id::VLIST {
760            return NodeRef::NULL;
761        }
762        let p = self.nodes.prev(tail);
763        let p = if p.is_null() { self.find_prev(tail) } else { p };
764        self.nodes.set_next(p, NodeRef::NULL);
765        self.nodes.set_prev(tail, NodeRef::NULL);
766        self.cur_list_mut().tail = p;
767        if let Some(b) = self.nodes.box_data_mut(tail) {
768            b.shift = 0;
769        }
770        tail
771    }
772
773    /// Finds the predecessor of `q` in the current list by a linear scan.
774    fn find_prev(&self, q: NodeRef) -> NodeRef {
775        let mut p = self.cur_list().head;
776        while !p.is_null() {
777            let n = self.nodes.next(p);
778            if n == q {
779                return p;
780            }
781            p = n;
782        }
783        self.cur_list().head
784    }
785
786    /// Does the right thing with a finished box (`box_end`).
787    pub fn box_end(&mut self, ctx: BoxContext, cur_box: NodeRef) {
788        match ctx {
789            BoxContext::Shift(s) => {
790                if cur_box.is_null() {
791                    return;
792                }
793                if let Some(b) = self.nodes.box_data_mut(cur_box) {
794                    b.shift = s;
795                }
796                match self.mode.abs_kind() {
797                    ModeKind::Vertical => {
798                        self.append_box_with_adjustments(cur_box);
799                        if self.mode.is_privileged() {
800                            self.build_page();
801                        }
802                    }
803                    ModeKind::Horizontal => {
804                        self.cur_list_mut().space_factor = 1000;
805                        self.tail_append(cur_box);
806                    }
807                    _ => {
808                        self.math_box_end(cur_box);
809                    }
810                }
811            }
812            BoxContext::SetBox(n) => self.set_box_register(n, false, cur_box),
813            BoxContext::GlobalSetBox(n) => self.set_box_register(n, true, cur_box),
814            BoxContext::Shipout => {
815                if !cur_box.is_null() {
816                    self.ship_out(cur_box);
817                }
818            }
819            BoxContext::Leaders(sub) => {
820                if cur_box.is_null() {
821                    return;
822                }
823                self.get_nonblank_nonrelax_noncall();
824                let v = self.mode.abs_kind() == ModeKind::Vertical;
825                if (self.cur_cmd == Cmd::Hskip && !v) || (self.cur_cmd == Cmd::Vskip && v) {
826                    self.append_glue();
827                    let t = self.cur_list().tail;
828                    self.nodes.set_subtype(t, sub);
829                    if let NodeData::Glue { leader, .. } = self.nodes.data_mut(t) {
830                        *leader = cur_box;
831                    }
832                } else {
833                    self.print_err("Leaders not followed by proper glue");
834                    self.help(&[
835                        "You should say `\\leaders <box or rule><hskip or vskip>'.",
836                        "I found the <box or rule>, but there's no suitable",
837                        "<hskip or vskip>, so I'm ignoring these leaders.",
838                    ]);
839                    self.back_error();
840                    self.nodes.flush_list(cur_box);
841                }
842            }
843        }
844    }
845
846    /// Starts collecting `\vadjust` and `\insert` material in `hpack`
847    /// (TeX's `adjust_tail:=adjust_head`).
848    pub fn start_adjust_lists(&mut self) {
849        if self.ts.adjust_head.is_null() {
850            self.ts.adjust_head = self.nodes.new_temp();
851            self.ts.pre_adjust_head = self.nodes.new_temp();
852        }
853        self.nodes.set_next(self.ts.adjust_head, NodeRef::NULL);
854        self.nodes.set_next(self.ts.pre_adjust_head, NodeRef::NULL);
855        self.ts.adjust_tail = Some(self.ts.adjust_head);
856        self.ts.pre_adjust_tail = Some(self.ts.pre_adjust_head);
857    }
858
859    /// Appends the material of one adjustment list to the current list.
860    fn append_adjust_list(&mut self, head: NodeRef) {
861        let first = self.nodes.next(head);
862        if first.is_null() {
863            return;
864        }
865        let tail = self.cur_list().tail;
866        self.nodes.link(tail, first);
867        let t = self.nodes.tail_of(first);
868        self.cur_list_mut().tail = t;
869        self.nodes.set_next(head, NodeRef::NULL);
870    }
871
872    /// Appends a box to a vertical list with the adjustment material that
873    /// `hpack` removed from it (§1076).
874    fn append_box_with_adjustments(&mut self, b: NodeRef) {
875        let active = self.ts.adjust_tail.is_some();
876        if active {
877            let h = self.ts.pre_adjust_head;
878            self.append_adjust_list(h);
879        }
880        self.append_to_vlist(b);
881        if active {
882            let h = self.ts.adjust_head;
883            self.append_adjust_list(h);
884        }
885        self.ts.adjust_tail = None;
886        self.ts.pre_adjust_tail = None;
887    }
888
889    /// Stores a box in a register, freeing the old contents when they are
890    /// not saved for restoration.
891    pub fn set_box_register(&mut self, n: u32, global: bool, b: NodeRef) {
892        let old = self.eqtb.boxes[n as usize].val;
893        let old_level = self.eqtb.boxes[n as usize].level;
894        let saved = !global && old_level != self.save.cur_level && self.save.cur_level > 1;
895        self.define(global, Loc::Box(n), Val::Node(b));
896        if !saved && !old.is_null() && old != b {
897            self.nodes.flush_list(old);
898        }
899    }
900
901    /// Finishes an `\hbox`, `\vbox` or `\vtop` (`package`).
902    fn package(&mut self, vtop: bool) {
903        let d = self.eqtb.dimen(dp::BOXMAXDEPTH);
904        let group = self.save.cur_group;
905        self.unsave();
906        self.end_box_dirs();
907        let ai = self.pop_saved() as usize;
908        let attr = self.ts.box_attrs.get(ai).cloned().flatten();
909        self.ts.box_attrs.truncate(ai);
910        let (spec, dir) = self.pop_spec();
911        let ctx = ctx_from_i64(self.pop_saved());
912        let kind = self.pop_saved() as u32;
913        let head = self.cur_list().head;
914        let first = self.nodes.next(head);
915        self.nodes.set_next(head, NodeRef::NULL);
916        let cur_box = if self.mode == Mode::RestrictedHorizontal {
917            let first = if kind == chr::HPACK {
918                first
919            } else {
920                let first = self.node_process_list(first, crate::nodeproc::Context::Hbox);
921                let g = if group == GroupCode::AdjustedHbox {
922                    "adjusted_hbox"
923                } else {
924                    "hbox"
925                };
926                self.filter_list("hpack_filter", first, g)
927            };
928            self.pack_dir = dir as i8;
929            self.hpack(first, spec)
930        } else {
931            let first = if kind == chr::VPACK || kind == chr::TPACK {
932                first
933            } else {
934                self.filter_list("vpack_filter", first, if vtop { "vtop" } else { "vbox" })
935            };
936            self.pack_dir = dir as i8;
937            let b = self.vpackage(first, spec, d);
938            if vtop {
939                // Readjust the height and depth for \vtop.
940                let p = self.nodes.list_ptr(b);
941                let h = if !p.is_null() && self.nodes.id(p) <= id::RULE {
942                    self.node_height(p)
943                } else {
944                    0
945                };
946                if let Some(bd) = self.nodes.box_data_mut(b) {
947                    bd.depth = bd.depth - h + bd.height;
948                    bd.height = h;
949                }
950            }
951            b
952        };
953        if self.nodes.id(cur_box) == id::HLIST {
954            self.nodes.set_subtype(cur_box, 2);
955        }
956        if self.eqtb.attrs_in_use || attr.is_some() {
957            self.nodes.set_attr(cur_box, attr);
958        }
959        self.pop_nest();
960        self.box_end(ctx, cur_box);
961    }
962
963    /// The height of a box or rule node.
964    pub fn node_height(&self, p: NodeRef) -> i32 {
965        match self.nodes.data(p) {
966            NodeData::Box(b) => b.height,
967            NodeData::Unset { b, .. } => b.height,
968            NodeData::Rule { height, .. } => *height,
969            _ => 0,
970        }
971    }
972
973    /// The depth of a box or rule node.
974    pub fn node_depth(&self, p: NodeRef) -> i32 {
975        match self.nodes.data(p) {
976            NodeData::Box(b) => b.depth,
977            NodeData::Unset { b, .. } => b.depth,
978            NodeData::Rule { depth, .. } => *depth,
979            _ => 0,
980        }
981    }
982
983    /// Makes a local paragraph node with the current local parameters.
984    pub fn new_local_par(&mut self) -> NodeRef {
985        let lb = self.eqtb.local_box[0].val;
986        let rb = self.eqtb.local_box[1].val;
987        let box_left = self.copy_node_list(lb);
988        let box_right = self.copy_node_list(rb);
989        let box_left_width = if lb.is_null() {
990            0
991        } else {
992            self.nodes.width(lb)
993        };
994        let box_right_width = if rb.is_null() {
995            0
996        } else {
997            self.nodes.width(rb)
998        };
999        let dir = self.dir_param(crate::primitives_gen::assign_dir::PARDIR) as i8;
1000        self.new_node(
1001            id::LOCAL_PAR,
1002            0,
1003            NodeData::LocalPar {
1004                pen_inter: self.eqtb.int(ip::LOCALINTERLINEPENALTY),
1005                pen_broken: self.eqtb.int(ip::LOCALBROKENPENALTY),
1006                dir,
1007                box_left,
1008                box_left_width,
1009                box_right,
1010                box_right_width,
1011            },
1012        )
1013    }
1014
1015    /// Starts a paragraph (`new_graf`).
1016    pub fn new_graf(&mut self, indented: bool) {
1017        self.cur_list_mut().prev_graf = 0;
1018        let (head, tail) = (self.cur_list().head, self.cur_list().tail);
1019        if self.mode == Mode::Vertical || head != tail {
1020            let g = self.new_param_glue(glue_sub::PAR_SKIP);
1021            self.tail_append(g);
1022        }
1023        self.push_nest(Mode::Horizontal);
1024        self.cur_list_mut().space_factor = 1000;
1025        let l = self.cur_lang() as i32;
1026        self.cur_list_mut().clang = l;
1027        let lp = self.new_local_par();
1028        self.tail_append(lp);
1029        self.par_text_dir();
1030        if indented {
1031            let b = self.new_null_box_node(true);
1032            let w = self.eqtb.dimen(dp::PARINDENT);
1033            let dir = self.dir_param(crate::primitives_gen::assign_dir::PARDIR) as i8;
1034            if let Some(bd) = self.nodes.box_data_mut(b) {
1035                bd.width = w;
1036                bd.dir = dir;
1037            }
1038            self.nodes.set_subtype(b, 3);
1039            self.tail_append(b);
1040        }
1041        let t = self
1042            .eqtb
1043            .toks_param(crate::primitives_gen::assign_toks::EVERYPAR)
1044            .clone();
1045        if !t.is_empty() {
1046            self.begin_token_list(t, TokKind::EveryParText);
1047        }
1048        if self.nest.len() == 2 {
1049            self.build_page();
1050        }
1051    }
1052
1053    /// `\indent` in horizontal or math mode (`indent_in_hmode`).
1054    fn indent_in_hmode(&mut self) {
1055        if self.cur_chr == chr::INDENT {
1056            let b = self.new_null_box_node(true);
1057            let w = self.eqtb.dimen(dp::PARINDENT);
1058            if let Some(bd) = self.nodes.box_data_mut(b) {
1059                bd.width = w;
1060            }
1061            if self.mode.abs_kind() == ModeKind::Horizontal {
1062                self.cur_list_mut().space_factor = 1000;
1063                self.tail_append(b);
1064            } else {
1065                self.math_box_end(b);
1066            }
1067        }
1068    }
1069
1070    /// Commands that end a paragraph in horizontal mode
1071    /// (`head_for_vmode`).
1072    fn head_for_vmode(&mut self) {
1073        if self.mode.is_inner() {
1074            if self.cur_cmd != Cmd::Hrule {
1075                self.off_save();
1076            } else {
1077                self.print_err("You can't use `");
1078                self.print_esc_str("hrule");
1079                self.printer.print_str("' here except with leaders");
1080                self.help(&[
1081                    "To put a horizontal rule in an hbox or an alignment,",
1082                    "you should use \\leaders or \\hrulefill (see The TeXbook).",
1083                ]);
1084                self.error();
1085            }
1086        } else {
1087            self.back_input();
1088            self.cur_tok = Token::cs(self.par_token_cs);
1089            self.back_input();
1090            if let Some(crate::input::Level::Tokens(t)) = self.input.stack.last_mut() {
1091                t.kind = TokKind::Inserted;
1092            }
1093        }
1094    }
1095
1096    /// Ends a paragraph (`end_graf`).
1097    pub fn end_graf(&mut self) {
1098        if self.mode == Mode::Horizontal {
1099            let (head, tail) = (self.cur_list().head, self.cur_list().tail);
1100            // A paragraph holding nothing but its \localpar node is empty
1101            // too: LuaTeX discards it.
1102            let only_local_par = self.nodes.next(head) == tail
1103                && matches!(self.nodes.data(tail), NodeData::LocalPar { .. });
1104            if head == tail || only_local_par {
1105                self.nodes.flush_list(tail);
1106                self.pop_nest();
1107            } else {
1108                let wp = self.eqtb.int(ip::WIDOWPENALTY);
1109                self.line_break(wp);
1110            }
1111            self.normal_paragraph();
1112            self.err.error_count = 0;
1113        }
1114    }
1115
1116    /// Resets paragraph shape parameters (`normal_paragraph`).
1117    pub fn normal_paragraph(&mut self) {
1118        use crate::primitives_gen::set_etex_shape as es;
1119        if self.eqtb.int(ip::LOOSENESS) != 0 {
1120            self.eq_define(Loc::IntPar(ip::LOOSENESS), Val::Int(0));
1121        }
1122        if self.eqtb.dimen(dp::HANGINDENT) != 0 {
1123            self.eq_define(Loc::DimenPar(dp::HANGINDENT), Val::Int(0));
1124        }
1125        if self.eqtb.int(ip::HANGAFTER) != 1 {
1126            self.eq_define(Loc::IntPar(ip::HANGAFTER), Val::Int(1));
1127        }
1128        if self.eqtb.par_shape.val.is_some() {
1129            self.eq_define(Loc::ParShape, Val::Shape(None));
1130        }
1131        if self.eqtb.etex_shape[es::INTERLINEPENALTIES as usize]
1132            .val
1133            .is_some()
1134        {
1135            self.eq_define(Loc::EtexShape(es::INTERLINEPENALTIES), Val::Shape(None));
1136        }
1137    }
1138
1139    /// `\insert` and `\vadjust` (`begin_insert_or_adjust`).
1140    fn begin_insert_or_adjust(&mut self) {
1141        let v: i64 = if self.cur_cmd == Cmd::Vadjust {
1142            let pre = self.scan_keyword("pre");
1143            if pre { -2 } else { -1 }
1144        } else {
1145            self.scan_register_num();
1146            let mut n = self.cur_val;
1147            if n == self.output_box_num() {
1148                self.print_err("You can't ");
1149                self.print_esc_str("insert");
1150                self.printer.print_int(i64::from(n));
1151                self.help(&["I'm changing to \\insert0; box \\outputbox is special."]);
1152                self.error();
1153                n = 0;
1154            }
1155            i64::from(n)
1156        };
1157        self.push_saved(v);
1158        self.new_save_level(GroupCode::Insert);
1159        self.scan_left_brace();
1160        self.normal_paragraph();
1161        self.push_nest(Mode::InternalVertical);
1162        self.cur_list_mut().prev_depth = IGNORE_DEPTH;
1163    }
1164
1165    /// The box register used by the output routine (`\outputbox`).
1166    pub fn output_box_num(&self) -> i32 {
1167        let n = self.eqtb.int(ip::OUTPUTBOX);
1168        if (0..65536).contains(&n) { n } else { 255 }
1169    }
1170
1171    /// With `\partokencontext` at least `level` and a paragraph in progress,
1172    /// a group end that would end the paragraph directly reads the
1173    /// par-token first: the right brace is put back behind it and true is
1174    /// returned (pdfTeX's \partokencontext: 1 for \vbox, \vtop and
1175    /// \vcenter; 2 also for \insert, \vadjust, \noalign and \valign items).
1176    pub fn par_token_before_group_end(&mut self, level: i32) -> bool {
1177        if self.mode != Mode::Horizontal || self.eqtb.int(ip::PARTOKENCONTEXT) < level {
1178            return false;
1179        }
1180        self.back_input();
1181        self.cur_tok = Token::cs(self.par_token_cs);
1182        self.back_input();
1183        true
1184    }
1185
1186    /// Ends an insertion or adjustment group (§1100).
1187    fn finish_insert_group(&mut self) {
1188        if self.par_token_before_group_end(2) {
1189            return;
1190        }
1191        self.end_graf();
1192        let q = self.eqtb.glue(gp::SPLITTOPSKIP);
1193        let d = self.eqtb.dimen(dp::SPLITMAXDEPTH);
1194        let f = self.eqtb.int(ip::FLOATINGPENALTY);
1195        self.unsave();
1196        let n = self.pop_saved();
1197        let head = self.cur_list().head;
1198        let first = self.nodes.next(head);
1199        self.nodes.set_next(head, NodeRef::NULL);
1200        let p = self.vpack(first, PackSpec::NATURAL);
1201        self.pop_nest();
1202        let list = self.nodes.list_ptr(p);
1203        self.nodes.set_list_ptr(p, NodeRef::NULL);
1204        let (h, dd) = match self.nodes.box_data(p) {
1205            Some(b) => (b.height, b.depth),
1206            None => (0, 0),
1207        };
1208        if n >= 0 {
1209            let node = self.new_node(
1210                id::INS,
1211                n as u16,
1212                NodeData::Ins {
1213                    cost: f,
1214                    depth: d,
1215                    height: h + dd,
1216                    spec: q,
1217                    head: list,
1218                },
1219            );
1220            self.tail_append(node);
1221        } else {
1222            let node = self.new_node(
1223                id::ADJUST,
1224                if n == -2 { 1 } else { 0 },
1225                NodeData::Adjust { head: list },
1226            );
1227            self.tail_append(node);
1228        }
1229        self.nodes.free(p);
1230        if self.nest.len() == 1 {
1231            self.build_page();
1232        }
1233    }
1234
1235    /// `\mark`, `\marks` (`make_mark`).
1236    fn make_mark(&mut self) {
1237        let c: u32 = if self.cur_chr == 0 {
1238            0
1239        } else if self.cur_chr == 1 {
1240            // \clearmarks n
1241            self.scan_register_num();
1242            let n = self.cur_val as u32;
1243            self.marks.classes.remove(&n);
1244            return;
1245        } else {
1246            self.scan_register_num();
1247            self.cur_val as u32
1248        };
1249        let toks = self.scan_toks_expanded();
1250        let p = self.new_node(
1251            id::MARK,
1252            0,
1253            NodeData::Mark {
1254                class: c,
1255                mark: std::rc::Rc::from(toks),
1256            },
1257        );
1258        self.tail_append(p);
1259    }
1260
1261    /// `\penalty` (`append_penalty`).
1262    fn append_penalty(&mut self) {
1263        self.scan_int();
1264        let p = self.new_penalty_node(self.cur_val);
1265        self.tail_append(p);
1266        if self.mode == Mode::Vertical {
1267            self.build_page();
1268        }
1269    }
1270
1271    /// `\unskip`, `\unkern`, `\unpenalty` (`delete_last`).
1272    fn delete_last(&mut self) {
1273        let (head, tail) = (self.cur_list().head, self.cur_list().tail);
1274        let want = self.cur_chr as u8;
1275        if self.mode == Mode::Vertical && tail == head {
1276            // Apologize, unless \unskip follows non-glue.
1277            if want != id::GLUE || self.page.last_glue.is_some() {
1278                self.you_cant();
1279                let l0 = if want == id::KERN {
1280                    "Try `I\\kern-\\lastkern' instead."
1281                } else if want != id::GLUE {
1282                    "Perhaps you can make the output routine do it."
1283                } else {
1284                    "Try `I\\vskip-\\lastskip' instead."
1285                };
1286                self.help(&[
1287                    "Sorry...I usually can't take things from the current page.",
1288                    l0,
1289                ]);
1290                self.error();
1291            }
1292            return;
1293        }
1294        if tail == head || self.nodes.id(tail) != want {
1295            return;
1296        }
1297        let p = self.find_prev(tail);
1298        self.nodes.set_next(p, NodeRef::NULL);
1299        self.nodes.flush_list(tail);
1300        self.cur_list_mut().tail = p;
1301    }
1302
1303    /// `\unhbox`, `\unvbox`, `\unhcopy`, `\unvcopy`, `\pagediscards`,
1304    /// `\splitdiscards` (`unpackage`).
1305    fn unpackage(&mut self) {
1306        let c = self.cur_chr;
1307        if c > chr::COPY {
1308            // \pagediscards (2), \splitdiscards (3).
1309            let k = c as usize;
1310            let p = self.ts.disc_ptr[k];
1311            self.ts.disc_ptr[k] = NodeRef::NULL;
1312            if !p.is_null() {
1313                let tail = self.cur_list().tail;
1314                self.nodes.link(tail, p);
1315                let t = self.nodes.tail_of(p);
1316                self.cur_list_mut().tail = t;
1317            }
1318            return;
1319        }
1320        self.scan_register_num();
1321        let n = self.cur_val as usize;
1322        let p = self.eqtb.boxes[n].val;
1323        if p.is_null() {
1324            return;
1325        }
1326        let t = self.nodes.id(p);
1327        let k = self.mode.abs_kind();
1328        if k == ModeKind::Math
1329            || (k == ModeKind::Vertical && t != id::VLIST)
1330            || (k == ModeKind::Horizontal && t != id::HLIST)
1331        {
1332            self.print_err("Incompatible list can't be unboxed");
1333            self.help(&[
1334                "Sorry, Pandora. (You sneaky devil.)",
1335                "I refuse to unbox an \\hbox in vertical mode or vice versa.",
1336                "And I can't open any boxes in math mode.",
1337            ]);
1338            self.error();
1339            return;
1340        }
1341        let list = if c == chr::COPY {
1342            let l = self.nodes.list_ptr(p);
1343            self.nodes.copy_list(l)
1344        } else {
1345            let l = self.nodes.list_ptr(p);
1346            self.nodes.set_list_ptr(p, NodeRef::NULL);
1347            self.eqtb.boxes[n].val = NodeRef::NULL;
1348            self.nodes.free(p);
1349            l
1350        };
1351        let list = if t == id::HLIST {
1352            self.strip_margin_kerns(list)
1353        } else {
1354            list
1355        };
1356        if !list.is_null() {
1357            let tail = self.cur_list().tail;
1358            self.nodes.link(tail, list);
1359            let t = self.nodes.tail_of(list);
1360            self.cur_list_mut().tail = t;
1361        }
1362    }
1363
1364    /// `\/` (`append_italic_correction`).
1365    fn append_italic_correction(&mut self) {
1366        let (head, tail) = (self.cur_list().head, self.cur_list().tail);
1367        if tail == head {
1368            return;
1369        }
1370        let Some(g) = self.nodes.glyph(tail) else {
1371            return;
1372        };
1373        let (f, c) = (g.font, g.chr);
1374        let ic = self.font_char_dimen(f, c, 3);
1375        let k = self.new_kern_node(ic, kern_sub::ITALIC);
1376        self.tail_append(k);
1377    }
1378
1379    /// `\-` and `\discretionary` (`append_discretionary`).
1380    fn append_discretionary(&mut self) {
1381        let c = self.cur_chr;
1382        if c == 1 {
1383            // \- : LuaTeX's explicit discretionary.
1384            let pen = self.eqtb.int(ip::EXHYPHENPENALTY);
1385            let d = self.new_node(
1386                id::DISC,
1387                1,
1388                NodeData::Disc {
1389                    pre: NodeRef::NULL,
1390                    post: NodeRef::NULL,
1391                    replace: NodeRef::NULL,
1392                    penalty: pen,
1393                },
1394            );
1395            let lang = self.cur_lang();
1396            let (pre_c, post_c) = {
1397                let l = self.lang_params(lang);
1398                (l.pre_hyphen_char, l.post_hyphen_char)
1399            };
1400            let f = self.eqtb.cur_font.val;
1401            let pre = if pre_c > 0 {
1402                self.new_char_node(f, pre_c as u32)
1403            } else {
1404                NodeRef::NULL
1405            };
1406            let post = if post_c > 0 {
1407                self.new_char_node(f, post_c as u32)
1408            } else {
1409                NodeRef::NULL
1410            };
1411            self.nodes.set_disc_lists(d, (pre, post, NodeRef::NULL));
1412            self.tail_append(d);
1413            return;
1414        }
1415        let mut pen = self.eqtb.int(ip::HYPHENPENALTY);
1416        if self.scan_keyword("penalty") {
1417            self.scan_int();
1418            pen = self.cur_val;
1419        }
1420        let d = self.new_node(
1421            id::DISC,
1422            if c == 2 { 2 } else { 0 },
1423            NodeData::Disc {
1424                pre: NodeRef::NULL,
1425                post: NodeRef::NULL,
1426                replace: NodeRef::NULL,
1427                penalty: pen,
1428            },
1429        );
1430        self.tail_append(d);
1431        self.push_saved(0);
1432        self.new_save_level(GroupCode::Disc);
1433        self.scan_left_brace();
1434        self.push_nest(Mode::RestrictedHorizontal);
1435        self.cur_list_mut().space_factor = 1000;
1436    }
1437
1438    /// Handles the end of a discretionary sublist
1439    /// (`build_discretionary`).
1440    fn build_discretionary(&mut self) {
1441        self.unsave();
1442        // Prune the current list to glyphs, kerns, boxes and rules.
1443        let head = self.cur_list().head;
1444        let mut q = head;
1445        let mut p = self.nodes.next(q);
1446        while !p.is_null() {
1447            let t = self.nodes.id(p);
1448            if !matches!(t, id::GLYPH | id::KERN | id::HLIST | id::VLIST | id::RULE) {
1449                self.print_err("Improper discretionary list");
1450                self.help(&["Discretionary lists must contain only boxes and kerns."]);
1451                self.error();
1452                self.begin_diagnostic();
1453                self.printer
1454                    .print_nl("The following discretionary sublist has been deleted:");
1455                self.show_box(p);
1456                self.end_diagnostic(true);
1457                self.nodes.flush_list(p);
1458                self.nodes.set_next(q, NodeRef::NULL);
1459                break;
1460            }
1461            q = p;
1462            p = self.nodes.next(q);
1463        }
1464        let list = self.nodes.next(head);
1465        self.nodes.set_next(head, NodeRef::NULL);
1466        if !list.is_null() {
1467            self.nodes.set_prev(list, NodeRef::NULL);
1468        }
1469        self.pop_nest();
1470        let k = self.pop_saved();
1471        let d = self.cur_list().tail;
1472        let (mut pre, mut post, mut rep) = self.nodes.disc_lists(d);
1473        match k {
1474            0 => pre = list,
1475            1 => post = list,
1476            _ => {
1477                if !list.is_null() && self.mode.abs_kind() == ModeKind::Math {
1478                    self.print_err("Illegal math ");
1479                    self.print_esc_str("discretionary");
1480                    self.help(&[
1481                        "Sorry: The third part of a discretionary break must be",
1482                        "empty, in math formulas. I had to delete your third part.",
1483                    ]);
1484                    self.nodes.flush_list(list);
1485                    self.error();
1486                } else {
1487                    rep = list;
1488                }
1489                self.nodes.set_disc_lists(d, (pre, post, rep));
1490                return;
1491            }
1492        }
1493        self.nodes.set_disc_lists(d, (pre, post, rep));
1494        self.push_saved(k + 1);
1495        self.new_save_level(GroupCode::Disc);
1496        self.scan_left_brace();
1497        self.push_nest(Mode::RestrictedHorizontal);
1498        self.cur_list_mut().space_factor = 1000;
1499    }
1500
1501    /// `\accent` (`make_accent`).
1502    fn make_accent(&mut self) {
1503        self.scan_char_num();
1504        let f = self.eqtb.cur_font.val;
1505        let p = self.new_char_node(f, self.cur_val as u32);
1506        let x = self.x_height(f);
1507        let s = f64::from(self.font(f).params.get(1).copied().unwrap_or(0)) / 65536.0;
1508        let a = self.char_width(f, self.cur_val as u32);
1509        self.do_assignments();
1510        let mut q = NodeRef::NULL;
1511        let f2 = self.eqtb.cur_font.val;
1512        if matches!(self.cur_cmd, Cmd::Letter | Cmd::OtherChar | Cmd::CharGiven) {
1513            q = self.new_char_node(f2, self.cur_chr);
1514        } else if self.cur_cmd == Cmd::CharNum {
1515            self.scan_char_num();
1516            q = self.new_char_node(f2, self.cur_val as u32);
1517        } else {
1518            self.back_input();
1519        }
1520        let mut p = p;
1521        if !q.is_null() {
1522            let t = f64::from(self.font(f2).params.get(1).copied().unwrap_or(0)) / 65536.0;
1523            let qc = self.nodes.glyph(q).map_or(0, |g| g.chr);
1524            let w = self.char_width(f2, qc);
1525            let h = self.font_char_dimen(f2, qc, 1);
1526            if h != x {
1527                p = self.hpack(p, PackSpec::NATURAL);
1528                if let Some(b) = self.nodes.box_data_mut(p) {
1529                    b.shift = x - h;
1530                }
1531            }
1532            let delta =
1533                ((f64::from(w - a)) / 2.0 + f64::from(h) * t - f64::from(x) * s).round() as i32;
1534            let r = self.new_kern_node(delta, kern_sub::ACCENT);
1535            self.tail_append(r);
1536            self.tail_append(p);
1537            let k = self.new_kern_node(-a - delta, kern_sub::ACCENT);
1538            self.tail_append(k);
1539            p = q;
1540        }
1541        self.tail_append(p);
1542        self.cur_list_mut().space_factor = 1000;
1543    }
1544
1545    /// The width of character `c` in font `f` (0 if absent).
1546    #[inline]
1547    pub fn char_width(&self, f: u32, c: u32) -> i32 {
1548        self.font(f).chars.get(&c).map_or(0, |m| m.width)
1549    }
1550
1551    /// Executes assignments until a non-assignment (`do_assignments`).
1552    pub fn do_assignments(&mut self) {
1553        loop {
1554            self.get_nonblank_nonrelax_noncall();
1555            if self.cur_cmd <= Cmd::MAX_NON_PREFIXED {
1556                return;
1557            }
1558            self.err.set_box_allowed = false;
1559            self.prefixed_command();
1560            self.err.set_box_allowed = true;
1561        }
1562    }
1563
1564    /// `\boundary`, `\noboundary`, `\protrusionboundary`,
1565    /// `\wordboundary`.
1566    pub fn append_boundary(&mut self) {
1567        let c = self.cur_chr;
1568        let v = if c == 1 || c == 2 {
1569            self.scan_int();
1570            self.cur_val
1571        } else {
1572            0
1573        };
1574        let p = self.new_node(id::BOUNDARY, c as u16, NodeData::Boundary { value: v });
1575        self.tail_append(p);
1576    }
1577
1578    /// `\leftghost`, `\rightghost`: LuaTeX 1.22 reads the next token,
1579    /// expanding macros, and appends nothing (observed: `\leftghost 65x`
1580    /// gives "5x", `\leftghost\char66` typesets "66", a macro loses its
1581    /// first token).
1582    fn append_ghost(&mut self) {
1583        self.get_x_token();
1584    }
1585
1586    /// `\localleftbox{…}`, `\localrightbox{…}`.
1587    fn begin_local_box(&mut self) {
1588        let k = self.cur_chr;
1589        self.push_saved(i64::from(k));
1590        self.new_save_level(GroupCode::LocalBox);
1591        self.scan_left_brace();
1592        self.push_nest(Mode::RestrictedHorizontal);
1593        self.cur_list_mut().space_factor = 1000;
1594    }
1595
1596    /// Ends a `\localleftbox`/`\localrightbox` group.
1597    fn finish_local_box(&mut self) {
1598        self.unsave();
1599        let k = self.pop_saved() as usize;
1600        let head = self.cur_list().head;
1601        let first = self.nodes.next(head);
1602        self.nodes.set_next(head, NodeRef::NULL);
1603        self.pop_nest();
1604        let b = if first.is_null() {
1605            NodeRef::NULL
1606        } else {
1607            let first = self.node_process_list(first, crate::nodeproc::Context::Hbox);
1608            let b = self.hpack(first, PackSpec::NATURAL);
1609            self.nodes.set_subtype(b, 2);
1610            b
1611        };
1612        self.eq_define(Loc::LocalBox(k as u32), Val::Node(b));
1613        if self.mode == Mode::Horizontal {
1614            // A change of the local boxes inserts a new local par node.
1615            let lp = self.new_local_par();
1616            self.tail_append(lp);
1617        }
1618    }
1619
1620    /// Recovers from a misplaced `&`, `\span` or `\cr` (`align_error`).
1621    fn align_error(&mut self) {
1622        if self.input.align_state.abs() > 2 {
1623            self.print_err("Misplaced ");
1624            self.print_cmd_chr(self.cur_cmd, self.cur_chr);
1625            if self.cur_tok == Token::char(Cmd::TabMark, '&' as u32) {
1626                self.help(&[
1627                    "I can't figure out why you would want to use a tab mark",
1628                    "here. If you just want an ampersand, the remedy is",
1629                    "simple: Just type `I\\&' now. But if some right brace",
1630                    "up above has ended a previous alignment prematurely,",
1631                    "you're probably due for more error messages, and you",
1632                    "might try typing `S' now just to see what is salvageable.",
1633                ]);
1634            } else {
1635                self.help(&[
1636                    "I can't figure out why you would want to use a tab mark",
1637                    "or \\cr or \\span just now. If something like a right brace",
1638                    "up above has ended a previous alignment prematurely,",
1639                    "you're probably due for more error messages, and you",
1640                    "might try typing `S' now just to see what is salvageable.",
1641                ]);
1642            }
1643            self.error();
1644        } else {
1645            self.back_input();
1646            if self.input.align_state < 0 {
1647                self.print_err("Missing { inserted");
1648                self.input.align_state += 1;
1649                self.cur_tok = Token::LEFT_BRACE;
1650            } else {
1651                self.print_err("Missing } inserted");
1652                self.input.align_state -= 1;
1653                self.cur_tok = Token::RIGHT_BRACE;
1654            }
1655            self.help(&[
1656                "I've put in what seems to be necessary to fix",
1657                "the current column of the current alignment.",
1658                "Try to go on, since this might almost work.",
1659            ]);
1660            self.ins_error();
1661        }
1662    }
1663
1664    fn no_align_error(&mut self) {
1665        self.print_err("Misplaced ");
1666        self.print_esc_str("noalign");
1667        self.help(&[
1668            "I expect to see \\noalign only after the \\cr of",
1669            "an alignment. Proceed, and I'll ignore this case.",
1670        ]);
1671        self.error();
1672    }
1673
1674    fn omit_error(&mut self) {
1675        self.print_err("Misplaced ");
1676        self.print_esc_str("omit");
1677        self.help(&[
1678            "I expect to see \\omit only after tab marks or the \\cr of",
1679            "an alignment. Proceed, and I'll ignore this case.",
1680        ]);
1681        self.error();
1682    }
1683
1684    fn cs_error(&mut self) {
1685        self.print_err("Extra ");
1686        self.print_esc_str("endcsname");
1687        self.help(&["I'm ignoring this, since I wasn't doing a \\csname."]);
1688        self.error();
1689    }
1690
1691    /// Appends a box to the current vertical list with interline glue
1692    /// (`append_to_vlist`).
1693    pub fn append_to_vlist(&mut self, b: NodeRef) {
1694        let pd = self.cur_list().prev_depth;
1695        if pd > IGNORE_DEPTH {
1696            let bs = self.eqtb.glue(gp::BASELINESKIP);
1697            let h = self.node_height(b);
1698            let d = bs.width - pd - h;
1699            let p = if d < self.eqtb.dimen(dp::LINESKIPLIMIT) {
1700                self.new_param_glue(glue_sub::LINE_SKIP)
1701            } else {
1702                let g = Glue { width: d, ..bs };
1703                self.new_glue_node(g, glue_sub::BASELINE_SKIP)
1704            };
1705            self.tail_append(p);
1706        }
1707        self.tail_append(b);
1708        let d = self.node_depth(b);
1709        self.cur_list_mut().prev_depth = d;
1710    }
1711
1712    /// Right braces closing box groups and other non-simple groups
1713    /// (cases of `handle_right_brace`).
1714    pub fn handle_right_brace_typesetting(&mut self) {
1715        match self.save.cur_group {
1716            GroupCode::Hbox => self.package(false),
1717            GroupCode::AdjustedHbox => {
1718                self.start_adjust_lists();
1719                self.package(false);
1720            }
1721            GroupCode::Vbox => {
1722                if self.par_token_before_group_end(1) {
1723                    return;
1724                }
1725                self.end_graf();
1726                self.package(false);
1727            }
1728            GroupCode::Vtop => {
1729                if self.par_token_before_group_end(1) {
1730                    return;
1731                }
1732                self.end_graf();
1733                self.package(true);
1734            }
1735            GroupCode::Insert => self.finish_insert_group(),
1736            GroupCode::Output => self.resume_output(),
1737            GroupCode::Disc => self.build_discretionary(),
1738            GroupCode::Align => {
1739                self.back_input();
1740                self.cur_tok = Token::cs(self.cs.frozen_cr);
1741                self.print_err("Missing ");
1742                self.print_esc_str("cr");
1743                self.printer.print_str(" inserted");
1744                self.help(&["I'm guessing that you meant to end an alignment here."]);
1745                self.ins_error();
1746            }
1747            GroupCode::NoAlign => {
1748                if self.par_token_before_group_end(2) {
1749                    return;
1750                }
1751                self.end_graf();
1752                self.unsave();
1753                self.align_peek();
1754            }
1755            GroupCode::LocalBox => self.finish_local_box(),
1756            GroupCode::Vcenter | GroupCode::Math | GroupCode::MathChoice => self.math_right_brace(),
1757            _ => self.confusion("rightbrace"),
1758        }
1759    }
1760}
1761
1762/// The glue parameter (eqtb index) belonging to a glue subtype.
1763pub fn glue_param_of_subtype(sub: u16) -> u32 {
1764    match sub {
1765        glue_sub::LINE_SKIP => gp::LINESKIP,
1766        glue_sub::BASELINE_SKIP => gp::BASELINESKIP,
1767        glue_sub::PAR_SKIP => gp::PARSKIP,
1768        glue_sub::ABOVE_DISPLAY => gp::ABOVEDISPLAYSKIP,
1769        glue_sub::BELOW_DISPLAY => gp::BELOWDISPLAYSKIP,
1770        glue_sub::ABOVE_DISPLAY_SHORT => gp::ABOVEDISPLAYSHORTSKIP,
1771        glue_sub::BELOW_DISPLAY_SHORT => gp::BELOWDISPLAYSHORTSKIP,
1772        glue_sub::LEFT_SKIP => gp::LEFTSKIP,
1773        glue_sub::RIGHT_SKIP => gp::RIGHTSKIP,
1774        glue_sub::TOP_SKIP => gp::TOPSKIP,
1775        glue_sub::SPLIT_TOP_SKIP => gp::SPLITTOPSKIP,
1776        glue_sub::TAB_SKIP => gp::TABSKIP,
1777        glue_sub::SPACE_SKIP => gp::SPACESKIP,
1778        glue_sub::XSPACE_SKIP => gp::XSPACESKIP,
1779        glue_sub::PAR_FILL_SKIP => gp::PARFILLSKIP,
1780        glue_sub::MATH_SKIP => gp::MATHSURROUNDSKIP,
1781        _ => gp::LINESKIP,
1782    }
1783}
1784
1785/// Fields of a fresh box with the given dimensions (helper for tests).
1786pub fn box_with(width: i32, height: i32, depth: i32) -> BoxData {
1787    BoxData {
1788        width,
1789        height,
1790        depth,
1791        ..BoxData::default()
1792    }
1793}