1use crate::{
2 cell::{Cell, HAlign, VAlign, DEFAULT_BLANK_CHAR, DEFAULT_H_ALIGN, DEFAULT_V_ALIGN},
3 options::Options,
4};
5
6use std::borrow::Cow;
7
8pub struct Row {
12 pub default_options: Options,
17
18 pub column_width: Option<usize>,
22
23 pub padding_size: Option<usize>,
25
26 pub cells: Vec<Cell>,
30}
31
32impl Row {
33 pub fn new(cells: Vec<Cell>) -> Self {
37 Self {
38 default_options: Options {
39 col_span: None,
40 h_align: None,
41 v_align: None,
42 blank_char: None,
43 },
44 column_width: None,
45 padding_size: None,
46 cells,
47 }
48 }
49
50 pub fn new_empty(col_span: usize) -> Self {
52 Row::new(vec![Cell::new_empty(col_span)])
53 }
54
55 pub fn new_fill(content: String, col_span: usize) -> Self {
57 Row::new(vec![Cell::new_fill(content, col_span)])
58 }
59
60 pub fn builder(cells: Vec<Cell>) -> RowBuilder {
66 RowBuilder {
67 inner: Self::new(cells),
68 }
69 }
70
71 pub fn render(
75 &self,
76 f: &mut std::fmt::Formatter<'_>,
77 default_options: &Options,
78 column_width: Option<usize>,
79 padding_size: Option<usize>,
80 ) -> std::fmt::Result {
81 let column_width = column_width.or(self.column_width).unwrap_or(1);
82 let padding_size = padding_size.or(self.padding_size).unwrap_or(1);
83 let mut cols_lines = self
84 .cells
85 .iter()
86 .map(|c| {
87 let mut lines = c.content.lines().map(|l| l.to_owned()).collect::<Vec<_>>();
88 if lines.is_empty() {
89 lines.push(String::new());
90 }
91 lines
92 })
93 .collect::<Vec<_>>();
94 let max_lines = cols_lines
95 .iter()
96 .map(|col_lines| col_lines.len())
97 .max()
98 .unwrap_or(0);
99 for line_index in 0..max_lines {
100 for (col_index, col) in self.cells.iter().enumerate() {
101 let col_lines = &mut cols_lines[col_index];
102 let col_span = col
103 .col_span
104 .or(self.default_options.col_span)
105 .or(default_options.col_span)
106 .unwrap_or(1);
107 let col_width = col_span * column_width + padding_size * (col_span - 1);
108 let h_align = col
109 .h_align
110 .or(self.default_options.h_align)
111 .or(default_options.h_align)
112 .unwrap_or(DEFAULT_H_ALIGN);
113 let v_align = col
114 .v_align
115 .or(self.default_options.v_align)
116 .or(default_options.v_align)
117 .unwrap_or(DEFAULT_V_ALIGN);
118 let blank_char = col
119 .blank_char
120 .or(self.default_options.blank_char)
121 .or(default_options.blank_char)
122 .unwrap_or(DEFAULT_BLANK_CHAR);
123 if col_index != 0 {
124 write!(f, "{s:<0$}", padding_size, s = "")?;
125 }
126 write!(
127 f,
128 "{}",
129 col_line(
130 h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
131 )
132 )?;
133 }
134 writeln!(f)?;
135 }
136 Ok(())
137 }
138}
139
140impl std::fmt::Display for Row {
141 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142 self.render(
143 f,
144 &self.default_options,
145 self.column_width,
146 self.padding_size,
147 )
148 }
149}
150
151fn col_line(
152 h_align: HAlign,
153 v_align: VAlign,
154 col_width: usize,
155 col_lines: &mut [String],
156 max_lines: usize,
157 line_index: usize,
158 blank_char: char,
159) -> Cow<str> {
160 let index = match v_align {
161 VAlign::Top => {
162 let start_line_index = 0;
163 let end_line_index = col_lines.len() - 1;
164 if line_index >= start_line_index && line_index <= end_line_index {
165 Some(line_index)
166 } else {
167 None
168 }
169 }
170 VAlign::Bottom => {
171 let start_line_index = max_lines - col_lines.len();
172 let end_line_index = max_lines - 1;
173 if line_index >= start_line_index && line_index <= end_line_index {
174 Some(line_index - start_line_index)
175 } else {
176 None
177 }
178 }
179 VAlign::Middle => {
180 let start_line_index = (max_lines - col_lines.len()) / 2;
181 let end_line_index = start_line_index + col_lines.len() - 1;
182 if line_index >= start_line_index && line_index <= end_line_index {
183 Some(line_index - start_line_index)
184 } else {
185 None
186 }
187 }
188 };
189 if let Some(i) = index {
190 return pad(h_align, &mut col_lines[i], col_width, blank_char);
191 }
192 Cow::Owned(blank_char.to_string().repeat(col_width))
193}
194
195fn pad(h_align: HAlign, s: &mut String, width: usize, blank_char: char) -> Cow<str> {
196 let s_chars_len = s.chars().count();
197 if s_chars_len >= width {
198 let bytes_index = byte_index(s, width);
199 return s[..bytes_index].into();
200 }
201 let blanks = width - s_chars_len;
202 match h_align {
203 HAlign::Left => {
204 s.extend(std::iter::repeat(blank_char).take(blanks));
205 s.as_str().into()
206 }
207 HAlign::Right => {
208 let mut new_str = std::iter::repeat(blank_char)
209 .take(blanks)
210 .collect::<String>();
211 new_str.push_str(s);
212 new_str.into()
213 }
214 HAlign::Center => {
215 let left_blanks = blanks / 2;
216 let right_blanks = blanks - left_blanks;
217 let mut new_str = std::iter::repeat(blank_char)
218 .take(left_blanks)
219 .collect::<String>();
220 new_str.push_str(s);
221 new_str.extend(std::iter::repeat(blank_char).take(right_blanks));
222 new_str.into()
223 }
224 HAlign::Fill => {
225 let repeats = width / s_chars_len + 1;
226 let s = s.repeat(repeats);
227 let bytes_index = byte_index(&s, width);
228 s[..bytes_index].to_owned().into()
229 }
230 }
231}
232
233fn byte_index(s: &str, char_index: usize) -> usize {
234 s.char_indices()
235 .take(char_index)
236 .last()
237 .map_or(0, |(i, ch)| i + ch.len_utf8())
238}
239
240pub struct RowBuilder {
244 inner: Row,
245}
246
247impl RowBuilder {
248 pub fn build(self) -> Row {
253 self.inner
254 }
255
256 pub fn default_colspan(mut self, default_col_span: usize) -> Self {
259 self.inner.default_options.col_span = Some(default_col_span);
260 self
261 }
262
263 pub fn default_h_align(mut self, default_h_align: HAlign) -> Self {
266 self.inner.default_options.h_align = Some(default_h_align);
267 self
268 }
269
270 pub fn default_v_align(mut self, default_v_align: VAlign) -> Self {
273 self.inner.default_options.v_align = Some(default_v_align);
274 self
275 }
276
277 pub fn default_blank_char(mut self, default_blank_char: char) -> Self {
280 self.inner.default_options.blank_char = Some(default_blank_char);
281 self
282 }
283
284 pub fn column_width(mut self, column_width: usize) -> Self {
288 self.inner.column_width = Some(column_width);
289 self
290 }
291
292 pub fn padding_size(mut self, padding_size: usize) -> Self {
296 self.inner.padding_size = Some(padding_size);
297 self
298 }
299
300 pub fn cells(mut self, cells: Vec<Cell>) -> Self {
304 self.inner.cells = cells;
305 self
306 }
307}
308
309#[cfg(test)]
310mod tests {
311 use super::*;
312
313 #[test]
314 fn test_byte_index_ascii() {
315 let s = "abc";
316 let result = byte_index(s, 2);
317 let expected = 2;
318 assert_eq!(result, expected);
319 }
320
321 #[test]
322 fn test_byte_index_unicode1() {
323 let s = "aµc";
324 let result = byte_index(s, 2);
325 let expected = 3;
326 assert_eq!(result, expected);
327 }
328
329 #[test]
330 fn test_byte_index_unicode2() {
331 let s = "µ∆c";
332 let result = byte_index(s, 2);
333 let expected = 5;
334 assert_eq!(result, expected);
335 }
336
337 #[test]
338 fn test_pad_left_empty() {
339 let s = &mut "".into();
340 let result = pad(HAlign::Right, s, 3, '.');
341 let expected = String::from("...");
342 assert_eq!(result, expected);
343 }
344
345 #[test]
346 fn test_pad_right_empty() {
347 let s = &mut "".into();
348 let result = pad(HAlign::Left, s, 3, '.');
349 let expected = String::from("...");
350 assert_eq!(result, expected);
351 }
352
353 #[test]
354 fn test_pad_left() {
355 let s = &mut "a".into();
356 let result = pad(HAlign::Right, s, 3, '.');
357 let expected = String::from("..a");
358 assert_eq!(result, expected);
359 }
360
361 #[test]
362 fn test_pad_right() {
363 let s = &mut "a".into();
364 let result = pad(HAlign::Left, s, 3, '.');
365 let expected = String::from("a..");
366 assert_eq!(result, expected);
367 }
368
369 #[test]
370 fn test_pad_center() {
371 let s = &mut "a".into();
372 let result = pad(HAlign::Center, s, 3, '.');
373 let expected = String::from(".a.");
374 assert_eq!(result, expected);
375 }
376
377 #[test]
378 fn test_pad_fill1() {
379 let s = &mut "a".into();
380 let result = pad(HAlign::Fill, s, 3, '.');
381 let expected = String::from("aaa");
382 assert_eq!(result, expected);
383 }
384
385 #[test]
386 fn test_pad_fill2() {
387 let s = &mut "ab".into();
388 let result = pad(HAlign::Fill, s, 3, '.');
389 let expected = String::from("aba");
390 assert_eq!(result, expected);
391 }
392
393 #[test]
394 fn test_pad_left_unicode() {
395 let s = &mut "∆".into();
396 let result = pad(HAlign::Right, s, 3, '.');
397 let expected = String::from("..∆");
398 assert_eq!(result, expected);
399 }
400
401 #[test]
402 fn test_pad_right_unicode() {
403 let s = &mut "∆".into();
404 let result = pad(HAlign::Left, s, 3, '.');
405 let expected = String::from("∆..");
406 assert_eq!(result, expected);
407 }
408
409 #[test]
410 fn test_pad_center_unicode() {
411 let s = &mut "∆".into();
412 let result = pad(HAlign::Center, s, 3, '.');
413 let expected = String::from(".∆.");
414 assert_eq!(result, expected);
415 }
416
417 #[test]
418 fn test_pad_fill_unicode() {
419 let s = &mut "∆".into();
420 let result = pad(HAlign::Fill, s, 3, '.');
421 let expected = String::from("∆∆∆");
422 assert_eq!(result, expected);
423 }
424
425 #[test]
426 fn test_col_line_for_a_content_with_1_row_in_a_3_lines_column_left_top() {
427 let h_align = HAlign::Left;
428 let v_align = VAlign::Top;
429 let col_width = 3;
430 let col_lines = &mut [String::from("a")];
431 let max_lines = 3;
432 let blank_char = '.';
433
434 let line_index = 0;
435 let result = col_line(
436 h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
437 );
438 let expected = "a..";
439 assert_eq!(result, expected);
440
441 let line_index = 1;
442 let result = col_line(
443 h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
444 );
445 let expected = "...";
446 assert_eq!(result, expected);
447
448 let line_index = 2;
449 let result = col_line(
450 h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
451 );
452 let expected = "...";
453 assert_eq!(result, expected);
454 }
455
456 #[test]
457 fn test_col_line_for_a_content_with_1_row_in_a_3_lines_column_left_middle() {
458 let h_align = HAlign::Left;
459 let v_align = VAlign::Middle;
460 let col_width = 3;
461 let col_lines = &mut [String::from("a")];
462 let max_lines = 3;
463 let blank_char = '.';
464
465 let line_index = 0;
466 let result = col_line(
467 h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
468 );
469 let expected = "...";
470 assert_eq!(result, expected);
471
472 let line_index = 1;
473 let result = col_line(
474 h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
475 );
476 let expected = "a..";
477 assert_eq!(result, expected);
478
479 let line_index = 2;
480 let result = col_line(
481 h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
482 );
483 let expected = "...";
484 assert_eq!(result, expected);
485 }
486
487 #[test]
488 fn test_col_line_for_a_content_with_1_row_in_a_3_lines_column_left_bottom() {
489 let h_align = HAlign::Left;
490 let v_align = VAlign::Bottom;
491 let col_width = 3;
492 let col_lines = &mut [String::from("a")];
493 let max_lines = 3;
494 let blank_char = '.';
495
496 let line_index = 0;
497 let result = col_line(
498 h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
499 );
500 let expected = "...";
501 assert_eq!(result, expected);
502
503 let line_index = 1;
504 let result = col_line(
505 h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
506 );
507 let expected = "...";
508 assert_eq!(result, expected);
509
510 let line_index = 2;
511 let result = col_line(
512 h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
513 );
514 let expected = "a..";
515 assert_eq!(result, expected);
516 }
517
518 #[test]
519 fn test_col_line_for_a_content_with_2_row_in_a_4_lines_column_left_top() {
520 let h_align = HAlign::Left;
521 let v_align = VAlign::Top;
522 let col_width = 3;
523 let col_lines = &mut [String::from("a"), String::from("b")];
524 let max_lines = 4;
525 let blank_char = '.';
526
527 let line_index = 0;
528 let result = col_line(
529 h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
530 );
531 let expected = "a..";
532 assert_eq!(result, expected);
533
534 let line_index = 1;
535 let result = col_line(
536 h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
537 );
538 let expected = "b..";
539 assert_eq!(result, expected);
540
541 let line_index = 2;
542 let result = col_line(
543 h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
544 );
545 let expected = "...";
546 assert_eq!(result, expected);
547
548 let line_index = 3;
549 let result = col_line(
550 h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
551 );
552 let expected = "...";
553 assert_eq!(result, expected);
554 }
555
556 #[test]
557 fn test_col_line_for_a_content_with_2_row_in_a_4_lines_column_left_middle() {
558 let h_align = HAlign::Left;
559 let v_align = VAlign::Middle;
560 let col_width = 3;
561 let col_lines = &mut [String::from("a"), String::from("b")];
562 let max_lines = 4;
563 let blank_char = '.';
564
565 let line_index = 0;
566 let result = col_line(
567 h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
568 );
569 let expected = "...";
570 assert_eq!(result, expected);
571
572 let line_index = 1;
573 let result = col_line(
574 h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
575 );
576 let expected = "a..";
577 assert_eq!(result, expected);
578
579 let line_index = 2;
580 let result = col_line(
581 h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
582 );
583 let expected = "b..";
584 assert_eq!(result, expected);
585
586 let line_index = 3;
587 let result = col_line(
588 h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
589 );
590 let expected = "...";
591 assert_eq!(result, expected);
592 }
593
594 #[test]
595 fn test_col_line_for_a_content_with_2_row_in_a_4_lines_column_left_bottom() {
596 let h_align = HAlign::Left;
597 let v_align = VAlign::Bottom;
598 let col_width = 3;
599 let col_lines = &mut [String::from("a"), String::from("b")];
600 let max_lines = 4;
601 let blank_char = '.';
602
603 let line_index = 0;
604 let result = col_line(
605 h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
606 );
607 let expected = "...";
608 assert_eq!(result, expected);
609
610 let line_index = 1;
611 let result = col_line(
612 h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
613 );
614 let expected = "...";
615 assert_eq!(result, expected);
616
617 let line_index = 2;
618 let result = col_line(
619 h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
620 );
621 let expected = "a..";
622 assert_eq!(result, expected);
623
624 let line_index = 3;
625 let result = col_line(
626 h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
627 );
628 let expected = "b..";
629 assert_eq!(result, expected);
630 }
631}