1#[derive(Clone, Debug)]
6pub struct MathBox {
7 content: Vec<Vec<char>>,
8 pub width: usize,
9 pub height: usize,
10 pub baseline: usize,
12}
13
14impl MathBox {
15 pub fn from_text(text: &str) -> Self {
17 let chars: Vec<char> = text.chars().collect();
18 let width = chars.len();
19 Self {
20 content: vec![chars],
21 width,
22 height: 1,
23 baseline: 0,
24 }
25 }
26
27 pub fn empty(width: usize, height: usize, baseline: usize) -> Self {
29 Self {
30 content: vec![vec![' '; width]; height],
31 width,
32 height,
33 baseline,
34 }
35 }
36
37 pub fn from_lines(lines: Vec<String>, baseline: usize) -> Self {
39 let height = lines.len();
40 let width = lines.iter().map(|l| l.chars().count()).max().unwrap_or(0);
41 let mut content = vec![vec![' '; width]; height];
42
43 for (y, line) in lines.iter().enumerate() {
44 for (x, ch) in line.chars().enumerate() {
45 if x < width {
46 content[y][x] = ch;
47 }
48 }
49 }
50
51 Self {
52 content,
53 width,
54 height,
55 baseline,
56 }
57 }
58
59 pub fn get(&self, x: usize, y: usize) -> char {
61 if y < self.height && x < self.width {
62 self.content[y][x]
63 } else {
64 ' '
65 }
66 }
67
68 pub fn set(&mut self, x: usize, y: usize, ch: char) {
70 if y < self.height && x < self.width {
71 self.content[y][x] = ch;
72 }
73 }
74
75 pub fn blit(&mut self, other: &MathBox, x_offset: usize, y_offset: usize) {
77 for y in 0..other.height {
78 for x in 0..other.width {
79 let target_x = x_offset + x;
80 let target_y = y_offset + y;
81 if target_y < self.height && target_x < self.width {
82 let ch = other.get(x, y);
83 if ch != ' ' {
84 self.set(target_x, target_y, ch);
85 }
86 }
87 }
88 }
89 }
90
91 pub fn concat_horizontal(boxes: &[MathBox]) -> MathBox {
93 if boxes.is_empty() {
94 return MathBox::empty(0, 1, 0);
95 }
96
97 let max_ascent = boxes.iter().map(|b| b.baseline).max().unwrap_or(0);
99 let max_descent = boxes
100 .iter()
101 .map(|b| b.height.saturating_sub(b.baseline + 1))
102 .max()
103 .unwrap_or(0);
104
105 let total_width: usize = boxes.iter().map(|b| b.width).sum();
106 let total_height = max_ascent + 1 + max_descent;
107
108 let mut result = MathBox::empty(total_width, total_height, max_ascent);
109 let mut x_pos = 0;
110
111 for b in boxes {
112 let y_offset = max_ascent - b.baseline;
113 result.blit(b, x_pos, y_offset);
114 x_pos += b.width;
115 }
116
117 result
118 }
119
120 pub fn stack_vertical(boxes: &[MathBox]) -> MathBox {
122 if boxes.is_empty() {
123 return MathBox::empty(0, 1, 0);
124 }
125
126 let max_width = boxes.iter().map(|b| b.width).max().unwrap_or(0);
127 let total_height: usize = boxes.iter().map(|b| b.height).sum();
128
129 let mut result = MathBox::empty(max_width, total_height, 0);
130 let mut y_pos = 0;
131
132 for b in boxes {
133 let x_offset = (max_width - b.width) / 2;
134 result.blit(b, x_offset, y_pos);
135 y_pos += b.height;
136 }
137
138 result.baseline = total_height / 2;
140 result
141 }
142
143 pub fn fill_row(&mut self, y: usize, ch: char) {
145 if y < self.height {
146 for x in 0..self.width {
147 self.set(x, y, ch);
148 }
149 }
150 }
151
152 pub fn fill_col(&mut self, x: usize, ch: char) {
154 if x < self.width {
155 for y in 0..self.height {
156 self.set(x, y, ch);
157 }
158 }
159 }
160
161 pub fn to_string(&self) -> String {
163 self.content
164 .iter()
165 .map(|row| row.iter().collect::<String>().trim_end().to_string())
166 .collect::<Vec<_>>()
167 .join("\n")
168 }
169
170 pub fn to_lines(&self) -> Vec<String> {
172 self.content
173 .iter()
174 .map(|row| row.iter().collect::<String>())
175 .collect()
176 }
177}
178
179impl Default for MathBox {
180 fn default() -> Self {
181 Self::empty(0, 1, 0)
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn test_from_text() {
191 let mb = MathBox::from_text("abc");
192 assert_eq!(mb.width, 3);
193 assert_eq!(mb.height, 1);
194 assert_eq!(mb.get(0, 0), 'a');
195 assert_eq!(mb.get(2, 0), 'c');
196 }
197
198 #[test]
199 fn test_concat_horizontal() {
200 let a = MathBox::from_text("x");
201 let b = MathBox::from_text("+");
202 let c = MathBox::from_text("y");
203 let result = MathBox::concat_horizontal(&[a, b, c]);
204 assert_eq!(result.to_string(), "x+y");
205 }
206}