use crate::build_common::{make_symbol, try_combine_chars};
use crate::context::KatexContext;
use crate::define_function::{FunctionContext, FunctionDefSpec, FunctionPropSpec};
use crate::dom_tree::{HtmlDomNode, Span};
use crate::mathml_tree::{MathDomNode, MathNode, MathNodeType, TextNode};
use crate::options::Options;
use crate::parser::parse_node::{NodeType, ParseNode, ParseNodeVerb};
use crate::style::TEXT;
use crate::symbols::Mode;
use crate::types::ParseError;
pub fn define_verb(ctx: &mut KatexContext) {
ctx.define_function(FunctionDefSpec {
node_type: Some(NodeType::Verb),
names: &["\\verb"],
props: FunctionPropSpec {
num_args: 0,
allowed_in_text: true,
..Default::default()
},
handler: Some(
|_context: FunctionContext,
_args: Vec<ParseNode>,
_opt_args: Vec<Option<ParseNode>>| {
Err(ParseError::new(
"\\verb ended by end of line instead of matching delimiter".to_owned(),
))
},
),
html_builder: Some(html_builder),
mathml_builder: Some(mathml_builder),
});
}
fn html_builder(
node: &ParseNode,
options: &Options,
ctx: &KatexContext,
) -> Result<HtmlDomNode, ParseError> {
if let ParseNode::Verb(verb_node) = node {
let text = make_verb(verb_node);
let mut body: Vec<HtmlDomNode> = Vec::new();
let new_options = options.having_style(TEXT);
for ch in text.chars() {
let c = if ch == '~' {
"\\textasciitilde".to_owned()
} else {
ch.to_string()
};
let symbol = make_symbol(
ctx,
&c,
"Typewriter-Regular",
Mode::Text,
None, Some(&["mord".to_owned(), "texttt".to_owned()]),
)?;
body.push(symbol.into());
}
try_combine_chars(&mut body);
let span_struct = Span::builder()
.children(body)
.classes(vec!["mord".to_owned(), "text".to_owned()])
.max_font_size(new_options.size_multiplier)
.build(Some(&new_options));
Ok(HtmlDomNode::DomSpan(span_struct))
} else {
Err(ParseError::new("Expected Verb node".to_owned()))
}
}
fn mathml_builder(
node: &ParseNode,
_options: &Options,
_ctx: &KatexContext,
) -> Result<MathDomNode, ParseError> {
if let ParseNode::Verb(verb_node) = node {
let text = make_verb(verb_node);
let text_node = TextNode { text };
let mut mtext = MathNode::builder()
.node_type(MathNodeType::Mtext)
.children(vec![MathDomNode::Text(text_node)])
.build();
mtext
.attributes
.insert("mathvariant".to_owned(), "monospace".to_owned());
Ok(MathDomNode::Math(mtext))
} else {
Err(ParseError::new("Expected Verb node".to_owned()))
}
}
fn make_verb(group: &ParseNodeVerb) -> String {
group
.body
.replace(' ', if group.star { "\u{2423}" } else { "\u{A0}" })
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::Mode;
#[test]
fn test_make_verb_no_star() {
let verb_node = ParseNodeVerb {
mode: Mode::Text,
loc: None,
body: "hello world".to_owned(),
star: false,
};
let result = make_verb(&verb_node);
assert_eq!(result, "hello\u{A0}world");
}
#[test]
fn test_make_verb_with_star() {
let verb_node = ParseNodeVerb {
mode: Mode::Text,
loc: None,
body: "hello world".to_owned(),
star: true,
};
let result = make_verb(&verb_node);
assert_eq!(result, "hello\u{2423}world");
}
#[test]
fn test_make_verb_no_spaces() {
let verb_node = ParseNodeVerb {
mode: Mode::Text,
loc: None,
body: "helloworld".to_owned(),
star: false,
};
let result = make_verb(&verb_node);
assert_eq!(result, "helloworld");
}
}