use crate::namespace::KeyMap;
use crate::build_common::make_span;
use crate::define_function::{FunctionContext, FunctionDefSpec, FunctionPropSpec, ord_argument};
use crate::dom_tree::HtmlDomNode;
use crate::mathml_tree::{MathDomNode, MathNode, MathNodeType};
use crate::options::Options;
use crate::parser::parse_node::{
AnyParseNode, NodeType, ParseNode, ParseNodeMclass, ParseNodeOp, ParseNodeSupSub,
};
use crate::symbols::Atom;
use crate::types::ParseError;
use crate::{KatexContext, build_html, build_mathml};
pub fn binrel_class(arg: &AnyParseNode) -> String {
let atom = match arg {
AnyParseNode::OrdGroup(ord) if !ord.body.is_empty() => &ord.body[0],
_ => arg,
};
match atom {
AnyParseNode::Atom(atom_node) => {
if atom_node.family == Atom::Bin || atom_node.family == Atom::Rel {
format!("m{}", atom_node.family.as_ref())
} else {
"mord".to_owned()
}
}
_ => "mord".to_owned(),
}
}
fn html_builder(
node: &ParseNode,
options: &Options,
ctx: &KatexContext,
) -> Result<HtmlDomNode, ParseError> {
let ParseNode::Mclass(mclass_node) = node else {
return Err(ParseError::new("Expected Mclass node"));
};
let elements = build_html::build_expression(
ctx,
&mclass_node.body,
options,
build_html::GroupType::True,
(None, None),
)?;
Ok(make_span(
vec![mclass_node.mclass.clone()],
elements,
Some(options),
None,
)
.into())
}
fn mathml_builder(
node: &ParseNode,
options: &Options,
ctx: &KatexContext,
) -> Result<MathDomNode, ParseError> {
let ParseNode::Mclass(mclass_node) = node else {
return Err(ParseError::new("Expected Mclass node"));
};
let inner = build_mathml::build_expression(ctx, &mclass_node.body, options, None)?;
let node_result = if mclass_node.mclass == "minner" {
MathNode::builder()
.node_type(MathNodeType::Mpadded)
.children(inner)
.build()
.into()
} else if mclass_node.mclass == "mord" {
if mclass_node.is_character_box {
if inner.is_empty() {
MathNode::builder()
.node_type(MathNodeType::Mi)
.children(inner)
.build()
.into()
} else {
inner[0].clone()
}
} else {
MathNode::builder()
.node_type(MathNodeType::Mi)
.children(inner)
.build()
.into()
}
} else {
let mut node = if mclass_node.is_character_box {
if inner.is_empty() {
MathNode::builder()
.node_type(MathNodeType::Mo)
.children(inner)
.build()
.into()
} else {
inner[0].clone()
}
} else {
MathNode::builder()
.node_type(MathNodeType::Mo)
.children(inner)
.build()
.into()
};
if let MathDomNode::Math(math_node) = &mut node {
let mut attributes = KeyMap::default();
if mclass_node.mclass == "mbin" {
attributes.insert("lspace".to_owned(), "0.22em".to_owned()); attributes.insert("rspace".to_owned(), "0.22em".to_owned());
} else if mclass_node.mclass == "mpunct" {
attributes.insert("lspace".to_owned(), "0em".to_owned());
attributes.insert("rspace".to_owned(), "0.17em".to_owned()); } else if mclass_node.mclass == "mopen" || mclass_node.mclass == "mclose" {
attributes.insert("lspace".to_owned(), "0em".to_owned());
attributes.insert("rspace".to_owned(), "0em".to_owned());
} else if mclass_node.mclass == "minner" {
attributes.insert("lspace".to_owned(), "0.0556em".to_owned()); attributes.insert("width".to_owned(), "+0.1111em".to_owned());
}
if !attributes.is_empty() {
math_node.attributes = attributes;
}
}
node
};
Ok(node_result)
}
pub fn define_mclass(ctx: &mut crate::KatexContext) {
let math_class_names = [
"\\mathord",
"\\mathbin",
"\\mathrel",
"\\mathopen",
"\\mathclose",
"\\mathpunct",
"\\mathinner",
];
ctx.define_function(FunctionDefSpec {
node_type: Some(NodeType::Mclass),
names: &math_class_names,
props: FunctionPropSpec {
num_args: 1,
..Default::default()
},
handler: Some(|context: FunctionContext, args, _opt_args| {
let body = &args[0];
let func_name = context.func_name.as_str();
let mclass = if func_name == "\\mathord" {
"mord"
} else if func_name == "\\mathbin" {
"mbin"
} else if func_name == "\\mathrel" {
"mrel"
} else if func_name == "\\mathopen" {
"mopen"
} else if func_name == "\\mathclose" {
"mclose"
} else if func_name == "\\mathpunct" {
"mpunct"
} else if func_name == "\\mathinner" {
"minner"
} else {
"mord" };
Ok(ParseNode::Mclass(ParseNodeMclass {
mode: context.parser.mode,
loc: context.loc(),
mclass: mclass.to_owned(),
body: ord_argument(body),
is_character_box: body.is_character_box()?,
}))
}),
html_builder: Some(html_builder),
mathml_builder: Some(mathml_builder),
});
ctx.define_function(FunctionDefSpec {
node_type: Some(NodeType::Mclass),
names: &["\\@binrel"],
props: FunctionPropSpec {
num_args: 2,
..Default::default()
},
handler: Some(|context: FunctionContext, args, _opt_args| {
let mclass = binrel_class(&args[0]);
Ok(ParseNode::Mclass(ParseNodeMclass {
mode: context.parser.mode,
loc: context.loc(),
mclass,
body: ord_argument(&args[1]),
is_character_box: args[1].is_character_box()?,
}))
}),
html_builder: Some(html_builder),
mathml_builder: Some(mathml_builder),
});
let stacked_names = ["\\stackrel", "\\overset", "\\underset"];
ctx.define_function(FunctionDefSpec {
node_type: Some(NodeType::Mclass),
names: &stacked_names,
props: FunctionPropSpec {
num_args: 2,
..Default::default()
},
handler: Some(|context: FunctionContext, args, _opt_args| {
let base_arg = &args[1];
let shifted_arg = &args[0];
let func_name = context.func_name.as_str();
let mclass = if func_name == "\\stackrel" {
"mrel".to_owned() } else {
binrel_class(base_arg)
};
let base_op = ParseNodeOp::Body {
mode: base_arg.mode(),
loc: context.loc(),
limits: true,
always_handle_sup_sub: Some(true),
suppress_base_shift: Some(func_name != "\\stackrel"),
parent_is_sup_sub: false,
body: ord_argument(base_arg),
};
let supsub = ParseNodeSupSub {
mode: shifted_arg.mode(),
loc: context.loc(),
base: Some(Box::new(ParseNode::Op(base_op))),
sup: if func_name == "\\underset" {
None
} else {
Some(Box::new(shifted_arg.clone()))
},
sub: (func_name == "\\underset").then(|| Box::new(shifted_arg.clone())),
};
let supsub = ParseNode::SupSub(supsub);
Ok(ParseNode::Mclass(ParseNodeMclass {
mode: context.parser.mode,
loc: context.loc(),
mclass,
is_character_box: supsub.is_character_box()?,
body: vec![supsub],
}))
}),
html_builder: Some(html_builder),
mathml_builder: Some(mathml_builder),
});
}