use std::rc::Rc;
use crate::Engine;
use crate::build::PackSpec;
use crate::cmd::Cmd;
use crate::eqtb::{Loc, Val};
use crate::glue::Glue;
use crate::input::{Level, ScannerStatus, TokKind};
use crate::maincontrol::{IGNORE_DEPTH, Mode, ModeKind};
use crate::nodes::{BoxData, NodeData, NodeRef, glue_sub, id};
use crate::primitives_gen::{assign_dimen as dp, assign_glue as gp, assign_int as ip};
use crate::save::GroupCode;
use crate::token::{Token, TokenList};
use crate::typeset::NULL_FLAG;
pub const SPAN_CODE: u32 = 0x11_0002;
pub const CR_CODE: u32 = 0x11_0003;
pub const CR_CR_CODE: u32 = 0x11_0004;
#[derive(Clone, Debug)]
struct Column {
u: TokenList,
v: TokenList,
width: i32,
spans: Vec<(usize, i32)>,
extra_info: u32,
}
#[derive(Debug)]
struct Level0 {
cols: Vec<Column>,
tabskips: Vec<Glue>,
cur_align: Option<usize>,
cur_span: usize,
cur_loop: Option<usize>,
saved_align_state: i32,
adj_head: NodeRef,
adj_tail: NodeRef,
outer: Option<Box<Level0>>,
lua_preamble: NodeRef,
lua_preamble_cols: usize,
}
#[derive(Debug, Default)]
pub struct AlignState {
cur: Option<Box<Level0>>,
}
impl Engine {
fn al(&mut self) -> &mut Level0 {
self.ts.align.cur.as_mut().expect("alignment level present")
}
fn al_ref(&self) -> Option<&Level0> {
self.ts.align.cur.as_deref()
}
fn push_alignment(&mut self) {
let outer = self.ts.align.cur.take();
let adj_head = self.nodes.new_temp();
self.ts.align.cur = Some(Box::new(Level0 {
cols: Vec::new(),
tabskips: Vec::new(),
cur_align: None,
cur_span: 0,
cur_loop: None,
saved_align_state: self.input.align_state,
adj_head,
adj_tail: adj_head,
outer,
lua_preamble: NodeRef::NULL,
lua_preamble_cols: 0,
}));
}
pub fn align_preamble_list(&mut self) -> NodeRef {
let Some(l) = self.ts.align.cur.as_deref() else {
return NodeRef::NULL;
};
let n = l.cols.len();
if !l.lua_preamble.is_null() && l.lua_preamble_cols == n {
return l.lua_preamble;
}
if !l.lua_preamble.is_null() && l.lua_preamble_cols < n {
let (head, from) = (l.lua_preamble, l.lua_preamble_cols);
let tabskips = l.tabskips.clone();
let mut tail = head;
while !self.nodes.next(tail).is_null() {
tail = self.nodes.next(tail);
}
for k in from..n {
let r = self.nodes.alloc(id::ALIGN_RECORD, 0, NodeData::Temp);
let spec = tabskips.get(k + 1).copied().unwrap_or(Glue::ZERO);
let g = self.nodes.alloc(
id::GLUE,
0,
NodeData::Glue {
spec,
leader: NodeRef::NULL,
},
);
self.nodes.link(tail, r);
self.nodes.link(r, g);
tail = g;
}
self.al().lua_preamble_cols = n;
return head;
}
let old = l.lua_preamble;
let tabskips = l.tabskips.clone();
self.free_lua_preamble(old);
let mut head = NodeRef::NULL;
let mut tail = NodeRef::NULL;
for k in 0..=n {
let spec = tabskips.get(k).copied().unwrap_or(Glue::ZERO);
let g = self.nodes.alloc(
id::GLUE,
crate::nodes::glue_sub::TAB_SKIP,
NodeData::Glue {
spec,
leader: NodeRef::NULL,
},
);
let mut items = vec![g];
if k < n {
items.push(self.nodes.alloc(id::ALIGN_RECORD, 0, NodeData::Temp));
}
for p in items {
if head.is_null() {
head = p;
} else {
self.nodes.link(tail, p);
}
tail = p;
}
}
let l = self.al();
l.lua_preamble = head;
l.lua_preamble_cols = n;
head
}
fn free_lua_preamble(&mut self, mut p: NodeRef) {
let head = p;
while !p.is_null() {
self.nodes
.props
.events
.push(crate::nodes::PropEvent::Flush(p.0));
p = self.nodes.next(p);
}
self.nodes.flush_list(head);
}
fn pop_alignment(&mut self) {
if let Some(l) = self.ts.align.cur.take() {
self.nodes.free(l.adj_head);
self.free_lua_preamble(l.lua_preamble);
self.input.align_state = l.saved_align_state;
self.ts.align.cur = l.outer;
}
}
pub fn init_align(&mut self) {
let save_cs_ptr = self.cur_cs;
self.push_alignment();
self.input.align_state = -1_000_000;
if self.mode == Mode::DisplayMath && self.cur_list().head != self.cur_list().tail {
self.print_err("Improper ");
self.print_esc_str("halign");
self.printer.print_str(" inside $$'s");
self.help(&[
"Displays can use special alignments (like \\eqalignno)",
"only if nothing but the alignment itself is between $$'s.",
"So I've deleted the formulas that preceded this alignment.",
]);
self.error();
self.flush_math();
}
let outer_pd = self.cur_list().prev_depth;
let outer_mode = self.mode;
let new_mode = match outer_mode {
Mode::DisplayMath => Mode::InternalVertical,
Mode::Vertical | Mode::InternalVertical => Mode::InternalVertical,
_ => Mode::RestrictedHorizontal,
};
self.push_nest(new_mode);
if new_mode == Mode::InternalVertical {
self.cur_list_mut().prev_depth = if outer_mode == Mode::DisplayMath {
let n = self.nest.len();
self.nest[n.saturating_sub(3)].prev_depth
} else {
outer_pd
};
}
self.scan_spec(GroupCode::Align, None);
self.input.scanner_status = ScannerStatus::Aligning;
self.input.warning_index = save_cs_ptr;
self.input.align_state = -1_000_000;
loop {
let g = self.eqtb.glue(gp::TABSKIP);
self.al().tabskips.push(g);
if self.cur_cmd == Cmd::CarRet {
break;
}
let mut u: Vec<Token> = Vec::new();
loop {
self.get_preamble_token();
if self.cur_cmd == Cmd::MacParam {
break;
}
if self.cur_cmd <= Cmd::CarRet
&& self.cur_cmd >= Cmd::TabMark
&& self.input.align_state == -1_000_000
{
let ncols = self.al().cols.len();
if u.is_empty() && self.al().cur_loop.is_none() && self.cur_cmd == Cmd::TabMark
{
self.al().cur_loop = Some(ncols);
} else {
self.print_err("Missing # inserted in alignment preamble");
self.help(&[
"There should be exactly one # between &'s, when an",
"\\halign or \\valign is being set up. In this case you had",
"none, so I've put one in; maybe that will work.",
]);
self.back_error();
break;
}
} else if self.cur_cmd != Cmd::Spacer || !u.is_empty() {
u.push(self.cur_tok);
}
}
let mut v: Vec<Token> = Vec::new();
loop {
self.get_preamble_token();
if self.cur_cmd <= Cmd::CarRet
&& self.cur_cmd >= Cmd::TabMark
&& self.input.align_state == -1_000_000
{
break;
}
if self.cur_cmd == Cmd::MacParam {
self.print_err("Only one # is allowed per tab");
self.help(&[
"There should be exactly one # between &'s, when an",
"\\halign or \\valign is being set up. In this case you had",
"more than one, so I'm ignoring all but the first.",
]);
self.error();
continue;
}
v.push(self.cur_tok);
}
v.push(Token::cs(self.cs.frozen_end_template));
self.al().cols.push(Column {
u: Rc::from(u),
v: Rc::from(v),
width: NULL_FLAG,
spans: Vec::new(),
extra_info: 0,
});
}
self.input.scanner_status = ScannerStatus::Normal;
self.new_save_level(GroupCode::Align);
let t = self
.eqtb
.toks_param(crate::primitives_gen::assign_toks::EVERYCR)
.clone();
if !t.is_empty() {
self.begin_token_list(t, TokKind::EveryCrText);
}
self.align_peek();
}
fn get_preamble_token(&mut self) {
loop {
self.get_token();
while self.cur_chr == SPAN_CODE && self.cur_cmd == Cmd::TabMark {
self.get_token();
if self.cur_cmd > Cmd::MAX_COMMAND {
self.expand();
self.get_token();
}
}
if self.cur_cmd == Cmd::Endv {
self.fatal_error("(interwoven alignment preambles are not allowed)");
}
if self.cur_cmd == Cmd::AssignGlue && self.cur_chr == gp::TABSKIP {
self.scan_optional_equals();
self.scan_glue(crate::scan::ValLevel::Glue);
let g = self.cur_glue;
if self.eqtb.int(ip::GLOBALDEFS) > 0 {
self.geq_define(Loc::GluePar(gp::TABSKIP), Val::Glue(g));
} else {
self.eq_define(Loc::GluePar(gp::TABSKIP), Val::Glue(g));
}
continue;
}
return;
}
}
pub fn align_peek(&mut self) {
loop {
self.input.align_state = 1_000_000;
self.get_nonblank_or_protected();
if self.cur_cmd == Cmd::NoAlign {
self.scan_left_brace();
self.new_save_level(GroupCode::NoAlign);
if self.mode == Mode::InternalVertical {
self.normal_paragraph();
}
} else if self.cur_cmd == Cmd::RightBrace {
self.fin_align();
} else if self.cur_cmd == Cmd::CarRet && self.cur_chr == CR_CR_CODE {
continue;
} else {
self.init_row();
self.init_col();
}
return;
}
}
fn init_row(&mut self) {
let h = self.mode == Mode::InternalVertical;
self.push_nest(if h {
Mode::RestrictedHorizontal
} else {
Mode::InternalVertical
});
if h {
self.cur_list_mut().space_factor = 0;
} else {
self.cur_list_mut().prev_depth = 0;
}
let g = self.al().tabskips[0];
let p = self.new_glue_node(g, glue_sub::TAB_SKIP);
self.tail_append(p);
let l = self.al();
l.cur_align = Some(0);
l.adj_tail = l.adj_head;
self.init_span(0);
}
fn init_span(&mut self, p: usize) {
let h = self.mode == Mode::RestrictedHorizontal;
self.push_nest(if h {
Mode::RestrictedHorizontal
} else {
Mode::InternalVertical
});
if h {
self.cur_list_mut().space_factor = 1000;
} else {
self.cur_list_mut().prev_depth = IGNORE_DEPTH;
self.normal_paragraph();
}
self.al().cur_span = p;
}
fn init_col(&mut self) {
let cmd = self.cur_cmd;
let ca = self.al().cur_align.unwrap_or(0);
self.al().cols[ca].extra_info = cmd as u32;
if cmd == Cmd::Omit {
self.input.align_state = 0;
} else {
self.back_input();
let u = self.al().cols[ca].u.clone();
self.begin_token_list(u, TokKind::UTemplate);
}
}
pub fn insert_v_template(&mut self) {
let ca = self.al_ref().and_then(|l| l.cur_align);
let Some(ca) = ca else {
self.fatal_error("(interwoven alignment preambles are not allowed)");
};
if self.input.scanner_status == ScannerStatus::Aligning {
self.fatal_error("(interwoven alignment preambles are not allowed)");
}
let extra = self.al().cols[ca].extra_info;
self.al().cols[ca].extra_info = self.cur_chr;
if extra == Cmd::Omit as u32 {
let t: TokenList = Rc::from(vec![Token::cs(self.cs.frozen_end_template)]);
self.begin_token_list(t, TokKind::VTemplate);
} else {
let v = self.al().cols[ca].v.clone();
self.begin_token_list(v, TokKind::VTemplate);
}
self.input.align_state = 1_000_000;
}
pub fn do_endv(&mut self) {
let mut ok = false;
for lvl in self.input.stack.iter().rev() {
match lvl {
Level::Tokens(t) if t.kind != TokKind::VTemplate && t.loc >= t.list.len() => {
continue;
}
Level::Tokens(t) => {
ok = t.kind == TokKind::VTemplate && t.loc >= t.list.len();
break;
}
Level::Text(_) => break,
}
}
if !ok {
self.fatal_error("(interwoven alignment preambles are not allowed)");
}
if self.save.cur_group == GroupCode::Align {
self.end_graf();
if self.fin_col() {
self.fin_row();
}
} else {
self.off_save();
}
}
fn fin_col(&mut self) -> bool {
let Some(ca) = self.al_ref().and_then(|l| l.cur_align) else {
self.confusion("endv");
};
if self.input.align_state < 500_000 {
self.fatal_error("(interwoven alignment preambles are not allowed)");
}
let ncols = self.al().cols.len();
let mut p = ca + 1;
if p >= ncols && self.al().cols[ca].extra_info < CR_CODE {
if let Some(cl) = self.al().cur_loop {
let l = self.al();
let src = l.cols[cl].clone();
l.cols.push(Column {
u: src.u,
v: src.v,
width: NULL_FLAG,
spans: Vec::new(),
extra_info: 0,
});
let g = l.tabskips[cl + 1];
l.tabskips.push(g);
l.cur_loop = Some(cl + 1);
p = l.cols.len() - 1;
} else {
self.print_err("Extra alignment tab has been changed to ");
self.print_esc_str("cr");
self.help(&[
"You have given more \\span or & marks than there were",
"in the preamble to the \\halign or \\valign now in progress.",
"So I'll assume that you meant to type \\cr instead.",
]);
self.al().cols[ca].extra_info = CR_CODE;
self.error();
}
}
if self.al().cols[ca].extra_info != SPAN_CODE {
self.unsave();
self.new_save_level(GroupCode::Align);
let head = self.cur_list().head;
let first = self.nodes.next(head);
self.nodes.set_next(head, NodeRef::NULL);
let h = self.mode == Mode::RestrictedHorizontal;
let (u, w) = if h {
let first = self.node_process_list(first, crate::nodeproc::Context::Hbox);
let first = self.filter_list("hpack_filter", first, "align_set");
let at = self.al().adj_tail;
self.ts.adjust_tail = Some(at);
self.ts.pre_adjust_tail = None;
let u = self.hpack(first, PackSpec::NATURAL);
let nt = self.ts.adjust_tail.take().unwrap_or(at);
self.al().adj_tail = nt;
let w = self.nodes.width(u);
(u, w)
} else {
let first = self.filter_list("vpack_filter", first, "align_set");
let u = self.vpackage(first, PackSpec::NATURAL, 0);
(u, self.node_height(u))
};
let cs = self.al().cur_span;
let mut n = 0usize;
if cs != ca {
n = ca - cs;
let col = &mut self.al().cols[cs];
match col.spans.iter_mut().find(|(k, _)| *k == n) {
Some(e) => {
if e.1 < w {
e.1 = w;
}
}
None => {
col.spans.push((n, w));
col.spans.sort_by_key(|e| e.0);
}
}
} else if w > self.al().cols[ca].width {
self.al().cols[ca].width = w;
}
let (st, sh) = self.ts.last_totals;
let so = (1..5).rev().find(|&o| st[o] != 0).unwrap_or(0);
let ho = (1..5).rev().find(|&o| sh[o] != 0).unwrap_or(0);
self.make_unset(u, n as u16, st[so], so as u8, sh[ho], ho as u8);
self.pop_nest();
self.tail_append(u);
let g = self.al().tabskips[ca + 1];
let gn = self.new_glue_node(g, glue_sub::TAB_SKIP);
self.tail_append(gn);
if self.al().cols[ca].extra_info >= CR_CODE {
return true;
}
self.init_span(p);
}
self.input.align_state = 1_000_000;
self.get_nonblank_or_protected();
self.al().cur_align = Some(p);
self.init_col();
false
}
fn make_unset(&mut self, u: NodeRef, span: u16, st: i32, so: u8, sh: i32, ho: u8) {
let b = match self.nodes.data(u) {
NodeData::Box(b) => (**b).clone(),
_ => BoxData::default(),
};
let n = self.nodes.get_mut(u);
n.id = id::UNSET;
n.subtype = 0;
n.data = NodeData::Unset {
b: Box::new(BoxData {
glue_set: 0.0,
glue_sign: 0,
glue_order: 0,
shift: 0,
..b
}),
span_count: span,
stretch: st,
shrink: sh,
stretch_order: so,
shrink_order: ho,
};
}
fn fin_row(&mut self) {
let head = self.cur_list().head;
let first = self.nodes.next(head);
self.nodes.set_next(head, NodeRef::NULL);
let p = if self.mode == Mode::RestrictedHorizontal {
let first = self.filter_list("hpack_filter", first, "fin_row");
let p = self.hpack(first, PackSpec::NATURAL);
self.pop_nest();
self.append_to_vlist(p);
let (ah, at) = (self.al().adj_head, self.al().adj_tail);
if ah != at {
let f = self.nodes.next(ah);
let tail = self.cur_list().tail;
self.nodes.link(tail, f);
self.cur_list_mut().tail = at;
self.nodes.set_next(ah, NodeRef::NULL);
self.al().adj_tail = ah;
}
p
} else {
let first = self.filter_list("vpack_filter", first, "fin_row");
let p = self.vpack(first, PackSpec::NATURAL);
self.pop_nest();
self.tail_append(p);
self.cur_list_mut().space_factor = 1000;
p
};
self.make_unset(p, 0, 0, 0, 0, 0);
let t = self
.eqtb
.toks_param(crate::primitives_gen::assign_toks::EVERYCR)
.clone();
if !t.is_empty() {
self.begin_token_list(t, TokKind::EveryCrText);
}
self.align_peek();
}
fn fin_align(&mut self) {
if self.save.cur_group != GroupCode::Align {
self.confusion("align1");
}
self.unsave();
if self.save.cur_group != GroupCode::Align {
self.confusion("align0");
}
self.unsave();
let n = self.nest.len();
let o = if n >= 2 && self.nest[n - 2].mode == Mode::DisplayMath {
self.eqtb.dimen(dp::DISPLAYINDENT)
} else {
0
};
let ncols = self.al().cols.len();
let mut widths: Vec<i32> = self.al().cols.iter().map(|c| c.width).collect();
let mut spans: Vec<Vec<(usize, i32)>> =
self.al().cols.iter().map(|c| c.spans.clone()).collect();
let mut tabs: Vec<Glue> = self.al().tabskips.clone();
for q in 0..ncols {
if widths[q] == NULL_FLAG {
widths[q] = 0;
tabs[q + 1] = Glue::ZERO;
}
let mine = std::mem::take(&mut spans[q]);
if mine.is_empty() || q + 1 >= ncols {
continue;
}
let t = widths[q] + tabs[q + 1].width;
for (k, w) in mine {
let w = w - t;
if k <= 1 {
if w > widths[q + 1] {
widths[q + 1] = w;
}
} else {
match spans[q + 1].iter_mut().find(|(kk, _)| *kk == k - 1) {
Some(e) => {
if w > e.1 {
e.1 = w;
}
}
None => spans[q + 1].push((k - 1, w)),
}
}
}
}
let (spec, _dir) = {
let dir = self.pop_saved();
let v = self.pop_saved() as i32;
let code = self.pop_saved();
(
if code == 0 {
PackSpec::Exactly(v)
} else {
PackSpec::Additional(v)
},
dir,
)
};
let hmode = self.mode == Mode::InternalVertical;
let mut plist = NodeRef::NULL;
let mut ptail = NodeRef::NULL;
let mut push = |e: &mut Engine, n: NodeRef| {
if plist.is_null() {
plist = n;
} else {
e.nodes.link(ptail, n);
}
ptail = n;
};
for k in 0..=ncols {
let g = self.nodes.new_glue(tabs[k], glue_sub::TAB_SKIP);
push(self, g);
if k < ncols {
let u = self.nodes.new_null_box(true);
if let Some(b) = self.nodes.box_data_mut(u) {
if hmode {
b.width = widths[k];
} else {
b.height = widths[k];
}
}
self.make_unset(u, 0, 0, 0, 0, 0);
push(self, u);
}
}
self.ts.pack_begin_line = -self.cur_list().mode_line;
let p = if hmode {
let rule_save = self.eqtb.dimen_par[dp::OVERFULLRULE as usize].val;
self.eqtb.dimen_par[dp::OVERFULLRULE as usize].val = 0;
let p = self.hpack(plist, spec);
self.eqtb.dimen_par[dp::OVERFULLRULE as usize].val = rule_save;
p
} else {
let plist = self.filter_list("vpack_filter", plist, "preamble");
let p = self.vpack(plist, spec);
let mut q = self.nodes.next(self.nodes.list_ptr(p));
while !q.is_null() {
if let Some(b) = self.nodes.box_data_mut(q) {
b.width = b.height;
b.height = 0;
}
q = self.nodes.next(q);
if !q.is_null() {
q = self.nodes.next(q);
}
}
p
};
self.ts.pack_begin_line = 0;
let pb = self.nodes.box_data(p).cloned().unwrap_or_default();
let head = self.cur_list().head;
let mut s = head;
let mut q = self.nodes.next(head);
while !q.is_null() {
match self.nodes.id(q) {
id::UNSET => self.set_unset_row(q, &pb, o, hmode),
id::RULE => {
let (pw, ph, pd) = (pb.width, pb.height, pb.depth);
if let NodeData::Rule {
width,
height,
depth,
..
} = self.nodes.data_mut(q)
{
if *width == NULL_FLAG {
*width = pw;
}
if *height == NULL_FLAG {
*height = ph;
}
if *depth == NULL_FLAG {
*depth = pd;
}
}
if o != 0 {
let r = self.nodes.next(q);
self.nodes.set_next(q, NodeRef::NULL);
let b = self.hpack(q, PackSpec::NATURAL);
if let Some(bd) = self.nodes.box_data_mut(b) {
bd.shift = o;
}
self.nodes.link(b, r);
self.nodes.link(s, b);
q = b;
}
}
_ => {}
}
s = q;
q = self.nodes.next(q);
}
self.nodes.flush_list(p);
self.pop_alignment();
let pd_save = self.cur_list().prev_depth;
let sf_save = self.cur_list().space_factor;
let head = self.cur_list().head;
let first = self.nodes.next(head);
let last = self.cur_list().tail;
self.nodes.set_next(head, NodeRef::NULL);
self.pop_nest();
if self.mode == Mode::DisplayMath {
self.finish_display_alignment(first, last, pd_save);
return;
}
if self.mode.abs_kind() == ModeKind::Vertical {
self.cur_list_mut().prev_depth = pd_save;
} else if self.mode.abs_kind() == ModeKind::Horizontal {
self.cur_list_mut().space_factor = sf_save;
}
if !first.is_null() {
let tail = self.cur_list().tail;
self.nodes.link(tail, first);
self.cur_list_mut().tail = last;
}
if self.mode == Mode::Vertical {
self.build_page_from("alignment");
}
}
fn set_unset_row(&mut self, q: NodeRef, pb: &BoxData, o: i32, hmode: bool) {
let first = self.nodes.next(self.nodes.list_ptr(q));
if first.is_null() || self.nodes.id(first) != id::UNSET {
self.luatex_fatal("alignment", "bad box");
}
let rb = match self.nodes.data(q) {
NodeData::Unset { b, .. } => (**b).clone(),
_ => return,
};
let mut nb = BoxData {
glue_order: pb.glue_order,
glue_sign: pb.glue_sign,
glue_set: pb.glue_set,
shift: o,
..rb
};
if hmode {
nb.width = pb.width;
} else {
nb.height = pb.height;
}
let row_h = nb.height;
let row_d = nb.depth;
let row_w = nb.width;
{
let n = self.nodes.get_mut(q);
n.id = if hmode { id::HLIST } else { id::VLIST };
n.subtype = 4;
n.data = NodeData::Box(Box::new(nb));
}
let mut r = self.nodes.next(self.nodes.list_ptr(q));
let mut s = self.nodes.next(pb.head);
while !r.is_null() && !s.is_null() {
let (rbd, n, rst, rsto, rsh, rsho) = match self.nodes.data(r) {
NodeData::Unset {
b,
span_count,
stretch,
shrink,
stretch_order,
shrink_order,
} => (
(**b).clone(),
*span_count,
*stretch,
*stretch_order,
*shrink,
*shrink_order,
),
_ => {
r = self.nodes.next(r);
if !r.is_null() {
r = self.nodes.next(r);
}
s = self.nodes.next(s);
if !s.is_null() {
s = self.nodes.next(s);
}
continue;
}
};
let sw = self.nodes.width(s);
let mut t = sw;
let w = t;
let hold = self.nodes.new_temp();
let mut u = hold;
for _ in 0..n {
s = self.nodes.next(s);
let v = self.nodes.glue_spec(s);
let g = self.new_glue_node(v, glue_sub::TAB_SKIP);
self.nodes.link(u, g);
u = g;
t += v.width;
if pb.glue_sign == 1 {
if v.stretch_order == pb.glue_order {
t += (pb.glue_set * f64::from(v.stretch)).round() as i32;
}
} else if pb.glue_sign == 2 && v.shrink_order == pb.glue_order {
t -= (pb.glue_set * f64::from(v.shrink)).round() as i32;
}
s = self.nodes.next(s);
let eb = self.nodes.new_null_box(hmode);
let swk = self.nodes.width(s);
if let Some(b) = self.nodes.box_data_mut(eb) {
if hmode {
b.width = swk;
} else {
b.height = swk;
}
}
self.nodes.link(u, eb);
u = eb;
t += swk;
}
let natural = if hmode { rbd.width } else { rbd.height };
let mut cb = BoxData { shift: 0, ..rbd };
if hmode {
cb.height = row_h;
cb.depth = row_d;
} else {
cb.width = row_w;
}
if t == natural {
cb.glue_sign = 0;
cb.glue_order = 0;
cb.glue_set = 0.0;
} else if t > natural {
cb.glue_sign = 1;
cb.glue_order = rsto;
cb.glue_set = if rst == 0 {
0.0
} else {
crate::nodes::glue_ratio(t - natural, rst)
};
} else {
cb.glue_order = rsho;
cb.glue_sign = 2;
cb.glue_set = if rsh == 0 {
0.0
} else if rsho == 0 && natural - t > rsh {
1.0
} else {
crate::nodes::glue_ratio(natural - t, rsh)
};
}
if hmode {
cb.width = w;
} else {
cb.height = w;
}
{
let nd = self.nodes.get_mut(r);
nd.id = if hmode { id::HLIST } else { id::VLIST };
nd.subtype = 5;
nd.data = NodeData::Box(Box::new(cb));
}
if u != hold {
let after = self.nodes.next(r);
let first = self.nodes.next(hold);
self.nodes.link(r, first);
self.nodes.link(u, after);
if after.is_null() {
self.nodes.set_next(u, NodeRef::NULL);
}
r = u;
}
self.nodes.free(hold);
r = self.nodes.next(r);
if !r.is_null() {
r = self.nodes.next(r);
}
s = self.nodes.next(s);
if !s.is_null() {
s = self.nodes.next(s);
}
}
}
}