use crate::build_common::{
self, VListChild, VListElem, VListKern, VListParam, make_span, make_v_list,
};
use crate::define_function::{FunctionDefSpec, FunctionPropSpec};
use crate::delimiter::make_sqrt_image;
use crate::dom_tree::HtmlDomNode;
use crate::mathml_tree::{MathDomNode, MathNode, MathNodeType};
use crate::options::Options;
use crate::parser::parse_node::{NodeType, ParseNode, ParseNodeSqrt};
use crate::style::{SCRIPTSCRIPT, TEXT};
use crate::types::ClassList;
use crate::types::{CssProperty, ParseError, ParseErrorKind};
use crate::units::make_em;
use crate::{KatexContext, build_html, build_mathml};
pub fn define_sqrt(ctx: &mut KatexContext) {
ctx.define_function(FunctionDefSpec {
node_type: Some(NodeType::Sqrt),
names: &["\\sqrt"],
props: FunctionPropSpec {
num_args: 1,
num_optional_args: 1,
..Default::default()
},
handler: Some(|context, args, opt_args| {
let body = args[0].clone();
let index = opt_args[0].clone();
Ok(ParseNode::Sqrt(Box::new(ParseNodeSqrt {
mode: context.parser.mode,
loc: context.loc(),
body,
index,
})))
}),
html_builder: Some(html_builder),
mathml_builder: Some(mathml_builder),
});
}
fn html_builder(
node: &ParseNode,
options: &Options,
ctx: &KatexContext,
) -> Result<HtmlDomNode, ParseError> {
let ParseNode::Sqrt(sqrt_node) = node else {
return Err(ParseError::new(ParseErrorKind::ExpectedNode {
node: NodeType::Sqrt,
}));
};
let mut inner =
build_html::build_group(ctx, &sqrt_node.body, &options.having_cramped_style(), None)?;
if let Some(height) = inner.height_mut()
&& *height == 0.0
{
*height = options.font_metrics().x_height;
}
inner = build_common::wrap_fragment(inner, options);
let theta = options.font_metrics().default_rule_thickness;
let phi = if options.style.id < TEXT.id {
options.font_metrics().x_height
} else {
theta
};
let line_clearance = theta + phi / 4.0;
let min_delimiter_height = inner.height() + inner.depth() + line_clearance + theta;
let sqrt_result = make_sqrt_image(ctx, min_delimiter_height, options)?;
let delim_depth = sqrt_result.span.height - sqrt_result.rule_width;
let line_clearance = if delim_depth > inner.height() + inner.depth() + line_clearance {
(line_clearance + delim_depth - inner.height() - inner.depth()) / 2.0
} else {
line_clearance
};
let img_shift =
sqrt_result.span.height - inner.height() - line_clearance - sqrt_result.rule_width;
if let HtmlDomNode::DomSpan(ref mut span) = inner {
span.style
.insert(CssProperty::PaddingLeft, make_em(sqrt_result.advance_width));
}
let inner_height = inner.height();
let body = make_v_list(
VListParam::FirstBaseline {
children: vec![
VListElem::builder()
.elem(inner)
.wrapper_classes(ClassList::Static("svg-align"))
.build()
.into(),
VListChild::Kern(VListKern {
size: -(inner_height + img_shift),
}),
VListElem::builder()
.elem(HtmlDomNode::DomSpan(sqrt_result.span))
.build()
.into(),
VListChild::Kern(VListKern {
size: sqrt_result.rule_width,
}),
],
},
options,
)?;
let Some(sqrt_index) = &sqrt_node.index else {
return Ok(make_span(
ClassList::Const(&["mord", "sqrt"]),
vec![body.into()],
Some(options),
None,
)
.into());
};
let new_options = options.having_style(SCRIPTSCRIPT);
let rootm = build_html::build_group(ctx, sqrt_index, &new_options, Some(options))?;
let to_shift = 0.6 * (body.height - body.depth);
let root_v_list = make_v_list(
VListParam::Shift {
position_data: -to_shift,
children: vec![VListElem::builder().elem(rootm).build().into()],
},
options,
)?;
let root_v_list_wrap = make_span("root", vec![root_v_list.into()], Some(options), None);
Ok(make_span(
ClassList::Const(&["mord", "sqrt"]),
vec![root_v_list_wrap.into(), body.into()],
Some(options),
None,
)
.into())
}
fn mathml_builder(
node: &ParseNode,
options: &Options,
ctx: &KatexContext,
) -> Result<MathDomNode, ParseError> {
let ParseNode::Sqrt(sqrt_node) = node else {
return Err(ParseError::new(ParseErrorKind::ExpectedNode {
node: NodeType::Sqrt,
}));
};
let body_group = build_mathml::build_group(ctx, &sqrt_node.body, options)?;
if let Some(index) = &sqrt_node.index {
let index_group = build_mathml::build_group(ctx, index, options)?;
Ok(MathNode::builder()
.node_type(MathNodeType::Mroot)
.children(vec![body_group, index_group])
.build()
.into())
} else {
Ok(MathNode::builder()
.node_type(MathNodeType::Msqrt)
.children(vec![body_group])
.build()
.into())
}
}