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