use crate::Engine;
use crate::build::PackSpec;
use crate::fonts::LigOp;
use crate::math::{Field, cramped_style, denom_style, half, num_style, sub_style, sup_style};
use crate::mathparams::mp;
use crate::nodes::{
AttrList, DEFAULT_CODE, NodeData, NodeRef, fence_sub, glue_sub, id, kern_sub, list_sub,
noad_opt, noad_sub, radical_sub,
};
use crate::primitives_gen::{assign_dimen as dp, assign_int as ip};
pub const PROTECTED_GLYPH: u16 = 256;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum NoadType {
Ord = 0,
Op = 1,
Bin = 2,
Rel = 3,
Open = 4,
Close = 5,
Punct = 6,
Inner = 7,
Radical,
Fraction,
Under,
Over,
Accent,
Vcenter,
Left,
Right,
}
const MATH_SPACING: &[u8; 64] = b"0234000122*4000133**3**344*0400400*000000234000111*1111112341011";
impl Engine {
pub fn is_opentype_math_font(&self, f: u32) -> bool {
self.font(f).math_constants.is_some()
}
pub fn math_spacing(&self, l: u32, r: u32, style: u32) -> Option<(crate::glue::Glue, u16)> {
let p = 49 + l * 8 + r;
let v = self.math_param_value(p, style);
if v != crate::codes::UNDEFINED_MATH_PARAM {
return Some((self.math_param_glue(v), 0));
}
let c = MATH_SPACING[(l * 8 + r) as usize];
let x = match c {
b'1' if style < 4 => glue_sub::THIN_MU_SKIP,
b'2' => glue_sub::THIN_MU_SKIP,
b'3' if style < 4 => glue_sub::MED_MU_SKIP,
b'4' if style < 4 => glue_sub::THICK_MU_SKIP,
_ => return None,
};
let par = match x {
glue_sub::THIN_MU_SKIP => crate::primitives_gen::assign_mu_glue::THINMUSKIP,
glue_sub::MED_MU_SKIP => crate::primitives_gen::assign_mu_glue::MEDMUSKIP,
_ => crate::primitives_gen::assign_mu_glue::THICKMUSKIP,
};
Some((self.eqtb.glue(par + crate::eqtb::MU_GLUE_OFFSET), x))
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct MathState {
pub style: i32,
pub size: u32,
pub mu: i32,
pub op_kern: i32,
}
pub fn style_size(s: i32) -> u32 {
if s < 4 { 0 } else { ((s - 2) / 2) as u32 }
}
impl Engine {
pub fn noad_type(&self, q: NodeRef) -> Option<NoadType> {
Some(match self.nodes.id(q) {
id::NOAD => match self.nodes.subtype(q) {
noad_sub::ORD => NoadType::Ord,
noad_sub::OP_DISPLAY_LIMITS | noad_sub::OP_LIMITS | noad_sub::OP_NO_LIMITS => {
NoadType::Op
}
noad_sub::BIN => NoadType::Bin,
noad_sub::REL => NoadType::Rel,
noad_sub::OPEN => NoadType::Open,
noad_sub::CLOSE => NoadType::Close,
noad_sub::PUNCT => NoadType::Punct,
noad_sub::INNER => NoadType::Inner,
noad_sub::UNDER => NoadType::Under,
noad_sub::OVER => NoadType::Over,
_ => NoadType::Vcenter,
},
id::RADICAL => NoadType::Radical,
id::ACCENT => NoadType::Accent,
id::FRACTION => NoadType::Fraction,
id::FENCE => {
if self.nodes.subtype(q) == fence_sub::LEFT {
NoadType::Left
} else {
NoadType::Right
}
}
_ => return None,
})
}
fn set_cur_style(&mut self, s: i32) {
self.ts.math.style = s;
self.ts.math.size = style_size(s);
let q = self.math_par(mp::QUAD);
self.ts.math.mu = retex_arith::x_over_n(q, 18).map_or(0, |r| r.quotient);
}
pub fn math_par(&self, p: u32) -> i32 {
let v = self.math_param_value(p, self.ts.math.style as u32);
if v == crate::codes::UNDEFINED_MATH_PARAM {
0
} else {
v
}
}
fn math_par_opt(&self, p: u32) -> Option<i32> {
let v = self.math_param_value(p, self.ts.math.style as u32);
(v != crate::codes::UNDEFINED_MATH_PARAM).then_some(v)
}
pub fn mlist_to_hlist(&mut self, mlist: NodeRef, style: i32, penalties: bool) -> NodeRef {
self.fonts.sync_dense();
self.init_math_params_once();
let saved = self.ts.math;
let saved_attr = self.ts.attr_override.clone();
let r = self.mlist_to_hlist_rec(mlist, style, penalties);
self.ts.math = saved;
self.ts.attr_override = saved_attr;
r
}
fn attr_of(&self, p: NodeRef) -> Option<AttrList> {
if p.is_null() {
None
} else {
self.nodes.attr(p).cloned()
}
}
fn attr_set(&mut self, a: Option<AttrList>) -> Option<Option<AttrList>> {
self.ts.attr_override.replace(a)
}
fn attr_from(&mut self, p: NodeRef) -> Option<Option<AttrList>> {
let a = self.attr_of(p);
self.attr_set(a)
}
fn attr_restore(&mut self, saved: Option<Option<AttrList>>) {
self.ts.attr_override = saved;
}
fn get_new_hlist(&self, q: NodeRef) -> NodeRef {
match self.nodes.data(q) {
NodeData::Noad(n) => n.new_hlist,
NodeData::Fraction(f) => f.new_hlist,
NodeData::Fence(f) => f.new_hlist,
_ => NodeRef::NULL,
}
}
fn set_new_hlist(&mut self, q: NodeRef, p: NodeRef) {
match self.nodes.data_mut(q) {
NodeData::Noad(n) => n.new_hlist = p,
NodeData::Fraction(f) => f.new_hlist = p,
NodeData::Fence(f) => f.new_hlist = p,
_ => {}
}
}
fn mlist_to_hlist_rec(&mut self, mlist: NodeRef, style: i32, penalties: bool) -> NodeRef {
let mut q = mlist;
let mut r = NodeRef::NULL;
let mut r_type = NoadType::Op;
let mut max_h = 0i32;
let mut max_d = 0i32;
self.set_cur_style(style);
while !q.is_null() {
let mut delta = 0;
let Some(mut t) = self.noad_type(q) else {
let next = self.first_pass_node(q, &mut max_h, &mut max_d);
q = next;
continue;
};
let saved_attr = self.attr_from(q);
let mut action = Action::Convert;
loop {
match t {
NoadType::Bin => {
if matches!(
r_type,
NoadType::Bin
| NoadType::Op
| NoadType::Rel
| NoadType::Open
| NoadType::Punct
| NoadType::Left
) {
self.nodes.set_subtype(q, noad_sub::ORD);
t = NoadType::Ord;
continue;
}
}
NoadType::Rel | NoadType::Close | NoadType::Punct | NoadType::Right => {
if r_type == NoadType::Bin {
self.nodes.set_subtype(r, noad_sub::ORD);
}
if t == NoadType::Right {
action = Action::DoneWithNoad;
}
}
NoadType::Left => action = Action::DoneWithNoad,
NoadType::Fraction => {
self.make_fraction(q);
action = Action::CheckDimensions;
}
NoadType::Op => {
delta = self.make_op(q);
if self.nodes.subtype(q) == noad_sub::OP_LIMITS {
action = Action::CheckDimensions;
}
}
NoadType::Ord => self.make_ord(q),
NoadType::Open | NoadType::Inner => {}
NoadType::Radical => self.make_radical(q),
NoadType::Over => self.make_over(q),
NoadType::Under => self.make_under(q),
NoadType::Accent => self.make_math_accent(q),
NoadType::Vcenter => self.make_vcenter(q),
}
break;
}
if action == Action::Convert {
self.convert_nucleus(q, delta);
}
if action != Action::DoneWithNoad {
let nh = self.get_new_hlist(q);
let (h, d) = self.list_height_depth(nh);
if h > max_h {
max_h = h;
}
if d > max_d {
max_d = d;
}
}
r = q;
r_type = self.noad_type(r).unwrap_or(NoadType::Ord);
if r_type == NoadType::Right {
r_type = NoadType::Left;
self.set_cur_style(style);
}
self.attr_restore(saved_attr);
q = self.nodes.next(q);
}
if r_type == NoadType::Bin {
self.nodes.set_subtype(r, noad_sub::ORD);
}
self.second_pass(mlist, style, penalties, max_h, max_d)
}
fn list_height_depth(&mut self, p: NodeRef) -> (i32, i32) {
let saved = self.ts.adjust_tail.take();
let pre = self.ts.pre_adjust_tail.take();
let z = self.hpack_quiet(p);
self.ts.adjust_tail = saved;
self.ts.pre_adjust_tail = pre;
let (h, d) = self
.nodes
.box_data(z)
.map_or((0, 0), |b| (b.height, b.depth));
self.nodes.set_list_ptr(z, NodeRef::NULL);
self.nodes.free(z);
(h, d)
}
fn hpack_quiet(&mut self, p: NodeRef) -> NodeRef {
self.hpack(p, PackSpec::NATURAL)
}
fn first_pass_node(&mut self, q: NodeRef, max_h: &mut i32, max_d: &mut i32) -> NodeRef {
match self.nodes.data(q) {
NodeData::Style { style } => {
let s = i32::from(*style);
self.set_cur_style(s);
}
NodeData::Choice(c) => {
let c = **c;
let k = (self.ts.math.style / 2) as usize;
let p = c[k.min(3)];
for (i, l) in c.iter().enumerate() {
if i != k.min(3) {
self.nodes.flush_list(*l);
}
}
let st = self.ts.math.style as u8;
*self.nodes.get_mut(q) = crate::nodes::Node {
data: NodeData::Style { style: st },
id: id::STYLE,
subtype: u16::from(st),
..self.nodes.get(q).clone()
};
if !p.is_null() {
let z = self.nodes.next(q);
self.nodes.link(q, p);
let t = self.nodes.tail_of(p);
self.nodes.link(t, z);
}
}
NodeData::Rule { height, depth, .. } => {
if *height > *max_h {
*max_h = *height;
}
if *depth > *max_d {
*max_d = *depth;
}
}
NodeData::Glue { spec, .. } => {
let spec = *spec;
let sub = self.nodes.subtype(q);
if sub == glue_sub::MU_GLUE {
let g = self.math_glue(spec, self.ts.math.mu);
if let NodeData::Glue { spec, .. } = self.nodes.data_mut(q) {
*spec = g;
}
self.nodes.set_subtype(q, glue_sub::USER);
} else if self.ts.math.size != 0 && sub == glue_sub::COND_MATH_GLUE {
let p = self.nodes.next(q);
if !p.is_null() && matches!(self.nodes.id(p), id::GLUE | id::KERN) {
let n = self.nodes.next(p);
self.nodes.link(q, n);
if n.is_null() {
self.nodes.set_next(q, NodeRef::NULL);
}
self.nodes.set_next(p, NodeRef::NULL);
self.nodes.flush_list(p);
}
}
}
NodeData::Kern { kern, .. } => {
if self.nodes.subtype(q) == glue_sub::MU_GLUE {
let w = self.mu_mult(*kern, self.ts.math.mu);
if let NodeData::Kern { kern, .. } = self.nodes.data_mut(q) {
*kern = w;
}
self.nodes.set_subtype(q, kern_sub::ITALIC);
}
}
_ => {}
}
self.nodes.next(q)
}
fn mu_mult(&self, x: i32, m: i32) -> i32 {
let r = retex_arith::x_over_n_unchecked(m, 0o200000);
let (mut n, mut f) = (r.quotient, r.remainder);
if f < 0 {
n -= 1;
f += 0o200000;
}
let frac = retex_arith::xn_over_d(x, f, 0o200000).map_or(0, |r| r.quotient);
retex_arith::nx_plus_y(n, x, frac).unwrap_or(0)
}
pub fn math_glue(&self, g: crate::glue::Glue, m: i32) -> crate::glue::Glue {
let mut r = g;
r.width = self.mu_mult(g.width, m);
if g.stretch_order == 0 {
r.stretch = self.mu_mult(g.stretch, m);
}
if g.shrink_order == 0 {
r.shrink = self.mu_mult(g.shrink, m);
}
r
}
fn fetch(&mut self, k: NodeRef) -> Option<(u32, u32, crate::fonts::CharMetrics)> {
let (fam, chr) = match self.nodes.data(k) {
NodeData::MathChar { fam, chr } => (*fam, *chr),
_ => return None,
};
let size = self.ts.math.size;
let f = self.fam_fnt(fam, size);
if f == 0 {
self.print_err("");
self.print_size(size);
self.printer.print_int(i64::from(fam));
self.printer.print_str(" is undefined (character ");
self.printer.print_int(i64::from(chr));
self.printer.print_char(')' as u32);
self.help(&[
"Somewhere in the math formula just ended, you used the",
"stated character from an undefined font family. For example,",
"plain TeX doesn't allow \\it or \\sl in subscripts. Proceed,",
"and I'll try to forget that I needed that character.",
]);
self.error();
return None;
}
match self.fonts.char_metrics(f, chr) {
Some(m) => Some((f, chr, *m)),
None => {
self.char_warning(f, chr);
None
}
}
}
pub fn print_size(&mut self, s: u32) {
self.print_esc_str(match s {
0 => "textfont",
1 => "scriptfont",
_ => "scriptscriptfont",
});
}
pub fn char_warning(&mut self, f: u32, c: u32) {
if self.eqtb.int(ip::TRACINGLOSTCHARS) > 0 {
let old = self.eqtb.int(ip::TRACINGONLINE);
self.begin_diagnostic();
self.printer.print_nl("Missing character: There is no ");
self.printer.print_char(c);
self.printer.print_str(&format!(" (U+{c:04X}) in font "));
let name = self.font(f).name.clone();
self.printer.print_bytes(&name);
self.printer.print_char('!' as u32);
self.end_diagnostic(false);
let _ = old;
}
}
fn new_math_glyph(&mut self, f: u32, c: u32) -> NodeRef {
let g = self.new_char_node(f, c);
self.nodes.set_subtype(g, PROTECTED_GLYPH);
g
}
fn char_box(&mut self, f: u32, c: u32) -> NodeRef {
let m = self.fonts.char_metrics(f, c).copied().unwrap_or_default();
let italic = if self.is_opentype_math_font(f) {
0
} else {
m.italic
};
let b = self.new_null_box_node(true);
let g = self.new_math_glyph(f, c);
if let Some(bd) = self.nodes.box_data_mut(b) {
bd.width = m.width + italic;
bd.height = m.height;
bd.depth = m.depth;
bd.head = g;
}
self.nodes.set_subtype(b, list_sub::MATH_CHAR);
b
}
fn char_tag(&self, f: u32, c: u32) -> (u8, u32) {
if let Some(t) = self.tfm_of(f)
&& let Some(ci) = t.char_info(c)
{
return (ci.tag, u32::from(ci.remainder));
}
if let Some(m) = self.math_char(f, c) {
if let Some(n) = m.next {
return (2, n);
}
if m.vert.is_some() {
return (3, c);
}
}
(0, 0)
}
fn clean_box(&mut self, k: NodeRef, s: i32, sub: u16) -> NodeRef {
let mut q;
let kid = if k.is_null() { 0 } else { self.nodes.id(k) };
match kid {
id::MATH_CHAR | id::MATH_TEXT_CHAR => {
let n = self.new_noad(noad_sub::ORD);
let kc = self.nodes.copy_node(k);
self.set_noad_field(n, Field::Nucleus, kc);
q = self.clean_box_mlist(n, s);
}
id::SUB_BOX => {
q = self.nodes.list_ptr_kernel(k);
self.nodes.set_kernel_list(k, NodeRef::NULL);
}
id::SUB_MLIST => {
let l = self.nodes.list_ptr_kernel(k);
self.nodes.set_kernel_list(k, NodeRef::NULL);
q = self.clean_box_mlist(l, s);
}
_ => {
q = self.new_null_box_node(true);
}
}
let x = if q.is_null() {
self.hpack_quiet(q)
} else if !self.nodes.is_glyph(q)
&& self.nodes.next(q).is_null()
&& matches!(self.nodes.id(q), id::HLIST | id::VLIST)
&& self.nodes.box_data(q).is_some_and(|b| b.shift == 0)
{
q
} else {
let saved = self.attr_from(q);
let x = self.hpack_quiet(q);
self.attr_restore(saved);
x
};
self.nodes.set_subtype(x, sub);
q = self.nodes.list_ptr(x);
if !q.is_null() && self.nodes.is_glyph(q) {
let r = self.nodes.next(q);
if !r.is_null() && self.nodes.next(r).is_null() && self.nodes.id(r) == id::KERN {
self.nodes.free(r);
self.nodes.set_next(q, NodeRef::NULL);
}
}
x
}
fn clean_box_mlist(&mut self, l: NodeRef, s: i32) -> NodeRef {
let saved = self.ts.math;
let r = self.mlist_to_hlist_rec(l, s, false);
self.ts.math = saved;
r
}
fn rebox(&mut self, b: NodeRef, w: i32) -> NodeRef {
let bw = self.nodes.box_data(b).map_or(0, |x| x.width);
let lp = self.nodes.list_ptr(b);
if bw != w && !lp.is_null() {
let saved = self.attr_from(b);
let mut b = b;
if self.nodes.id(b) == id::VLIST {
b = self.hpack_quiet(b);
}
let mut p = self.nodes.list_ptr(b);
let bw = self.nodes.box_data(b).map_or(0, |x| x.width);
if self.nodes.is_glyph(p) && self.nodes.next(p).is_null() {
let (f, c) = {
let g = self.nodes.glyph(p).expect("glyph");
(g.font, g.chr)
};
let v = self.fonts.char_metrics(f, c).map_or(0, |m| m.width);
if v != bw {
let k = self.new_kern_node(bw - v, kern_sub::FONT);
self.nodes.link(p, k);
}
}
self.nodes.set_list_ptr(b, NodeRef::NULL);
self.nodes.free(b);
let ss = crate::glue::Glue {
stretch: retex_arith::UNITY,
stretch_order: crate::glue::FIL,
shrink: retex_arith::UNITY,
shrink_order: crate::glue::FIL,
..crate::glue::Glue::ZERO
};
let g1 = self.new_glue_node(ss, glue_sub::USER);
self.nodes.link(g1, p);
while !self.nodes.next(p).is_null() {
p = self.nodes.next(p);
}
let g2 = self.new_glue_node(ss, glue_sub::USER);
self.nodes.link(p, g2);
let r = self.hpack(g1, PackSpec::Exactly(w));
self.attr_restore(saved);
r
} else {
if let Some(x) = self.nodes.box_data_mut(b) {
x.width = w;
}
b
}
}
fn fraction_rule(&mut self, t: i32) -> NodeRef {
self.new_node(
id::RULE,
0,
NodeData::Rule {
width: crate::typeset::NULL_FLAG,
depth: 0,
height: t,
dir: 0,
index: 0,
left: 0,
right: 0,
},
)
}
fn overbar(&mut self, b: NodeRef, k: i32, t: i32, top: i32) -> NodeRef {
let p = self.new_kern_node(k, kern_sub::FONT);
self.nodes.link(p, b);
let q = self.fraction_rule(t);
self.nodes.link(q, p);
let p = self.new_kern_node(top, kern_sub::FONT);
self.nodes.link(p, q);
self.vpack(p, PackSpec::NATURAL)
}
pub fn var_delimiter(&mut self, d: NodeRef, s: u32, v: i32, shift: bool) -> NodeRef {
let saved = self.attr_from(d);
let b = self.var_delimiter_box(d, s, v, shift);
self.attr_restore(saved);
b
}
fn var_delimiter_box(&mut self, d: NodeRef, s: u32, v: i32, shift: bool) -> NodeRef {
let (sf, sc, lf, lc) = self.delim_fields(d);
let mut best: Option<(u32, u32, bool)> = None;
let mut w = 0i32;
'search: for (z0, x0) in [(sf, sc), (lf, lc)] {
if z0 == 0 && x0 == 0 {
continue;
}
let mut z = s as i32;
while z >= 0 {
let g = self.fam_fnt(z0, z as u32);
if g != 0 {
let mut y = x0;
let mut guard = 0;
loop {
guard += 1;
if guard > 256 {
break;
}
let Some(m) = self.fonts.char_metrics(g, y).copied() else {
break;
};
let (tag, rem) = self.char_tag(g, y);
let opentype = self.is_opentype_math_font(g);
if tag == 3 && !opentype {
best = Some((g, y, true));
break 'search;
}
let u = m.height + m.depth;
if u > w {
best = Some((g, y, false));
w = u;
if u >= v {
break 'search;
}
}
if opentype && self.vert_parts(g, y).is_some() {
best = Some((g, y, true));
break 'search;
}
if tag == 2 {
y = rem;
} else {
break;
}
}
}
z -= 1;
}
}
let b = match best {
Some((f, c, true)) if self.is_opentype_math_font(f) => {
let saved = self.ts.math;
self.set_cur_style(if s == 0 { 2 } else { 2 * s as i32 + 2 });
let overlap = self.math_par(mp::CONNECTOR_OVERLAP_MIN);
self.ts.math = saved;
self.ot_extensible_box(f, c, v, overlap)
}
Some((f, c, true)) => self.extensible_box(f, c, v),
Some((f, c, false)) => self.char_box(f, c),
None => {
let b = self.new_null_box_node(true);
let nds = self.eqtb.dimen(dp::NULLDELIMITERSPACE);
if let Some(bd) = self.nodes.box_data_mut(b) {
bd.width = nds;
}
self.nodes.set_subtype(b, list_sub::V_DELIMITER);
if !shift {
return b;
}
b
}
};
let saved = self.ts.math;
self.set_cur_style(if s == 0 { 2 } else { 2 * s as i32 + 2 });
let axis = self.math_par(mp::AXIS);
self.ts.math = saved;
if let Some(bd) = self.nodes.box_data_mut(b) {
bd.shift = half(bd.height - bd.depth) - axis;
}
b
}
fn nested_accent_char(&mut self, k: NodeRef) -> Option<(u32, u32)> {
if k.is_null() || self.nodes.id(k) != id::SUB_MLIST {
return None;
}
let l = self.nodes.list_ptr_kernel(k);
if l.is_null()
|| !self.nodes.next(l).is_null()
|| self.noad_type(l) != Some(NoadType::Accent)
{
return None;
}
let n = self.noad_field(l, Field::Nucleus);
if n.is_null() {
return None;
}
if self.nodes.id(n) == id::MATH_CHAR {
return self.fetch(n).map(|(f, c, _)| (f, c));
}
self.nested_accent_char(n)
}
fn vert_parts(&self, f: u32, c: u32) -> Option<std::rc::Rc<[crate::fonts::ExtPart]>> {
self.math_char(f, c)?.vert.clone()
}
fn ot_extensible_box(&mut self, f: u32, c: u32, v: i32, overlap: i32) -> NodeRef {
let parts = self
.vert_parts(f, c)
.unwrap_or_else(|| std::rc::Rc::from(Vec::new()));
let expand = |n: usize| -> Vec<crate::fonts::ExtPart> {
let mut out = Vec::new();
for p in parts.iter() {
let k = if p.extender { n } else { 1 };
for _ in 0..k {
out.push(*p);
}
}
out
};
let joint = |a: &crate::fonts::ExtPart, b: &crate::fonts::ExtPart| {
let max = a.end.min(b.start).max(0);
(max, overlap.min(max))
};
let largest = |seq: &[crate::fonts::ExtPart]| -> i64 {
let mut t: i64 = seq.iter().map(|p| i64::from(p.advance)).sum();
for w in seq.windows(2) {
t -= i64::from(joint(&w[0], &w[1]).1);
}
t
};
let has_extender = parts.iter().any(|p| p.extender);
let mut n = 0;
let mut seq = expand(n);
while has_extender && largest(&seq) < i64::from(v) && n < 1000 {
n += 1;
seq = expand(n);
}
let mut head = NodeRef::NULL;
let mut tail = NodeRef::NULL;
let mut width = 0;
for (i, p) in seq.iter().enumerate().rev() {
if i + 1 < seq.len() {
let (max, min) = joint(p, &seq[i + 1]);
let g = crate::glue::Glue {
width: -max,
stretch: max - min,
..crate::glue::Glue::ZERO
};
let gn = self.new_glue_node(g, 0);
self.nodes.link(tail, gn);
tail = gn;
}
let m = self
.fonts
.char_metrics(f, p.code)
.copied()
.unwrap_or_default();
let b = self.new_null_box_node(true);
self.nodes.set_subtype(b, list_sub::MATH_CHAR);
let gl = self.new_math_glyph(f, p.code);
if let Some(bd) = self.nodes.box_data_mut(b) {
bd.width = m.width;
bd.height = m.height + m.depth;
bd.depth = 0;
bd.head = gl;
}
width = width.max(m.width);
if head.is_null() {
head = b;
} else {
self.nodes.link(tail, b);
}
tail = b;
}
let natural = largest(&seq) as i32;
let target = v.max(natural.min(v));
let b = self.vpack(head, crate::build::PackSpec::Exactly(target));
self.nodes.set_subtype(b, list_sub::V_EXTENSIBLE);
if let Some(bd) = self.nodes.box_data_mut(b) {
bd.width = width;
}
b
}
fn extensible_box(&mut self, f: u32, c: u32, v: i32) -> NodeRef {
let recipe = self
.tfm_of(f)
.and_then(|t| {
let (_, rem) = self.char_tag(f, c);
t.exten.get(rem as usize).copied()
})
.unwrap_or([0, 0, 0, 0]);
let [top, mid, bot, rep] = recipe.map(u32::from);
let hd = |e: &Engine, ch: u32| {
e.fonts
.char_metrics(f, ch)
.map_or(0, |m| m.height + m.depth)
};
let b = self.new_null_box_node(false);
let u = hd(self, rep);
let mut w = 0;
let rm = self.fonts.char_metrics(f, rep).copied().unwrap_or_default();
let width = rm.width + rm.italic;
if bot != 0 {
w += hd(self, bot);
}
if mid != 0 {
w += hd(self, mid);
}
if top != 0 {
w += hd(self, top);
}
let mut n = 0;
if u > 0 {
while w < v {
w += u;
n += 1;
if mid != 0 {
w += u;
}
}
}
if let Some(bd) = self.nodes.box_data_mut(b) {
bd.width = width;
}
if bot != 0 {
self.stack_into_box(b, f, bot);
}
for _ in 0..n {
self.stack_into_box(b, f, rep);
}
if mid != 0 {
self.stack_into_box(b, f, mid);
for _ in 0..n {
self.stack_into_box(b, f, rep);
}
}
if top != 0 {
self.stack_into_box(b, f, top);
}
if let Some(bd) = self.nodes.box_data_mut(b) {
bd.height = w;
bd.depth = 0;
}
self.nodes.set_subtype(b, list_sub::V_EXTENSIBLE);
b
}
fn stack_into_box(&mut self, b: NodeRef, f: u32, c: u32) {
let p = self.char_box(f, c);
let head = self.nodes.list_ptr(b);
self.nodes.link(p, head);
self.nodes.set_list_ptr(b, p);
let h = self.nodes.box_data(p).map_or(0, |x| x.height);
if let Some(bd) = self.nodes.box_data_mut(b) {
bd.height = h;
}
}
fn take_field(&mut self, q: NodeRef, f: Field) -> NodeRef {
let k = self.noad_field(q, f);
self.set_noad_field(q, f, NodeRef::NULL);
k
}
fn set_nucleus_box(&mut self, q: NodeRef, b: NodeRef) {
let old = self.take_field(q, Field::Nucleus);
self.nodes.flush_list(old);
let k = self.new_sub_list(false, b);
self.set_noad_field(q, Field::Nucleus, k);
}
fn make_over(&mut self, q: NodeRef) {
let k = self.noad_field(q, Field::Nucleus);
let st = cramped_style(self.ts.math.style);
let x = self.clean_box(k, st, list_sub::NUCLEUS);
let saved = self.attr_from(k);
let b = self.overbar(
x,
self.math_par(mp::OVERBAR_VGAP),
self.math_par(mp::OVERBAR_RULE),
self.math_par(mp::OVERBAR_KERN),
);
self.attr_restore(saved);
self.nodes.set_subtype(b, list_sub::OVER);
self.set_nucleus_box(q, b);
}
fn make_under(&mut self, q: NodeRef) {
let k = self.noad_field(q, Field::Nucleus);
let x = self.clean_box(k, self.ts.math.style, list_sub::NUCLEUS);
let p = self.new_kern_node(self.math_par(mp::UNDERBAR_VGAP), kern_sub::FONT);
self.nodes.link(x, p);
let r = self.fraction_rule(self.math_par(mp::UNDERBAR_RULE));
self.nodes.link(p, r);
let y = self.vpack(x, PackSpec::NATURAL);
self.nodes.set_subtype(y, list_sub::UNDER);
let (yh, yd) = self
.nodes
.box_data(y)
.map_or((0, 0), |b| (b.height, b.depth));
let xh = self.nodes.box_data(x).map_or(0, |b| b.height);
let delta = yh + yd + self.math_par(mp::UNDERBAR_KERN);
if let Some(b) = self.nodes.box_data_mut(y) {
b.height = xh;
b.depth = delta - xh;
}
self.set_nucleus_box(q, y);
}
fn make_vcenter(&mut self, q: NodeRef) {
let k = self.noad_field(q, Field::Nucleus);
let v = self.nodes.list_ptr_kernel(k);
if v.is_null() || self.nodes.id(v) != id::VLIST {
self.confusion("vcenter");
}
let axis = self.math_par(mp::AXIS);
if let Some(b) = self.nodes.box_data_mut(v) {
let delta = b.height + b.depth;
b.height = axis + half(delta);
b.depth = delta - b.height;
}
}
fn make_radical(&mut self, q: NodeRef) {
let sub = self.nodes.subtype(q);
match sub {
radical_sub::UUNDERDELIMITER
| radical_sub::UOVERDELIMITER
| radical_sub::UDELIMITERUNDER
| radical_sub::UDELIMITEROVER
| radical_sub::UHEXTENSIBLE => {
self.make_delimiter_radical(q, sub);
return;
}
_ => {}
}
let k = self.noad_field(q, Field::Nucleus);
let x = self.clean_box(k, cramped_style(self.ts.math.style), list_sub::NUCLEUS);
let (xh, xd) = self
.nodes
.box_data(x)
.map_or((0, 0), |b| (b.height, b.depth));
let mut clr = self.math_par(mp::RADICAL_VGAP);
let theta = self.math_par_opt(mp::RADICAL_RULE);
let kern = self.math_par(mp::RADICAL_KERN);
let t = theta.unwrap_or(self.math_par(mp::FRACTION_RULE));
let left = self.nodes.noad(q).map_or(NodeRef::NULL, |n| n.left);
let size = self.ts.math.size;
let y = self.var_delimiter(left, size, xh + xd + clr + t, true);
let (yh, yd) = self
.nodes
.box_data(y)
.map_or((0, 0), |b| (b.height, b.depth));
let theta = if let Some(th) = theta {
let delta = yh + yd - th - (xh + xd + clr);
if delta > 0 {
clr += half(delta);
}
if let Some(b) = self.nodes.box_data_mut(y) {
b.shift = yh - (xh + clr + th);
}
th
} else {
let top_h = if self.nodes.id(y) == id::VLIST {
let h = self.nodes.list_ptr(y);
self.nodes.box_data(h).map_or(yh, |b| b.height)
} else {
yh
};
let eq_depth = yh + yd - top_h;
let delta = eq_depth - (xh + xd + clr);
if delta > 0 {
clr += half(delta);
}
if let Some(b) = self.nodes.box_data_mut(y) {
b.shift = -(xh + clr) + (yh - top_h);
}
top_h
};
let saved = self.attr_from(left);
let ob = self.overbar(x, clr, theta, kern);
self.attr_restore(saved);
self.nodes.set_subtype(ob, list_sub::RADICAL);
self.nodes.link(y, ob);
let deg = self.noad_field(q, Field::Degree);
let list = if deg.is_null() {
y
} else {
let r = self.clean_box(deg, 6, list_sub::DEGREE);
let (rw, rh, rd) = self
.nodes
.box_data(r)
.map_or((0, 0, 0), |b| (b.width, b.height, b.depth));
let _ = rd;
let before = self.math_par(mp::RADICAL_DEGREE_BEFORE);
let after = self.math_par(mp::RADICAL_DEGREE_AFTER);
let raise = self.math_par(mp::RADICAL_DEGREE_RAISE);
let (yh2, yd2, ys) = self
.nodes
.box_data(y)
.map_or((0, 0, 0), |b| (b.height, b.depth, b.shift));
let ytot = yh2 + yd2;
let shift = retex_arith::xn_over_d(ytot, raise, 100).map_or(0, |r| r.quotient);
if self.math_par_opt(mp::RADICAL_RULE).is_some() {
if let Some(b) = self.nodes.box_data_mut(r) {
b.shift = -(shift - (yd2 + ys));
}
} else if let Some(b) = self.nodes.box_data_mut(r) {
b.shift = -(shift - yd2 + b.shift.min(0));
}
let _ = rh;
let saved = self.attr_from(r);
let k1 = self.new_kern_node(before, kern_sub::FONT);
let k2 = self.new_kern_node(after, kern_sub::FONT);
self.attr_restore(saved);
let _ = rw;
self.nodes.link(k1, r);
self.nodes.link(r, k2);
self.nodes.link(k2, y);
k1
};
let b = self.hpack_quiet(list);
self.set_nucleus_box(q, b);
}
fn make_delimiter_radical(&mut self, q: NodeRef, sub: u16) {
let k = self.noad_field(q, Field::Nucleus);
let cs = self.ts.math.style;
let left = self.nodes.noad(q).map_or(NodeRef::NULL, |n| n.left);
let size = self.ts.math.size;
if sub == radical_sub::UHEXTENSIBLE {
let width = self.nodes.noad(q).map_or(0, |n| n.width);
let y = self.h_delimiter(left, size, width);
let b = self.hpack_quiet(y);
self.set_nucleus_box(q, b);
return;
}
let st = match sub {
radical_sub::UOVERDELIMITER => sup_style(cs),
radical_sub::UUNDERDELIMITER => sub_style(cs),
_ => cs,
};
let x = if k.is_null() {
self.new_null_box_node(true)
} else {
self.clean_box(k, st, list_sub::NUCLEUS)
};
let dsize = match sub {
radical_sub::UDELIMITEROVER | radical_sub::UDELIMITERUNDER => (size + 1).min(2),
_ => size,
};
let xw0 = self.nodes.box_data(x).map_or(0, |b| b.width);
let y = self.h_delimiter(left, dsize, xw0);
let dim = |e: &Self, b: NodeRef| {
e.nodes
.box_data(b)
.map_or((0, 0, 0), |b| (b.width, b.height, b.depth))
};
let (xw, xh, xd) = dim(self, x);
let (yw, yh, yd) = dim(self, y);
let w = xw.max(yw);
for b in [x, y] {
if let Some(bd) = self.nodes.box_data_mut(b) {
bd.width = w;
}
}
let over = matches!(
sub,
radical_sub::UOVERDELIMITER | radical_sub::UDELIMITEROVER
);
let (vgap, bgap) = if over {
(
self.math_par(mp::OVER_DELIMITER_VGAP),
self.math_par(mp::OVER_DELIMITER_BGAP),
)
} else {
(
self.math_par(mp::UNDER_DELIMITER_VGAP),
self.math_par(mp::UNDER_DELIMITER_BGAP),
)
};
let (top, bottom, kern) = match sub {
radical_sub::UOVERDELIMITER => (x, y, vgap.max(bgap - yh - xd)),
radical_sub::UUNDERDELIMITER => (y, x, vgap.max(bgap - xh - yd)),
radical_sub::UDELIMITEROVER => (y, x, vgap.max(bgap - xh - (yh + yd)) - yd),
_ => (x, y, vgap.max(bgap - xd - (yh + yd)) - yh),
};
let kk = self.new_kern_node(kern, kern_sub::FONT);
self.nodes.link(top, kk);
self.nodes.link(kk, bottom);
let v = self.vpack(top, PackSpec::NATURAL);
self.nodes.set_subtype(
v,
if over {
list_sub::OVER_DELIMITER
} else {
list_sub::UNDER_DELIMITER
},
);
if matches!(
sub,
radical_sub::UUNDERDELIMITER | radical_sub::UDELIMITERUNDER
) {
let th = self.nodes.box_data(top).map_or(0, |b| b.height);
if let Some(b) = self.nodes.box_data_mut(v) {
let tot = b.height + b.depth;
b.height = th;
b.depth = tot - th;
}
}
self.set_nucleus_box(q, v);
}
fn h_delimiter(&mut self, d: NodeRef, s: u32, w: i32) -> NodeRef {
let saved = self.attr_from(d);
let (sf, sc, _, _) = self.delim_fields(d);
let f = self.fam_fnt(sf, s);
let b = if f == 0 || self.fonts.char_metrics(f, sc).is_none() {
self.new_null_box_node(true)
} else if !self.is_opentype_math_font(f) {
self.char_box(f, sc)
} else {
let mut c = sc;
let mut guard = 0;
let mut assembly = None;
loop {
guard += 1;
let m = self.fonts.char_metrics(f, c).copied().unwrap_or_default();
if m.width >= w || guard > 256 {
break;
}
if let Some(parts) = self.math_char(f, c).and_then(|mc| mc.horiz.clone()) {
assembly = Some(parts);
break;
}
match self.math_char(f, c).and_then(|mc| mc.next) {
Some(n) if self.fonts.char_metrics(f, n).is_some() => c = n,
_ => break,
}
}
match assembly {
Some(parts) => {
let overlap = self.math_par(mp::CONNECTOR_OVERLAP_MIN);
self.ot_h_extensible_box(f, &parts, w, overlap)
}
None => self.char_box(f, c),
}
};
self.attr_restore(saved);
b
}
fn ot_h_extensible_box(
&mut self,
f: u32,
parts: &[crate::fonts::ExtPart],
w: i32,
overlap: i32,
) -> NodeRef {
let expand = |n: usize| -> Vec<crate::fonts::ExtPart> {
let mut out = Vec::new();
for p in parts.iter() {
let k = if p.extender { n } else { 1 };
for _ in 0..k {
out.push(*p);
}
}
out
};
let joint = |a: &crate::fonts::ExtPart, b: &crate::fonts::ExtPart| {
let max = a.end.min(b.start).max(0);
(max, overlap.min(max))
};
let largest = |seq: &[crate::fonts::ExtPart]| -> i64 {
let mut t: i64 = seq.iter().map(|p| i64::from(p.advance)).sum();
for win in seq.windows(2) {
t -= i64::from(joint(&win[0], &win[1]).1);
}
t
};
let has_extender = parts.iter().any(|p| p.extender);
let mut n = 0;
let mut seq = expand(n);
while has_extender && largest(&seq) < i64::from(w) && n < 1000 {
n += 1;
seq = expand(n);
}
let mut head = NodeRef::NULL;
let mut tail = NodeRef::NULL;
for (i, p) in seq.iter().enumerate() {
if i > 0 {
let (max, min) = joint(&seq[i - 1], p);
let g = crate::glue::Glue {
width: -max,
stretch: max - min,
..crate::glue::Glue::ZERO
};
let gn = self.new_glue_node(g, 0);
self.nodes.link(tail, gn);
tail = gn;
}
let b = self.char_box(f, p.code);
if head.is_null() {
head = b;
} else {
self.nodes.link(tail, b);
}
tail = b;
}
let natural = largest(&seq) as i32;
let target = w.max(natural.min(w));
let b = self.hpack(head, PackSpec::Exactly(target));
self.nodes.set_subtype(b, list_sub::H_EXTENSIBLE);
b
}
fn make_math_accent(&mut self, q: NodeRef) {
let acc = self.nodes.noad(q).map_or(NodeRef::NULL, |n| n.accent);
if acc.is_null() {
self.make_bottom_accent(q);
return;
}
let Some((f, mut c, mut i)) = self.fetch(acc) else {
return;
};
let mut s = 0;
let nuc = self.noad_field(q, Field::Nucleus);
let mut nuc_char = None;
if !nuc.is_null()
&& self.nodes.id(nuc) == id::MATH_CHAR
&& let Some((nf, nc, _)) = self.fetch(nuc)
{
nuc_char = Some((nf, nc));
let sk = self.font(nf).skew_char;
if sk >= 0
&& let Some(LigOp::Kern(k)) = self.lig_kern_step(nf, nc, sk as u32)
{
s = k;
}
}
let st = cramped_style(self.ts.math.style);
let mut char_nucleus = !nuc.is_null() && self.nodes.id(nuc) == id::MATH_CHAR;
if nuc_char.is_none()
&& self.is_opentype_math_font(f)
&& let Some(fc) = self.nested_accent_char(nuc)
{
nuc_char = Some(fc);
char_nucleus = true;
}
let mut x = self.clean_box(nuc, st, list_sub::NUCLEUS);
let (w, mut h) = self
.nodes
.box_data(x)
.map_or((0, 0), |b| (b.width, b.height));
let fixed = matches!(
self.nodes.subtype(q),
crate::nodes::accent_sub::FIXED_TOP | crate::nodes::accent_sub::FIXED_BOTH
);
if !fixed {
loop {
let (tag, rem) = self.char_tag(f, c);
if tag != 2 {
break;
}
let Some(m) = self.fonts.char_metrics(f, rem).copied() else {
break;
};
if m.width > w {
break;
}
c = rem;
i = m;
}
}
let _ = i;
let opentype = self.is_opentype_math_font(f);
let xh = if opentype {
self.font(f)
.math_constants
.as_ref()
.and_then(|m| m.get("AccentBaseHeight").copied())
.unwrap_or_else(|| self.x_height(f))
} else {
self.x_height(f)
};
let mut delta = if h < xh { h } else { xh };
let sup = self.noad_field(q, Field::Sup);
let sub = self.noad_field(q, Field::Sub);
if (!sup.is_null() || !sub.is_null())
&& !nuc.is_null()
&& self.nodes.id(nuc) == id::MATH_CHAR
{
self.nodes.flush_list(x);
let n = self.new_noad(noad_sub::ORD);
let nk = self.take_field(q, Field::Nucleus);
let a = self.take_field(q, Field::Sup);
let b = self.take_field(q, Field::Sub);
self.set_noad_field(n, Field::Nucleus, nk);
self.set_noad_field(n, Field::Sup, a);
self.set_noad_field(n, Field::Sub, b);
let k = self.new_sub_list(true, n);
self.set_noad_field(q, Field::Nucleus, k);
x = self.clean_box(k, self.ts.math.style, list_sub::NUCLEUS);
let nh = self.nodes.box_data(x).map_or(0, |b| b.height);
delta = delta + nh - h;
h = nh;
}
let saved = self.attr_from(acc);
let y = self.char_box(f, c);
self.attr_restore(saved);
let yw = self.nodes.box_data(y).map_or(0, |b| b.width);
let shift = if opentype {
let a = if char_nucleus {
nuc_char
.and_then(|(nf, nc)| self.math_char(nf, nc).and_then(|m| m.top_accent))
.unwrap_or(half(w))
} else {
half(w)
};
let b = self
.math_char(f, c)
.and_then(|m| m.top_accent)
.unwrap_or(half(yw));
a - b
} else if char_nucleus {
s + half(w - yw)
} else {
s + half(w - yw) + 1
};
if let Some(b) = self.nodes.box_data_mut(y) {
b.shift = shift;
b.width = 0;
}
let p = self.new_kern_node(-delta, kern_sub::FONT);
self.nodes.link(p, x);
self.nodes.link(y, p);
let y = self.vpack(y, PackSpec::NATURAL);
self.nodes.set_subtype(y, list_sub::ACCENT);
let mut xw = self.nodes.box_data(x).map_or(0, |b| b.width);
if opentype
&& !self.nodes.next(q).is_null()
&& let Some((nf, nc)) = nuc_char
&& self.nodes.id(nuc) == id::MATH_CHAR
{
xw += self.fonts.char_metrics(nf, nc).map_or(0, |m| m.italic);
}
let yh = self.nodes.box_data(y).map_or(0, |b| b.height);
if let Some(b) = self.nodes.box_data_mut(y) {
b.width = xw;
}
if yh < h {
let p = self.new_kern_node(h - yh, kern_sub::FONT);
let l = self.nodes.list_ptr(y);
self.nodes.link(p, l);
self.nodes.set_list_ptr(y, p);
if let Some(b) = self.nodes.box_data_mut(y) {
b.height = h;
}
}
self.set_nucleus_box(q, y);
let bot = self.nodes.noad(q).map_or(NodeRef::NULL, |n| n.bot_accent);
if !bot.is_null() {
self.make_bottom_accent(q);
}
}
fn make_bottom_accent(&mut self, q: NodeRef) {
let bot = match self.nodes.noad_mut(q) {
Some(n) => std::mem::replace(&mut n.bot_accent, NodeRef::NULL),
None => return,
};
let Some((f, mut c, _)) = self.fetch(bot) else {
self.nodes.flush_list(bot);
return;
};
let bot_attr = self.attr_of(bot);
self.nodes.flush_list(bot);
let fixed = matches!(
self.nodes.subtype(q),
crate::nodes::accent_sub::FIXED_BOTTOM | crate::nodes::accent_sub::FIXED_BOTH
);
let nuc = self.noad_field(q, Field::Nucleus);
let char_nucleus = !nuc.is_null() && self.nodes.id(nuc) == id::MATH_CHAR;
let nuc_char = if char_nucleus {
self.fetch(nuc).map(|(nf, nc, _)| (nf, nc))
} else {
None
};
let sup = self.noad_field(q, Field::Sup);
let sub = self.noad_field(q, Field::Sub);
let x = if char_nucleus && (!sup.is_null() || !sub.is_null()) {
let n = self.new_noad(noad_sub::ORD);
let nk = self.take_field(q, Field::Nucleus);
let a = self.take_field(q, Field::Sup);
let b = self.take_field(q, Field::Sub);
self.set_noad_field(n, Field::Nucleus, nk);
self.set_noad_field(n, Field::Sup, a);
self.set_noad_field(n, Field::Sub, b);
let k = self.new_sub_list(true, n);
self.set_noad_field(q, Field::Nucleus, k);
self.clean_box(k, self.ts.math.style, list_sub::NUCLEUS)
} else {
self.clean_box(nuc, self.ts.math.style, list_sub::NUCLEUS)
};
let (bw, xh) = self
.nodes
.box_data(x)
.map_or((0, 0), |b| (b.width, b.height));
let w = match nuc_char {
Some((nf, nc)) => self.fonts.char_metrics(nf, nc).map_or(bw, |m| {
if self.is_opentype_math_font(nf) {
m.width
} else {
m.width + m.italic
}
}),
None => bw,
};
if !fixed {
loop {
let (tag, rem) = self.char_tag(f, c);
if tag != 2 {
break;
}
match self.fonts.char_metrics(f, rem) {
Some(m) if m.width <= w => c = rem,
_ => break,
}
}
}
let saved = self.attr_set(bot_attr);
let y = self.char_box(f, c);
self.attr_restore(saved);
let yw = self.nodes.box_data(y).map_or(0, |b| b.width);
let shift = if self.is_opentype_math_font(f) {
let a = nuc_char
.filter(|_| self.noad_field(q, Field::Sup).is_null())
.and_then(|(nf, nc)| self.math_char(nf, nc).and_then(|m| m.top_accent))
.unwrap_or(half(w));
let b = self
.math_char(f, c)
.and_then(|m| m.top_accent)
.unwrap_or(half(yw));
a - b
} else if nuc_char.is_some() {
half(w - yw)
} else {
w / 2 - yw / 2 + 1
};
if let Some(b) = self.nodes.box_data_mut(y) {
b.shift = shift;
b.width = 0;
}
self.nodes.link(x, y);
let v = self.vpack(x, PackSpec::NATURAL);
self.nodes.set_subtype(v, list_sub::ACCENT);
if let Some(b) = self.nodes.box_data_mut(v) {
b.shift = b.height - xh;
b.width = bw;
}
self.set_nucleus_box(q, v);
}
fn make_fraction(&mut self, q: NodeRef) {
let (mut thickness, num, denom, left, right, options) = match self.nodes.data(q) {
NodeData::Fraction(f) => (f.thickness, f.num, f.denom, f.left, f.right, f.options),
_ => return,
};
if thickness == DEFAULT_CODE {
thickness = self.math_par(mp::FRACTION_RULE);
}
let cs = self.ts.math.style;
let mut x = self.clean_box(num, num_style(cs), list_sub::NUMERATOR);
let mut z = self.clean_box(denom, denom_style(cs), list_sub::DENOMINATOR);
let (xw, zw) = (
self.nodes.box_data(x).map_or(0, |b| b.width),
self.nodes.box_data(z).map_or(0, |b| b.width),
);
if xw < zw {
x = self.rebox(x, zw);
} else {
z = self.rebox(z, xw);
}
let (mut shift_up, mut shift_down) = if thickness == 0 {
(
self.math_par(mp::STACK_NUM_UP),
self.math_par(mp::STACK_DENOM_DOWN),
)
} else {
(
self.math_par(mp::FRACTION_NUM_UP),
self.math_par(mp::FRACTION_DENOM_DOWN),
)
};
let (xh, xd) = self
.nodes
.box_data(x)
.map_or((0, 0), |b| (b.height, b.depth));
let (zh, zd) = self
.nodes
.box_data(z)
.map_or((0, 0), |b| (b.height, b.depth));
let axis = self.math_par(mp::AXIS);
let delta;
if thickness == 0 {
let clr = self.math_par(mp::STACK_VGAP);
delta = half(clr - ((shift_up - xd) - (zh - shift_down)));
if delta > 0 {
shift_up += delta;
shift_down += delta;
}
} else {
let exact = options & noad_opt::EXACT == noad_opt::EXACT;
let (clr_n, clr_d) = if exact {
(0, 0)
} else if thickness == self.math_par(mp::FRACTION_RULE) {
(
self.math_par(mp::FRACTION_NUM_VGAP),
self.math_par(mp::FRACTION_DENOM_VGAP),
)
} else if cs < 2 {
(3 * thickness, 3 * thickness)
} else {
(thickness, thickness)
};
delta = half(thickness);
let delta1 = clr_n - ((shift_up - xd) - (axis + delta));
let delta2 = clr_d - ((axis - delta) - (zh - shift_down));
if delta1 > 0 {
shift_up += delta1;
}
if delta2 > 0 {
shift_down += delta2;
}
}
let v = self.new_null_box_node(false);
self.nodes.set_subtype(v, list_sub::FRACTION);
let width = self.nodes.box_data(x).map_or(0, |b| b.width);
if let Some(b) = self.nodes.box_data_mut(v) {
b.height = shift_up + xh;
b.depth = zd + shift_down;
b.width = width;
}
let p;
if thickness == 0 {
p = self.new_kern_node((shift_up - xd) - (zh - shift_down), kern_sub::FONT);
self.nodes.link(p, z);
} else {
let y = self.fraction_rule(thickness);
let p2 = self.new_kern_node((axis - delta) - (zh - shift_down), kern_sub::FONT);
self.nodes.link(y, p2);
self.nodes.link(p2, z);
p = self.new_kern_node((shift_up - xd) - (axis + delta), kern_sub::FONT);
self.nodes.link(p, y);
}
self.nodes.link(x, p);
self.nodes.set_list_ptr(v, x);
let dsize = self.math_par(mp::FRACTION_DEL_SIZE);
let size = self.ts.math.size;
let lx = self.var_delimiter(left, size, dsize, left.is_null());
self.nodes.link(lx, v);
let rz = self.var_delimiter(right, size, dsize, right.is_null());
self.nodes.link(v, rz);
let h = self.hpack_quiet(lx);
self.nodes.set_subtype(h, list_sub::FRACTION);
self.set_new_hlist(q, h);
}
fn make_op(&mut self, q: NodeRef) -> i32 {
let cs = self.ts.math.style;
if self.nodes.subtype(q) == noad_sub::OP_DISPLAY_LIMITS && cs < 2 {
self.nodes.set_subtype(q, noad_sub::OP_LIMITS);
}
let limits = self.nodes.subtype(q) == noad_sub::OP_LIMITS;
let nuc = self.noad_field(q, Field::Nucleus);
let mut delta = 0;
let mut opentype = false;
if !nuc.is_null() && self.nodes.id(nuc) == id::MATH_CHAR {
if let Some((f, c, m)) = self.fetch(nuc) {
let mut m = m;
if cs < 2 && self.is_opentype_math_font(f) {
let target = self.math_par(mp::OPERATOR_SIZE);
let mut c = c;
let mut guard = 0;
while m.height + m.depth < target && guard < 64 {
guard += 1;
let (tag, rem) = self.char_tag(f, c);
let Some(m2) = self
.fonts
.char_metrics(f, rem)
.copied()
.filter(|_| tag == 2)
else {
break;
};
m = m2;
c = rem;
}
if let NodeData::MathChar { chr, .. } = self.nodes.data_mut(nuc) {
*chr = c;
}
} else if cs < 2 {
let (tag, rem) = self.char_tag(f, c);
if tag == 2
&& let Some(m2) = self.fonts.char_metrics(f, rem).copied()
{
m = m2;
if let NodeData::MathChar { chr, .. } = self.nodes.data_mut(nuc) {
*chr = rem;
}
}
}
delta = m.italic;
opentype = self.is_opentype_math_font(f);
}
let x = self.clean_box(nuc, cs, list_sub::NUCLEUS);
let has_sub = !self.noad_field(q, Field::Sub).is_null();
let axis = self.math_par(mp::AXIS);
if let Some(b) = self.nodes.box_data_mut(x) {
if has_sub && !limits && !opentype {
b.width -= delta;
}
b.shift = half(b.height - b.depth) - axis;
}
self.set_nucleus_box(q, x);
if opentype && !limits {
if has_sub {
self.ts.math.op_kern = -delta;
} else {
delta = 0;
}
}
}
if limits {
let supk = self.noad_field(q, Field::Sup);
let subk = self.noad_field(q, Field::Sub);
let x = self.clean_box(supk, sup_style(cs), list_sub::SUP);
let nk = self.noad_field(q, Field::Nucleus);
let y = self.clean_box(nk, cs, list_sub::NUCLEUS);
let z = self.clean_box(subk, sub_style(cs), list_sub::SUB);
let v = self.new_null_box_node(false);
self.nodes.set_subtype(v, list_sub::LIMITS);
let wy = self.nodes.box_data(y).map_or(0, |b| b.width);
let wx = self.nodes.box_data(x).map_or(0, |b| b.width);
let wz = self.nodes.box_data(z).map_or(0, |b| b.width);
let wv = wy.max(wx).max(wz);
let x = self.rebox(x, wv);
let y = self.rebox(y, wv);
let z = self.rebox(z, wv);
if let Some(b) = self.nodes.box_data_mut(x) {
b.shift = half(delta);
}
if let Some(b) = self.nodes.box_data_mut(z) {
b.shift = -half(delta);
}
let (yh, yd) = self
.nodes
.box_data(y)
.map_or((0, 0), |b| (b.height, b.depth));
let (mut vh, mut vd) = (yh, yd);
let kern = self.math_par(mp::LIMIT_ABOVE_KERN);
let kern_below = self.math_par(mp::LIMIT_BELOW_KERN);
let head;
if supk.is_null() {
self.nodes.free(x);
head = y;
} else {
let (xh, xd) = self
.nodes
.box_data(x)
.map_or((0, 0), |b| (b.height, b.depth));
let mut shift_up = self.math_par(mp::LIMIT_ABOVE_BGAP) - xd;
let g1 = self.math_par(mp::LIMIT_ABOVE_VGAP);
if shift_up < g1 {
shift_up = g1;
}
let p = self.new_kern_node(shift_up, kern_sub::FONT);
self.nodes.link(p, y);
self.nodes.link(x, p);
let p = self.new_kern_node(kern, kern_sub::FONT);
self.nodes.link(p, x);
head = p;
vh += kern + xh + xd + shift_up;
}
if subk.is_null() {
self.nodes.free(z);
} else {
let (zh, zd) = self
.nodes
.box_data(z)
.map_or((0, 0), |b| (b.height, b.depth));
let mut shift_down = self.math_par(mp::LIMIT_BELOW_BGAP) - zh;
let g2 = self.math_par(mp::LIMIT_BELOW_VGAP);
if shift_down < g2 {
shift_down = g2;
}
let p = self.new_kern_node(shift_down, kern_sub::FONT);
self.nodes.link(y, p);
self.nodes.link(p, z);
let p = self.new_kern_node(kern_below, kern_sub::FONT);
self.nodes.link(z, p);
vd += kern_below + zh + zd + shift_down;
}
if let Some(b) = self.nodes.box_data_mut(v) {
b.width = wv;
b.height = vh;
b.depth = vd;
b.head = head;
}
let a = self.take_field(q, Field::Sup);
let b = self.take_field(q, Field::Sub);
self.nodes.flush_list(a);
self.nodes.flush_list(b);
self.set_new_hlist(q, v);
}
delta
}
fn make_ord(&mut self, q: NodeRef) {
'restart: loop {
if !self.noad_field(q, Field::Sub).is_null()
|| !self.noad_field(q, Field::Sup).is_null()
{
return;
}
let nuc = self.noad_field(q, Field::Nucleus);
if nuc.is_null() || self.nodes.id(nuc) != id::MATH_CHAR {
return;
}
let p = self.nodes.next(q);
if p.is_null()
|| self.nodes.id(p) != id::NOAD
|| !matches!(
self.nodes.subtype(p),
noad_sub::ORD
| noad_sub::OP_DISPLAY_LIMITS
| noad_sub::OP_LIMITS
| noad_sub::OP_NO_LIMITS
| noad_sub::BIN
| noad_sub::REL
| noad_sub::OPEN
| noad_sub::CLOSE
| noad_sub::PUNCT
)
{
return;
}
let pn = self.noad_field(p, Field::Nucleus);
if pn.is_null() || self.nodes.id(pn) != id::MATH_CHAR {
return;
}
let (qfam, pfam, pchr) = match (self.nodes.data(nuc), self.nodes.data(pn)) {
(NodeData::MathChar { fam: a, .. }, NodeData::MathChar { fam: b, chr }) => {
(*a, *b, *chr)
}
_ => return,
};
if qfam != pfam {
return;
}
self.nodes.get_mut(nuc).id = id::MATH_TEXT_CHAR;
let Some((f, c, _)) = self.fetch(nuc) else {
return;
};
let Some(op) = self
.lig_kern_step(f, c, pchr)
.or_else(|| self.base_mode_kern(f, c, pchr))
else {
return;
};
match op {
LigOp::Kern(k) => {
let kn = self.new_kern_node(k, kern_sub::FONT);
let n = self.nodes.next(q);
self.nodes.link(kn, n);
self.nodes.link(q, kn);
return;
}
LigOp::Lig { op, chr } => {
match op {
1 | 5 => self.set_char(nuc, chr),
2 | 6 => self.set_char(pn, chr),
3 | 7 | 11 => {
let r = self.new_noad(noad_sub::ORD);
let k = self.new_math_char(qfam, chr);
if op >= 11 {
self.nodes.get_mut(k).id = id::MATH_TEXT_CHAR;
}
self.set_noad_field(r, Field::Nucleus, k);
self.nodes.link(q, r);
self.nodes.link(r, p);
}
_ => {
let n = self.nodes.next(p);
self.nodes.link(q, n);
if n.is_null() {
self.nodes.set_next(q, NodeRef::NULL);
}
self.set_char(nuc, chr);
let a = self.take_field(p, Field::Sub);
let b = self.take_field(p, Field::Sup);
self.set_noad_field(q, Field::Sub, a);
self.set_noad_field(q, Field::Sup, b);
self.nodes.set_next(p, NodeRef::NULL);
self.nodes.flush_list(p);
}
}
if op > 3 {
return;
}
self.nodes.get_mut(nuc).id = id::MATH_CHAR;
continue 'restart;
}
}
}
}
fn set_char(&mut self, k: NodeRef, c: u32) {
if let NodeData::MathChar { chr, .. } = self.nodes.data_mut(k) {
*chr = c;
}
}
fn convert_nucleus(&mut self, q: NodeRef, delta: i32) {
let mut delta = delta;
let nuc = self.noad_field(q, Field::Nucleus);
let kid = if nuc.is_null() { 0 } else { self.nodes.id(nuc) };
let has_sub = !self.noad_field(q, Field::Sub).is_null();
let p = match kid {
id::MATH_CHAR | id::MATH_TEXT_CHAR => match self.fetch(nuc) {
Some((f, c, m)) => {
delta = m.italic;
let saved = self.attr_from(nuc);
let p = self.new_math_glyph(f, c);
let has_sup = !self.noad_field(q, Field::Sup).is_null();
if self.is_opentype_math_font(f) {
if kid == id::MATH_CHAR && !has_sup && !has_sub {
delta = 0;
}
} else if kid == id::MATH_TEXT_CHAR
&& self.font(f).params.get(2).copied().unwrap_or(0) != 0
{
delta = 0;
}
if kid == id::MATH_TEXT_CHAR && self.is_opentype_math_font(f) {
self.attr_restore(saved.clone());
}
if !has_sub && delta != 0 {
let k = self.new_kern_node(delta, kern_sub::ITALIC);
self.nodes.link(p, k);
delta = 0;
}
self.attr_restore(saved);
p
}
None => NodeRef::NULL,
},
id::SUB_BOX => {
let b = self.nodes.list_ptr_kernel(nuc);
self.nodes.set_kernel_list(nuc, NodeRef::NULL);
let k = std::mem::take(&mut self.ts.math.op_kern);
if k != 0 && !b.is_null() {
let kn = self.new_kern_node(k, kern_sub::FONT);
self.nodes.link(b, kn);
}
b
}
id::SUB_MLIST => {
let l = self.nodes.list_ptr_kernel(nuc);
self.nodes.set_kernel_list(nuc, NodeRef::NULL);
let hl = self.clean_box_mlist(l, self.ts.math.style);
let saved = self.attr_from(nuc);
let b = self.hpack_quiet(hl);
self.attr_restore(saved);
b
}
_ => {
if has_sub || !self.noad_field(q, Field::Sup).is_null() {
self.new_null_box_node(true)
} else {
NodeRef::NULL
}
}
};
self.set_new_hlist(q, p);
let has_sup = !self.noad_field(q, Field::Sup).is_null();
if !has_sub && !has_sup {
return;
}
self.make_scripts(q, delta);
}
fn make_scripts(&mut self, q: NodeRef, delta: i32) {
let p = self.get_new_hlist(q);
let cs = self.ts.math.style;
let (mut shift_up, mut shift_down);
if !p.is_null() && self.nodes.is_glyph(p) {
shift_up = 0;
shift_down = 0;
} else {
let (h, d) = self.list_height_depth(p);
shift_up = h - self.math_par(mp::SUP_SHIFT_DROP);
shift_down = d + self.math_par(mp::SUB_SHIFT_DROP);
}
let opts = self.nodes.noad(q).map_or(0, |n| n.options);
let supk = self.take_field(q, Field::Sup);
let subk = self.take_field(q, Field::Sub);
if !p.is_null() && self.nodes.is_glyph(p) {
for k in [supk, subk] {
if !k.is_null() && self.nodes.id(k) == id::MATH_CHAR {
let _ = self.fetch(k);
}
}
}
let script_space = self.math_par(mp::SPACE_AFTER_SCRIPT);
let x;
if supk.is_null() {
let st = if opts & noad_opt::NO_SUB_SCRIPT == noad_opt::NO_SUB_SCRIPT {
cs
} else {
sub_style(cs)
};
x = self.clean_box(subk, st, list_sub::SUB);
self.nodes.flush_list(subk);
let xh = self.nodes.box_data(x).map_or(0, |b| b.height);
if let Some(b) = self.nodes.box_data_mut(x) {
b.width += script_space;
}
let s1 = self.math_par(mp::SUB_SHIFT_DOWN);
if shift_down < s1 {
shift_down = s1;
}
let clr = xh - self.math_par(mp::SUB_TOP_MAX);
if shift_down < clr {
shift_down = clr;
}
if let Some(b) = self.nodes.box_data_mut(x) {
b.shift = shift_down;
}
} else {
let st = if opts & noad_opt::NO_SUPER_SCRIPT == noad_opt::NO_SUPER_SCRIPT {
cs
} else {
sup_style(cs)
};
let sx = self.clean_box(supk, st, list_sub::SUP);
self.nodes.flush_list(supk);
let xd = self.nodes.box_data(sx).map_or(0, |b| b.depth);
if let Some(b) = self.nodes.box_data_mut(sx) {
b.width += script_space;
}
let clr = self.math_par(mp::SUP_SHIFT_UP);
if shift_up < clr {
shift_up = clr;
}
let clr = xd + self.math_par(mp::SUP_BOTTOM_MIN);
if shift_up < clr {
shift_up = clr;
}
if subk.is_null() {
if let Some(b) = self.nodes.box_data_mut(sx) {
b.shift = -shift_up;
}
x = sx;
} else {
let st = if opts & noad_opt::NO_SUB_SCRIPT == noad_opt::NO_SUB_SCRIPT {
cs
} else {
sub_style(cs)
};
let y = self.clean_box(subk, st, list_sub::SUB);
self.nodes.flush_list(subk);
let yh = self.nodes.box_data(y).map_or(0, |b| b.height);
if let Some(b) = self.nodes.box_data_mut(y) {
b.width += script_space;
}
let s2 = self.math_par(mp::SUB_SUP_SHIFT_DOWN);
if shift_down < s2 {
shift_down = s2;
}
let mut clr =
self.math_par(mp::SUB_SUP_VGAP) - ((shift_up - xd) - (yh - shift_down));
if clr > 0 {
shift_down += clr;
clr = self.math_par(mp::SUP_SUB_BOTTOM_MAX) - (shift_up - xd);
if clr > 0 {
shift_up += clr;
shift_down -= clr;
}
}
if let Some(b) = self.nodes.box_data_mut(sx) {
b.shift = delta;
}
let p = self.new_kern_node((shift_up - xd) - (yh - shift_down), kern_sub::FONT);
self.nodes.link(sx, p);
self.nodes.link(p, y);
x = self.vpack(sx, PackSpec::NATURAL);
self.nodes.set_subtype(x, list_sub::SCRIPTS);
if let Some(b) = self.nodes.box_data_mut(x) {
b.shift = shift_down;
}
}
}
let nh = self.get_new_hlist(q);
if nh.is_null() {
self.set_new_hlist(q, x);
} else {
let t = self.nodes.tail_of(nh);
self.nodes.link(t, x);
}
}
fn extensible_fence(
&mut self,
delim: NodeRef,
height: i32,
depth: i32,
options: u32,
axis: i32,
) -> NodeRef {
let size = self.ts.math.size;
if height == 0 && depth == 0 {
let b = self.var_delimiter(delim, size, 0, false);
if let Some(bd) = self.nodes.box_data_mut(b) {
bd.shift = 0;
}
return b;
}
let up = if options & noad_opt::AXIS == noad_opt::AXIS {
axis
} else {
0
};
let b = self.var_delimiter(delim, size, height + depth, false);
if let Some(bd) = self.nodes.box_data_mut(b) {
bd.shift = half(bd.height - bd.depth) - up;
}
let w = self.hpack_quiet(b);
if options & noad_opt::EXACT != noad_opt::EXACT
&& let Some(bd) = self.nodes.box_data_mut(w)
{
bd.height = height + up;
bd.depth = depth - up;
}
w
}
fn make_left_right(&mut self, q: NodeRef, style: i32, max_d: i32, max_h: i32) -> NoadType {
self.set_cur_style(style);
let (delim, height, depth, options) = match self.nodes.data(q) {
NodeData::Fence(f) => (f.delim, f.height, f.depth, f.options),
_ => (NodeRef::NULL, 0, 0, 0),
};
let axis = self.math_par(mp::AXIS);
if self.nodes.subtype(q) == fence_sub::NO {
let b = self.extensible_fence(delim, height, depth, options, axis);
self.set_new_hlist(q, b);
return NoadType::Close;
}
let b = if height != 0 || depth != 0 {
let total = height + depth;
let b = self.var_delimiter(delim, self.ts.math.size, total, true);
if options & noad_opt::NO_AXIS == noad_opt::NO_AXIS
&& let Some(bd) = self.nodes.box_data_mut(b)
{
bd.shift = 0;
}
b
} else {
let mut delta2 = max_d + axis;
let mut delta1 = max_h + max_d - delta2;
if delta2 > delta1 {
delta1 = delta2;
}
let mut delta = (delta1 / 500) * self.eqtb.int(ip::DELIMITERFACTOR);
delta2 = delta1 + delta1 - self.eqtb.dimen(dp::DELIMITERSHORTFALL);
if delta < delta2 {
delta = delta2;
}
self.var_delimiter(delim, self.ts.math.size, delta, false)
};
self.set_new_hlist(q, b);
if self.nodes.subtype(q) == fence_sub::LEFT {
NoadType::Open
} else {
NoadType::Close
}
}
fn second_pass(
&mut self,
mlist: NodeRef,
style: i32,
penalties: bool,
max_h: i32,
max_d: i32,
) -> NodeRef {
let head = self.nodes.new_temp();
let mut p = head;
let mut q = mlist;
let mut r_type: Option<NoadType> = None;
self.set_cur_style(style);
let binpen = self.eqtb.int(ip::BINOPPENALTY);
let relpen = self.eqtb.int(ip::RELPENALTY);
while !q.is_null() {
let next = self.nodes.next(q);
let Some(nt) = self.noad_type(q) else {
if let NodeData::Style { style } = self.nodes.data(q) {
let s = i32::from(*style);
self.set_cur_style(s);
self.nodes.free(q);
} else {
self.nodes.link(p, q);
self.nodes.set_next(q, NodeRef::NULL);
p = q;
}
q = next;
continue;
};
let mut t = NoadType::Ord;
let mut pen = 10000;
match nt {
NoadType::Op
| NoadType::Open
| NoadType::Close
| NoadType::Punct
| NoadType::Inner => t = nt,
NoadType::Bin => {
t = NoadType::Bin;
pen = binpen;
}
NoadType::Rel => {
t = NoadType::Rel;
pen = relpen;
}
NoadType::Fraction => t = NoadType::Inner,
NoadType::Left | NoadType::Right => {
let saved = self.attr_from(q);
t = self.make_left_right(q, style, max_d, max_h);
self.attr_restore(saved);
}
_ => {}
}
if let Some(rt) = r_type {
let style = self.ts.math.style as u32;
if let Some((g, sub)) = self.math_spacing(rt as u32, t as u32, style) {
let y = self.math_glue(g, self.ts.math.mu);
let saved = self.attr_from(p);
let z = self.new_glue_node(y, sub);
self.attr_restore(saved);
self.nodes.link(p, z);
p = z;
}
}
let nh = self.get_new_hlist(q);
self.set_new_hlist(q, NodeRef::NULL);
if !nh.is_null() {
self.nodes.link(p, nh);
p = self.nodes.tail_of(nh);
}
if penalties && !next.is_null() && pen < 10000 {
let nid = self.nodes.id(next);
let next_is_rel = self.noad_type(next) == Some(NoadType::Rel);
if nid != id::PENALTY && !next_is_rel {
let saved = self.attr_from(q);
let z = self.new_penalty_node(pen);
self.attr_restore(saved);
self.nodes.set_subtype(z, 5);
self.nodes.link(p, z);
p = z;
}
}
if nt == NoadType::Right {
t = NoadType::Open;
}
r_type = Some(t);
self.nodes.set_next(q, NodeRef::NULL);
self.nodes.flush_list(q);
q = next;
}
let first = self.nodes.next(head);
self.nodes.free(head);
if !first.is_null() {
self.nodes.set_prev(first, NodeRef::NULL);
}
first
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Action {
Convert,
CheckDimensions,
DoneWithNoad,
}