use crate::build_common::{VListChild, VListElem, VListKern, VListParam, make_span, make_v_list};
use crate::dom_tree::HtmlDomNode;
use crate::options::Options;
use crate::parser::parse_node::ParseNode;
use crate::style::Style;
use crate::types::{CssProperty, ParseError};
use crate::units::make_em;
use crate::{ClassList, KatexContext, build_html};
struct SupSubElem {
elem: HtmlDomNode,
kern: f64,
}
#[expect(clippy::too_many_arguments)]
pub fn assemble_sup_sub(
ctx: &KatexContext,
base: HtmlDomNode,
super_group: Option<&ParseNode>,
sub_group: Option<&ParseNode>,
options: &Options,
style: &Style,
slant: f64,
base_shift: f64,
) -> Result<HtmlDomNode, ParseError> {
let base = HtmlDomNode::from(make_span(ClassList::Empty, vec![base], Some(options), None));
let base_height = base.height();
let base_depth = base.depth();
let sub_is_single_character = sub_group
.as_ref()
.is_some_and(|sub| sub.is_character_box().unwrap_or(false));
let superscript_layout = if let Some(sup_group) = super_group {
let elem = build_html::build_group(
ctx,
sup_group,
&options.having_style(style.sup()),
Some(options),
)?;
let elem_depth = elem.depth();
Some(SupSubElem {
elem,
kern: options
.font_metrics()
.big_op_spacing1
.max(options.font_metrics().big_op_spacing3 - elem_depth),
})
} else {
None
};
let subscript_layout = if let Some(sub_group) = sub_group {
let elem = build_html::build_group(
ctx,
sub_group,
&options.having_style(style.sub()),
Some(options),
)?;
let elem_height = elem.height();
Some(SupSubElem {
elem,
kern: options
.font_metrics()
.big_op_spacing2
.max(options.font_metrics().big_op_spacing4 - elem_height),
})
} else {
None
};
let has_sub = subscript_layout.is_some();
let final_group = match (superscript_layout, subscript_layout, base) {
(Some(super_segment), Some(sub_segment), base) => {
let SupSubElem {
elem: super_elem,
kern: super_kern,
} = super_segment;
let SupSubElem {
elem: sub_elem,
kern: sub_kern,
} = sub_segment;
let sub_height = sub_elem.height();
let sub_depth = sub_elem.depth();
let bottom = options.font_metrics().big_op_spacing5
+ sub_height
+ sub_depth
+ sub_kern
+ base_depth
+ base_shift;
make_v_list(
VListParam::Bottom {
position_data: bottom,
children: vec![
VListChild::Kern(VListKern {
size: options.font_metrics().big_op_spacing5,
}),
VListChild::Elem(Box::new(VListElem {
elem: sub_elem,
shift: None,
margin_left: Some(make_em(-slant)),
margin_right: None,
wrapper_classes: None,
wrapper_style: None,
})),
VListChild::Kern(VListKern { size: sub_kern }),
VListChild::Elem(Box::new(VListElem {
elem: base,
shift: None,
margin_left: None,
margin_right: None,
wrapper_classes: None,
wrapper_style: None,
})),
VListChild::Kern(VListKern { size: super_kern }),
VListChild::Elem(Box::new(VListElem {
elem: super_elem,
shift: None,
margin_left: Some(make_em(slant)),
margin_right: None,
wrapper_classes: None,
wrapper_style: None,
})),
VListChild::Kern(VListKern {
size: options.font_metrics().big_op_spacing5,
}),
],
},
options,
)?
}
(None, Some(sub_segment), base) => {
let SupSubElem {
elem: sub_elem,
kern: sub_kern,
} = sub_segment;
let top = base_height - base_shift;
make_v_list(
VListParam::Top {
position_data: top,
children: vec![
VListChild::Kern(VListKern {
size: options.font_metrics().big_op_spacing5,
}),
VListChild::Elem(Box::new(VListElem {
elem: sub_elem,
shift: None,
margin_left: Some(make_em(-slant)),
margin_right: None,
wrapper_classes: None,
wrapper_style: None,
})),
VListChild::Kern(VListKern { size: sub_kern }),
VListChild::Elem(Box::new(VListElem {
elem: base,
shift: None,
margin_left: None,
margin_right: None,
wrapper_classes: None,
wrapper_style: None,
})),
],
},
options,
)?
}
(Some(super_segment), None, base) => {
let SupSubElem {
elem: sup_elem,
kern: sup_kern,
} = super_segment;
let bottom = base_depth + base_shift;
make_v_list(
VListParam::Bottom {
position_data: bottom,
children: vec![
VListChild::Elem(Box::new(VListElem {
elem: base,
shift: None,
margin_left: None,
margin_right: None,
wrapper_classes: None,
wrapper_style: None,
})),
VListChild::Kern(VListKern { size: sup_kern }),
VListChild::Elem(Box::new(VListElem {
elem: sup_elem,
shift: None,
margin_left: Some(make_em(slant)),
margin_right: None,
wrapper_classes: None,
wrapper_style: None,
})),
VListChild::Kern(VListKern {
size: options.font_metrics().big_op_spacing5,
}),
],
},
options,
)?
}
(None, None, base) => {
return Ok(base);
}
};
let mut parts = vec![final_group.into()];
if has_sub && slant != 0.0 && !sub_is_single_character {
let mut spacer = make_span("mspace", vec![], Some(options), None);
spacer
.style
.insert(CssProperty::MarginRight, make_em(slant));
parts.insert(0, spacer.into());
}
Ok(make_span(
ClassList::Const(&["mop", "op-limits"]),
parts,
Some(options),
None,
)
.into())
}