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