use crate::{MathBox, MathRenderer};
use ratatui::{
buffer::Buffer,
layout::Rect,
style::{Color, Style},
symbols::Marker,
widgets::{
canvas::{Canvas, Line},
Block, Widget,
},
};
#[derive(Clone)]
pub struct CanvasMathWidget<'a> {
latex: &'a str,
style: Style,
block: Option<Block<'a>>,
color: Color,
}
impl<'a> CanvasMathWidget<'a> {
pub fn new(latex: &'a str) -> Self {
Self {
latex,
style: Style::default(),
block: None,
color: Color::White,
}
}
pub fn style(mut self, style: Style) -> Self {
self.style = style;
self
}
pub fn color(mut self, color: Color) -> Self {
self.color = color;
self
}
pub fn block(mut self, block: Block<'a>) -> Self {
self.block = Some(block);
self
}
}
struct BrailleLine {
x1: f64,
y1: f64,
x2: f64,
y2: f64,
}
fn extract_elements(mbox: &MathBox, area_height: f64) -> (Vec<BrailleLine>, Vec<(usize, usize, char)>) {
let mut lines = Vec::new();
let mut text_chars = Vec::new();
let content = mbox.to_lines();
for (row, line) in content.iter().enumerate() {
for (col, ch) in line.chars().enumerate() {
let canvas_y_mid = area_height - row as f64 - 0.5;
let _canvas_y_top = area_height - row as f64;
let _canvas_y_bot = area_height - row as f64 - 1.0;
match ch {
'─' => {
let x1 = col as f64;
let x2 = (col + 1) as f64;
lines.push(BrailleLine { x1, y1: canvas_y_mid, x2, y2: canvas_y_mid });
}
'╱' | '╲' | '│' => {
text_chars.push((col, row, ch));
}
' ' => {} _ => {
text_chars.push((col, row, ch));
}
}
}
}
(lines, text_chars)
}
impl Widget for CanvasMathWidget<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
let renderer = MathRenderer::new();
let mbox = match renderer.render_to_box(self.latex) {
Ok(b) => b,
Err(e) => {
buf.set_string(area.x, area.y, format!("Error: {}", e), self.style);
return;
}
};
let content_area = if let Some(ref block) = self.block {
let inner = block.inner(area);
block.clone().render(area, buf);
inner
} else {
area
};
let mbox_height_f = mbox.height as f64;
let (braille_lines, text_chars) = extract_elements(&mbox, mbox_height_f);
if !braille_lines.is_empty() {
let mbox_width = mbox.width as u16;
let mbox_height = mbox.height as u16;
let color = self.color;
let canvas_area = Rect::new(
content_area.x,
content_area.y,
mbox_width.min(content_area.width),
mbox_height.min(content_area.height),
);
let canvas_width = canvas_area.width as f64;
let canvas_height = canvas_area.height as f64;
let canvas = Canvas::default()
.marker(Marker::Braille)
.x_bounds([0.0, canvas_width])
.y_bounds([0.0, canvas_height])
.paint(move |ctx| {
for line in &braille_lines {
ctx.draw(&Line {
x1: line.x1 + 0.5,
y1: line.y1,
x2: line.x2 + 0.5,
y2: line.y2,
color,
});
}
});
canvas.render(canvas_area, buf);
}
for (col, row, ch) in &text_chars {
let x = content_area.x + *col as u16;
let y = content_area.y + *row as u16;
if x < content_area.right() && y < content_area.bottom() {
buf.set_string(x, y, ch.to_string(), self.style);
}
}
}
}