use crate::build_common::{VListChild, VListElem, VListParam, make_span, make_v_list};
use crate::define_function::{FunctionDefSpec, FunctionPropSpec};
use crate::dom_tree::HtmlDomNode;
use crate::mathml_tree::{MathDomNode, MathNode, MathNodeType};
use crate::options::Options;
use crate::parser::parse_node::{NodeType, ParseNode, ParseNodeHorizBrace};
use crate::stretchy::{math_ml_node, svg_span};
use crate::style::DISPLAY;
use crate::types::ClassList;
use crate::types::{ParseError, ParseErrorKind};
use crate::{KatexContext, build_html, build_mathml};
pub fn define_horiz_brace(ctx: &mut KatexContext) {
ctx.define_function(FunctionDefSpec {
node_type: Some(NodeType::HorizBrace),
names: &["\\overbrace", "\\underbrace"],
props: FunctionPropSpec {
num_args: 1,
..Default::default()
},
handler: Some(|context, args, _opt_args| {
let base = args[0].clone();
let is_over = context.func_name.starts_with("\\over");
Ok(ParseNode::HorizBrace(ParseNodeHorizBrace {
mode: context.parser.mode,
loc: context.loc(),
label: context.func_name.to_owned(),
is_over,
base: Box::new(base),
}))
}),
html_builder: Some(html_builder),
mathml_builder: Some(mathml_builder),
});
}
pub fn html_builder(
node: &ParseNode,
options: &Options,
ctx: &KatexContext,
) -> Result<HtmlDomNode, ParseError> {
let (group, sup_sub_group) = match node {
ParseNode::SupSub(supsub) => {
let base = supsub
.base
.as_ref()
.ok_or_else(|| ParseError::new(ParseErrorKind::ExpectedBaseInSupSub))?;
let ParseNode::HorizBrace(group) = base.as_ref() else {
return Err(ParseError::new(ParseErrorKind::ExpectedHorizBraceBase));
};
let style = options.style;
let sup_group = if let Some(sup) = &supsub.sup {
let sup_options = options.having_style(style.sup());
Some(build_html::build_group(
ctx,
sup,
&sup_options,
Some(options),
)?)
} else if let Some(sub) = &supsub.sub {
let sub_options = options.having_style(style.sub());
Some(build_html::build_group(
ctx,
sub,
&sub_options,
Some(options),
)?)
} else {
None
};
(group, sup_group)
}
ParseNode::HorizBrace(hb) => (hb, None),
_ => {
return Err(ParseError::new(ParseErrorKind::ExpectedHorizBraceOrSupSub));
}
};
let body = build_html::build_group(
ctx,
&group.base,
&options.having_base_style(Some(DISPLAY)),
None,
)?;
let brace_body = svg_span(&ParseNode::HorizBrace(group.clone()), options)?;
let vlist = if group.is_over {
make_v_list(
VListParam::FirstBaseline {
children: vec![
VListElem::builder().elem(body).build().into(),
VListChild::Kern(0.1.into()),
VListElem::builder()
.elem(brace_body)
.wrapper_classes(ClassList::Static("svg-align"))
.build()
.into(),
],
},
options,
)?
} else {
make_v_list(
VListParam::Bottom {
position_data: body.depth() + 0.1 + brace_body.height(),
children: vec![
VListElem::builder()
.elem(brace_body)
.wrapper_classes(ClassList::Static("svg-align"))
.build()
.into(),
VListChild::Kern(0.1.into()),
VListElem::builder().elem(body).build().into(),
],
},
options,
)?
};
let classes = if group.is_over {
ClassList::Const(&["mord", "mover"])
} else {
ClassList::Const(&["mord", "munder"])
};
if let Some(sup_sub_group) = sup_sub_group {
let v_span = make_span(classes, vec![vlist.into()], Some(options), None);
if group.is_over {
let vlist = make_v_list(
VListParam::FirstBaseline {
children: vec![
VListElem::builder().elem(v_span.into()).build().into(),
VListChild::Kern(0.2.into()),
VListElem::builder().elem(sup_sub_group).build().into(),
],
},
options,
)?;
Ok(make_span(
ClassList::Const(&["mord", "mover"]),
vec![vlist.into()],
Some(options),
None,
)
.into())
} else {
let vlist = make_v_list(
VListParam::Bottom {
position_data: v_span.depth
+ 0.2
+ sup_sub_group.height()
+ sup_sub_group.depth(),
children: vec![
VListElem::builder().elem(sup_sub_group).build().into(),
VListChild::Kern(0.2.into()),
VListElem::builder().elem(v_span.into()).build().into(),
],
},
options,
)?;
Ok(make_span(
ClassList::Const(&["mord", "munder"]),
vec![vlist.into()],
Some(options),
None,
)
.into())
}
} else {
Ok(make_span(classes, vec![vlist.into()], Some(options), None).into())
}
}
fn mathml_builder(
node: &ParseNode,
options: &Options,
ctx: &KatexContext,
) -> Result<MathDomNode, ParseError> {
let ParseNode::HorizBrace(group) = node else {
return Err(ParseError::new(ParseErrorKind::ExpectedNode {
node: NodeType::HorizBrace,
}));
};
let accent_node = math_ml_node(&group.label);
let base_group = build_mathml::build_group(ctx, &group.base, options)?;
let mut mover = MathNode::builder()
.node_type(if group.is_over {
MathNodeType::Mover
} else {
MathNodeType::Munder
})
.children(vec![base_group, MathDomNode::Math(accent_node)])
.build();
mover
.attributes
.insert("accent".to_owned(), "true".to_owned());
Ok(MathDomNode::Math(mover))
}