use crate::build_common::make_fragment;
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, ParseNodeSizing};
use crate::types::{ParseError, ParseErrorKind};
use crate::units::make_em;
use crate::{build_html, build_mathml};
const SIZE_FUNCS: &[&str] = &[
"\\tiny",
"\\sixptsize",
"\\scriptsize",
"\\footnotesize",
"\\small",
"\\normalsize",
"\\large",
"\\Large",
"\\LARGE",
"\\huge",
"\\Huge",
];
pub fn sizing_group(
ctx: &crate::KatexContext,
value: &[ParseNode],
options: &Options,
base_options: &Options,
) -> Result<HtmlDomNode, ParseError> {
let mut inner = build_html::build_expression(
ctx,
value,
options,
build_html::GroupType::False,
(None, None),
)?;
let multiplier = options.size_multiplier / base_options.size_multiplier;
for item in &mut inner {
let Some(classes) = item.classes_mut() else {
continue;
};
let pos = classes.into_iter().position(|c| c == "sizing");
if let Some(pos) = pos {
let expected = format!("reset-size{}", options.size);
if classes.get(pos + 1) == Some(expected.as_str()) {
if let Some(class) = classes.get_mut(pos + 1) {
*class = format!("reset-size{}", base_options.size).into();
}
}
} else {
let sizing_classes = options.sizing_classes(base_options);
classes.extend(sizing_classes);
}
if let Some(h) = item.height_mut() {
*h *= multiplier;
}
if let Some(d) = item.depth_mut() {
*d *= multiplier;
}
}
Ok(make_fragment(inner).into())
}
fn html_builder(
node: &ParseNode,
options: &Options,
ctx: &crate::KatexContext,
) -> Result<HtmlDomNode, ParseError> {
let ParseNode::Sizing(sizing_node) = node else {
return Err(ParseError::new(ParseErrorKind::ExpectedNode {
node: NodeType::Sizing,
}));
};
let new_options = options.having_size(sizing_node.size);
sizing_group(ctx, &sizing_node.body, &new_options, options)
}
fn mathml_builder(
node: &ParseNode,
options: &Options,
ctx: &crate::KatexContext,
) -> Result<MathDomNode, ParseError> {
let ParseNode::Sizing(sizing_node) = node else {
return Err(ParseError::new(ParseErrorKind::ExpectedNode {
node: NodeType::Sizing,
}));
};
let new_options = options.having_size(sizing_node.size);
let inner = build_mathml::build_expression(ctx, &sizing_node.body, &new_options, None)?;
let mut node = MathNode::builder()
.node_type(MathNodeType::Mstyle)
.children(inner)
.build();
node.set_attribute("mathsize", make_em(new_options.size_multiplier));
Ok(MathDomNode::Math(node))
}
pub fn define_sizing(ctx: &mut crate::KatexContext) {
ctx.define_function(FunctionDefSpec {
node_type: Some(NodeType::Sizing),
names: SIZE_FUNCS,
props: FunctionPropSpec {
num_args: 0,
allowed_in_text: true,
..Default::default()
},
handler: Some(|context, args, _opt_args| {
if !args.is_empty() {
return Err(ParseError::new(ParseErrorKind::SizingTakesNoArguments));
}
let body = context
.parser
.parse_expression(false, context.break_on_token_text)?;
Ok(ParseNode::Sizing(ParseNodeSizing {
mode: context.parser.mode,
loc: context.loc(),
size: SIZE_FUNCS
.iter()
.position(|&s| s == context.func_name)
.unwrap_or(0)
+ 1,
body,
}))
}),
html_builder: Some(html_builder),
mathml_builder: Some(mathml_builder),
});
}