1use crate::{
2 grid::{
3 color::AnsiColor,
4 config::{self, ColoredConfig, SpannedConfig},
5 dimension::{Dimension, Estimate},
6 records::{ExactRecords, Records},
7 },
8 settings::{
9 object::{FirstRow, LastRow},
10 Color, TableOption,
11 },
12};
13
14use super::Offset;
15
16/// [`BorderText`] writes a custom text on a border.
17///
18/// # Example
19///
20/// ```rust
21/// use tabled::{Table, settings::style::BorderText, settings::object::Rows};
22///
23/// let mut table = Table::new(["Hello World"]);
24/// table
25/// .with(BorderText::new("+-.table").horizontal(Rows::first()));
26///
27/// assert_eq!(
28/// table.to_string(),
29/// "+-.table------+\n\
30/// | &str |\n\
31/// +-------------+\n\
32/// | Hello World |\n\
33/// +-------------+"
34/// );
35/// ```
36#[derive(Debug)]
37pub struct BorderText<L> {
38 text: String,
39 offset: Offset,
40 color: Option<AnsiColor<'static>>,
41 line: L,
42}
43
44impl BorderText<()> {
45 /// Creates a [`BorderText`] instance.
46 ///
47 /// Lines are numbered from 0 to the `count_rows` included
48 /// (`line >= 0 && line <= count_rows`).
49 pub fn new<S: Into<String>>(text: S) -> Self {
50 BorderText {
51 text: text.into(),
52 line: (),
53 offset: Offset::Begin(0),
54 color: None,
55 }
56 }
57}
58
59impl<Line> BorderText<Line> {
60 /// Set a line on which we will set the text.
61 pub fn horizontal<L>(self, line: L) -> BorderText<L> {
62 BorderText {
63 line,
64 text: self.text,
65 offset: self.offset,
66 color: self.color,
67 }
68 }
69
70 /// Set an offset from which the text will be started.
71 pub fn offset(self, offset: Offset) -> Self {
72 BorderText {
73 offset,
74 text: self.text,
75 line: self.line,
76 color: self.color,
77 }
78 }
79
80 /// Set a color of the text.
81 pub fn color(self, color: Color) -> Self {
82 BorderText {
83 color: Some(color.into()),
84 text: self.text,
85 line: self.line,
86 offset: self.offset,
87 }
88 }
89}
90
91impl<R, D> TableOption<R, D, ColoredConfig> for BorderText<usize>
92where
93 R: Records + ExactRecords,
94 for<'a> &'a R: Records,
95 for<'a> D: Estimate<&'a R, ColoredConfig>,
96 D: Dimension,
97{
98 fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) {
99 dims.estimate(records, config:cfg);
100 let shape: (usize, usize) = (records.count_rows(), records.count_columns());
101 let line: usize = self.line;
102 set_horizontal_chars(cfg, dims, self.offset, line, &self.text, &self.color, shape);
103 }
104}
105
106impl<R, D> TableOption<R, D, ColoredConfig> for BorderText<FirstRow>
107where
108 R: Records + ExactRecords,
109 for<'a> &'a R: Records,
110 for<'a> D: Estimate<&'a R, ColoredConfig>,
111 D: Dimension,
112{
113 fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) {
114 dims.estimate(records, config:cfg);
115 let shape: (usize, usize) = (records.count_rows(), records.count_columns());
116 let line: usize = 0;
117 set_horizontal_chars(cfg, dims, self.offset, line, &self.text, &self.color, shape);
118 }
119}
120
121impl<R, D> TableOption<R, D, ColoredConfig> for BorderText<LastRow>
122where
123 R: Records + ExactRecords,
124 for<'a> &'a R: Records,
125 for<'a> D: Estimate<&'a R, ColoredConfig>,
126 D: Dimension,
127{
128 fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) {
129 dims.estimate(records, config:cfg);
130 let shape: (usize, usize) = (records.count_rows(), records.count_columns());
131 let line: usize = records.count_rows();
132 set_horizontal_chars(cfg, dims, self.offset, line, &self.text, &self.color, shape);
133 }
134}
135
136fn set_horizontal_chars<D: Dimension>(
137 cfg: &mut SpannedConfig,
138 dims: &D,
139 offset: Offset,
140 line: usize,
141 text: &str,
142 color: &Option<AnsiColor<'static>>,
143 shape: (usize, usize),
144) {
145 let (_, count_columns) = shape;
146 let pos = get_start_pos(cfg, dims, offset, count_columns);
147 let pos = match pos {
148 Some(pos) => pos,
149 None => return,
150 };
151
152 let mut chars = text.chars();
153 let mut i = cfg.has_vertical(0, count_columns) as usize;
154 if i == 1 && pos == 0 {
155 let c = match chars.next() {
156 Some(c) => c,
157 None => return,
158 };
159
160 let mut b = cfg.get_border((line, 0), shape);
161 b.left_top_corner = b.left_top_corner.map(|_| c);
162 cfg.set_border((line, 0), b);
163
164 if let Some(color) = color.as_ref() {
165 let mut b = cfg.get_border_color((line, 0), shape).cloned();
166 b.left_top_corner = Some(color.clone());
167 cfg.set_border_color((line, 0), b);
168 }
169 }
170
171 for col in 0..count_columns {
172 let w = dims.get_width(col);
173 if i + w > pos {
174 for off in 0..w {
175 if i + off < pos {
176 continue;
177 }
178
179 let c = match chars.next() {
180 Some(c) => c,
181 None => return,
182 };
183
184 cfg.set_horizontal_char((line, col), c, config::Offset::Begin(off));
185 if let Some(color) = color.as_ref() {
186 cfg.set_horizontal_color(
187 (line, col),
188 color.clone(),
189 config::Offset::Begin(off),
190 );
191 }
192 }
193 }
194
195 i += w;
196
197 if cfg.has_vertical(col + 1, count_columns) {
198 i += 1;
199
200 if i > pos {
201 let c = match chars.next() {
202 Some(c) => c,
203 None => return,
204 };
205
206 let mut b = cfg.get_border((line, col), shape);
207 b.right_top_corner = b.right_top_corner.map(|_| c);
208 cfg.set_border((line, col), b);
209
210 if let Some(color) = color.as_ref() {
211 let mut b = cfg.get_border_color((line, col), shape).cloned();
212 b.right_top_corner = Some(color.clone());
213 cfg.set_border_color((line, col), b);
214 }
215 }
216 }
217 }
218}
219
220fn get_start_pos<D: Dimension>(
221 cfg: &SpannedConfig,
222 dims: &D,
223 offset: Offset,
224 count_columns: usize,
225) -> Option<usize> {
226 let totalw: usize = total_width(cfg, dims, count_columns);
227 match offset {
228 Offset::Begin(i: usize) => {
229 if i > totalw {
230 None
231 } else {
232 Some(i)
233 }
234 }
235 Offset::End(i: usize) => {
236 if i > totalw {
237 None
238 } else {
239 Some(totalw - i)
240 }
241 }
242 }
243}
244
245fn total_width<D: Dimension>(cfg: &SpannedConfig, dims: &D, count_columns: usize) -> usize {
246 let mut totalw: usize = cfg.has_vertical(col:0, count_columns) as usize;
247 for col: usize in 0..count_columns {
248 totalw += dims.get_width(column:col);
249 totalw += cfg.has_vertical(col:col + 1, count_columns) as usize;
250 }
251
252 totalw
253}
254