use crate::formula::ast::{MathNode, MatrixFence};
use super::LatexError;
#[inline]
pub fn matrix_fence_to_env(fence_type: MatrixFence) -> &'static str {
match fence_type {
MatrixFence::None => "matrix",
MatrixFence::Paren => "pmatrix",
MatrixFence::Bracket => "bmatrix",
MatrixFence::Brace => "Bmatrix",
MatrixFence::Pipe => "vmatrix",
MatrixFence::DoublePipe => "Vmatrix",
}
}
#[allow(dead_code)]
pub fn convert_matrix(
buffer: &mut String,
rows: &[Vec<Vec<MathNode>>],
fence_type: MatrixFence,
node_converter: &dyn Fn(&mut String, &MathNode) -> Result<(), LatexError>,
) -> Result<(), LatexError> {
use std::fmt::Write;
if rows.is_empty() {
return Ok(());
}
let env = matrix_fence_to_env(fence_type);
let estimated_capacity = estimate_matrix_capacity(rows);
buffer.reserve(estimated_capacity);
write!(buffer, "\\begin{{{}}}", env)
.map_err(|e| LatexError::FormatError(e.to_string()))?;
for (i, row) in rows.iter().enumerate() {
if i > 0 {
buffer.push_str(" \\\\ ");
}
for (j, cell) in row.iter().enumerate() {
if j > 0 {
buffer.push_str(" & ");
}
for node in cell {
node_converter(buffer, node)?;
}
}
}
write!(buffer, "\\end{{{}}}", env)
.map_err(|e| LatexError::FormatError(e.to_string()))?;
Ok(())
}
#[allow(dead_code)]
pub fn estimate_matrix_capacity(rows: &[Vec<Vec<MathNode>>]) -> usize {
if rows.is_empty() {
return 0;
}
let num_rows = rows.len();
let num_cols = rows[0].len();
let env_overhead = 20; let row_separators = (num_rows.saturating_sub(1)) * 4; let col_separators = num_rows * (num_cols.saturating_sub(1)) * 3;
let content_estimate = rows.iter()
.flatten()
.flatten()
.count() * 5;
env_overhead + row_separators + col_separators + content_estimate
}
#[allow(dead_code)]
pub fn convert_matrix_with_alignment(
buffer: &mut String,
rows: &[Vec<Vec<MathNode>>],
fence_type: MatrixFence,
alignments: Option<&[char]>,
node_converter: &dyn Fn(&mut String, &MathNode) -> Result<(), LatexError>,
) -> Result<(), LatexError> {
use std::fmt::Write;
if rows.is_empty() {
return Ok(());
}
let use_array_env = alignments.is_some();
let env = if use_array_env {
"array"
} else {
matrix_fence_to_env(fence_type)
};
let mut estimated_capacity = estimate_matrix_capacity(rows);
if use_array_env {
estimated_capacity += 20; }
buffer.reserve(estimated_capacity);
if use_array_env {
if let Some(aligns) = alignments {
write!(buffer, "\\begin{{{}}}", env)
.map_err(|e| LatexError::FormatError(e.to_string()))?;
buffer.push('{');
for &align in aligns {
buffer.push(align);
}
buffer.push('}');
} else {
let num_cols = rows[0].len();
write!(buffer, "\\begin{{{}}}", env)
.map_err(|e| LatexError::FormatError(e.to_string()))?;
buffer.push('{');
for _ in 0..num_cols {
buffer.push('c');
}
buffer.push('}');
}
match fence_type {
MatrixFence::Paren => buffer.push_str("\\left("),
MatrixFence::Bracket => buffer.push_str("\\left["),
MatrixFence::Brace => buffer.push_str("\\left\\{"),
MatrixFence::Pipe => buffer.push_str("\\left|"),
MatrixFence::DoublePipe => buffer.push_str("\\left\\|"),
MatrixFence::None => {} }
} else {
write!(buffer, "\\begin{{{}}}", env)
.map_err(|e| LatexError::FormatError(e.to_string()))?;
}
for (i, row) in rows.iter().enumerate() {
if i > 0 {
buffer.push_str(" \\\\ ");
}
for (j, cell) in row.iter().enumerate() {
if j > 0 {
buffer.push_str(" & ");
}
for node in cell {
node_converter(buffer, node)?;
}
}
}
if use_array_env {
match fence_type {
MatrixFence::Paren => buffer.push_str("\\right)"),
MatrixFence::Bracket => buffer.push_str("\\right]"),
MatrixFence::Brace => buffer.push_str("\\right\\}"),
MatrixFence::Pipe => buffer.push_str("\\right|"),
MatrixFence::DoublePipe => buffer.push_str("\\right\\|"),
MatrixFence::None => {} }
}
write!(buffer, "\\end{{{}}}", env)
.map_err(|e| LatexError::FormatError(e.to_string()))?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::formula::ast::{MathNode, Operator};
fn dummy_converter(buffer: &mut String, node: &MathNode) -> Result<(), LatexError> {
match node {
MathNode::Number(n) => buffer.push_str(n),
MathNode::Operator(op) => buffer.push_str(match op {
Operator::Plus => "+",
Operator::Minus => "-",
_ => "?",
}),
_ => buffer.push('?'),
}
Ok(())
}
#[test]
fn test_matrix_fence_to_env() {
assert_eq!(matrix_fence_to_env(MatrixFence::None), "matrix");
assert_eq!(matrix_fence_to_env(MatrixFence::Paren), "pmatrix");
assert_eq!(matrix_fence_to_env(MatrixFence::Bracket), "bmatrix");
assert_eq!(matrix_fence_to_env(MatrixFence::Brace), "Bmatrix");
assert_eq!(matrix_fence_to_env(MatrixFence::Pipe), "vmatrix");
assert_eq!(matrix_fence_to_env(MatrixFence::DoublePipe), "Vmatrix");
}
#[test]
fn test_convert_simple_matrix() {
let mut buffer = String::new();
let rows = vec![
vec![
vec![MathNode::Number("1".into())],
vec![MathNode::Number("2".into())],
],
vec![
vec![MathNode::Number("3".into())],
vec![MathNode::Number("4".into())],
],
];
convert_matrix(&mut buffer, &rows, MatrixFence::Bracket, &dummy_converter).unwrap();
assert_eq!(buffer, "\\begin{bmatrix}1 & 2 \\\\ 3 & 4\\end{bmatrix}");
}
#[test]
fn test_convert_empty_matrix() {
let mut buffer = String::new();
let rows: Vec<Vec<Vec<MathNode>>> = vec![];
convert_matrix(&mut buffer, &rows, MatrixFence::None, &dummy_converter).unwrap();
assert_eq!(buffer, "");
}
#[test]
fn test_convert_matrix_with_alignment() {
let mut buffer = String::new();
let rows = vec![
vec![
vec![MathNode::Number("1".into())],
vec![MathNode::Number("2".into())],
vec![MathNode::Number("3".into())],
],
vec![
vec![MathNode::Number("4".into())],
vec![MathNode::Number("5".into())],
vec![MathNode::Number("6".into())],
],
];
let alignments = vec!['l', 'c', 'r'];
convert_matrix_with_alignment(&mut buffer, &rows, MatrixFence::Bracket, Some(&alignments), &dummy_converter).unwrap();
assert!(buffer.contains("\\begin{array}{lcr}"));
assert!(buffer.contains("\\left["));
assert!(buffer.contains("\\right]"));
assert!(buffer.contains("\\end{array}"));
assert!(buffer.contains("1 & 2 & 3"));
assert!(buffer.contains("4 & 5 & 6"));
}
#[test]
fn test_convert_matrix_with_alignment_default() {
let mut buffer = String::new();
let rows = vec![
vec![
vec![MathNode::Number("1".into())],
vec![MathNode::Number("2".into())],
],
];
convert_matrix_with_alignment(&mut buffer, &rows, MatrixFence::Paren, None, &dummy_converter).unwrap();
assert!(buffer.contains("\\begin{pmatrix}"));
assert!(buffer.contains("\\end{pmatrix}"));
}
#[test]
fn test_matrix_fence_to_env_comprehensive() {
assert_eq!(matrix_fence_to_env(MatrixFence::None), "matrix");
assert_eq!(matrix_fence_to_env(MatrixFence::Paren), "pmatrix");
assert_eq!(matrix_fence_to_env(MatrixFence::Bracket), "bmatrix");
assert_eq!(matrix_fence_to_env(MatrixFence::Brace), "Bmatrix");
assert_eq!(matrix_fence_to_env(MatrixFence::Pipe), "vmatrix");
assert_eq!(matrix_fence_to_env(MatrixFence::DoublePipe), "Vmatrix");
}
}