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