Skip to main content

retex_engine/
build.rs

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