extern crate strip_ansi_escapes;
extern crate unicode_segmentation;
#[cfg(feature = "nbsp")]
#[macro_use]
extern crate lazy_static;
#[cfg(feature = "nbsp")]
extern crate regex;
#[cfg(feature = "nbsp")]
use regex::Regex;
use std::fmt;
use unicode_segmentation::UnicodeSegmentation;
#[derive(Debug)]
pub enum ColonnadeError {
InconsistentColumns(usize, usize, usize), OutOfBounds,
InsufficientColumns,
InsufficientSpace,
MinGreaterThanMax(usize), }
impl std::fmt::Display for ColonnadeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
impl std::error::Error for ColonnadeError {}
#[derive(Debug, Clone)]
pub enum Alignment {
Left,
Right,
Center,
Justify,
}
#[derive(Debug, Clone, PartialEq)]
pub enum VerticalAlignment {
Top,
Middle,
Bottom,
}
#[derive(Debug, Clone)]
pub struct Column {
index: usize,
alignment: Alignment,
vertical_alignment: VerticalAlignment,
left_margin: usize,
pub width: usize,
priority: usize,
min_width: Option<usize>,
max_width: Option<usize>,
padding_left: usize,
padding_right: usize,
padding_top: usize,
padding_bottom: usize,
hyphenate: bool,
adjusted: bool,
}
impl Column {
fn default(index: usize) -> Column {
Column {
index: index,
alignment: Alignment::Left,
vertical_alignment: VerticalAlignment::Top,
left_margin: 1,
width: 0, priority: usize::max_value(),
min_width: None,
max_width: None,
padding_left: 0,
padding_right: 0,
padding_top: 0,
padding_bottom: 0,
hyphenate: true,
adjusted: false,
}
}
fn horizontal_padding(&self) -> usize {
self.padding_left + self.padding_right
}
fn vertical_padding(&self) -> usize {
self.padding_top + self.padding_bottom
}
fn minimum_width(&self) -> usize {
let w1 = self.horizontal_padding();
let w2 = self.min_width.unwrap_or(w1);
if w2 > w1 {
w2
} else {
w1
}
}
fn effective_width(&self) -> usize {
let w = if self.max_width.unwrap_or(self.width) < self.width {
self.max_width.unwrap()
} else {
self.width
};
let m = self.minimum_width();
if m > w {
m
} else {
w
}
}
fn inner_width(&self) -> usize {
self.width - self.padding_right
}
fn hyphenating(&self) -> bool {
self.hyphenate && self.inner_width() > 1
}
fn is_shrinkable(&self) -> bool {
self.minimum_width() < self.width
}
fn shrink(&mut self, width: usize) {
let m = self.minimum_width();
self.width = if m > width { m } else { width }
}
fn shrink_by(&mut self, decrease: usize) -> bool {
if self.is_shrinkable() {
let decrease = if decrease >= self.width {
1
} else {
self.width - decrease
};
let before = self.width;
self.shrink(decrease);
before != self.width
} else {
false
}
}
fn is_expandable(&self) -> bool {
self.max_width.unwrap_or(usize::max_value()) > self.width
}
fn expand(&mut self, width: usize) -> bool {
if width <= self.width {
return false;
}
let change = if self.max_width.unwrap_or(width) < width {
self.max_width.unwrap()
} else if self.minimum_width() > width {
self.minimum_width()
} else {
width
};
let changed = self.width != change;
if changed {
self.width = change
}
changed
}
fn expand_by(&mut self, increase: usize) -> bool {
self.expand(self.width + increase)
}
fn outer_width(&self) -> usize {
self.left_margin + self.effective_width()
}
fn blank_line(&self) -> String {
" ".repeat(self.width)
}
fn margin(&self) -> String {
" ".repeat(self.left_margin)
}
pub fn priority(&mut self, priority: usize) -> &mut Self {
self.adjusted = false;
self.priority = priority;
self
}
pub fn max_width(&mut self, max_width: usize) -> Result<&mut Self, ColonnadeError> {
if self.min_width.unwrap_or(max_width) > max_width {
Err(ColonnadeError::MinGreaterThanMax(self.index))
} else {
self.max_width = Some(max_width);
self.adjusted = false;
Ok(self)
}
}
pub fn min_width(&mut self, min_width: usize) -> Result<&mut Self, ColonnadeError> {
if self.max_width.unwrap_or(min_width) < min_width {
return Err(ColonnadeError::MinGreaterThanMax(self.index));
}
self.width = min_width;
self.min_width = Some(min_width);
self.adjusted = false;
Ok(self)
}
pub fn fixed_width(&mut self, width: usize) -> Result<&mut Self, ColonnadeError> {
self.min_width = None;
self.max_width = None;
match self.min_width(width) {
Err(e) => return Err(e),
Ok(_) => (),
}
match self.max_width(width) {
Err(e) => return Err(e),
Ok(_) => (),
}
Ok(self)
}
pub fn clear_limits(&mut self) -> &mut Self {
self.max_width = None;
self.min_width = None;
self.adjusted = false;
self
}
pub fn alignment(&mut self, alignment: Alignment) -> &mut Self {
self.alignment = alignment;
self
}
pub fn vertical_alignment(&mut self, vertical_alignment: VerticalAlignment) -> &mut Self {
self.vertical_alignment = vertical_alignment;
self
}
pub fn left_margin(&mut self, left_margin: usize) -> &mut Self {
self.left_margin = left_margin;
self.adjusted = false;
self
}
pub fn padding(&mut self, padding: usize) -> &mut Self {
self.padding_left = padding;
self.padding_right = padding;
self.padding_top = padding;
self.padding_bottom = padding;
self.adjusted = false;
self
}
pub fn padding_horizontal(&mut self, padding: usize) -> &mut Self {
self.padding_left = padding;
self.padding_right = padding;
self.adjusted = false;
self
}
pub fn padding_left(&mut self, padding: usize) -> &mut Self {
self.padding_left = padding;
self.adjusted = false;
self
}
pub fn padding_right(&mut self, padding: usize) -> &mut Self {
self.padding_right = padding;
self.adjusted = false;
self
}
pub fn padding_vertical(&mut self, padding: usize) -> &mut Self {
self.padding_top = padding;
self.padding_bottom = padding;
self
}
pub fn padding_top(&mut self, padding: usize) -> &mut Self {
self.padding_top = padding;
self
}
pub fn padding_bottom(&mut self, padding: usize) -> &mut Self {
self.padding_bottom = padding;
self
}
pub fn hyphenate(&mut self, hyphenate: bool) -> &mut Self {
self.hyphenate = hyphenate;
self
}
}
#[derive(Debug, Clone)]
pub struct Colonnade {
pub columns: Vec<Column>,
width: usize,
spaces_between_rows: usize,
}
#[cfg(feature = "nbsp")]
fn to_words<'a>(s: &'a str) -> Vec<&'a str> {
lazy_static! {
static ref SPLITTABLE_SPACE: Regex = Regex::new(r"[\s&&[^\u00A0]]+").unwrap();
}
SPLITTABLE_SPACE
.split(s)
.filter(|s| s.len() > 0)
.collect::<Vec<&'a str>>()
}
#[cfg(not(feature = "nbsp"))]
fn to_words<'a>(s: &'a str) -> Vec<&'a str> {
s.split_whitespace()
.filter(|s| s.len() > 0)
.collect::<Vec<&'a str>>()
}
fn longest_word(s: &str) -> usize {
to_words(s).iter().fold(0, |acc, v| {
let c = true_width(v);
if c > acc {
c
} else {
acc
}
})
}
fn true_width(s: &str) -> usize {
UnicodeSegmentation::graphemes(s, true).count()
}
impl Colonnade {
pub fn new(columns: usize, width: usize) -> Result<Colonnade, ColonnadeError> {
if columns == 0 {
return Err(ColonnadeError::InsufficientColumns);
}
let mut columns: Vec<Column> = (0..columns).map(|i| Column::default(i)).collect();
columns[0].left_margin = 0;
let spec = Colonnade {
columns,
width,
spaces_between_rows: 0,
};
if !spec.sufficient_space() {
return Err(ColonnadeError::InsufficientSpace);
}
Ok(spec)
}
fn minimal_width(&self) -> usize {
self.columns
.iter()
.fold(0, |acc, v| acc + v.left_margin + v.min_width.unwrap_or(1)) }
fn sufficient_space(&self) -> bool {
self.minimal_width() <= self.width
}
fn required_width(&self) -> usize {
self.columns.iter().fold(0, |acc, v| acc + v.outer_width())
}
fn blank_line(&self) -> String {
" ".repeat(self.required_width())
}
fn maximum_vertical_padding(&self) -> usize {
let mut p = 0;
for c in &self.columns {
let p2 = c.vertical_padding();
if p2 > p {
p = p2;
}
}
p
}
fn len(&self) -> usize {
self.columns.len()
}
fn width_after_normalization(s: &str) -> usize {
let mut l = 0;
for w in to_words(s) {
if l != 0 {
l += 1;
}
l += true_width(w);
}
l
}
pub fn width(&self) -> Option<usize> {
if self.adjusted() {
Some(self.required_width())
} else {
None
}
}
fn priorities(&self) -> Vec<usize> {
let mut v = self.columns.iter().map(|c| c.priority).collect::<Vec<_>>();
v.sort_unstable();
v.dedup();
v.reverse();
v
}
pub fn tabulate<T, U, V, W, X>(&mut self, table: T) -> Result<Vec<String>, ColonnadeError>
where
T: IntoIterator<Item = U, IntoIter = V>,
U: IntoIterator<Item = W, IntoIter = X>,
V: Iterator<Item = U>,
W: ToString,
X: Iterator<Item = W>,
{
self.macerate(table)
.and_then(|buffer| Ok(Colonnade::reconstitute_rows(buffer)))
}
pub fn macerate<T, U, V, W, X>(
&mut self,
table: T,
) -> Result<Vec<Vec<Vec<(String, String)>>>, ColonnadeError>
where
T: IntoIterator<Item = U, IntoIter = V>,
U: IntoIterator<Item = W, IntoIter = X>,
V: Iterator<Item = U>,
W: ToString,
X: Iterator<Item = W>,
{
self.lay_out(table).and_then(|owned_table| {
let ref_table = Colonnade::ref_table(&owned_table);
let table = &ref_table;
let mut buffer = vec![];
let mut p = self.maximum_vertical_padding();
if p == 0 {
p = 1;
}
for (i, row) in table.iter().enumerate() {
self.add_row(&mut buffer, row, i == table.len() - 1, p);
}
Ok(buffer)
})
}
fn own_table<T, U, V, W, X>(&self, table: T) -> Vec<Vec<String>>
where
T: IntoIterator<Item = U, IntoIter = V>,
U: IntoIterator<Item = W, IntoIter = X>,
V: Iterator<Item = U>,
W: ToString,
X: Iterator<Item = W>,
{
let mut table = table
.into_iter()
.map(|v| {
v.into_iter()
.map(|t| {
let s = t.to_string();
let bytes = strip_ansi_escapes::strip(&s);
std::str::from_utf8(&bytes).expect(&format!("failed to restores bytes to utf8 string after stripping ansi escape sequences from {}", s)).to_string()
})
.collect::<Vec<String>>()
})
.collect::<Vec<Vec<String>>>();
for i in 0..table.len() {
while table[i].len() < self.len() {
table[i].push(String::new());
}
}
table
}
fn ref_table(table: &Vec<Vec<String>>) -> Vec<Vec<&str>> {
table
.iter()
.map(|v| v.iter().map(|s| s.as_ref()).collect::<Vec<&str>>())
.collect::<Vec<Vec<&str>>>()
}
fn reconstitute_rows(maceration: Vec<Vec<Vec<(String, String)>>>) -> Vec<String> {
maceration
.iter()
.flat_map(|row| {
row.iter().map(|line| {
if line.len() == 1 && line[0].1.len() == 0 {
String::new() } else {
let mut l = String::new();
for (margin, text) in line {
l += margin;
l += text;
}
l
}
})
})
.collect()
}
fn add_row(
&self,
buffer: &mut Vec<Vec<Vec<(String, String)>>>,
row: &Vec<&str>,
last_row: bool,
maximum_vertical_padding: usize,
) {
let mut words: Vec<(usize, Vec<&str>, usize)> = row
.iter()
.enumerate()
.map(|(i, w)| {
(
self.columns[i].padding_top,
to_words(w),
self.columns[i].padding_bottom,
)
})
.collect();
let mut current_lines: Vec<Vec<(String, String)>> = Vec::new();
if words.iter().all(|(_, sentence, _)| sentence.is_empty()) {
for _ in 0..maximum_vertical_padding {
current_lines.push(
self.columns
.iter()
.map(|c| (c.margin(), c.blank_line()))
.collect(),
);
}
if !last_row {
for _ in 0..self.spaces_between_rows {
current_lines.push(vec![(self.blank_line(), String::new())]);
}
}
} else {
while !words
.iter()
.all(|(pt, sentence, pb)| pb == &0 && pt == &0 && sentence.is_empty())
{
let mut pieces = vec![];
for (i, c) in self.columns.iter().enumerate() {
let left_margin = c.margin();
let mut line = String::new();
let mut tuple = &mut words[i];
if tuple.0 > 0 {
line = c.blank_line();
tuple.0 -= 1;
} else if tuple.1.is_empty() {
line = c.blank_line();
if tuple.2 > 0 {
tuple.2 -= 1;
}
} else {
let mut l = c.padding_left;
let mut phrase = " ".repeat(l);
let mut first = true;
while !tuple.1.is_empty() {
let w = tuple.1.remove(0); if first {
let wl = true_width(w) + c.padding_right;
if wl == c.width {
phrase += w;
break;
} else if wl > c.width {
let hyphenating = c.hyphenating();
let mut offset = c.inner_width();
if hyphenating {
offset -= 1;
}
let graphemes = UnicodeSegmentation::graphemes(w, true)
.collect::<Vec<&str>>();
let prefix = graphemes[0..offset]
.iter()
.map(|&s| s)
.collect::<Vec<_>>()
.join("");
let byte_offset = prefix.len();
phrase += &prefix;
tuple.1.insert(0, &w[byte_offset..w.len()]); if hyphenating {
phrase += "-";
}
break;
}
}
let new_length = l + true_width(w) + if first { 0 } else { 1 };
if new_length + c.padding_right > c.width {
tuple.1.insert(0, w);
break;
} else {
if first {
first = false;
} else {
phrase += " ";
}
phrase += w;
l = new_length;
}
}
let true_width = true_width(phrase.as_str());
if true_width < c.width {
let surplus = c.width - true_width;
match c.alignment {
Alignment::Left => {
line += &phrase;
for _ in 0..surplus {
line += " "
}
}
Alignment::Center => {
let left_bit = surplus / 2;
for _ in 0..left_bit {
line += " "
}
line += &phrase;
for _ in 0..(surplus - left_bit) {
line += " "
}
}
Alignment::Right => {
for _ in 0..(surplus - c.padding_right) {
line += " "
}
line += &phrase;
for _ in 0..c.padding_right {
line += " "
}
}
Alignment::Justify => {
let words = phrase.split(" ").collect::<Vec<_>>(); let last_words = tuple.1.is_empty();
if last_words || words.len() == 1 {
line += &phrase;
for _ in 0..surplus {
line += " "
}
} else {
let gaps = words.len() - 1;
let rearrangeable = surplus + gaps - c.padding_right;
let min_spacer = rearrangeable / gaps;
let extra = rearrangeable - min_spacer * gaps;
let extra_offset = words.len() - extra;
for (i, word) in words.iter().enumerate() {
if i == 0 {
line += word;
} else {
for _ in 0..(min_spacer) {
line += " ";
}
if i >= extra_offset {
line += " ";
}
line += word;
}
}
for _ in 0..c.padding_right {
line += " "
}
}
}
}
} else {
line += &phrase;
}
}
pieces.push((left_margin, line));
}
current_lines.push(pieces);
}
'outer: for c in self.columns.iter() {
match c.vertical_alignment {
VerticalAlignment::Top => (),
_ => {
let blank = c.blank_line();
let end = current_lines.len() - c.padding_bottom;
let mut movable_lines = 0;
let mut pointer = end - 1;
let top_pointer = c.padding_top;
while current_lines[pointer][c.index].1 == blank {
movable_lines += 1;
if pointer == top_pointer {
continue 'outer;
}
pointer -= 1;
}
if movable_lines == 0 {
continue 'outer;
}
let lines_to_move = if c.vertical_alignment == VerticalAlignment::Middle {
movable_lines / 2
} else {
movable_lines
};
let mut rotator = Vec::with_capacity(end - top_pointer);
for i in top_pointer..end {
rotator.push(current_lines[i].remove(c.index));
}
for _ in 0..lines_to_move {
let pair = rotator.remove(rotator.len() - 1);
rotator.insert(0, pair);
}
for i in top_pointer..end {
current_lines[i].insert(c.index, rotator.remove(0));
}
}
}
}
if !last_row {
for _ in 0..self.spaces_between_rows {
current_lines.push(vec![(self.blank_line(), String::new())]);
}
}
}
buffer.push(current_lines);
}
pub fn reset(&mut self) {
for i in 0..self.len() {
self.columns[i].adjusted = false;
self.columns[i].width = 0;
}
}
fn adjusted(&self) -> bool {
self.columns.iter().all(|c| c.adjusted)
}
fn lay_out<T, U, V, W, X>(&mut self, table: T) -> Result<Vec<Vec<String>>, ColonnadeError>
where
T: IntoIterator<Item = U, IntoIter = V>,
U: IntoIterator<Item = W, IntoIter = X>,
V: Iterator<Item = U>,
W: ToString,
X: Iterator<Item = W>,
{
let owned_table = self.own_table(table);
if self.adjusted() {
return Ok(owned_table);
}
self.reset();
let ref_table = Colonnade::ref_table(&owned_table);
let table = &ref_table;
for i in 0..table.len() {
let row = &table[i];
if row.len() != self.len() {
return Err(ColonnadeError::InconsistentColumns(
i,
row.len(),
self.len(),
));
}
}
if !self.sufficient_space() {
return Err(ColonnadeError::InsufficientSpace);
}
for i in 0..table.len() {
for c in 0..self.len() {
let m = Colonnade::width_after_normalization(&table[i][c])
+ self.columns[c].horizontal_padding();
if m >= self.columns[c].width {
self.columns[c].expand(m);
}
}
}
if self.required_width() <= self.width {
self.mark_adjusted();
return Ok(owned_table);
}
let mut modified_columns: Vec<usize> = Vec::with_capacity(self.len());
for p in self.priorities() {
for c in 0..self.len() {
if self.columns[c].priority == p && self.columns[c].is_shrinkable() {
modified_columns.push(c);
self.columns[c].shrink(0);
for r in 0..table.len() {
let m = longest_word(&table[r][c]) + self.columns[c].horizontal_padding();
if m > self.columns[c].width {
self.columns[c].expand(m);
}
}
}
}
if self.required_width() <= self.width {
break;
}
}
if self.required_width() > self.width {
let mut truncatable_columns = self.columns.iter().enumerate().collect::<Vec<_>>();
truncatable_columns.retain(|(_, c)| c.is_shrinkable());
let truncatable_columns: Vec<usize> =
truncatable_columns.iter().map(|(i, _)| *i).collect();
let mut priorities: Vec<usize> = truncatable_columns
.iter()
.map(|&i| self.columns[i].priority)
.collect();
priorities.sort_unstable();
priorities.dedup();
priorities.reverse();
'outer: for p in priorities {
let mut shrinkables: Vec<&usize> = truncatable_columns
.iter()
.filter(|&&i| self.columns[i].priority == p)
.collect();
loop {
let excess = self.required_width() - self.width;
if excess == 0 {
break 'outer;
}
if excess <= shrinkables.len() {
shrinkables.retain(|&&i| self.columns[i].shrink_by(1));
} else {
let share = excess / shrinkables.len();
shrinkables.retain(|&&i| self.columns[i].shrink_by(share));
}
if shrinkables.is_empty() {
break;
}
}
}
if self.required_width() > self.width {
return Err(ColonnadeError::InsufficientSpace);
}
} else if self.required_width() < self.width {
modified_columns.retain(|&i| self.columns[i].is_expandable());
if !modified_columns.is_empty() {
while self.required_width() < self.width {
if let Some(priority) = modified_columns
.iter()
.map(|&i| self.columns[i].priority)
.min()
{
let mut winners: Vec<&usize> = modified_columns
.iter()
.filter(|&&i| self.columns[i].priority == priority)
.collect();
let surplus = self.width - self.required_width();
if surplus <= winners.len() {
for &&i in winners.iter().take(surplus) {
self.columns[i].width += 1;
}
} else {
loop {
let surplus = self.width - self.required_width();
if surplus == 0 {
break;
}
winners.retain(|&&i| self.columns[i].is_expandable());
if winners.is_empty() {
break;
}
if surplus <= winners.len() {
for &&i in winners.iter().take(surplus) {
self.columns[i].width += 1;
}
break;
}
let mut changed = false;
let share = surplus / winners.len();
for &&i in winners.iter() {
let change = self.columns[i].expand_by(share);
changed = changed || change;
}
if !changed {
break;
}
}
modified_columns.retain(|&i| self.columns[i].priority != priority);
}
} else {
break;
}
}
}
}
self.mark_adjusted();
Ok(owned_table)
}
fn mark_adjusted(&mut self) {
for i in 0..self.len() {
self.columns[i].adjusted = true;
}
}
pub fn spaces_between_rows(&mut self, n: usize) -> &mut Self {
self.spaces_between_rows = n;
self
}
pub fn priority(&mut self, priority: usize) -> &mut Self {
for i in 0..self.len() {
self.columns[i].priority = priority;
}
self
}
pub fn max_width(&mut self, max_width: usize) -> Result<&mut Self, ColonnadeError> {
for i in 0..self.len() {
match self.columns[i].max_width(max_width) {
Err(e) => return Err(e),
Ok(_) => (),
}
}
Ok(self)
}
pub fn min_width(&mut self, min_width: usize) -> Result<&mut Self, ColonnadeError> {
for i in 0..self.len() {
match self.columns[i].min_width(min_width) {
Err(e) => return Err(e),
Ok(_) => (),
}
}
if !self.sufficient_space() {
Err(ColonnadeError::InsufficientSpace)
} else {
Ok(self)
}
}
pub fn fixed_width(&mut self, width: usize) -> Result<&mut Self, ColonnadeError> {
for i in 0..self.len() {
match self.columns[i].fixed_width(width) {
Err(e) => return Err(e),
Ok(_) => (),
}
}
Ok(self)
}
pub fn clear_limits(&mut self) -> &mut Self {
for i in 0..self.len() {
self.columns[i].clear_limits();
}
self
}
pub fn alignment(&mut self, alignment: Alignment) -> &mut Self {
for i in 0..self.len() {
self.columns[i].alignment(alignment.clone());
}
self
}
pub fn vertical_alignment(&mut self, vertical_alignment: VerticalAlignment) -> &mut Self {
for i in 0..self.len() {
self.columns[i].vertical_alignment(vertical_alignment.clone());
}
self
}
pub fn left_margin(&mut self, left_margin: usize) -> Result<&mut Self, ColonnadeError> {
for i in 0..self.len() {
self.columns[i].left_margin(left_margin);
}
if !self.sufficient_space() {
Err(ColonnadeError::InsufficientSpace)
} else {
Ok(self)
}
}
pub fn padding(&mut self, padding: usize) -> Result<&mut Self, ColonnadeError> {
for i in 0..self.len() {
self.columns[i].padding(padding);
}
if !self.sufficient_space() {
Err(ColonnadeError::InsufficientSpace)
} else {
Ok(self)
}
}
pub fn padding_horizontal(&mut self, padding: usize) -> Result<&mut Self, ColonnadeError> {
for i in 0..self.len() {
self.columns[i].padding_horizontal(padding);
}
if !self.sufficient_space() {
Err(ColonnadeError::InsufficientSpace)
} else {
Ok(self)
}
}
pub fn padding_left(&mut self, padding: usize) -> Result<&mut Self, ColonnadeError> {
for i in 0..self.len() {
self.columns[i].padding_left(padding);
}
if !self.sufficient_space() {
Err(ColonnadeError::InsufficientSpace)
} else {
Ok(self)
}
}
pub fn padding_right(&mut self, padding: usize) -> Result<&mut Self, ColonnadeError> {
for i in 0..self.len() {
self.columns[i].padding_right(padding);
}
if !self.sufficient_space() {
Err(ColonnadeError::InsufficientSpace)
} else {
Ok(self)
}
}
pub fn padding_vertical(&mut self, padding: usize) -> &mut Self {
for i in 0..self.len() {
self.columns[i].padding_vertical(padding);
}
self
}
pub fn padding_top(&mut self, padding: usize) -> &mut Self {
for i in 0..self.len() {
self.columns[i].padding_top(padding);
}
self
}
pub fn padding_bottom(&mut self, padding: usize) -> &mut Self {
for i in 0..self.len() {
self.columns[i].padding_bottom(padding);
}
self
}
pub fn hyphenate(&mut self, hyphenate: bool) -> &mut Self {
for i in 0..self.len() {
self.columns[i].hyphenate(hyphenate);
}
self
}
}