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.one_step = true;
322                    self.expand();
323                    self.get_token();
324                }
325            }
326            if self.cur_cmd == Cmd::Endv {
327                self.fatal_error("(interwoven alignment preambles are not allowed)");
328            }
329            if self.cur_cmd == Cmd::AssignGlue && self.cur_chr == gp::TABSKIP {
330                self.scan_optional_equals();
331                self.scan_glue(crate::scan::ValLevel::Glue);
332                let g = self.cur_glue;
333                if self.eqtb.int(ip::GLOBALDEFS) > 0 {
334                    self.geq_define(Loc::GluePar(gp::TABSKIP), Val::Glue(g));
335                } else {
336                    self.eq_define(Loc::GluePar(gp::TABSKIP), Val::Glue(g));
337                }
338                continue;
339            }
340            return;
341        }
342    }
343
344    /// Looks for `\noalign`, `\omit` or the end after `\cr`
345    /// (`align_peek`).
346    pub fn align_peek(&mut self) {
347        loop {
348            self.input.align_state = 1_000_000;
349            self.get_nonblank_or_protected();
350            if self.cur_cmd == Cmd::NoAlign {
351                self.scan_left_brace();
352                self.new_save_level(GroupCode::NoAlign);
353                if self.mode == Mode::InternalVertical {
354                    self.normal_paragraph();
355                }
356            } else if self.cur_cmd == Cmd::RightBrace {
357                self.fin_align();
358            } else if self.cur_cmd == Cmd::CarRet && self.cur_chr == CR_CR_CODE {
359                continue;
360            } else {
361                self.init_row();
362                self.init_col();
363            }
364            return;
365        }
366    }
367
368    /// Starts a row (`init_row`).
369    fn init_row(&mut self) {
370        let h = self.mode == Mode::InternalVertical;
371        self.push_nest(if h {
372            Mode::RestrictedHorizontal
373        } else {
374            Mode::InternalVertical
375        });
376        if h {
377            self.cur_list_mut().space_factor = 0;
378        } else {
379            self.cur_list_mut().prev_depth = 0;
380        }
381        let g = self.al().tabskips[0];
382        let p = self.new_glue_node(g, glue_sub::TAB_SKIP);
383        self.tail_append(p);
384        let l = self.al();
385        l.cur_align = Some(0);
386        l.adj_tail = l.adj_head;
387        self.init_span(0);
388    }
389
390    /// Starts a group of spanned columns (`init_span`).
391    fn init_span(&mut self, p: usize) {
392        let h = self.mode == Mode::RestrictedHorizontal;
393        self.push_nest(if h {
394            Mode::RestrictedHorizontal
395        } else {
396            Mode::InternalVertical
397        });
398        if h {
399            self.cur_list_mut().space_factor = 1000;
400        } else {
401            self.cur_list_mut().prev_depth = IGNORE_DEPTH;
402            self.normal_paragraph();
403        }
404        self.al().cur_span = p;
405    }
406
407    /// Starts a column (`init_col`).
408    fn init_col(&mut self) {
409        let cmd = self.cur_cmd;
410        let ca = self.al().cur_align.unwrap_or(0);
411        self.al().cols[ca].extra_info = cmd as u32;
412        if cmd == Cmd::Omit {
413            self.input.align_state = 0;
414        } else {
415            self.back_input();
416            let u = self.al().cols[ca].u.clone();
417            self.begin_token_list(u, TokKind::UTemplate);
418        }
419    }
420
421    /// Inserts the `v` part of an alignment template (§789).
422    pub fn insert_v_template(&mut self) {
423        let ca = self.al_ref().and_then(|l| l.cur_align);
424        let Some(ca) = ca else {
425            self.fatal_error("(interwoven alignment preambles are not allowed)");
426        };
427        if self.input.scanner_status == ScannerStatus::Aligning {
428            self.fatal_error("(interwoven alignment preambles are not allowed)");
429        }
430        let extra = self.al().cols[ca].extra_info;
431        self.al().cols[ca].extra_info = self.cur_chr;
432        if extra == Cmd::Omit as u32 {
433            let t: TokenList = Rc::from(vec![Token::cs(self.cs.frozen_end_template)]);
434            self.begin_token_list(t, TokKind::VTemplate);
435        } else {
436            let v = self.al().cols[ca].v.clone();
437            self.begin_token_list(v, TokKind::VTemplate);
438        }
439        self.input.align_state = 1_000_000;
440    }
441
442    /// `\endtemplate` (`do_endv`).
443    pub fn do_endv(&mut self) {
444        // The current input must be an exhausted v template.
445        let mut ok = false;
446        for lvl in self.input.stack.iter().rev() {
447            match lvl {
448                Level::Tokens(t) if t.kind != TokKind::VTemplate && t.loc >= t.list.len() => {
449                    continue;
450                }
451                Level::Tokens(t) => {
452                    ok = t.kind == TokKind::VTemplate && t.loc >= t.list.len();
453                    break;
454                }
455                Level::Text(_) => break,
456            }
457        }
458        if !ok {
459            self.fatal_error("(interwoven alignment preambles are not allowed)");
460        }
461        if self.save.cur_group == GroupCode::Align {
462            self.end_graf();
463            if self.fin_col() {
464                self.fin_row();
465            }
466        } else {
467            self.off_save();
468        }
469    }
470
471    /// Finishes a column; returns whether the row is finished too
472    /// (`fin_col`).
473    fn fin_col(&mut self) -> bool {
474        let Some(ca) = self.al_ref().and_then(|l| l.cur_align) else {
475            self.confusion("endv");
476        };
477        if self.input.align_state < 500_000 {
478            self.fatal_error("(interwoven alignment preambles are not allowed)");
479        }
480        let ncols = self.al().cols.len();
481        let mut p = ca + 1;
482        // If the preamble list has been traversed, check that the row has
483        // ended.
484        if p >= ncols && self.al().cols[ca].extra_info < CR_CODE {
485            if let Some(cl) = self.al().cur_loop {
486                // Lengthen the preamble periodically.
487                let l = self.al();
488                let src = l.cols[cl].clone();
489                l.cols.push(Column {
490                    u: src.u,
491                    v: src.v,
492                    width: NULL_FLAG,
493                    spans: Vec::new(),
494                    extra_info: 0,
495                });
496                let g = l.tabskips[cl + 1];
497                l.tabskips.push(g);
498                l.cur_loop = Some(cl + 1);
499                p = l.cols.len() - 1;
500            } else {
501                self.print_err("Extra alignment tab has been changed to ");
502                self.print_esc_str("cr");
503                self.help(&[
504                    "You have given more \\span or & marks than there were",
505                    "in the preamble to the \\halign or \\valign now in progress.",
506                    "So I'll assume that you meant to type \\cr instead.",
507                ]);
508                self.al().cols[ca].extra_info = CR_CODE;
509                self.error();
510            }
511        }
512        if self.al().cols[ca].extra_info != SPAN_CODE {
513            self.unsave();
514            self.new_save_level(GroupCode::Align);
515            // Package an unset box for the current column.
516            let head = self.cur_list().head;
517            let first = self.nodes.next(head);
518            self.nodes.set_next(head, NodeRef::NULL);
519            let h = self.mode == Mode::RestrictedHorizontal;
520            let (u, w) = if h {
521                let first = self.node_process_list(first, crate::nodeproc::Context::Hbox);
522                let first = self.filter_list("hpack_filter", first, "align_set");
523                let at = self.al().adj_tail;
524                self.ts.adjust_tail = Some(at);
525                self.ts.pre_adjust_tail = None;
526                let u = self.hpack(first, PackSpec::NATURAL);
527                let nt = self.ts.adjust_tail.take().unwrap_or(at);
528                self.al().adj_tail = nt;
529                let w = self.nodes.width(u);
530                (u, w)
531            } else {
532                let first = self.filter_list("vpack_filter", first, "align_set");
533                let u = self.vpackage(first, PackSpec::NATURAL, 0);
534                (u, self.node_height(u))
535            };
536            let cs = self.al().cur_span;
537            let mut n = 0usize;
538            if cs != ca {
539                // Update the width entry for spanned columns.
540                n = ca - cs;
541                let col = &mut self.al().cols[cs];
542                match col.spans.iter_mut().find(|(k, _)| *k == n) {
543                    Some(e) => {
544                        if e.1 < w {
545                            e.1 = w;
546                        }
547                    }
548                    None => {
549                        col.spans.push((n, w));
550                        col.spans.sort_by_key(|e| e.0);
551                    }
552                }
553            } else if w > self.al().cols[ca].width {
554                self.al().cols[ca].width = w;
555            }
556            let (st, sh) = self.ts.last_totals;
557            let so = (1..5).rev().find(|&o| st[o] != 0).unwrap_or(0);
558            let ho = (1..5).rev().find(|&o| sh[o] != 0).unwrap_or(0);
559            self.make_unset(u, n as u16, st[so], so as u8, sh[ho], ho as u8);
560            self.pop_nest();
561            self.tail_append(u);
562            // Copy the tabskip glue between columns.
563            let g = self.al().tabskips[ca + 1];
564            let gn = self.new_glue_node(g, glue_sub::TAB_SKIP);
565            self.tail_append(gn);
566            if self.al().cols[ca].extra_info >= CR_CODE {
567                return true;
568            }
569            self.init_span(p);
570        }
571        self.input.align_state = 1_000_000;
572        self.get_nonblank_or_protected();
573        self.al().cur_align = Some(p);
574        self.init_col();
575        false
576    }
577
578    /// Converts a packed box into an unset node.
579    fn make_unset(&mut self, u: NodeRef, span: u16, st: i32, so: u8, sh: i32, ho: u8) {
580        let b = match self.nodes.data(u) {
581            NodeData::Box(b) => (**b).clone(),
582            _ => BoxData::default(),
583        };
584        let n = self.nodes.get_mut(u);
585        n.id = id::UNSET;
586        n.subtype = 0;
587        n.data = NodeData::Unset {
588            b: Box::new(BoxData {
589                glue_set: 0.0,
590                glue_sign: 0,
591                glue_order: 0,
592                shift: 0,
593                ..b
594            }),
595            span_count: span,
596            stretch: st,
597            shrink: sh,
598            stretch_order: so,
599            shrink_order: ho,
600        };
601    }
602
603    /// Finishes a row (`fin_row`).
604    fn fin_row(&mut self) {
605        let head = self.cur_list().head;
606        let first = self.nodes.next(head);
607        self.nodes.set_next(head, NodeRef::NULL);
608        let p = if self.mode == Mode::RestrictedHorizontal {
609            // Observed: the row goes through hpack_filter (group
610            // "fin_row") while its list is still the current one.
611            let first = self.filter_list("hpack_filter", first, "fin_row");
612            let p = self.hpack(first, PackSpec::NATURAL);
613            self.pop_nest();
614            self.append_to_vlist(p);
615            let (ah, at) = (self.al().adj_head, self.al().adj_tail);
616            if ah != at {
617                let f = self.nodes.next(ah);
618                let tail = self.cur_list().tail;
619                self.nodes.link(tail, f);
620                self.cur_list_mut().tail = at;
621                self.nodes.set_next(ah, NodeRef::NULL);
622                self.al().adj_tail = ah;
623            }
624            p
625        } else {
626            let first = self.filter_list("vpack_filter", first, "fin_row");
627            let p = self.vpack(first, PackSpec::NATURAL);
628            self.pop_nest();
629            self.tail_append(p);
630            self.cur_list_mut().space_factor = 1000;
631            p
632        };
633        self.make_unset(p, 0, 0, 0, 0, 0);
634        let t = self
635            .eqtb
636            .toks_param(crate::primitives_gen::assign_toks::EVERYCR)
637            .clone();
638        if !t.is_empty() {
639            self.begin_token_list(t, TokKind::EveryCrText);
640        }
641        self.align_peek();
642    }
643
644    /// Finishes the alignment (`fin_align`).
645    fn fin_align(&mut self) {
646        if self.save.cur_group != GroupCode::Align {
647            self.confusion("align1");
648        }
649        self.unsave();
650        if self.save.cur_group != GroupCode::Align {
651            self.confusion("align0");
652        }
653        self.unsave();
654        let n = self.nest.len();
655        let o = if n >= 2 && self.nest[n - 2].mode == Mode::DisplayMath {
656            self.eqtb.dimen(dp::DISPLAYINDENT)
657        } else {
658            0
659        };
660        // Determine the column widths (§801).
661        let ncols = self.al().cols.len();
662        let mut widths: Vec<i32> = self.al().cols.iter().map(|c| c.width).collect();
663        let mut spans: Vec<Vec<(usize, i32)>> =
664            self.al().cols.iter().map(|c| c.spans.clone()).collect();
665        let mut tabs: Vec<Glue> = self.al().tabskips.clone();
666        for q in 0..ncols {
667            if widths[q] == NULL_FLAG {
668                widths[q] = 0;
669                tabs[q + 1] = Glue::ZERO;
670            }
671            let mine = std::mem::take(&mut spans[q]);
672            if mine.is_empty() || q + 1 >= ncols {
673                continue;
674            }
675            // Merge the widths in the span entries of q with those of q+1.
676            let t = widths[q] + tabs[q + 1].width;
677            for (k, w) in mine {
678                let w = w - t;
679                if k <= 1 {
680                    if w > widths[q + 1] {
681                        widths[q + 1] = w;
682                    }
683                } else {
684                    match spans[q + 1].iter_mut().find(|(kk, _)| *kk == k - 1) {
685                        Some(e) => {
686                            if w > e.1 {
687                                e.1 = w;
688                            }
689                        }
690                        None => spans[q + 1].push((k - 1, w)),
691                    }
692                }
693            }
694        }
695        // Package the preamble list to determine the tabskip amounts.
696        let (spec, _dir) = {
697            let dir = self.pop_saved();
698            let v = self.pop_saved() as i32;
699            let code = self.pop_saved();
700            (
701                if code == 0 {
702                    PackSpec::Exactly(v)
703                } else {
704                    PackSpec::Additional(v)
705                },
706                dir,
707            )
708        };
709        let hmode = self.mode == Mode::InternalVertical;
710        let mut plist = NodeRef::NULL;
711        let mut ptail = NodeRef::NULL;
712        let mut push = |e: &mut Engine, n: NodeRef| {
713            if plist.is_null() {
714                plist = n;
715            } else {
716                e.nodes.link(ptail, n);
717            }
718            ptail = n;
719        };
720        for k in 0..=ncols {
721            let g = self.nodes.new_glue(tabs[k], glue_sub::TAB_SKIP);
722            push(self, g);
723            if k < ncols {
724                let u = self.nodes.new_null_box(true);
725                if let Some(b) = self.nodes.box_data_mut(u) {
726                    if hmode {
727                        b.width = widths[k];
728                    } else {
729                        b.height = widths[k];
730                    }
731                }
732                self.make_unset(u, 0, 0, 0, 0, 0);
733                push(self, u);
734            }
735        }
736        self.ts.pack_begin_line = -self.cur_list().mode_line;
737        let p = if hmode {
738            let rule_save = self.eqtb.dimen_par[dp::OVERFULLRULE as usize].val;
739            self.eqtb.dimen_par[dp::OVERFULLRULE as usize].val = 0;
740            let p = self.hpack(plist, spec);
741            self.eqtb.dimen_par[dp::OVERFULLRULE as usize].val = rule_save;
742            p
743        } else {
744            // Observed: a \valign's preamble goes through vpack_filter
745            // (group "preamble"); an \halign's is not filtered.
746            let plist = self.filter_list("vpack_filter", plist, "preamble");
747            let p = self.vpack(plist, spec);
748            // Restore widths from heights.
749            let mut q = self.nodes.next(self.nodes.list_ptr(p));
750            while !q.is_null() {
751                if let Some(b) = self.nodes.box_data_mut(q) {
752                    b.width = b.height;
753                    b.height = 0;
754                }
755                q = self.nodes.next(q);
756                if !q.is_null() {
757                    q = self.nodes.next(q);
758                }
759            }
760            p
761        };
762        self.ts.pack_begin_line = 0;
763        let pb = self.nodes.box_data(p).cloned().unwrap_or_default();
764        // Set the glue in all the unset boxes of the current list.
765        let head = self.cur_list().head;
766        let mut s = head;
767        let mut q = self.nodes.next(head);
768        while !q.is_null() {
769            match self.nodes.id(q) {
770                id::UNSET => self.set_unset_row(q, &pb, o, hmode),
771                id::RULE => {
772                    let (pw, ph, pd) = (pb.width, pb.height, pb.depth);
773                    if let NodeData::Rule {
774                        width,
775                        height,
776                        depth,
777                        ..
778                    } = self.nodes.data_mut(q)
779                    {
780                        if *width == NULL_FLAG {
781                            *width = pw;
782                        }
783                        if *height == NULL_FLAG {
784                            *height = ph;
785                        }
786                        if *depth == NULL_FLAG {
787                            *depth = pd;
788                        }
789                    }
790                    if o != 0 {
791                        let r = self.nodes.next(q);
792                        self.nodes.set_next(q, NodeRef::NULL);
793                        let b = self.hpack(q, PackSpec::NATURAL);
794                        if let Some(bd) = self.nodes.box_data_mut(b) {
795                            bd.shift = o;
796                        }
797                        self.nodes.link(b, r);
798                        self.nodes.link(s, b);
799                        q = b;
800                    }
801                }
802                _ => {}
803            }
804            s = q;
805            q = self.nodes.next(q);
806        }
807        self.nodes.flush_list(p);
808        self.pop_alignment();
809        // Insert the current list into its environment.
810        let pd_save = self.cur_list().prev_depth;
811        let sf_save = self.cur_list().space_factor;
812        let head = self.cur_list().head;
813        let first = self.nodes.next(head);
814        let last = self.cur_list().tail;
815        self.nodes.set_next(head, NodeRef::NULL);
816        self.pop_nest();
817        if self.mode == Mode::DisplayMath {
818            self.finish_display_alignment(first, last, pd_save);
819            return;
820        }
821        if self.mode.abs_kind() == ModeKind::Vertical {
822            self.cur_list_mut().prev_depth = pd_save;
823        } else if self.mode.abs_kind() == ModeKind::Horizontal {
824            self.cur_list_mut().space_factor = sf_save;
825        }
826        if !first.is_null() {
827            let tail = self.cur_list().tail;
828            self.nodes.link(tail, first);
829            self.cur_list_mut().tail = last;
830        }
831        if self.mode == Mode::Vertical {
832            self.build_page_from("alignment");
833        }
834    }
835
836    /// Sets an unset row and its cells (§810).
837    fn set_unset_row(&mut self, q: NodeRef, pb: &BoxData, o: i32, hmode: bool) {
838        // Observed: a row whose list a fin_row callback replaced by
839        // something without cells stops LuaTeX.
840        let first = self.nodes.next(self.nodes.list_ptr(q));
841        if first.is_null() || self.nodes.id(first) != id::UNSET {
842            self.luatex_fatal("alignment", "bad box");
843        }
844        // Convert the row.
845        let rb = match self.nodes.data(q) {
846            NodeData::Unset { b, .. } => (**b).clone(),
847            _ => return,
848        };
849        let mut nb = BoxData {
850            glue_order: pb.glue_order,
851            glue_sign: pb.glue_sign,
852            glue_set: pb.glue_set,
853            shift: o,
854            ..rb
855        };
856        if hmode {
857            nb.width = pb.width;
858        } else {
859            nb.height = pb.height;
860        }
861        let row_h = nb.height;
862        let row_d = nb.depth;
863        let row_w = nb.width;
864        {
865            let n = self.nodes.get_mut(q);
866            n.id = if hmode { id::HLIST } else { id::VLIST };
867            n.subtype = 4;
868            n.data = NodeData::Box(Box::new(nb));
869        }
870        let mut r = self.nodes.next(self.nodes.list_ptr(q));
871        let mut s = self.nodes.next(pb.head);
872        while !r.is_null() && !s.is_null() {
873            // Set the glue in node r and change it from an unset node.
874            let (rbd, n, rst, rsto, rsh, rsho) = match self.nodes.data(r) {
875                NodeData::Unset {
876                    b,
877                    span_count,
878                    stretch,
879                    shrink,
880                    stretch_order,
881                    shrink_order,
882                } => (
883                    (**b).clone(),
884                    *span_count,
885                    *stretch,
886                    *stretch_order,
887                    *shrink,
888                    *shrink_order,
889                ),
890                _ => {
891                    // Not an unset cell (should not happen): skip.
892                    r = self.nodes.next(r);
893                    if !r.is_null() {
894                        r = self.nodes.next(r);
895                    }
896                    s = self.nodes.next(s);
897                    if !s.is_null() {
898                        s = self.nodes.next(s);
899                    }
900                    continue;
901                }
902            };
903            let sw = self.nodes.width(s);
904            let mut t = sw;
905            let w = t;
906            let hold = self.nodes.new_temp();
907            let mut u = hold;
908            for _ in 0..n {
909                s = self.nodes.next(s);
910                let v = self.nodes.glue_spec(s);
911                let g = self.new_glue_node(v, glue_sub::TAB_SKIP);
912                self.nodes.link(u, g);
913                u = g;
914                t += v.width;
915                if pb.glue_sign == 1 {
916                    if v.stretch_order == pb.glue_order {
917                        t += (pb.glue_set * f64::from(v.stretch)).round() as i32;
918                    }
919                } else if pb.glue_sign == 2 && v.shrink_order == pb.glue_order {
920                    t -= (pb.glue_set * f64::from(v.shrink)).round() as i32;
921                }
922                s = self.nodes.next(s);
923                let eb = self.nodes.new_null_box(hmode);
924                let swk = self.nodes.width(s);
925                if let Some(b) = self.nodes.box_data_mut(eb) {
926                    if hmode {
927                        b.width = swk;
928                    } else {
929                        b.height = swk;
930                    }
931                }
932                self.nodes.link(u, eb);
933                u = eb;
934                t += swk;
935            }
936            let natural = if hmode { rbd.width } else { rbd.height };
937            let mut cb = BoxData { shift: 0, ..rbd };
938            if hmode {
939                cb.height = row_h;
940                cb.depth = row_d;
941            } else {
942                cb.width = row_w;
943            }
944            if t == natural {
945                cb.glue_sign = 0;
946                cb.glue_order = 0;
947                cb.glue_set = 0.0;
948            } else if t > natural {
949                cb.glue_sign = 1;
950                cb.glue_order = rsto;
951                cb.glue_set = if rst == 0 {
952                    0.0
953                } else {
954                    crate::nodes::glue_ratio(t - natural, rst)
955                };
956            } else {
957                cb.glue_order = rsho;
958                cb.glue_sign = 2;
959                cb.glue_set = if rsh == 0 {
960                    0.0
961                } else if rsho == 0 && natural - t > rsh {
962                    1.0
963                } else {
964                    crate::nodes::glue_ratio(natural - t, rsh)
965                };
966            }
967            if hmode {
968                cb.width = w;
969            } else {
970                cb.height = w;
971            }
972            {
973                let nd = self.nodes.get_mut(r);
974                nd.id = if hmode { id::HLIST } else { id::VLIST };
975                nd.subtype = 5;
976                nd.data = NodeData::Box(Box::new(cb));
977            }
978            if u != hold {
979                let after = self.nodes.next(r);
980                let first = self.nodes.next(hold);
981                self.nodes.link(r, first);
982                self.nodes.link(u, after);
983                if after.is_null() {
984                    self.nodes.set_next(u, NodeRef::NULL);
985                }
986                r = u;
987            }
988            self.nodes.free(hold);
989            r = self.nodes.next(r);
990            if !r.is_null() {
991                r = self.nodes.next(r);
992            }
993            s = self.nodes.next(s);
994            if !s.is_null() {
995                s = self.nodes.next(s);
996            }
997        }
998    }
999}