use crate::utils::*;
use crate::consts::*;
use crate::markdown::escape::undo_html_escapes;
use crate::markdown::inline::{tag::is_tag, link::render_link};
use lazy_static::lazy_static;
lazy_static! {
static ref LATEX_SYMBOLS: Vec<Vec<u16>> = {
LATEX_SYMBOLS_RAW.clone().into_iter().map(into_v16).collect()
};
static ref LATEX_1ARG_FUNCS: Vec<Vec<u16>> = {
LATEX_1ARG_FUNCS_RAW.clone().into_iter().map(into_v16).collect()
};
static ref LATEX_2ARG_FUNCS: Vec<Vec<u16>> = {
LATEX_2ARG_FUNCS_RAW.clone().into_iter().map(into_v16).collect()
};
}
pub fn render_math(content: &[u16]) -> Vec<u16> {
let mut curr_index = 0;
let mut result = vec![];
while curr_index < content.len() {
if is_opening_math_tag(content, curr_index) {
match get_closing_math_tag_index(content, curr_index + 1) {
None => {result.push(content[curr_index]);}
Some(i) => {
let math_begin_index = get_bracket_end_index(content, curr_index).unwrap() + 1;
let math_tag_end_index = get_bracket_end_index(content, i).unwrap() + 1;
return vec![
render_link(&result),
vec![U16_BACKSLASH, U16_LEFT_PARENTHESIS],
translate_to_latex(&content[math_begin_index..i].to_vec()),
vec![U16_BACKSLASH, U16_RIGHT_PARENTHESIS],
render_math(&content[math_tag_end_index..])
].concat();
}
}
}
else {
result.push(content[curr_index]);
}
curr_index += 1;
}
render_link(&result)
}
pub fn translate_to_latex(content: &Vec<u16>) -> Vec<u16> {
let mut result = Vec::with_capacity(content.len());
let mut tmp = vec![];
let mut index = 0;
let mut content = escape_special_characters(content);
content.push(U16_SPACE); // so that it handles the last word
while index < content.len() {
if is_alphabet(content[index]) {
tmp.push(content[index]);
}
else {
if tmp.len() > 0 {
if LATEX_SYMBOLS.contains(&tmp) {
if tmp == into_v16("leftcurlybrace") {
result.push(into_v16("\\{ "));
}
else if tmp == into_v16("rightcurlybrace") {
result.push(into_v16("\\} "));}
else {
result.push(
vec![
vec![U16_BACKSLASH],
tmp,
vec![U16_SPACE]
].concat()
);
}
}
else if LATEX_1ARG_FUNCS.contains(&tmp) {
let (arguments, indexes) = get_arguments(&content, index, vec![], vec![]);
if arguments.len() > 0 {
if tmp == into_v16("lim") {
let argument = translate_to_latex(&arguments[0]);
result.push(vec![
into_v16("\\lim\\limits_{"),
argument,
vec![U16_RIGHT_CURLY_BRACE],
].concat());
}
else {
let argument = if tmp == into_v16("text") {
arguments[0].clone()
} else {
translate_to_latex(&arguments[0])
};
let func_name = if tmp == into_v16("sup") {
vec![U16_CARET]
}
else if tmp == into_v16("sub") {
vec![U16_UNDERBAR]
}
else {
vec![
vec![U16_BACKSLASH],
tmp
].concat()
};
result.push(vec![
func_name,
vec![U16_LEFT_CURLY_BRACE],
argument,
vec![U16_RIGHT_CURLY_BRACE],
].concat());
}
tmp = vec![];
index = indexes[0] + 1;
}
else {
result.push(tmp);
result.push(vec![content[index]]);
tmp = vec![];
index += 1;
}
continue;
}
else if LATEX_2ARG_FUNCS.contains(&tmp) {
let (arguments, indexes) = get_arguments(&content, index, vec![], vec![]);
if arguments.len() == 0 {
result.push(tmp);
result.push(vec![content[index]]);
tmp = vec![];
index += 1;
}
else if arguments.len() == 1 {
if tmp == into_v16("sqrt") {
let argument = translate_to_latex(&arguments[0]);
result.push(vec![
into_v16("\\sqrt{"),
argument,
vec![U16_RIGHT_CURLY_BRACE],
].concat());
tmp = vec![];
index = indexes[0] + 1;
}
else {
result.push(tmp);
result.push(vec![content[index]]);
tmp = vec![];
index += 1;
}
}
else if arguments.len() >= 2 {
let argument1 = translate_to_latex(&arguments[0]);
let argument2 = translate_to_latex(&arguments[1]);
if tmp == into_v16("frac") || tmp == into_v16("cfrac") {
result.push(vec![
vec![U16_BACKSLASH],
tmp,
vec![U16_LEFT_CURLY_BRACE],
argument1,
vec![U16_RIGHT_CURLY_BRACE, U16_LEFT_CURLY_BRACE],
argument2,
vec![U16_RIGHT_CURLY_BRACE]
].concat());
}
else if tmp == into_v16("sqrt") {
result.push(vec![
vec![U16_BACKSLASH],
tmp,
vec![U16_LEFT_SQUARE_BRACKET],
argument1,
vec![U16_RIGHT_SQUARE_BRACKET, U16_LEFT_CURLY_BRACE],
argument2,
vec![U16_RIGHT_CURLY_BRACE]
].concat());
}
else {
result.push(vec![
vec![U16_BACKSLASH],
tmp,
into_v16("\\limits _{"),
argument1,
vec![U16_RIGHT_CURLY_BRACE, U16_CARET, U16_LEFT_CURLY_BRACE],
argument2,
vec![U16_RIGHT_CURLY_BRACE]
].concat());
}
tmp = vec![];
index = indexes[1] + 1;
}
continue;
}
else {
result.push(tmp);
}
tmp = vec![];
}
result.push(vec![content[index]]);
}
index += 1;
}
result.pop(); // space character that I temporalily pushed at the beginning of this function
result.concat()
}
fn get_arguments(content: &Vec<u16>, index: usize, mut current_args: Vec<Vec<u16>>, mut arg_end_indexes: Vec<usize>) -> (Vec<Vec<u16>>, Vec<usize>) {
if index >= content.len() {
(current_args, arg_end_indexes)
}
else if content[index] == U16_LEFT_CURLY_BRACE {
match get_curly_brace_end_index(content, index) {
None => (current_args, arg_end_indexes),
Some(i) => {
current_args.push(content[index + 1..i].to_vec());
arg_end_indexes.push(i);
get_arguments(content, i + 1, current_args, arg_end_indexes)
}
}
}
else if content[index] == U16_SPACE {
get_arguments(content, index + 1, current_args, arg_end_indexes)
}
else {
(current_args, arg_end_indexes)
}
}
fn escape_special_characters(content: &Vec<u16>) -> Vec<u16> {
let content = undo_html_escapes(content);
let mut result = Vec::with_capacity(content.len() + content.len() / 6);
for c in content.iter() {
if *c == U16_LESS_THAN {
result.push(U16_SPACE);
result.push(U16_SMALL_L);
result.push(U16_SMALL_T);
result.push(U16_SPACE);
}
else if *c == U16_GREATER_THAN {
result.push(U16_SPACE);
result.push(U16_SMALL_G);
result.push(U16_SMALL_T);
result.push(U16_SPACE);
}
else if *c == U16_ASTERISK {
result.push(BACKSLASH_ESCAPE_MARKER);
result.push(u16::MAX - U16_ASTERISK);
}
else if *c == U16_TILDE {
result.push(BACKSLASH_ESCAPE_MARKER);
result.push(u16::MAX - U16_TILDE);
}
else if *c == U16_LEFT_SQUARE_BRACKET {
result.push(BACKSLASH_ESCAPE_MARKER);
result.push(u16::MAX - U16_LEFT_SQUARE_BRACKET);
}
else if *c == U16_RIGHT_SQUARE_BRACKET {
result.push(BACKSLASH_ESCAPE_MARKER);
result.push(u16::MAX - U16_RIGHT_SQUARE_BRACKET);
}
else if *c == U16_CARET {
result.push(BACKSLASH_ESCAPE_MARKER);
result.push(u16::MAX - U16_CARET);
}
else {
result.push(*c);
}
}
result
}
fn get_closing_math_tag_index(content: &[u16], mut index: usize) -> Option<usize> {
while index < content.len() {
if is_closing_math_tag(content, index) {
return Some(index);
}
index += 1;
}
None
}
fn is_opening_math_tag(content: &[u16], index: usize) -> bool {
is_tag(content, index) && {
let tag_end_index = get_bracket_end_index(content, index + 1).unwrap();
lowercase_and_remove_spaces(&content[index + 2..tag_end_index]) == into_v16("math")
}
}
fn is_closing_math_tag(content: &[u16], index: usize) -> bool {
is_tag(content, index) && {
let tag_end_index = get_bracket_end_index(content, index + 1).unwrap();
lowercase_and_remove_spaces(&content[index + 2..tag_end_index]) == into_v16("/math")
}
}
const LATEX_SYMBOLS_RAW: [&str;103] = ["lt", "gt", "leq", "geq", "ll", "gg", "equiv", "subset", "supset", "approx", "in", "ni", "subseteq", "supseteq", "cong", "simeq", "notin", "propto", "neq", "therefore", "because", "pm", "mp", "times", "div", "star", "cap", "cup", "vee", "wedge", "cdot", "diamond", "bullet", "oplus", "ominus", "otimes", "oslash", "odot", "circ", "exists", "nexists", "forall", "neg", "land", "lor", "rightarrow", "leftarrow", "iff", "top", "bot", "varnothing", "quad", "backslash", "leftcurlybrace", "rightcurlybrace", "alpha", "beta", "gamma", "Gamma", "delta", "Delta", "epsilon", "zeta", "eta", "theta", "Theta", "iota", "kappa", "lambda", "Lambda", "mu", "nu", "xi", "Xi", "pi", "Pi", "rho", "sigma", "Sigma", "tau", "upsilon", "Upsilon", "phi", "Phi", "chi", "psi", "Psi", "omega", "Omega", "partial", "nabla", "infty", "cos", "sin", "tan", "cosh", "sinh", "tanh", "angle", "leftrightarrow", "sqcap", "sqcup", "space"];
const LATEX_1ARG_FUNCS_RAW: [&str;13] = ["sub", "sup", "hat", "bar", "dot", "tilde", "vec", "check", "overleftarrow", "overrightarrow", "underline", "text", "lim"];
const LATEX_2ARG_FUNCS_RAW: [&str;9] = ["frac", "cfrac", "sqrt", "int", "oint", "iint", "iiint", "sum", "prod"];
#[cfg(test)]
mod tests {
#[test]
fn math_test1() {
let orig = crate::utils::into_v16("sub{sub{-3}}sup {-x}{4} neq infty text{1234}");
let rendered = String::from_utf16_lossy(&crate::markdown::math::translate_to_latex(&orig));
assert_eq!(rendered, "_{_{-3}}^{-x}{4} \\neq \\infty \\text{1234}".to_string());
}
#[test]
fn math_test2() {
let orig = crate::utils::into_v16("sqrt{1 + sqrt{2 + 3}} leq sqrt{3}{5 + frac {2 + 5} {3 + 7}}");
let rendered = String::from_utf16_lossy(&crate::markdown::math::translate_to_latex(&orig));
assert_eq!(rendered, "\\sqrt{1 + \\sqrt{2 + 3}} \\leq \\sqrt[3]{5 + \\frac{2 + 5}{3 + 7}}".to_string());
}
#[test]
fn math_test3() {
let orig = crate::utils::into_v16("frac {3} sum {3} sqrt {3}");
let rendered = String::from_utf16_lossy(&crate::markdown::math::translate_to_latex(&orig));
assert_eq!(rendered, "frac {3} sum {3} \\sqrt{3}".to_string());
}
#[test]
fn math_test4() {
let orig = crate::utils::into_v16("int{0}{infty} e sup{-x} dx");
let rendered = String::from_utf16_lossy(&crate::markdown::math::translate_to_latex(&orig));
assert_eq!(rendered, "\\int\\limits _{0}^{\\infty } e ^{-x} dx".to_string());
}
#[test]
fn math_test5() {
let orig = crate::utils::into_v16("(vec{a} neq vec {b}) = (hat{a} neq hat {b})");
let rendered = String::from_utf16_lossy(&crate::markdown::math::translate_to_latex(&orig));
assert_eq!(rendered, "(\\vec{a} \\neq \\vec{b}) = (\\hat{a} \\neq \\hat{b})".to_string());
}
#[test]
fn math_test6() {
let orig = crate::utils::into_v16("sum{n=1}{infty} frac{1}{n sup{2}} < 10");
let rendered = String::from_utf16_lossy(&crate::markdown::math::translate_to_latex(&orig));
assert_eq!(rendered, "\\sum\\limits _{n=1}^{\\infty } \\frac{1}{n ^{2}} \\lt 10".to_string());
}
#[test]
fn math_test7() {
let orig = crate::utils::into_v16("text{delta} delta");
let rendered = String::from_utf16_lossy(&crate::markdown::math::translate_to_latex(&orig));
assert_eq!(rendered, "\\text{delta} \\delta ".to_string());
}
#[test]
fn math_test8() {
let orig = "[[math]] text{when} x sup{*} (t) = x(t), space X(f) = X sup{*}(-f) [[/math]]".to_string();
let rendered = "<p>\\( \\text{when} x ^{*} (t) = x(t), \\space X(f) = X ^{*}(-f) \\)</p>";
assert_eq!(
rendered.trim_end_matches('\n'),
crate::markdown::render(&orig).unwrap().trim_end_matches('\n')
);
}
}