Skip to main content

retex_engine/
align.rs

1//! Alignments: `\halign` and `\valign` (*TeX: The Program* §768–§812).
2//!
3//! The preamble is kept as vectors of templates and tabskip glue instead
4//! of TeX's node list; the algorithm is otherwise TeX's. Spanned cells are
5//! followed by tabskip glue and empty boxes, as in TeX82 (and LuaTeX).
6
7use std::rc::Rc;
8
9use crate::Engine;
10use crate::build::PackSpec;
11use crate::cmd::Cmd;
12use crate::eqtb::{Loc, Val};
13use crate::glue::Glue;
14use crate::input::{Level, ScannerStatus, TokKind};
15use crate::maincontrol::{IGNORE_DEPTH, Mode, ModeKind};
16use crate::nodes::{BoxData, NodeData, NodeRef, glue_sub, id};
17use crate::primitives_gen::{assign_dimen as dp, assign_glue as gp, assign_int as ip};
18use crate::save::GroupCode;
19use crate::token::{Token, TokenList};
20use crate::typeset::NULL_FLAG;
21
22/// `span_code`: the modifier of `\span` (LuaTeX numbering).
23pub const SPAN_CODE: u32 = 0x11_0002;
24/// `cr_code`: the modifier of `\cr`.
25pub const CR_CODE: u32 = 0x11_0003;
26/// `cr_cr_code`: the modifier of `\crcr`.
27pub const CR_CR_CODE: u32 = 0x11_0004;
28
29/// One column of the preamble (an "alignrecord").
30#[derive(Clone, Debug)]
31struct Column {
32    u: TokenList,
33    v: TokenList,
34    /// Maximum natural width so far (`null_flag` if none).
35    width: i32,
36    /// Maximum widths of entries spanning this and `n` further columns,
37    /// sorted by `n`.
38    spans: Vec<(usize, i32)>,
39    /// What to do when the template ends (`extra_info`).
40    extra_info: u32,
41}
42
43/// One alignment level (TeX's alignment stack node plus the current
44/// globals).
45#[derive(Debug)]
46struct Level0 {
47    cols: Vec<Column>,
48    /// Tabskip glue: `tabskips[k]` precedes column `k`.
49    tabskips: Vec<Glue>,
50    cur_align: Option<usize>,
51    cur_span: usize,
52    /// The tabskip glue before the column to copy next when the preamble
53    /// is extended periodically.
54    cur_loop: Option<usize>,
55    saved_align_state: i32,
56    /// Adjustment material of the current row.
57    adj_head: NodeRef,
58    adj_tail: NodeRef,
59    /// The `align_state` saved by `push_alignment`.
60    outer: Option<Box<Level0>>,
61    /// The preamble as a node list for Lua (`tex.lists.align_head`) and
62    /// its number of columns, made when asked for.
63    lua_preamble: NodeRef,
64    lua_preamble_cols: usize,
65}
66
67/// The alignment stack.
68#[derive(Debug, Default)]
69pub struct AlignState {
70    cur: Option<Box<Level0>>,
71}
72
73impl Engine {
74    fn al(&mut self) -> &mut Level0 {
75        self.ts.align.cur.as_mut().expect("alignment level present")
76    }
77
78    fn al_ref(&self) -> Option<&Level0> {
79        self.ts.align.cur.as_deref()
80    }
81
82    /// `push_alignment`.
83    fn push_alignment(&mut self) {
84        let outer = self.ts.align.cur.take();
85        let adj_head = self.nodes.new_temp();
86        self.ts.align.cur = Some(Box::new(Level0 {
87            cols: Vec::new(),
88            tabskips: Vec::new(),
89            cur_align: None,
90            cur_span: 0,
91            cur_loop: None,
92            saved_align_state: self.input.align_state,
93            adj_head,
94            adj_tail: adj_head,
95            outer,
96            lua_preamble: NodeRef::NULL,
97            lua_preamble_cols: 0,
98        }));
99    }
100
101    /// The current alignment's preamble as LuaTeX shows it after
102    /// `align_head` (`tex.lists.align_head`): tabskip glue around an
103    /// align record per column; null outside alignments.
104    pub fn align_preamble_list(&mut self) -> NodeRef {
105        let Some(l) = self.ts.align.cur.as_deref() else {
106            return NodeRef::NULL;
107        };
108        let n = l.cols.len();
109        if !l.lua_preamble.is_null() && l.lua_preamble_cols == n {
110            return l.lua_preamble;
111        }
112        let old = l.lua_preamble;
113        let tabskips = l.tabskips.clone();
114        self.free_lua_preamble(old);
115        let mut head = NodeRef::NULL;
116        let mut tail = NodeRef::NULL;
117        for k in 0..=n {
118            let spec = tabskips.get(k).copied().unwrap_or(Glue::ZERO);
119            let g = self.nodes.alloc(
120                id::GLUE,
121                crate::nodes::glue_sub::TAB_SKIP,
122                NodeData::Glue {
123                    spec,
124                    leader: NodeRef::NULL,
125                },
126            );
127            let mut items = vec![g];
128            if k < n {
129                items.push(self.nodes.alloc(id::ALIGN_RECORD, 0, NodeData::Temp));
130            }
131            for p in items {
132                if head.is_null() {
133                    head = p;
134                } else {
135                    self.nodes.link(tail, p);
136                }
137                tail = p;
138            }
139        }
140        let l = self.al();
141        l.lua_preamble = head;
142        l.lua_preamble_cols = n;
143        head
144    }
145
146    /// Frees a preamble list made for Lua, with the properties Lua gave
147    /// its nodes (they belong to that alignment only).
148    fn free_lua_preamble(&mut self, mut p: NodeRef) {
149        let head = p;
150        while !p.is_null() {
151            self.nodes
152                .props
153                .events
154                .push(crate::nodes::PropEvent::Flush(p.0));
155            p = self.nodes.next(p);
156        }
157        self.nodes.flush_list(head);
158    }
159
160    /// `pop_alignment`.
161    fn pop_alignment(&mut self) {
162        if let Some(l) = self.ts.align.cur.take() {
163            self.nodes.free(l.adj_head);
164            self.free_lua_preamble(l.lua_preamble);
165            self.input.align_state = l.saved_align_state;
166            self.ts.align.cur = l.outer;
167        }
168    }
169
170    /// Starts an alignment (`init_align`).
171    pub fn init_align(&mut self) {
172        let save_cs_ptr = self.cur_cs;
173        self.push_alignment();
174        self.input.align_state = -1_000_000;
175        if self.mode == Mode::DisplayMath && self.cur_list().head != self.cur_list().tail {
176            self.print_err("Improper ");
177            self.print_esc_str("halign");
178            self.printer.print_str(" inside $$'s");
179            self.help(&[
180                "Displays can use special alignments (like \\eqalignno)",
181                "only if nothing but the alignment itself is between $$'s.",
182                "So I've deleted the formulas that preceded this alignment.",
183            ]);
184            self.error();
185            self.flush_math();
186        }
187        let outer_pd = self.cur_list().prev_depth;
188        let outer_mode = self.mode;
189        let new_mode = match outer_mode {
190            Mode::DisplayMath => Mode::InternalVertical,
191            Mode::Vertical | Mode::InternalVertical => Mode::InternalVertical,
192            _ => Mode::RestrictedHorizontal,
193        };
194        self.push_nest(new_mode);
195        if new_mode == Mode::InternalVertical {
196            self.cur_list_mut().prev_depth = if outer_mode == Mode::DisplayMath {
197                let n = self.nest.len();
198                self.nest[n.saturating_sub(3)].prev_depth
199            } else {
200                outer_pd
201            };
202        }
203        self.scan_spec(GroupCode::Align, None);
204        // Scan the preamble.
205        self.input.scanner_status = ScannerStatus::Aligning;
206        self.input.warning_index = save_cs_ptr;
207        self.input.align_state = -1_000_000;
208        loop {
209            let g = self.eqtb.glue(gp::TABSKIP);
210            self.al().tabskips.push(g);
211            if self.cur_cmd == Cmd::CarRet {
212                break;
213            }
214            // Scan the template <u_j>.
215            let mut u: Vec<Token> = Vec::new();
216            loop {
217                self.get_preamble_token();
218                if self.cur_cmd == Cmd::MacParam {
219                    break;
220                }
221                if self.cur_cmd <= Cmd::CarRet
222                    && self.cur_cmd >= Cmd::TabMark
223                    && self.input.align_state == -1_000_000
224                {
225                    let ncols = self.al().cols.len();
226                    if u.is_empty() && self.al().cur_loop.is_none() && self.cur_cmd == Cmd::TabMark
227                    {
228                        self.al().cur_loop = Some(ncols);
229                    } else {
230                        self.print_err("Missing # inserted in alignment preamble");
231                        self.help(&[
232                            "There should be exactly one # between &'s, when an",
233                            "\\halign or \\valign is being set up. In this case you had",
234                            "none, so I've put one in; maybe that will work.",
235                        ]);
236                        self.back_error();
237                        break;
238                    }
239                } else if self.cur_cmd != Cmd::Spacer || !u.is_empty() {
240                    u.push(self.cur_tok);
241                }
242            }
243            // Scan the template <v_j>.
244            let mut v: Vec<Token> = Vec::new();
245            loop {
246                self.get_preamble_token();
247                if self.cur_cmd <= Cmd::CarRet
248                    && self.cur_cmd >= Cmd::TabMark
249                    && self.input.align_state == -1_000_000
250                {
251                    break;
252                }
253                if self.cur_cmd == Cmd::MacParam {
254                    self.print_err("Only one # is allowed per tab");
255                    self.help(&[
256                        "There should be exactly one # between &'s, when an",
257                        "\\halign or \\valign is being set up. In this case you had",
258                        "more than one, so I'm ignoring all but the first.",
259                    ]);
260                    self.error();
261                    continue;
262                }
263                v.push(self.cur_tok);
264            }
265            v.push(Token::cs(self.cs.frozen_end_template));
266            self.al().cols.push(Column {
267                u: Rc::from(u),
268                v: Rc::from(v),
269                width: NULL_FLAG,
270                spans: Vec::new(),
271                extra_info: 0,
272            });
273        }
274        self.input.scanner_status = ScannerStatus::Normal;
275        self.new_save_level(GroupCode::Align);
276        let t = self
277            .eqtb
278            .toks_param(crate::primitives_gen::assign_toks::EVERYCR)
279            .clone();
280        if !t.is_empty() {
281            self.begin_token_list(t, TokKind::EveryCrText);
282        }
283        self.align_peek();
284    }
285
286    /// Gets a token of the preamble, expanding after `\span` and handling
287    /// `\tabskip` assignments (`get_preamble_token`).
288    fn get_preamble_token(&mut self) {
289        loop {
290            self.get_token();
291            while self.cur_chr == SPAN_CODE && self.cur_cmd == Cmd::TabMark {
292                self.get_token();
293                if self.cur_cmd > Cmd::MAX_COMMAND {
294                    self.expand();
295                    self.get_token();
296                }
297            }
298            if self.cur_cmd == Cmd::Endv {
299                self.fatal_error("(interwoven alignment preambles are not allowed)");
300            }
301            if self.cur_cmd == Cmd::AssignGlue && self.cur_chr == gp::TABSKIP {
302                self.scan_optional_equals();
303                self.scan_glue(crate::scan::ValLevel::Glue);
304                let g = self.cur_glue;
305                if self.eqtb.int(ip::GLOBALDEFS) > 0 {
306                    self.geq_define(Loc::GluePar(gp::TABSKIP), Val::Glue(g));
307                } else {
308                    self.eq_define(Loc::GluePar(gp::TABSKIP), Val::Glue(g));
309                }
310                continue;
311            }
312            return;
313        }
314    }
315
316    /// Looks for `\noalign`, `\omit` or the end after `\cr`
317    /// (`align_peek`).
318    pub fn align_peek(&mut self) {
319        loop {
320            self.input.align_state = 1_000_000;
321            self.get_nonblank_or_protected();
322            if self.cur_cmd == Cmd::NoAlign {
323                self.scan_left_brace();
324                self.new_save_level(GroupCode::NoAlign);
325                if self.mode == Mode::InternalVertical {
326                    self.normal_paragraph();
327                }
328            } else if self.cur_cmd == Cmd::RightBrace {
329                self.fin_align();
330            } else if self.cur_cmd == Cmd::CarRet && self.cur_chr == CR_CR_CODE {
331                continue;
332            } else {
333                self.init_row();
334                self.init_col();
335            }
336            return;
337        }
338    }
339
340    /// Starts a row (`init_row`).
341    fn init_row(&mut self) {
342        let h = self.mode == Mode::InternalVertical;
343        self.push_nest(if h {
344            Mode::RestrictedHorizontal
345        } else {
346            Mode::InternalVertical
347        });
348        if h {
349            self.cur_list_mut().space_factor = 0;
350        } else {
351            self.cur_list_mut().prev_depth = 0;
352        }
353        let g = self.al().tabskips[0];
354        let p = self.new_glue_node(g, glue_sub::TAB_SKIP);
355        self.tail_append(p);
356        let l = self.al();
357        l.cur_align = Some(0);
358        l.adj_tail = l.adj_head;
359        self.init_span(0);
360    }
361
362    /// Starts a group of spanned columns (`init_span`).
363    fn init_span(&mut self, p: usize) {
364        let h = self.mode == Mode::RestrictedHorizontal;
365        self.push_nest(if h {
366            Mode::RestrictedHorizontal
367        } else {
368            Mode::InternalVertical
369        });
370        if h {
371            self.cur_list_mut().space_factor = 1000;
372        } else {
373            self.cur_list_mut().prev_depth = IGNORE_DEPTH;
374            self.normal_paragraph();
375        }
376        self.al().cur_span = p;
377    }
378
379    /// Starts a column (`init_col`).
380    fn init_col(&mut self) {
381        let cmd = self.cur_cmd;
382        let ca = self.al().cur_align.unwrap_or(0);
383        self.al().cols[ca].extra_info = cmd as u32;
384        if cmd == Cmd::Omit {
385            self.input.align_state = 0;
386        } else {
387            self.back_input();
388            let u = self.al().cols[ca].u.clone();
389            self.begin_token_list(u, TokKind::UTemplate);
390        }
391    }
392
393    /// Inserts the `v` part of an alignment template (§789).
394    pub fn insert_v_template(&mut self) {
395        let ca = self.al_ref().and_then(|l| l.cur_align);
396        let Some(ca) = ca else {
397            self.fatal_error("(interwoven alignment preambles are not allowed)");
398        };
399        if self.input.scanner_status == ScannerStatus::Aligning {
400            self.fatal_error("(interwoven alignment preambles are not allowed)");
401        }
402        let extra = self.al().cols[ca].extra_info;
403        self.al().cols[ca].extra_info = self.cur_chr;
404        if extra == Cmd::Omit as u32 {
405            let t: TokenList = Rc::from(vec![Token::cs(self.cs.frozen_end_template)]);
406            self.begin_token_list(t, TokKind::VTemplate);
407        } else {
408            let v = self.al().cols[ca].v.clone();
409            self.begin_token_list(v, TokKind::VTemplate);
410        }
411        self.input.align_state = 1_000_000;
412    }
413
414    /// `\endtemplate` (`do_endv`).
415    pub fn do_endv(&mut self) {
416        // The current input must be an exhausted v template.
417        let mut ok = false;
418        for lvl in self.input.stack.iter().rev() {
419            match lvl {
420                Level::Tokens(t) if t.kind != TokKind::VTemplate && t.loc >= t.list.len() => {
421                    continue;
422                }
423                Level::Tokens(t) => {
424                    ok = t.kind == TokKind::VTemplate && t.loc >= t.list.len();
425                    break;
426                }
427                Level::Text(_) => break,
428            }
429        }
430        if !ok {
431            self.fatal_error("(interwoven alignment preambles are not allowed)");
432        }
433        if self.save.cur_group == GroupCode::Align {
434            self.end_graf();
435            if self.fin_col() {
436                self.fin_row();
437            }
438        } else {
439            self.off_save();
440        }
441    }
442
443    /// Finishes a column; returns whether the row is finished too
444    /// (`fin_col`).
445    fn fin_col(&mut self) -> bool {
446        let Some(ca) = self.al_ref().and_then(|l| l.cur_align) else {
447            self.confusion("endv");
448        };
449        if self.input.align_state < 500_000 {
450            self.fatal_error("(interwoven alignment preambles are not allowed)");
451        }
452        let ncols = self.al().cols.len();
453        let mut p = ca + 1;
454        // If the preamble list has been traversed, check that the row has
455        // ended.
456        if p >= ncols && self.al().cols[ca].extra_info < CR_CODE {
457            if let Some(cl) = self.al().cur_loop {
458                // Lengthen the preamble periodically.
459                let l = self.al();
460                let src = l.cols[cl].clone();
461                l.cols.push(Column {
462                    u: src.u,
463                    v: src.v,
464                    width: NULL_FLAG,
465                    spans: Vec::new(),
466                    extra_info: 0,
467                });
468                let g = l.tabskips[cl + 1];
469                l.tabskips.push(g);
470                l.cur_loop = Some(cl + 1);
471                p = l.cols.len() - 1;
472            } else {
473                self.print_err("Extra alignment tab has been changed to ");
474                self.print_esc_str("cr");
475                self.help(&[
476                    "You have given more \\span or & marks than there were",
477                    "in the preamble to the \\halign or \\valign now in progress.",
478                    "So I'll assume that you meant to type \\cr instead.",
479                ]);
480                self.al().cols[ca].extra_info = CR_CODE;
481                self.error();
482            }
483        }
484        if self.al().cols[ca].extra_info != SPAN_CODE {
485            self.unsave();
486            self.new_save_level(GroupCode::Align);
487            // Package an unset box for the current column.
488            let head = self.cur_list().head;
489            let first = self.nodes.next(head);
490            self.nodes.set_next(head, NodeRef::NULL);
491            let h = self.mode == Mode::RestrictedHorizontal;
492            let (u, w) = if h {
493                let first = self.node_process_list(first, crate::nodeproc::Context::Hbox);
494                let first = self.filter_list("hpack_filter", first, "align_set");
495                let at = self.al().adj_tail;
496                self.ts.adjust_tail = Some(at);
497                self.ts.pre_adjust_tail = None;
498                let u = self.hpack(first, PackSpec::NATURAL);
499                let nt = self.ts.adjust_tail.take().unwrap_or(at);
500                self.al().adj_tail = nt;
501                let w = self.nodes.width(u);
502                (u, w)
503            } else {
504                let first = self.filter_list("vpack_filter", first, "align_set");
505                let u = self.vpackage(first, PackSpec::NATURAL, 0);
506                (u, self.node_height(u))
507            };
508            let cs = self.al().cur_span;
509            let mut n = 0usize;
510            if cs != ca {
511                // Update the width entry for spanned columns.
512                n = ca - cs;
513                let col = &mut self.al().cols[cs];
514                match col.spans.iter_mut().find(|(k, _)| *k == n) {
515                    Some(e) => {
516                        if e.1 < w {
517                            e.1 = w;
518                        }
519                    }
520                    None => {
521                        col.spans.push((n, w));
522                        col.spans.sort_by_key(|e| e.0);
523                    }
524                }
525            } else if w > self.al().cols[ca].width {
526                self.al().cols[ca].width = w;
527            }
528            let (st, sh) = self.ts.last_totals;
529            let so = (1..5).rev().find(|&o| st[o] != 0).unwrap_or(0);
530            let ho = (1..5).rev().find(|&o| sh[o] != 0).unwrap_or(0);
531            self.make_unset(u, n as u16, st[so], so as u8, sh[ho], ho as u8);
532            self.pop_nest();
533            self.tail_append(u);
534            // Copy the tabskip glue between columns.
535            let g = self.al().tabskips[ca + 1];
536            let gn = self.new_glue_node(g, glue_sub::TAB_SKIP);
537            self.tail_append(gn);
538            if self.al().cols[ca].extra_info >= CR_CODE {
539                return true;
540            }
541            self.init_span(p);
542        }
543        self.input.align_state = 1_000_000;
544        self.get_nonblank_or_protected();
545        self.al().cur_align = Some(p);
546        self.init_col();
547        false
548    }
549
550    /// Converts a packed box into an unset node.
551    fn make_unset(&mut self, u: NodeRef, span: u16, st: i32, so: u8, sh: i32, ho: u8) {
552        let b = match self.nodes.data(u) {
553            NodeData::Box(b) => (**b).clone(),
554            _ => BoxData::default(),
555        };
556        let n = self.nodes.get_mut(u);
557        n.id = id::UNSET;
558        n.subtype = 0;
559        n.data = NodeData::Unset {
560            b: Box::new(BoxData {
561                glue_set: 0.0,
562                glue_sign: 0,
563                glue_order: 0,
564                shift: 0,
565                ..b
566            }),
567            span_count: span,
568            stretch: st,
569            shrink: sh,
570            stretch_order: so,
571            shrink_order: ho,
572        };
573    }
574
575    /// Finishes a row (`fin_row`).
576    fn fin_row(&mut self) {
577        let head = self.cur_list().head;
578        let first = self.nodes.next(head);
579        self.nodes.set_next(head, NodeRef::NULL);
580        let p = if self.mode == Mode::RestrictedHorizontal {
581            // Observed: the row goes through hpack_filter (group
582            // "fin_row") while its list is still the current one.
583            let first = self.filter_list("hpack_filter", first, "fin_row");
584            let p = self.hpack(first, PackSpec::NATURAL);
585            self.pop_nest();
586            self.append_to_vlist(p);
587            let (ah, at) = (self.al().adj_head, self.al().adj_tail);
588            if ah != at {
589                let f = self.nodes.next(ah);
590                let tail = self.cur_list().tail;
591                self.nodes.link(tail, f);
592                self.cur_list_mut().tail = at;
593                self.nodes.set_next(ah, NodeRef::NULL);
594                self.al().adj_tail = ah;
595            }
596            p
597        } else {
598            let first = self.filter_list("vpack_filter", first, "fin_row");
599            let p = self.vpack(first, PackSpec::NATURAL);
600            self.pop_nest();
601            self.tail_append(p);
602            self.cur_list_mut().space_factor = 1000;
603            p
604        };
605        self.make_unset(p, 0, 0, 0, 0, 0);
606        let t = self
607            .eqtb
608            .toks_param(crate::primitives_gen::assign_toks::EVERYCR)
609            .clone();
610        if !t.is_empty() {
611            self.begin_token_list(t, TokKind::EveryCrText);
612        }
613        self.align_peek();
614    }
615
616    /// Finishes the alignment (`fin_align`).
617    fn fin_align(&mut self) {
618        if self.save.cur_group != GroupCode::Align {
619            self.confusion("align1");
620        }
621        self.unsave();
622        if self.save.cur_group != GroupCode::Align {
623            self.confusion("align0");
624        }
625        self.unsave();
626        let n = self.nest.len();
627        let o = if n >= 2 && self.nest[n - 2].mode == Mode::DisplayMath {
628            self.eqtb.dimen(dp::DISPLAYINDENT)
629        } else {
630            0
631        };
632        // Determine the column widths (§801).
633        let ncols = self.al().cols.len();
634        let mut widths: Vec<i32> = self.al().cols.iter().map(|c| c.width).collect();
635        let mut spans: Vec<Vec<(usize, i32)>> =
636            self.al().cols.iter().map(|c| c.spans.clone()).collect();
637        let mut tabs: Vec<Glue> = self.al().tabskips.clone();
638        for q in 0..ncols {
639            if widths[q] == NULL_FLAG {
640                widths[q] = 0;
641                tabs[q + 1] = Glue::ZERO;
642            }
643            let mine = std::mem::take(&mut spans[q]);
644            if mine.is_empty() || q + 1 >= ncols {
645                continue;
646            }
647            // Merge the widths in the span entries of q with those of q+1.
648            let t = widths[q] + tabs[q + 1].width;
649            for (k, w) in mine {
650                let w = w - t;
651                if k <= 1 {
652                    if w > widths[q + 1] {
653                        widths[q + 1] = w;
654                    }
655                } else {
656                    match spans[q + 1].iter_mut().find(|(kk, _)| *kk == k - 1) {
657                        Some(e) => {
658                            if w > e.1 {
659                                e.1 = w;
660                            }
661                        }
662                        None => spans[q + 1].push((k - 1, w)),
663                    }
664                }
665            }
666        }
667        // Package the preamble list to determine the tabskip amounts.
668        let (spec, _dir) = {
669            let dir = self.pop_saved();
670            let v = self.pop_saved() as i32;
671            let code = self.pop_saved();
672            (
673                if code == 0 {
674                    PackSpec::Exactly(v)
675                } else {
676                    PackSpec::Additional(v)
677                },
678                dir,
679            )
680        };
681        let hmode = self.mode == Mode::InternalVertical;
682        let mut plist = NodeRef::NULL;
683        let mut ptail = NodeRef::NULL;
684        let mut push = |e: &mut Engine, n: NodeRef| {
685            if plist.is_null() {
686                plist = n;
687            } else {
688                e.nodes.link(ptail, n);
689            }
690            ptail = n;
691        };
692        for k in 0..=ncols {
693            let g = self.nodes.new_glue(tabs[k], glue_sub::TAB_SKIP);
694            push(self, g);
695            if k < ncols {
696                let u = self.nodes.new_null_box(true);
697                if let Some(b) = self.nodes.box_data_mut(u) {
698                    if hmode {
699                        b.width = widths[k];
700                    } else {
701                        b.height = widths[k];
702                    }
703                }
704                self.make_unset(u, 0, 0, 0, 0, 0);
705                push(self, u);
706            }
707        }
708        self.ts.pack_begin_line = -self.cur_list().mode_line;
709        let p = if hmode {
710            let rule_save = self.eqtb.dimen_par[dp::OVERFULLRULE as usize].val;
711            self.eqtb.dimen_par[dp::OVERFULLRULE as usize].val = 0;
712            let p = self.hpack(plist, spec);
713            self.eqtb.dimen_par[dp::OVERFULLRULE as usize].val = rule_save;
714            p
715        } else {
716            // Observed: a \valign's preamble goes through vpack_filter
717            // (group "preamble"); an \halign's is not filtered.
718            let plist = self.filter_list("vpack_filter", plist, "preamble");
719            let p = self.vpack(plist, spec);
720            // Restore widths from heights.
721            let mut q = self.nodes.next(self.nodes.list_ptr(p));
722            while !q.is_null() {
723                if let Some(b) = self.nodes.box_data_mut(q) {
724                    b.width = b.height;
725                    b.height = 0;
726                }
727                q = self.nodes.next(q);
728                if !q.is_null() {
729                    q = self.nodes.next(q);
730                }
731            }
732            p
733        };
734        self.ts.pack_begin_line = 0;
735        let pb = self.nodes.box_data(p).cloned().unwrap_or_default();
736        // Set the glue in all the unset boxes of the current list.
737        let head = self.cur_list().head;
738        let mut s = head;
739        let mut q = self.nodes.next(head);
740        while !q.is_null() {
741            match self.nodes.id(q) {
742                id::UNSET => self.set_unset_row(q, &pb, o, hmode),
743                id::RULE => {
744                    let (pw, ph, pd) = (pb.width, pb.height, pb.depth);
745                    if let NodeData::Rule {
746                        width,
747                        height,
748                        depth,
749                        ..
750                    } = self.nodes.data_mut(q)
751                    {
752                        if *width == NULL_FLAG {
753                            *width = pw;
754                        }
755                        if *height == NULL_FLAG {
756                            *height = ph;
757                        }
758                        if *depth == NULL_FLAG {
759                            *depth = pd;
760                        }
761                    }
762                    if o != 0 {
763                        let r = self.nodes.next(q);
764                        self.nodes.set_next(q, NodeRef::NULL);
765                        let b = self.hpack(q, PackSpec::NATURAL);
766                        if let Some(bd) = self.nodes.box_data_mut(b) {
767                            bd.shift = o;
768                        }
769                        self.nodes.link(b, r);
770                        self.nodes.link(s, b);
771                        q = b;
772                    }
773                }
774                _ => {}
775            }
776            s = q;
777            q = self.nodes.next(q);
778        }
779        self.nodes.flush_list(p);
780        self.pop_alignment();
781        // Insert the current list into its environment.
782        let pd_save = self.cur_list().prev_depth;
783        let sf_save = self.cur_list().space_factor;
784        let head = self.cur_list().head;
785        let first = self.nodes.next(head);
786        let last = self.cur_list().tail;
787        self.nodes.set_next(head, NodeRef::NULL);
788        self.pop_nest();
789        if self.mode == Mode::DisplayMath {
790            self.finish_display_alignment(first, last, pd_save);
791            return;
792        }
793        if self.mode.abs_kind() == ModeKind::Vertical {
794            self.cur_list_mut().prev_depth = pd_save;
795        } else if self.mode.abs_kind() == ModeKind::Horizontal {
796            self.cur_list_mut().space_factor = sf_save;
797        }
798        if !first.is_null() {
799            let tail = self.cur_list().tail;
800            self.nodes.link(tail, first);
801            self.cur_list_mut().tail = last;
802        }
803        if self.mode == Mode::Vertical {
804            self.build_page();
805        }
806    }
807
808    /// Sets an unset row and its cells (§810).
809    fn set_unset_row(&mut self, q: NodeRef, pb: &BoxData, o: i32, hmode: bool) {
810        // Convert the row.
811        let rb = match self.nodes.data(q) {
812            NodeData::Unset { b, .. } => (**b).clone(),
813            _ => return,
814        };
815        let mut nb = BoxData {
816            glue_order: pb.glue_order,
817            glue_sign: pb.glue_sign,
818            glue_set: pb.glue_set,
819            shift: o,
820            ..rb
821        };
822        if hmode {
823            nb.width = pb.width;
824        } else {
825            nb.height = pb.height;
826        }
827        let row_h = nb.height;
828        let row_d = nb.depth;
829        let row_w = nb.width;
830        {
831            let n = self.nodes.get_mut(q);
832            n.id = if hmode { id::HLIST } else { id::VLIST };
833            n.subtype = 4;
834            n.data = NodeData::Box(Box::new(nb));
835        }
836        let mut r = self.nodes.next(self.nodes.list_ptr(q));
837        let mut s = self.nodes.next(pb.head);
838        while !r.is_null() && !s.is_null() {
839            // Set the glue in node r and change it from an unset node.
840            let (rbd, n, rst, rsto, rsh, rsho) = match self.nodes.data(r) {
841                NodeData::Unset {
842                    b,
843                    span_count,
844                    stretch,
845                    shrink,
846                    stretch_order,
847                    shrink_order,
848                } => (
849                    (**b).clone(),
850                    *span_count,
851                    *stretch,
852                    *stretch_order,
853                    *shrink,
854                    *shrink_order,
855                ),
856                _ => {
857                    // Not an unset cell (should not happen): skip.
858                    r = self.nodes.next(r);
859                    if !r.is_null() {
860                        r = self.nodes.next(r);
861                    }
862                    s = self.nodes.next(s);
863                    if !s.is_null() {
864                        s = self.nodes.next(s);
865                    }
866                    continue;
867                }
868            };
869            let sw = self.nodes.width(s);
870            let mut t = sw;
871            let w = t;
872            let hold = self.nodes.new_temp();
873            let mut u = hold;
874            for _ in 0..n {
875                s = self.nodes.next(s);
876                let v = self.nodes.glue_spec(s);
877                let g = self.new_glue_node(v, glue_sub::TAB_SKIP);
878                self.nodes.link(u, g);
879                u = g;
880                t += v.width;
881                if pb.glue_sign == 1 {
882                    if v.stretch_order == pb.glue_order {
883                        t += (pb.glue_set * f64::from(v.stretch)).round() as i32;
884                    }
885                } else if pb.glue_sign == 2 && v.shrink_order == pb.glue_order {
886                    t -= (pb.glue_set * f64::from(v.shrink)).round() as i32;
887                }
888                s = self.nodes.next(s);
889                let eb = self.nodes.new_null_box(hmode);
890                let swk = self.nodes.width(s);
891                if let Some(b) = self.nodes.box_data_mut(eb) {
892                    if hmode {
893                        b.width = swk;
894                    } else {
895                        b.height = swk;
896                    }
897                }
898                self.nodes.link(u, eb);
899                u = eb;
900                t += swk;
901            }
902            let natural = if hmode { rbd.width } else { rbd.height };
903            let mut cb = BoxData { shift: 0, ..rbd };
904            if hmode {
905                cb.height = row_h;
906                cb.depth = row_d;
907            } else {
908                cb.width = row_w;
909            }
910            if t == natural {
911                cb.glue_sign = 0;
912                cb.glue_order = 0;
913                cb.glue_set = 0.0;
914            } else if t > natural {
915                cb.glue_sign = 1;
916                cb.glue_order = rsto;
917                cb.glue_set = if rst == 0 {
918                    0.0
919                } else {
920                    crate::nodes::glue_ratio(t - natural, rst)
921                };
922            } else {
923                cb.glue_order = rsho;
924                cb.glue_sign = 2;
925                cb.glue_set = if rsh == 0 {
926                    0.0
927                } else if rsho == 0 && natural - t > rsh {
928                    1.0
929                } else {
930                    crate::nodes::glue_ratio(natural - t, rsh)
931                };
932            }
933            if hmode {
934                cb.width = w;
935            } else {
936                cb.height = w;
937            }
938            {
939                let nd = self.nodes.get_mut(r);
940                nd.id = if hmode { id::HLIST } else { id::VLIST };
941                nd.subtype = 5;
942                nd.data = NodeData::Box(Box::new(cb));
943            }
944            if u != hold {
945                let after = self.nodes.next(r);
946                let first = self.nodes.next(hold);
947                self.nodes.link(r, first);
948                self.nodes.link(u, after);
949                if after.is_null() {
950                    self.nodes.set_next(u, NodeRef::NULL);
951                }
952                r = u;
953            }
954            self.nodes.free(hold);
955            r = self.nodes.next(r);
956            if !r.is_null() {
957                r = self.nodes.next(r);
958            }
959            s = self.nodes.next(s);
960            if !s.is_null() {
961                s = self.nodes.next(s);
962            }
963        }
964    }
965}