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, "box");
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                // Lines that a picture being recorded would add to the
1129                // list around it.
1130                if self.figures.recording() {
1131                    self.figure_paragraph_end();
1132                }
1133                let wp = self.eqtb.int(ip::WIDOWPENALTY);
1134                self.line_break(wp);
1135            }
1136            self.normal_paragraph();
1137            self.err.error_count = 0;
1138        }
1139    }
1140
1141    /// Resets paragraph shape parameters (`normal_paragraph`).
1142    pub fn normal_paragraph(&mut self) {
1143        use crate::primitives_gen::set_etex_shape as es;
1144        if self.eqtb.int(ip::LOOSENESS) != 0 {
1145            self.eq_define(Loc::IntPar(ip::LOOSENESS), Val::Int(0));
1146        }
1147        if self.eqtb.dimen(dp::HANGINDENT) != 0 {
1148            self.eq_define(Loc::DimenPar(dp::HANGINDENT), Val::Int(0));
1149        }
1150        if self.eqtb.int(ip::HANGAFTER) != 1 {
1151            self.eq_define(Loc::IntPar(ip::HANGAFTER), Val::Int(1));
1152        }
1153        if self.eqtb.par_shape.val.is_some() {
1154            self.eq_define(Loc::ParShape, Val::Shape(None));
1155        }
1156        if self.eqtb.etex_shape[es::INTERLINEPENALTIES as usize]
1157            .val
1158            .is_some()
1159        {
1160            self.eq_define(Loc::EtexShape(es::INTERLINEPENALTIES), Val::Shape(None));
1161        }
1162    }
1163
1164    /// `\insert` and `\vadjust` (`begin_insert_or_adjust`).
1165    fn begin_insert_or_adjust(&mut self) {
1166        let v: i64 = if self.cur_cmd == Cmd::Vadjust {
1167            let pre = self.scan_keyword("pre");
1168            if pre { -2 } else { -1 }
1169        } else {
1170            self.scan_register_num();
1171            let mut n = self.cur_val;
1172            if n == self.output_box_num() {
1173                self.print_err("You can't ");
1174                self.print_esc_str("insert");
1175                self.printer.print_int(i64::from(n));
1176                self.help(&["I'm changing to \\insert0; box \\outputbox is special."]);
1177                self.error();
1178                n = 0;
1179            }
1180            i64::from(n)
1181        };
1182        self.push_saved(v);
1183        self.new_save_level(GroupCode::Insert);
1184        self.scan_left_brace();
1185        self.normal_paragraph();
1186        self.push_nest(Mode::InternalVertical);
1187        self.cur_list_mut().prev_depth = IGNORE_DEPTH;
1188    }
1189
1190    /// The box register used by the output routine (`\outputbox`).
1191    pub fn output_box_num(&self) -> i32 {
1192        let n = self.eqtb.int(ip::OUTPUTBOX);
1193        if (0..65536).contains(&n) { n } else { 255 }
1194    }
1195
1196    /// With `\partokencontext` at least `level` and a paragraph in progress,
1197    /// a group end that would end the paragraph directly reads the
1198    /// par-token first: the right brace is put back behind it and true is
1199    /// returned (pdfTeX's \partokencontext: 1 for \vbox, \vtop and
1200    /// \vcenter; 2 also for \insert, \vadjust, \noalign and \valign items).
1201    pub fn par_token_before_group_end(&mut self, level: i32) -> bool {
1202        if self.mode != Mode::Horizontal || self.eqtb.int(ip::PARTOKENCONTEXT) < level {
1203            return false;
1204        }
1205        self.back_input();
1206        self.cur_tok = Token::cs(self.par_token_cs);
1207        self.back_input();
1208        true
1209    }
1210
1211    /// Ends an insertion or adjustment group (§1100).
1212    fn finish_insert_group(&mut self) {
1213        if self.par_token_before_group_end(2) {
1214            return;
1215        }
1216        self.end_graf();
1217        let q = self.eqtb.glue(gp::SPLITTOPSKIP);
1218        let d = self.eqtb.dimen(dp::SPLITMAXDEPTH);
1219        let f = self.eqtb.int(ip::FLOATINGPENALTY);
1220        self.unsave();
1221        let n = self.pop_saved();
1222        let head = self.cur_list().head;
1223        let first = self.nodes.next(head);
1224        self.nodes.set_next(head, NodeRef::NULL);
1225        let p = self.vpack(first, PackSpec::NATURAL);
1226        self.pop_nest();
1227        let list = self.nodes.list_ptr(p);
1228        self.nodes.set_list_ptr(p, NodeRef::NULL);
1229        let (h, dd) = match self.nodes.box_data(p) {
1230            Some(b) => (b.height, b.depth),
1231            None => (0, 0),
1232        };
1233        if n >= 0 {
1234            let node = self.new_node(
1235                id::INS,
1236                n as u16,
1237                NodeData::Ins {
1238                    cost: f,
1239                    depth: d,
1240                    height: h + dd,
1241                    spec: q,
1242                    head: list,
1243                },
1244            );
1245            self.tail_append(node);
1246        } else {
1247            let node = self.new_node(
1248                id::ADJUST,
1249                if n == -2 { 1 } else { 0 },
1250                NodeData::Adjust { head: list },
1251            );
1252            self.tail_append(node);
1253        }
1254        self.nodes.free(p);
1255        if self.nest.len() == 1 {
1256            self.build_page_from("insert");
1257        }
1258    }
1259
1260    /// `\mark`, `\marks` (`make_mark`).
1261    fn make_mark(&mut self) {
1262        let c: u32 = if self.cur_chr == 0 {
1263            0
1264        } else if self.cur_chr == 1 {
1265            // \clearmarks n
1266            self.scan_register_num();
1267            let n = self.cur_val as u32;
1268            self.marks.classes.remove(&n);
1269            return;
1270        } else {
1271            self.scan_register_num();
1272            self.cur_val as u32
1273        };
1274        let toks = self.scan_toks_expanded();
1275        let p = self.new_node(
1276            id::MARK,
1277            0,
1278            NodeData::Mark {
1279                class: c,
1280                mark: std::rc::Rc::from(toks),
1281            },
1282        );
1283        self.tail_append(p);
1284    }
1285
1286    /// `\penalty` (`append_penalty`).
1287    fn append_penalty(&mut self) {
1288        self.scan_int();
1289        let p = self.new_penalty_node(self.cur_val);
1290        self.tail_append(p);
1291        if self.mode == Mode::Vertical {
1292            self.build_page_from("penalty");
1293        }
1294    }
1295
1296    /// `\unskip`, `\unkern`, `\unpenalty` (`delete_last`).
1297    fn delete_last(&mut self) {
1298        let (head, tail) = (self.cur_list().head, self.cur_list().tail);
1299        let want = self.cur_chr as u8;
1300        if self.mode == Mode::Vertical && tail == head {
1301            // Apologize, unless \unskip follows non-glue.
1302            if want != id::GLUE || self.page.last_glue.is_some() {
1303                self.you_cant();
1304                let l0 = if want == id::KERN {
1305                    "Try `I\\kern-\\lastkern' instead."
1306                } else if want != id::GLUE {
1307                    "Perhaps you can make the output routine do it."
1308                } else {
1309                    "Try `I\\vskip-\\lastskip' instead."
1310                };
1311                self.help(&[
1312                    "Sorry...I usually can't take things from the current page.",
1313                    l0,
1314                ]);
1315                self.error();
1316            }
1317            return;
1318        }
1319        if tail == head || self.nodes.id(tail) != want {
1320            return;
1321        }
1322        let p = self.find_prev(tail);
1323        self.nodes.set_next(p, NodeRef::NULL);
1324        self.nodes.flush_list(tail);
1325        self.cur_list_mut().tail = p;
1326    }
1327
1328    /// `\unhbox`, `\unvbox`, `\unhcopy`, `\unvcopy`, `\pagediscards`,
1329    /// `\splitdiscards` (`unpackage`).
1330    fn unpackage(&mut self) {
1331        let c = self.cur_chr;
1332        if c > chr::COPY {
1333            // \pagediscards (2), \splitdiscards (3).
1334            let k = c as usize;
1335            let p = self.ts.disc_ptr[k];
1336            self.ts.disc_ptr[k] = NodeRef::NULL;
1337            if !p.is_null() {
1338                let tail = self.cur_list().tail;
1339                self.nodes.link(tail, p);
1340                let t = self.nodes.tail_of(p);
1341                self.cur_list_mut().tail = t;
1342            }
1343            return;
1344        }
1345        self.scan_register_num();
1346        let n = self.cur_val as usize;
1347        let p = self.eqtb.boxes[n].val;
1348        if p.is_null() {
1349            return;
1350        }
1351        let t = self.nodes.id(p);
1352        let k = self.mode.abs_kind();
1353        if k == ModeKind::Math
1354            || (k == ModeKind::Vertical && t != id::VLIST)
1355            || (k == ModeKind::Horizontal && t != id::HLIST)
1356        {
1357            self.print_err("Incompatible list can't be unboxed");
1358            self.help(&[
1359                "Sorry, Pandora. (You sneaky devil.)",
1360                "I refuse to unbox an \\hbox in vertical mode or vice versa.",
1361                "And I can't open any boxes in math mode.",
1362            ]);
1363            self.error();
1364            return;
1365        }
1366        let list = if c == chr::COPY {
1367            let l = self.nodes.list_ptr(p);
1368            self.nodes.copy_list(l)
1369        } else {
1370            let l = self.nodes.list_ptr(p);
1371            self.nodes.set_list_ptr(p, NodeRef::NULL);
1372            self.eqtb.boxes[n].val = NodeRef::NULL;
1373            self.nodes.free(p);
1374            l
1375        };
1376        let list = if t == id::HLIST {
1377            self.strip_margin_kerns(list)
1378        } else {
1379            list
1380        };
1381        if !list.is_null() {
1382            let tail = self.cur_list().tail;
1383            self.nodes.link(tail, list);
1384            let t = self.nodes.tail_of(list);
1385            self.cur_list_mut().tail = t;
1386        }
1387    }
1388
1389    /// `\/` (`append_italic_correction`).
1390    fn append_italic_correction(&mut self) {
1391        let (head, tail) = (self.cur_list().head, self.cur_list().tail);
1392        if tail == head {
1393            return;
1394        }
1395        let Some(g) = self.nodes.glyph(tail) else {
1396            return;
1397        };
1398        let (f, c) = (g.font, g.chr);
1399        let ic = self.font_char_dimen(f, c, 3);
1400        let k = self.new_kern_node(ic, kern_sub::ITALIC);
1401        self.tail_append(k);
1402    }
1403
1404    /// `\-` and `\discretionary` (`append_discretionary`).
1405    fn append_discretionary(&mut self) {
1406        let c = self.cur_chr;
1407        if c == 1 {
1408            // \- : LuaTeX's explicit discretionary.
1409            let pen = self.eqtb.int(ip::EXHYPHENPENALTY);
1410            let d = self.new_node(
1411                id::DISC,
1412                1,
1413                NodeData::Disc {
1414                    pre: NodeRef::NULL,
1415                    post: NodeRef::NULL,
1416                    replace: NodeRef::NULL,
1417                    penalty: pen,
1418                },
1419            );
1420            let lang = self.cur_lang();
1421            let (pre_c, post_c) = {
1422                let l = self.lang_params(lang);
1423                (l.pre_hyphen_char, l.post_hyphen_char)
1424            };
1425            let f = self.eqtb.cur_font.val;
1426            let pre = if pre_c > 0 {
1427                self.new_char_node(f, pre_c as u32)
1428            } else {
1429                NodeRef::NULL
1430            };
1431            let post = if post_c > 0 {
1432                self.new_char_node(f, post_c as u32)
1433            } else {
1434                NodeRef::NULL
1435            };
1436            self.nodes.set_disc_lists(d, (pre, post, NodeRef::NULL));
1437            self.tail_append(d);
1438            return;
1439        }
1440        let mut pen = self.eqtb.int(ip::HYPHENPENALTY);
1441        if self.scan_keyword("penalty") {
1442            self.scan_int();
1443            pen = self.cur_val;
1444        }
1445        let d = self.new_node(
1446            id::DISC,
1447            if c == 2 { 2 } else { 0 },
1448            NodeData::Disc {
1449                pre: NodeRef::NULL,
1450                post: NodeRef::NULL,
1451                replace: NodeRef::NULL,
1452                penalty: pen,
1453            },
1454        );
1455        self.tail_append(d);
1456        self.push_saved(0);
1457        self.new_save_level(GroupCode::Disc);
1458        self.scan_left_brace();
1459        self.push_nest(Mode::RestrictedHorizontal);
1460        self.cur_list_mut().space_factor = 1000;
1461    }
1462
1463    /// Handles the end of a discretionary sublist
1464    /// (`build_discretionary`).
1465    fn build_discretionary(&mut self) {
1466        self.unsave();
1467        // Prune the current list to glyphs, kerns, boxes and rules.
1468        let head = self.cur_list().head;
1469        let mut q = head;
1470        let mut p = self.nodes.next(q);
1471        while !p.is_null() {
1472            let t = self.nodes.id(p);
1473            if !matches!(t, id::GLYPH | id::KERN | id::HLIST | id::VLIST | id::RULE) {
1474                self.print_err("Improper discretionary list");
1475                self.help(&["Discretionary lists must contain only boxes and kerns."]);
1476                self.error();
1477                self.begin_diagnostic();
1478                self.printer
1479                    .print_nl("The following discretionary sublist has been deleted:");
1480                self.show_box(p);
1481                self.end_diagnostic(true);
1482                self.nodes.flush_list(p);
1483                self.nodes.set_next(q, NodeRef::NULL);
1484                break;
1485            }
1486            q = p;
1487            p = self.nodes.next(q);
1488        }
1489        let list = self.nodes.next(head);
1490        self.nodes.set_next(head, NodeRef::NULL);
1491        if !list.is_null() {
1492            self.nodes.set_prev(list, NodeRef::NULL);
1493        }
1494        self.pop_nest();
1495        let k = self.pop_saved();
1496        let d = self.cur_list().tail;
1497        let (mut pre, mut post, mut rep) = self.nodes.disc_lists(d);
1498        match k {
1499            0 => pre = list,
1500            1 => post = list,
1501            _ => {
1502                if !list.is_null() && self.mode.abs_kind() == ModeKind::Math {
1503                    self.print_err("Illegal math ");
1504                    self.print_esc_str("discretionary");
1505                    self.help(&[
1506                        "Sorry: The third part of a discretionary break must be",
1507                        "empty, in math formulas. I had to delete your third part.",
1508                    ]);
1509                    self.nodes.flush_list(list);
1510                    self.error();
1511                } else {
1512                    rep = list;
1513                }
1514                self.nodes.set_disc_lists(d, (pre, post, rep));
1515                return;
1516            }
1517        }
1518        self.nodes.set_disc_lists(d, (pre, post, rep));
1519        self.push_saved(k + 1);
1520        self.new_save_level(GroupCode::Disc);
1521        self.scan_left_brace();
1522        self.push_nest(Mode::RestrictedHorizontal);
1523        self.cur_list_mut().space_factor = 1000;
1524    }
1525
1526    /// `\accent` (`make_accent`).
1527    fn make_accent(&mut self) {
1528        self.scan_char_num();
1529        let f = self.eqtb.cur_font.val;
1530        let p = self.new_char_node(f, self.cur_val as u32);
1531        let x = self.x_height(f);
1532        let s = f64::from(self.font(f).params.get(1).copied().unwrap_or(0)) / 65536.0;
1533        let a = self.char_width(f, self.cur_val as u32);
1534        self.do_assignments();
1535        let mut q = NodeRef::NULL;
1536        let f2 = self.eqtb.cur_font.val;
1537        if matches!(self.cur_cmd, Cmd::Letter | Cmd::OtherChar | Cmd::CharGiven) {
1538            q = self.new_char_node(f2, self.cur_chr);
1539        } else if self.cur_cmd == Cmd::CharNum {
1540            self.scan_char_num();
1541            q = self.new_char_node(f2, self.cur_val as u32);
1542        } else {
1543            self.back_input();
1544        }
1545        let mut p = p;
1546        if !q.is_null() {
1547            let t = f64::from(self.font(f2).params.get(1).copied().unwrap_or(0)) / 65536.0;
1548            let qc = self.nodes.glyph(q).map_or(0, |g| g.chr);
1549            let w = self.char_width(f2, qc);
1550            let h = self.font_char_dimen(f2, qc, 1);
1551            if h != x {
1552                p = self.hpack(p, PackSpec::NATURAL);
1553                if let Some(b) = self.nodes.box_data_mut(p) {
1554                    b.shift = x - h;
1555                }
1556            }
1557            let delta =
1558                ((f64::from(w - a)) / 2.0 + f64::from(h) * t - f64::from(x) * s).round() as i32;
1559            let r = self.new_kern_node(delta, kern_sub::ACCENT);
1560            self.tail_append(r);
1561            self.tail_append(p);
1562            let k = self.new_kern_node(-a - delta, kern_sub::ACCENT);
1563            self.tail_append(k);
1564            p = q;
1565        }
1566        self.tail_append(p);
1567        self.cur_list_mut().space_factor = 1000;
1568    }
1569
1570    /// The width of character `c` in font `f` (0 if absent).
1571    #[inline]
1572    pub fn char_width(&self, f: u32, c: u32) -> i32 {
1573        self.font(f).chars.get(&c).map_or(0, |m| m.width)
1574    }
1575
1576    /// Executes assignments until a non-assignment (`do_assignments`).
1577    pub fn do_assignments(&mut self) {
1578        loop {
1579            self.get_nonblank_nonrelax_noncall();
1580            if self.cur_cmd <= Cmd::MAX_NON_PREFIXED {
1581                return;
1582            }
1583            self.err.set_box_allowed = false;
1584            self.prefixed_command();
1585            self.err.set_box_allowed = true;
1586        }
1587    }
1588
1589    /// `\boundary`, `\noboundary`, `\protrusionboundary`,
1590    /// `\wordboundary`.
1591    pub fn append_boundary(&mut self) {
1592        let c = self.cur_chr;
1593        let v = if c == 1 || c == 2 {
1594            self.scan_int();
1595            self.cur_val
1596        } else {
1597            0
1598        };
1599        let p = self.new_node(id::BOUNDARY, c as u16, NodeData::Boundary { value: v });
1600        self.tail_append(p);
1601    }
1602
1603    /// `\leftghost`, `\rightghost`: LuaTeX 1.22 reads the next token,
1604    /// expanding macros, and appends nothing (observed: `\leftghost 65x`
1605    /// gives "5x", `\leftghost\char66` typesets "66", a macro loses its
1606    /// first token).
1607    fn append_ghost(&mut self) {
1608        self.get_x_token();
1609    }
1610
1611    /// `\localleftbox{…}`, `\localrightbox{…}`.
1612    fn begin_local_box(&mut self) {
1613        let k = self.cur_chr;
1614        self.push_saved(i64::from(k));
1615        self.new_save_level(GroupCode::LocalBox);
1616        self.scan_left_brace();
1617        self.push_nest(Mode::RestrictedHorizontal);
1618        self.cur_list_mut().space_factor = 1000;
1619    }
1620
1621    /// Ends a `\localleftbox`/`\localrightbox` group.
1622    fn finish_local_box(&mut self) {
1623        self.unsave();
1624        let k = self.pop_saved() as usize;
1625        let head = self.cur_list().head;
1626        let first = self.nodes.next(head);
1627        self.nodes.set_next(head, NodeRef::NULL);
1628        self.pop_nest();
1629        let b = if first.is_null() {
1630            NodeRef::NULL
1631        } else {
1632            let first = self.node_process_list(first, crate::nodeproc::Context::Hbox);
1633            let b = self.hpack(first, PackSpec::NATURAL);
1634            self.nodes.set_subtype(b, 2);
1635            b
1636        };
1637        self.eq_define(Loc::LocalBox(k as u32), Val::Node(b));
1638        if self.mode == Mode::Horizontal {
1639            // A change of the local boxes inserts a new local par node.
1640            let lp = self.new_local_par();
1641            self.tail_append(lp);
1642        }
1643    }
1644
1645    /// Recovers from a misplaced `&`, `\span` or `\cr` (`align_error`).
1646    fn align_error(&mut self) {
1647        if self.input.align_state.abs() > 2 {
1648            self.print_err("Misplaced ");
1649            self.print_cmd_chr(self.cur_cmd, self.cur_chr);
1650            if self.cur_tok == Token::char(Cmd::TabMark, '&' as u32) {
1651                self.help(&[
1652                    "I can't figure out why you would want to use a tab mark",
1653                    "here. If you just want an ampersand, the remedy is",
1654                    "simple: Just type `I\\&' now. But if some right brace",
1655                    "up above has ended a previous alignment prematurely,",
1656                    "you're probably due for more error messages, and you",
1657                    "might try typing `S' now just to see what is salvageable.",
1658                ]);
1659            } else {
1660                self.help(&[
1661                    "I can't figure out why you would want to use a tab mark",
1662                    "or \\cr or \\span just now. If something like a right brace",
1663                    "up above has ended a previous alignment prematurely,",
1664                    "you're probably due for more error messages, and you",
1665                    "might try typing `S' now just to see what is salvageable.",
1666                ]);
1667            }
1668            self.error();
1669        } else {
1670            self.back_input();
1671            if self.input.align_state < 0 {
1672                self.print_err("Missing { inserted");
1673                self.input.align_state += 1;
1674                self.cur_tok = Token::LEFT_BRACE;
1675            } else {
1676                self.print_err("Missing } inserted");
1677                self.input.align_state -= 1;
1678                self.cur_tok = Token::RIGHT_BRACE;
1679            }
1680            self.help(&[
1681                "I've put in what seems to be necessary to fix",
1682                "the current column of the current alignment.",
1683                "Try to go on, since this might almost work.",
1684            ]);
1685            self.ins_error();
1686        }
1687    }
1688
1689    fn no_align_error(&mut self) {
1690        self.print_err("Misplaced ");
1691        self.print_esc_str("noalign");
1692        self.help(&[
1693            "I expect to see \\noalign only after the \\cr of",
1694            "an alignment. Proceed, and I'll ignore this case.",
1695        ]);
1696        self.error();
1697    }
1698
1699    fn omit_error(&mut self) {
1700        self.print_err("Misplaced ");
1701        self.print_esc_str("omit");
1702        self.help(&[
1703            "I expect to see \\omit only after tab marks or the \\cr of",
1704            "an alignment. Proceed, and I'll ignore this case.",
1705        ]);
1706        self.error();
1707    }
1708
1709    fn cs_error(&mut self) {
1710        self.print_err("Extra ");
1711        self.print_esc_str("endcsname");
1712        self.help(&["I'm ignoring this, since I wasn't doing a \\csname."]);
1713        self.error();
1714    }
1715
1716    /// Appends a box to the current vertical list with interline glue
1717    /// (`append_to_vlist`). With an `append_to_vlist_filter` callback
1718    /// (told `location`), the list it returns is appended instead, with
1719    /// no interline glue of the engine's, and `\prevdepth` becomes the
1720    /// depth it returns, if any (observed: unchanged otherwise).
1721    pub fn append_to_vlist(&mut self, b: NodeRef, location: &str) {
1722        let pd = self.cur_list().prev_depth;
1723        if let Some(h) = self.lua.clone()
1724            && h.has_callback("append_to_vlist_filter")
1725        {
1726            self.figure_callback("append_to_vlist_filter");
1727            if let Some((list, depth)) = h.append_to_vlist_filter(self, b, location, pd) {
1728                let mut p = list;
1729                while !p.is_null() {
1730                    let next = self.nodes.next(p);
1731                    self.tail_append(p);
1732                    p = next;
1733                }
1734                if let Some(d) = depth {
1735                    self.cur_list_mut().prev_depth = d;
1736                }
1737            }
1738            return;
1739        }
1740        if let Some(p) = self.interline_glue(b, pd) {
1741            self.tail_append(p);
1742        }
1743        self.tail_append(b);
1744        let d = self.node_depth(b);
1745        self.cur_list_mut().prev_depth = d;
1746    }
1747
1748    /// The interline glue before box `b` after depth `prev_depth`:
1749    /// `\baselineskip` less the depth and the box's height, or
1750    /// `\lineskip` below `\lineskiplimit`; none after the ignored depth
1751    /// (also `node.prepend_prevdepth`).
1752    pub fn interline_glue(&mut self, b: NodeRef, prev_depth: i32) -> Option<NodeRef> {
1753        if prev_depth <= IGNORE_DEPTH {
1754            return None;
1755        }
1756        let bs = self.eqtb.glue(gp::BASELINESKIP);
1757        let h = self.node_height(b);
1758        let d = bs.width - prev_depth - h;
1759        Some(if d < self.eqtb.dimen(dp::LINESKIPLIMIT) {
1760            self.new_param_glue(glue_sub::LINE_SKIP)
1761        } else {
1762            let g = Glue { width: d, ..bs };
1763            self.new_glue_node(g, glue_sub::BASELINE_SKIP)
1764        })
1765    }
1766
1767    /// Right braces closing box groups and other non-simple groups
1768    /// (cases of `handle_right_brace`).
1769    pub fn handle_right_brace_typesetting(&mut self) {
1770        match self.save.cur_group {
1771            GroupCode::Hbox => self.package(false),
1772            GroupCode::AdjustedHbox => {
1773                self.start_adjust_lists();
1774                self.package(false);
1775            }
1776            GroupCode::Vbox => {
1777                if self.par_token_before_group_end(1) {
1778                    return;
1779                }
1780                self.end_graf();
1781                self.package(false);
1782            }
1783            GroupCode::Vtop => {
1784                if self.par_token_before_group_end(1) {
1785                    return;
1786                }
1787                self.end_graf();
1788                self.package(true);
1789            }
1790            GroupCode::Insert => self.finish_insert_group(),
1791            GroupCode::Output => self.resume_output(),
1792            GroupCode::Disc => self.build_discretionary(),
1793            GroupCode::Align => {
1794                self.back_input();
1795                self.cur_tok = Token::cs(self.cs.frozen_cr);
1796                self.print_err("Missing ");
1797                self.print_esc_str("cr");
1798                self.printer.print_str(" inserted");
1799                self.help(&["I'm guessing that you meant to end an alignment here."]);
1800                self.ins_error();
1801            }
1802            GroupCode::NoAlign => {
1803                if self.par_token_before_group_end(2) {
1804                    return;
1805                }
1806                self.end_graf();
1807                self.unsave();
1808                self.align_peek();
1809            }
1810            GroupCode::LocalBox => self.finish_local_box(),
1811            GroupCode::Vcenter | GroupCode::Math | GroupCode::MathChoice => self.math_right_brace(),
1812            _ => self.confusion("rightbrace"),
1813        }
1814    }
1815}
1816
1817/// The glue parameter (eqtb index) belonging to a glue subtype.
1818pub fn glue_param_of_subtype(sub: u16) -> u32 {
1819    match sub {
1820        glue_sub::LINE_SKIP => gp::LINESKIP,
1821        glue_sub::BASELINE_SKIP => gp::BASELINESKIP,
1822        glue_sub::PAR_SKIP => gp::PARSKIP,
1823        glue_sub::ABOVE_DISPLAY => gp::ABOVEDISPLAYSKIP,
1824        glue_sub::BELOW_DISPLAY => gp::BELOWDISPLAYSKIP,
1825        glue_sub::ABOVE_DISPLAY_SHORT => gp::ABOVEDISPLAYSHORTSKIP,
1826        glue_sub::BELOW_DISPLAY_SHORT => gp::BELOWDISPLAYSHORTSKIP,
1827        glue_sub::LEFT_SKIP => gp::LEFTSKIP,
1828        glue_sub::RIGHT_SKIP => gp::RIGHTSKIP,
1829        glue_sub::TOP_SKIP => gp::TOPSKIP,
1830        glue_sub::SPLIT_TOP_SKIP => gp::SPLITTOPSKIP,
1831        glue_sub::TAB_SKIP => gp::TABSKIP,
1832        glue_sub::SPACE_SKIP => gp::SPACESKIP,
1833        glue_sub::XSPACE_SKIP => gp::XSPACESKIP,
1834        glue_sub::PAR_FILL_SKIP => gp::PARFILLSKIP,
1835        glue_sub::MATH_SKIP => gp::MATHSURROUNDSKIP,
1836        _ => gp::LINESKIP,
1837    }
1838}
1839
1840/// Fields of a fresh box with the given dimensions (helper for tests).
1841pub fn box_with(width: i32, height: i32, depth: i32) -> BoxData {
1842    BoxData {
1843        width,
1844        height,
1845        depth,
1846        ..BoxData::default()
1847    }
1848}