use crate::formula::ast::{Formula, MathNode};
use crate::formula::latex::{LatexStringCache, LatexConversionStats};
use super::error::LatexError;
use super::utils::extend_buffer_with_capacity;
use super::utils::estimate_nodes_size;
use smallvec::SmallVec;
pub struct LatexConverter {
pub(super) buffer: String,
pub(super) temp_buffer: SmallVec<[String; 4]>,
pub(super) string_cache: LatexStringCache,
pub(super) stats: LatexConversionStats,
}
impl LatexConverter {
pub fn new() -> Self {
let mut converter = Self {
buffer: String::with_capacity(2048), temp_buffer: SmallVec::new(),
string_cache: LatexStringCache::new(),
stats: LatexConversionStats::default(),
};
converter.initialize_cache();
converter
}
pub fn with_capacity(capacity: usize) -> Self {
let mut converter = Self {
buffer: String::with_capacity(capacity),
temp_buffer: SmallVec::new(),
string_cache: LatexStringCache::new(),
stats: LatexConversionStats::default(),
};
converter.initialize_cache();
converter
}
fn initialize_cache(&mut self) {
let common_commands = [
"+", "-", "\\cdot", "\\div", "=", "\\neq", "<", ">", "\\leq", "\\geq",
"\\pm", "\\mp", "\\times", "\\cdot", "\\ast", "\\circ", "\\bullet",
"\\wedge", "\\vee", "\\cap", "\\cup", "\\in", "\\notin", "\\subset",
"\\supset", "\\subseteq", "\\supseteq", "\\approx", "\\cong", "\\equiv",
"\\propto", "\\parallel", "\\perp", "\\angle", "\\nabla", "\\partial",
"\\infty", "\\emptyset", "\\cup", "\\cap", "\\sim", "\\simeq", "\\asymp",
"\\mathrm{d}", "\\ldots", "\\cdots", "\\vdots", "\\ddots", "\\leftarrow",
"\\rightarrow", "\\uparrow", "\\downarrow", "\\leftrightarrow", "\\updownarrow",
"\\forall", "\\exists", "\\neg", "\\land", "\\lor", "\\implies", "\\iff",
"\\therefore", "\\because", "\\Box", "\\Diamond", "\\square",
"\\frac{", "\\sqrt{", "\\begin{pmatrix}", "\\end{pmatrix}",
"\\begin{bmatrix}", "\\end{bmatrix}", "\\begin{Bmatrix}", "\\end{Bmatrix}",
"\\begin{vmatrix}", "\\end{vmatrix}", "\\begin{Vmatrix}", "\\end{Vmatrix}",
"\\begin{matrix}", "\\end{matrix}", "\\begin{align*}", "\\end{align*}",
"\\text{", "\\mathrm{", "\\mathbf{", "\\mathit{", "\\mathsf{",
"\\mathtt{", "\\mathcal{", "\\mathfrak{", "\\mathbb{",
"\\hat{", "\\check{", "\\tilde{", "\\acute{", "\\grave{",
"\\dot{", "\\ddot{", "\\dddot{", "\\bar{", "\\breve{", "\\vec{",
"\\boxed{", "\\overline{", "\\underline{", "\\sout{",
"\\phantom{", "\\overset{", "\\underset{", "\\lim_{",
];
for cmd in &common_commands {
self.string_cache.get_or_insert(cmd);
}
}
pub fn convert(&mut self, formula: &Formula) -> Result<&str, LatexError> {
self.reset();
let estimated_size = super::utils::estimate_formula_size(formula.root());
extend_buffer_with_capacity(&mut self.buffer, "", estimated_size);
if formula.display_style() {
self.buffer.push_str("\\[");
} else {
self.buffer.push_str("\\(");
}
for node in formula.root() {
self.convert_node(node)?;
}
if formula.display_style() {
self.buffer.push_str("\\]");
} else {
self.buffer.push_str("\\)");
}
Ok(&self.buffer)
}
pub fn convert_nodes(&mut self, nodes: &[MathNode]) -> Result<&str, LatexError> {
self.reset();
let estimated_size = estimate_nodes_size(nodes);
extend_buffer_with_capacity(&mut self.buffer, "", estimated_size);
for node in nodes {
self.convert_node(node)?;
}
Ok(&self.buffer)
}
#[inline]
pub fn buffer(&self) -> &str {
&self.buffer
}
#[inline]
pub fn reset(&mut self) {
self.buffer.clear();
self.temp_buffer.clear();
self.string_cache.clear();
}
#[inline]
pub fn clear(&mut self) {
self.reset();
}
#[inline]
pub fn stats(&self) -> &LatexConversionStats {
&self.stats
}
#[inline]
#[allow(dead_code)]
fn get_cached_command(&mut self, cmd: &str) -> &str {
let index = self.string_cache.get_or_insert(cmd);
self.string_cache.get(index)
}
#[inline]
pub fn append_cached_command(&mut self, cmd: &str) {
let index = self.string_cache.get_or_insert(cmd);
let cached = self.string_cache.get(index);
self.buffer.push_str(cached);
}
}
impl Default for LatexConverter {
fn default() -> Self {
Self::new()
}
}
impl AsRef<str> for LatexConverter {
fn as_ref(&self) -> &str {
&self.buffer
}
}