1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
//! Environment function implementations for KaTeX Rust
//!
//! This module handles the `\begin` and `\end` commands for LaTeX environments,
//! providing the core functionality for structured content like matrices,
//! arrays, and other mathematical constructs.
//!
//! Migrated from KaTeX's functions/environment.js.
use crate::KatexContext;
use crate::define_environment::EnvContext;
use crate::define_function::{FunctionDefSpec, FunctionPropSpec};
use crate::parser::parse_node::{AnyParseNode, NodeType, ParseNode, ParseNodeEnvironment};
use crate::types::{ArgType, Mode, ParseError};
/// Environment delimiters. HTML/MathML rendering is defined in the
/// corresponding defineEnvironment definitions.
pub fn define_environment(ctx: &mut KatexContext) {
let spec = FunctionDefSpec {
node_type: Some(NodeType::Environment),
names: &["\\begin", "\\end"],
props: FunctionPropSpec {
num_args: 1,
arg_types: Some(vec![ArgType::Mode(Mode::Text)]),
..Default::default()
},
handler: Some(|context, args, _opt_args| {
let func_name = &context.func_name;
let parser = context.parser;
let ParseNode::OrdGroup(name_group) = &args[0] else {
return Err(ParseError::new(format!(
"Invalid environment name{:?}",
args[0]
)));
};
let mut env_name = String::new();
for item in &name_group.body {
if let AnyParseNode::TextOrd(text_ord) = item {
env_name.push_str(&text_ord.text);
} else {
return Err(ParseError::new(format!(
"Invalid environment name{:?}",
args[0]
)));
}
}
// Handles \\end
if func_name != "\\begin" {
let env = ParseNodeEnvironment {
mode: parser.mode,
loc: None,
name: env_name.clone(),
// right_delim: None,
// size: None,
// bar_size: None,
name_group: args[0].clone().into(),
};
return Ok(env.into());
}
// begin...end is similar to left...right
let Some(env) = parser.ctx.environments.get(&env_name) else {
return Err(ParseError::new(format!("No such environment: {env_name}")));
};
// Build the environment object. Arguments and other information will
// be made available to the begin and end methods using properties.
let (args, opt_args) =
parser.parse_arguments(&format!("\\begin{{{env_name}}}"), env)?;
let env_context = EnvContext {
mode: parser.mode,
parser,
env_name: env_name.clone(),
};
let result = (env.handler)(env_context, args, opt_args)?;
parser.expect("\\end", false)?;
let end_name_token = parser.next_token.clone();
let Some(ParseNode::Environment(end)) = parser.parse_function(None, None)? else {
return Err(ParseError::new(format!(
"Expected environment after \\end, got {end_name_token:?}",
)));
};
if end.name != env_name {
return Err(ParseError::new(format!(
"Mismatched: \\begin{{{env_name}}} matched by \\end{{{}}}",
end.name
)));
}
Ok(result)
}),
html_builder: None, // Environment-specific builders are handled by individual environments
mathml_builder: None,
};
ctx.define_function(spec);
}