1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
use {
crate::{
composite::*,
fit::{
wrap,
TblFit,
},
line::FmtLine,
skin::MadSkin,
spacing::Spacing,
},
minimad::{
Alignment,
TableRow,
},
};
/// Wrap a standard table row
#[derive(Debug)]
pub struct FmtTableRow<'s> {
pub cells: Vec<FmtComposite<'s>>,
}
/// Top, Bottom, or other
#[derive(Debug)]
pub enum RelativePosition {
Top,
Other, // or unknown
Bottom,
}
/// A separator or alignment rule in a table.
///
/// Represent this kind of lines in tables:
/// |----|:-:|--
#[derive(Debug)]
pub struct FmtTableRule {
pub position: RelativePosition, // position relative to the table
pub widths: Vec<usize>,
pub aligns: Vec<Alignment>,
}
impl FmtTableRule {
pub fn set_nbcols(&mut self, nbcols: usize) {
self.widths.truncate(nbcols);
self.aligns.truncate(nbcols);
for ic in 0..nbcols {
if ic >= self.widths.len() {
self.widths.push(0);
}
if ic >= self.aligns.len() {
self.aligns.push(Alignment::Unspecified);
}
}
}
}
impl<'s> FmtTableRow<'s> {
pub fn from(table_row: TableRow<'s>, skin: &MadSkin) -> FmtTableRow<'s> {
let mut table_row = table_row;
FmtTableRow {
cells: table_row
.cells
.drain(..)
.map(|composite| FmtComposite::from(composite, skin))
.collect(),
}
}
}
/// Tables are the sequences of lines whose line style is TableRow.
///
/// A table is just the indices, without the text
/// This structure isn't public because the indices are invalid as
/// soon as rows are inserted. It only serves during the formatting
/// process.
struct Table {
start: usize,
height: usize, // number of lines
nbcols: usize, // number of columns
}
#[allow(clippy::needless_range_loop)]
impl Table {
pub fn fix_columns(&mut self, lines: &mut Vec<FmtLine<'_>>, width: usize, skin: &MadSkin) {
let mut nbcols = self.nbcols;
if nbcols == 0 || width == 0 {
return;
}
let mut cols_removed = false;
// we add the missing cells and also prepare the fitter
// We also add the missing cells
let widths = match TblFit::new(nbcols, width) {
Ok(mut tbl_fit) => {
for line in lines.iter_mut().skip(self.start).take(self.height) {
if let FmtLine::TableRow(FmtTableRow { cells }) = line {
for ic in 0..nbcols {
if cells.len() <= ic {
cells.push(FmtComposite::new());
} else {
tbl_fit.see_cell(ic, cells[ic].visible_length);
}
}
} else if let FmtLine::TableRule(rule) = line {
rule.set_nbcols(nbcols);
} else {
panic!("not a table row, should not happen");
}
}
tbl_fit.fit().col_widths
}
Err(_) => {
// there's not enough width, we'll have to remove columns
nbcols = (width - 1) / 4;
cols_removed = true;
vec![3; nbcols]
}
};
// At this step, all widths are at least 3 wide
// Now we resize all cells and we insert new rows if necessary.
// We iterate in reverse order so that we can insert rows
// without recomputing row indices.
for ir in (self.start..self.start + self.height).rev() {
let line = &mut lines[ir];
if let FmtLine::TableRow(FmtTableRow { cells }) = line {
let mut cells_to_add: Vec<Vec<FmtComposite<'_>>> = Vec::new();
cells.truncate(nbcols);
for ic in 0..nbcols {
if cells.len() <= ic {
//FIXME isn't this already done ?
cells.push(FmtComposite::new());
continue;
}
cells_to_add.push(Vec::new());
if cells[ic].visible_length > widths[ic] {
// we must wrap the cell over several lines
let mut composites =
wrap::hard_wrap_composite(&cells[ic], widths[ic], skin)
.expect("tbl fitter guaranteed all columns to be wide enough");
// the first composite replaces the cell, while the other
// ones go to cells_to_add
let mut drain = composites.drain(..);
cells[ic] = drain.next().unwrap();
for c in drain {
cells_to_add[ic].push(c);
}
}
}
let nb_new_lines = cells_to_add.iter().fold(0, |m, cells| m.max(cells.len()));
for inl in (0..nb_new_lines).rev() {
let mut new_cells: Vec<FmtComposite<'_>> = Vec::new();
for cell in cells_to_add.iter_mut().take(nbcols) {
new_cells.push(if cell.len() > inl {
cell.remove(inl)
} else {
FmtComposite::new()
});
}
let new_line = FmtLine::TableRow(FmtTableRow { cells: new_cells });
lines.insert(ir + 1, new_line);
self.height += 1;
}
}
}
// Finally we iterate in normal order to specify alignment
// (the alignments of a row are the ones of the last rule line)
let mut current_aligns: Vec<Alignment> = vec![Alignment::Center; nbcols];
for ir in self.start..self.start + self.height {
let line = &mut lines[ir];
match line {
FmtLine::TableRow(FmtTableRow { cells }) => {
for ic in 0..nbcols {
cells[ic].spacing = Some(Spacing {
width: widths[ic],
align: current_aligns[ic],
});
}
}
FmtLine::TableRule(rule) => {
if cols_removed {
rule.set_nbcols(nbcols);
}
if ir == self.start {
rule.position = RelativePosition::Top;
} else if ir == self.start + self.height - 1 {
rule.position = RelativePosition::Bottom;
}
rule.widths[..nbcols].clone_from_slice(&widths[..nbcols]);
current_aligns[..nbcols].clone_from_slice(&rule.aligns[..nbcols]);
}
_ => {
panic!("It should be a table part");
}
}
}
}
}
/// find the positions of all tables
fn find_tables(lines: &[FmtLine<'_>]) -> Vec<Table> {
let mut tables: Vec<Table> = Vec::new();
let mut current: Option<Table> = None;
for (idx, line) in lines.iter().enumerate() {
match line {
FmtLine::TableRule(FmtTableRule { aligns, .. }) => match current.as_mut() {
Some(b) => {
b.height += 1;
b.nbcols = b.nbcols.max(aligns.len());
}
None => {
current = Some(Table {
start: idx,
height: 1,
nbcols: aligns.len(),
});
}
},
FmtLine::TableRow(FmtTableRow { cells }) => match current.as_mut() {
Some(b) => {
b.height += 1;
b.nbcols = b.nbcols.max(cells.len());
}
None => {
current = Some(Table {
start: idx,
height: 1,
nbcols: cells.len(),
});
}
},
_ => {
if let Some(c) = current.take() {
tables.push(c);
}
}
}
}
if let Some(c) = current.take() {
tables.push(c);
}
tables
}
/// Modify the rows of all tables in order to ensure it fits the widths
/// and all cells have the widths of their column.
///
/// Some lines may be added to the table in the process, which means any
/// precedent indexing might be invalid.
pub fn fix_all_tables(lines: &mut Vec<FmtLine<'_>>, width: usize, skin: &MadSkin) {
for tbl in find_tables(lines).iter_mut().rev() {
tbl.fix_columns(lines, width, skin);
}
}