tui_math/
canvas_widget.rs1use crate::{MathBox, MathRenderer};
7use ratatui::{
8 buffer::Buffer,
9 layout::Rect,
10 style::{Color, Style},
11 symbols::Marker,
12 widgets::{
13 canvas::{Canvas, Line},
14 Block, Widget,
15 },
16};
17
18#[derive(Clone)]
20pub struct CanvasMathWidget<'a> {
21 latex: &'a str,
22 style: Style,
23 block: Option<Block<'a>>,
24 color: Color,
25}
26
27impl<'a> CanvasMathWidget<'a> {
28 pub fn new(latex: &'a str) -> Self {
30 Self {
31 latex,
32 style: Style::default(),
33 block: None,
34 color: Color::White,
35 }
36 }
37
38 pub fn style(mut self, style: Style) -> Self {
40 self.style = style;
41 self
42 }
43
44 pub fn color(mut self, color: Color) -> Self {
46 self.color = color;
47 self
48 }
49
50 pub fn block(mut self, block: Block<'a>) -> Self {
52 self.block = Some(block);
53 self
54 }
55}
56
57struct BrailleLine {
59 x1: f64,
60 y1: f64,
61 x2: f64,
62 y2: f64,
63}
64
65fn extract_elements(mbox: &MathBox, area_height: f64) -> (Vec<BrailleLine>, Vec<(usize, usize, char)>) {
68 let mut lines = Vec::new();
69 let mut text_chars = Vec::new();
70
71 let content = mbox.to_lines();
72
73 for (row, line) in content.iter().enumerate() {
74 for (col, ch) in line.chars().enumerate() {
75 let canvas_y_mid = area_height - row as f64 - 0.5;
79 let _canvas_y_top = area_height - row as f64;
80 let _canvas_y_bot = area_height - row as f64 - 1.0;
81
82 match ch {
83 '─' => {
85 let x1 = col as f64;
86 let x2 = (col + 1) as f64;
87 lines.push(BrailleLine { x1, y1: canvas_y_mid, x2, y2: canvas_y_mid });
88 }
89 '╱' | '╲' | '│' => {
92 text_chars.push((col, row, ch));
93 }
94 ' ' => {} _ => {
97 text_chars.push((col, row, ch));
98 }
99 }
100 }
101 }
102
103 (lines, text_chars)
104}
105
106impl Widget for CanvasMathWidget<'_> {
107 fn render(self, area: Rect, buf: &mut Buffer) {
108 let renderer = MathRenderer::new();
110 let mbox = match renderer.render_to_box(self.latex) {
111 Ok(b) => b,
112 Err(e) => {
113 buf.set_string(area.x, area.y, format!("Error: {}", e), self.style);
114 return;
115 }
116 };
117
118 let content_area = if let Some(ref block) = self.block {
120 let inner = block.inner(area);
121 block.clone().render(area, buf);
122 inner
123 } else {
124 area
125 };
126
127 let mbox_height_f = mbox.height as f64;
130 let (braille_lines, text_chars) = extract_elements(&mbox, mbox_height_f);
131
132 if !braille_lines.is_empty() {
134 let mbox_width = mbox.width as u16;
135 let mbox_height = mbox.height as u16;
136 let color = self.color;
137
138 let canvas_area = Rect::new(
140 content_area.x,
141 content_area.y,
142 mbox_width.min(content_area.width),
143 mbox_height.min(content_area.height),
144 );
145
146 let canvas_width = canvas_area.width as f64;
147 let canvas_height = canvas_area.height as f64;
148
149 let canvas = Canvas::default()
150 .marker(Marker::Braille)
151 .x_bounds([0.0, canvas_width])
152 .y_bounds([0.0, canvas_height])
153 .paint(move |ctx| {
154 for line in &braille_lines {
155 ctx.draw(&Line {
157 x1: line.x1 + 0.5,
158 y1: line.y1,
159 x2: line.x2 + 0.5,
160 y2: line.y2,
161 color,
162 });
163 }
164 });
165
166 canvas.render(canvas_area, buf);
167 }
168
169 for (col, row, ch) in &text_chars {
171 let x = content_area.x + *col as u16;
172 let y = content_area.y + *row as u16;
173 if x < content_area.right() && y < content_area.bottom() {
174 buf.set_string(x, y, ch.to_string(), self.style);
175 }
176 }
177 }
178}