katex_parser/unicode/
block.rs1use crate::unicode::unicode_width::{is_east_asian_wide, is_zero_width_mark};
2
3#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct Block {
9 pub lines: Vec<String>,
10 pub width: usize,
11 pub baseline: usize,
12}
13
14impl Block {
15 pub fn from(s: &str) -> Block {
16 let raw: Vec<&str> = s.split('\n').collect();
17 let width = raw.iter().map(|l| display_width(l)).max().unwrap_or(0);
18 let lines: Vec<String> = raw.iter().map(|l| pad_right(l, width)).collect();
19 Block {
20 lines,
21 width,
22 baseline: 0,
23 }
24 }
25
26 pub fn empty() -> Block {
27 Block {
28 lines: Vec::new(),
29 width: 0,
30 baseline: 0,
31 }
32 }
33
34 pub fn render(&self) -> String {
35 self.lines.join("\n")
36 }
37
38 pub fn height(&self) -> usize {
39 self.lines.len()
40 }
41
42 pub fn width(&self) -> usize {
43 self.width
44 }
45
46 pub fn baseline(&self) -> usize {
47 self.baseline
48 }
49
50 pub fn pad_to(&self, target_width: usize) -> Block {
51 if target_width <= self.width {
52 return self.clone();
53 }
54 let lines: Vec<String> = self.lines.iter().map(|l| pad_right(l, target_width)).collect();
55 Block {
56 lines,
57 width: target_width,
58 baseline: self.baseline,
59 }
60 }
61
62 pub fn center(&self, target_width: usize) -> Block {
63 if self.lines.is_empty() || target_width <= self.width {
64 return self.clone();
65 }
66 let lines: Vec<String> = self.lines.iter().map(|l| center_text(l, target_width)).collect();
67 Block {
68 lines,
69 width: target_width,
70 baseline: self.baseline,
71 }
72 }
73
74 pub fn above(&self, other: &Block) -> Block {
76 let new_w = self.width.max(other.width);
77 let self_lines: Vec<String> = self.lines.iter().map(|l| pad_right(l, new_w)).collect();
78 let other_lines: Vec<String> = other.lines.iter().map(|l| pad_right(l, new_w)).collect();
79 let mut lines = self_lines;
80 lines.extend(other_lines);
81 Block {
82 lines,
83 width: new_w,
84 baseline: self.baseline,
85 }
86 }
87
88 pub fn beside(&self, other: &Block) -> Block {
90 let self_h = self.lines.len();
91 let other_h = other.lines.len();
92 let new_h = self_h.max(other_h);
93 let self_bl = self.baseline;
94 let other_bl = other.baseline;
95 let diff = self_bl.abs_diff(other_bl);
96 let top_pad_left = if self_bl < other_bl { diff } else { 0 };
97 let top_pad_right = if other_bl < self_bl { diff } else { 0 };
98 let result_h = new_h.max(self_bl).max(other_bl);
99 let self_padded = vpad_at(self, result_h, top_pad_left);
100 let other_padded = vpad_at(other, result_h, top_pad_right);
101 let new_w = self.width + other.width;
102 let result_bl = self_bl.max(other_bl);
103 let lines: Vec<String> = (0..result_h)
104 .map(|i| self_padded[i].clone() + &other_padded[i])
105 .collect();
106 Block {
107 lines,
108 width: new_w,
109 baseline: result_bl,
110 }
111 }
112
113 pub fn append_left(&self, label: &Block) -> Block {
114 label.beside(self)
115 }
116
117 pub fn append_right(&self, label: &Block) -> Block {
118 self.beside(label)
119 }
120
121 pub fn enclose(&self, left: &str, right: &str) -> Block {
123 if self.lines.is_empty() {
124 return Block::from(&format!("{left}{right}"));
125 }
126 let lines: Vec<String> = self
127 .lines
128 .iter()
129 .map(|l| format!("{left}{l}{right}"))
130 .collect();
131 Block {
132 lines,
133 width: self.width + display_width(left) + display_width(right),
134 baseline: self.baseline,
135 }
136 }
137
138 pub fn row(cells: &[String], widths: &[usize], gap: &str) -> Block {
141 let joined: Vec<String> = cells
142 .iter()
143 .enumerate()
144 .map(|(j, c)| pad_right(c, widths[j]))
145 .collect();
146 Block::from(&joined.join(gap))
147 }
148}
149
150pub fn pad_right(s: &str, width: usize) -> String {
151 let dw = display_width(s);
152 if width <= dw {
153 return s.to_string();
154 }
155 format!("{s}{}", " ".repeat(width - dw))
156}
157
158#[allow(dead_code)]
160pub fn column_widths(cells: &[Vec<String>]) -> Vec<usize> {
161 column_max_widths(cells, |s| display_width(s))
162}
163
164pub fn column_max_widths<W>(rows: &[Vec<W>], width_of: impl Fn(&W) -> usize) -> Vec<usize> {
167 let max_cols = rows.iter().map(|row| row.len()).max().unwrap_or(0);
168 let mut widths = vec![0usize; max_cols];
169 for row in rows {
170 for (j, cell) in row.iter().enumerate() {
171 let w = width_of(cell);
172 if w > widths[j] {
173 widths[j] = w;
174 }
175 }
176 }
177 widths
178}
179
180pub(crate) fn center_text(s: &str, width: usize) -> String {
181 let dw = display_width(s);
182 if width <= dw {
183 return s.to_string();
184 }
185 let need = width - dw;
186 let left = need / 2;
187 let right = need - left;
188 format!("{}{s}{}", " ".repeat(left), " ".repeat(right))
189}
190
191pub fn display_width(s: &str) -> usize {
196 s.chars().map(char_width).sum()
197}
198
199fn char_width(c: char) -> usize {
200 let code = c as u32;
201 if code == 0 {
202 return 0;
203 }
204 if is_zero_width_mark(code) {
205 0
206 } else if is_east_asian_wide(code) {
207 2
208 } else {
209 1
210 }
211}
212
213fn vpad_at(b: &Block, target: usize, top_offset: usize) -> Vec<String> {
216 let h = b.lines.len();
217 if h >= target {
218 return b.lines.clone();
219 }
220 let empty = " ".repeat(b.width);
221 let mut top = vec![empty.clone(); top_offset];
222 let bottom = vec![empty; target.saturating_sub(top_offset.saturating_add(h))];
223 top.extend(b.lines.clone());
224 top.extend(bottom);
225 top
226}