1use ctl_text::{Context, Line, Measurement, Overflow, Renderable, Text};
4
5use crate::column::Column;
6use crate::frame::{Frame, RowSeparation};
7use crate::glyphs::Glyphs;
8use crate::lines::{literal, push_cell, rule_line};
9use crate::widths::{Extent, Framing, solve};
10
11const GAP: u16 = 2;
13
14#[derive(Clone, Debug)]
43pub struct Table {
44 columns: Vec<Column>,
45 header: Option<Vec<Text>>,
48 rows: Vec<Vec<Text>>,
50 frame: Frame,
51 separation: RowSeparation,
52}
53
54impl Table {
55 #[must_use]
58 pub fn new(columns: impl IntoIterator<Item = Column>) -> Self {
59 Self::from_columns(columns.into_iter().collect())
60 }
61
62 fn from_columns(mut columns: Vec<Column>) -> Self {
63 let titled = columns.iter().any(|column| column.header.is_some());
64 let header: Option<Vec<Text>> = titled.then(|| {
65 columns
66 .iter_mut()
67 .map(|column| {
68 column
69 .header
70 .take()
71 .unwrap_or_default()
72 .overflow(column.overflow)
73 })
74 .collect()
75 });
76 Self {
77 columns,
78 header,
79 rows: Vec::new(),
80 frame: Frame::default(),
81 separation: RowSeparation::default(),
82 }
83 }
84
85 #[must_use]
88 pub fn frame(mut self, frame: Frame) -> Self {
89 self.frame = frame;
90 self
91 }
92
93 #[must_use]
96 pub fn row_separation(mut self, separation: RowSeparation) -> Self {
97 self.separation = separation;
98 self
99 }
100
101 #[must_use]
108 pub fn row<C: Into<Text>>(self, cells: impl IntoIterator<Item = C>) -> Self {
109 self.push_row(cells.into_iter().map(Into::into).collect())
110 }
111
112 #[must_use]
114 pub fn rows<C, R>(self, rows: impl IntoIterator<Item = R>) -> Self
115 where
116 R: IntoIterator<Item = C>,
117 C: Into<Text>,
118 {
119 rows.into_iter().fold(self, Table::row)
120 }
121
122 fn push_row(mut self, mut cells: Vec<Text>) -> Self {
123 cells.resize_with(self.columns.len(), Text::new);
124 let cells = cells
125 .into_iter()
126 .zip(&self.columns)
127 .map(|(cell, column)| cell.overflow(column.overflow))
128 .collect();
129 self.rows.push(cells);
130 self
131 }
132
133 pub(crate) fn into_rows(self) -> Vec<Vec<Text>> {
136 self.rows
137 }
138
139 fn is_blank(&self) -> bool {
142 self.columns.is_empty() || (self.header.is_none() && self.rows.is_empty())
143 }
144
145 fn frame_width(&self) -> u16 {
147 let count = u16::try_from(self.columns.len()).unwrap_or(u16::MAX);
148 match self.frame.framing() {
149 Framing::Framed => count.saturating_mul(3).saturating_add(1),
150 Framing::Unframed => count.saturating_sub(1).saturating_mul(GAP),
151 }
152 }
153
154 fn grid(&self) -> impl Iterator<Item = &[Text]> {
156 self.header.iter().chain(&self.rows).map(Vec::as_slice)
157 }
158
159 fn cell_overflow(&self, column: &Column) -> Overflow {
163 match self.frame.framing() {
164 Framing::Framed if column.overflow == Overflow::KeepWords => Overflow::Fold,
165 Framing::Framed | Framing::Unframed => column.overflow,
166 }
167 }
168
169 fn extents(&self, context: &Context) -> Vec<Extent> {
170 let framing = self.frame.framing();
171 self.columns
172 .iter()
173 .enumerate()
174 .map(|(index, column)| {
175 let mut extent = Extent {
176 natural: 0,
177 minimum: 0,
178 floor: 0,
179 };
180 for cell in self.grid().filter_map(|row| row.get(index)) {
181 let measured = cell.measure(context);
182 let floor = match framing {
183 Framing::Framed if column.overflow != Overflow::Fold => cell
184 .measure_as(context, Overflow::Fold)
185 .minimum()
186 .min(measured.minimum()),
187 Framing::Framed | Framing::Unframed => measured.minimum(),
188 };
189 extent.natural = extent.natural.max(measured.maximum());
190 extent.minimum = extent.minimum.max(measured.minimum());
191 extent.floor = extent.floor.max(floor);
192 }
193 extent
194 })
195 .collect()
196 }
197
198 fn render_cells(&self, widths: &[u16], context: &Context) -> Vec<Vec<Vec<Line>>> {
200 self.grid()
201 .map(|row| {
202 row.iter()
203 .zip(widths)
204 .zip(&self.columns)
205 .map(|((cell, width), column)| {
206 let context = context.at_width(*width);
207 cell.render_as(&context, self.cell_overflow(column))
208 })
209 .collect()
210 })
211 .collect()
212 }
213
214 fn row_lines(&self, cells: &[Vec<Line>], widths: &[u16], glyphs: &Glyphs) -> Vec<Line> {
215 let height = cells.iter().map(Vec::len).max().unwrap_or(0).max(1);
216 let empty = Line::new();
217 let framing = self.frame.framing();
218 let (edge, gap, padding) = match framing {
219 Framing::Framed => (
220 glyphs.outer.to_string(),
221 glyphs.inner.to_string(),
222 " ".to_owned(),
223 ),
224 Framing::Unframed => (String::new(), " ".repeat(usize::from(GAP)), String::new()),
225 };
226 (0..height)
227 .map(|index| {
228 let mut line = Line::from(literal(&edge));
229 for (column, (width, spec)) in widths.iter().zip(&self.columns).enumerate() {
230 if column > 0 {
231 line.push(literal(&gap));
232 }
233 line.push(literal(&padding));
234 let cell = cells
235 .get(column)
236 .and_then(|lines| lines.get(index))
237 .unwrap_or(&empty);
238 push_cell(&mut line, cell, *width, spec.align);
239 line.push(literal(&padding));
240 }
241 match framing {
242 Framing::Framed => line.push(literal(&edge)),
243 Framing::Unframed => line.trim_end(),
244 }
245 line
246 })
247 .collect()
248 }
249
250 fn compose(&self, cells: &[Vec<Vec<Line>>], widths: &[u16], glyphs: &Glyphs) -> Vec<Line> {
251 let header = self.header.is_some();
252 let framing = self.frame.framing();
253 let total = widths
254 .iter()
255 .fold(self.frame_width(), |sum, width| sum.saturating_add(*width));
256 let blank_cells = vec![Vec::new(); widths.len()];
257 let mut lines = Vec::new();
258 match framing {
259 Framing::Framed => lines.push(glyphs.top.line(widths)),
260 Framing::Unframed => {}
261 }
262 for (index, row) in cells.iter().enumerate() {
263 if header && index == 1 {
264 match self.frame {
265 Frame::Grid => lines.push(glyphs.header.line(widths)),
266 Frame::HeaderRule => lines.push(rule_line(glyphs.rule, total)),
267 Frame::Plain => {}
268 }
269 } else if index > 0 {
270 match (self.separation, framing) {
271 (RowSeparation::Rule, Framing::Framed) => lines.push(glyphs.row.line(widths)),
272 (RowSeparation::Rule, Framing::Unframed) => {
273 lines.push(rule_line(glyphs.rule, total));
274 }
275 (RowSeparation::Blank, Framing::Framed | Framing::Unframed) => {
276 lines.extend(self.row_lines(&blank_cells, widths, glyphs));
277 }
278 (RowSeparation::None, Framing::Framed | Framing::Unframed) => {}
279 }
280 }
281 lines.extend(self.row_lines(row, widths, glyphs));
282 }
283 match framing {
284 Framing::Framed => lines.push(glyphs.bottom.line(widths)),
285 Framing::Unframed => {}
286 }
287 lines
288 }
289}
290
291fn narrowed(cells: &[Vec<Vec<Line>>], widths: &[u16]) -> Vec<u16> {
295 widths
296 .iter()
297 .enumerate()
298 .map(|(index, width)| {
299 cells
300 .iter()
301 .filter_map(|row| row.get(index))
302 .flatten()
303 .map(Line::width)
304 .max()
305 .unwrap_or(0)
306 .min(*width)
307 })
308 .collect()
309}
310
311impl Renderable for Table {
312 fn measure(&self, context: &Context) -> Measurement {
313 if self.is_blank() {
314 return Measurement::exact(0);
315 }
316 let extents = self.extents(context);
317 let frame = u64::from(self.frame_width());
318 let sum = |value: fn(&Extent) -> u16| {
319 extents.iter().fold(frame, |total, extent| {
320 total.saturating_add(u64::from(value(extent)))
321 })
322 };
323 let narrowest = match self.frame.framing() {
324 Framing::Framed => sum(|extent| extent.floor),
325 Framing::Unframed => sum(|extent| extent.minimum),
326 };
327 let natural = sum(|extent| extent.natural);
328 Measurement::new(
329 u16::try_from(narrowest).unwrap_or(u16::MAX),
330 u16::try_from(natural).unwrap_or(u16::MAX),
331 )
332 }
333
334 fn render(&self, context: &Context) -> Vec<Line> {
335 if self.is_blank() {
336 return Vec::new();
337 }
338 let extents = self.extents(context);
339 let available = context.width().saturating_sub(self.frame_width());
340 let widths = solve(&extents, available, self.frame.framing());
341 let cells = self.render_cells(&widths, context);
342 let widths = narrowed(&cells, &widths);
343 let glyphs = Glyphs::for_chars(context.box_chars());
344 self.compose(&cells, &widths, &glyphs)
345 }
346}