Skip to content

Commit

Permalink
Support (relatively) huge and empty cells
Browse files Browse the repository at this point in the history
  • Loading branch information
PeterJFB committed Oct 16, 2024
1 parent f14c66f commit af5bcb9
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 54 deletions.
2 changes: 1 addition & 1 deletion src/md.pest
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ italic = {
sentence = _{ (latex | code | link | bold_italic | italic | bold | strikethrough | normal+)+ }
t_sentence = _{ (!"|" ~ (latex | code | link | italic | bold | strikethrough | t_normal))+ }

table_cell = { "|" ~ WHITESPACE_S* ~ t_sentence+ ~ WHITESPACE_S* ~ ("|" ~ " "* ~ NEWLINE)? }
table_cell = { "|" ~ WHITESPACE_S* ~ t_sentence* ~ WHITESPACE_S* ~ ("|" ~ " "* ~ NEWLINE)? }
table_seperator = { ("|"? ~ (WHITESPACE_S | ":")* ~ "-"+ ~ (WHITESPACE_S | ":")* ~ "|") }

u_list = { indent ~ ("-" | "*") ~ WHITESPACE_S ~ sentence+ }
Expand Down
109 changes: 68 additions & 41 deletions src/nodes/textcomponent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -561,19 +561,29 @@ fn transform_list(component: &mut TextComponent, width: u16) {
}

fn transform_table(component: &mut TextComponent, width: u16) {
component.content.retain(|c| !c.is_empty());

let column_count = component.meta_info.len();
let content = &mut component.content;

////////////////////////////////////
// Find `row_count`` and `widths` //
////////////////////////////////////
let (widths, row_count) = {
let mut row_count = 0;
let column_count = component
.meta_info
.iter()
.filter(|w| w.kind() == WordType::MetaInfo(MetaData::ColumnsCount))
.count();

assert!(
content.len() % column_count == 0,
"Invalid table cell distribution: content.len() = {}, column_count = {}",
content.len(),
column_count
);

let row_count = content.len() / column_count;

///////////////////////////
// Find unbalanced width //
///////////////////////////
let widths = {
let mut widths = vec![0; column_count];
content.chunks(column_count).for_each(|row| {
row_count += 1;
row.iter().enumerate().for_each(|(col_i, entry)| {
let len = content_entry_len(entry);
if len > widths[col_i] as usize {
Expand All @@ -582,7 +592,7 @@ fn transform_table(component: &mut TextComponent, width: u16) {
});
});

(widths, row_count)
widths
};

let styling_width = column_count as u16;
Expand All @@ -592,11 +602,8 @@ fn transform_table(component: &mut TextComponent, width: u16) {
// Return if unbalanced width fits //
/////////////////////////////////////
if width >= unbalanced_cells_width + styling_width {
assert!(content.len() % column_count == 0);
component.height = (content.len() / column_count) as u16;

component.kind = TextNode::Table(widths, vec![1; component.height as usize]);

return;
}

Expand All @@ -605,38 +612,56 @@ fn transform_table(component: &mut TextComponent, width: u16) {
//////////////////////////////
let overflow_threshold = (width - styling_width) / column_count as u16;
let mut overflowing_columns = vec![];
let mut non_overflowing_columns_total_width = 0;
let mut overflowing_columns_total_width = 0;

for (column_i, column_width) in widths.iter().enumerate() {
if *column_width > overflow_threshold {
overflowing_columns.push((column_i, column_width));
let (overflowing_width, non_overflowing_width) = {
let mut overflowing_width = 0;
let mut non_overflowing_width = 0;

overflowing_columns_total_width += column_width;
} else {
non_overflowing_columns_total_width += column_width;
for (column_i, column_width) in widths.iter().enumerate() {
if *column_width > overflow_threshold {
overflowing_columns.push((column_i, column_width));

overflowing_width += column_width;
} else {
non_overflowing_width += column_width;
}
}
}

(overflowing_width, non_overflowing_width)
};

assert!(
overflowing_columns.len() > 0,
"table overflow should not be handled when there are no overflowing columns"
);

/////////////////////////////////////////////
// Assign new width to overflowing columns //
/////////////////////////////////////////////
let overflowing_columns_balanced_width =
width - non_overflowing_columns_total_width - styling_width;
let mut available_balanced_width = width - non_overflowing_width - styling_width;
let mut available_overflowing_width = overflowing_width;

let overflowing_column_min_width =
(available_balanced_width / (2 * overflowing_columns.len() as u16)).max(1);

let mut widths_balanced = widths.clone();
for (column_i, old_column_width) in overflowing_columns {
let mut widths_balanced: Vec<u16> = widths.clone();
for (column_i, old_column_width) in overflowing_columns
.iter()
// Sorting ensures the smallest overflowing cells receive minimum area without the
// need for recalculating the larger cells
.sorted_by(|a, b| Ord::cmp(a.1, b.1))
{
// Ensure the longest cell gets the most amount of area
let ratio = (*old_column_width as f32) / (overflowing_columns_total_width as f32);
let balanced_column_width =
(ratio * overflowing_columns_balanced_width as f32).floor() as u16;
let ratio = (**old_column_width as f32) / (available_overflowing_width as f32);
let mut balanced_column_width = (ratio * available_balanced_width as f32).floor() as u16;

assert!(
balanced_column_width != 0,
"TODO: Ensure every overflowing column gets a width of at least 1"
);
if balanced_column_width < overflowing_column_min_width {
balanced_column_width = overflowing_column_min_width;
available_overflowing_width -= **old_column_width;
available_balanced_width -= balanced_column_width;
}

widths_balanced[column_i] = balanced_column_width;
widths_balanced[*column_i] = balanced_column_width;
}

///////////////////////////////////
Expand Down Expand Up @@ -678,24 +703,26 @@ fn transform_table(component: &mut TextComponent, width: u16) {
for mut word in entry.drain(..) {
let word_len = word.content().len() as u16;
line_len += word_len;

if line_len <= widths_balanced[column_i] {
new_entry.push(word);
} else {
let mut end_word = word
let mut newline_word = word
.split_off((word_len - (line_len - widths_balanced[column_i])) as usize);

if word.content().len() > 0 {
new_entry.push(word);
}

while end_word.content().len() > widths_balanced[column_i] as usize {
let new_end_word = end_word.split_off(widths_balanced[column_i] as usize);
new_entry.push(end_word);
end_word = new_end_word;
while newline_word.content().len() > widths_balanced[column_i] as usize {
let new_end_word =
newline_word.split_off(widths_balanced[column_i] as usize);
new_entry.push(newline_word);
newline_word = new_end_word;
}

line_len = end_word.content().len() as u16;
new_entry.push(end_word);
line_len = newline_word.content().len() as u16;
new_entry.push(newline_word);
}
}

Expand Down
1 change: 0 additions & 1 deletion src/nodes/word.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ impl From<MdParseEnum> for WordType {
| MdParseEnum::AltText
| MdParseEnum::Quote
| MdParseEnum::Sentence
| MdParseEnum::TableRow
| MdParseEnum::Word => WordType::Normal,

MdParseEnum::LinkData => WordType::LinkData,
Expand Down
7 changes: 2 additions & 5 deletions src/pages/markdown_renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,6 @@ impl Widget for TextComponent {
.cloned()
.unwrap_or_else(|| Word::new("".to_string(), WordType::Normal));

let table_meta = self.meta_info().to_owned();

let area = Rect { height, y, ..area };

match kind {
Expand All @@ -88,7 +86,7 @@ impl Widget for TextComponent {
TextNode::List => render_list(area, buf, self, clips),
TextNode::CodeBlock => render_code_block(area, buf, self, clips),
TextNode::Table(widths, heights) => {
render_table(area, buf, self, clips, table_meta, widths, heights)
render_table(area, buf, self, clips, widths, heights)
}
TextNode::Quote => render_quote(area, buf, self, clips),
TextNode::LineBreak => (),
Expand Down Expand Up @@ -378,15 +376,14 @@ fn render_table(
buf: &mut Buffer,
component: TextComponent,
clip: Clipping,
meta_info: Vec<Word>,
widths: Vec<u16>,
heights: Vec<u16>,
) {
let scroll_offset = component.scroll_offset();
let y_offset = component.y_offset();
let height = component.height();

let column_count = meta_info.len();
let column_count = widths.len();

let content = component.content_owned();
let titles = content.chunks(column_count).next().unwrap().to_vec();
Expand Down
17 changes: 11 additions & 6 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,16 +311,22 @@ fn parse_component(parse_node: ParseNode) -> Component {

MdParseEnum::Table => {
let mut words = Vec::new();
for row in parse_node.children_owned() {
if row.kind() == MdParseEnum::TableSeperator {
for cell in parse_node.children_owned() {
if cell.kind() == MdParseEnum::TableSeperator {
words.push(vec![Word::new(
row.content().to_owned(),
cell.content().to_owned(),
WordType::MetaInfo(MetaData::ColumnsCount),
)]);
continue;
}
let mut inner_words = Vec::new();
for word in get_leaf_nodes(row) {

if cell.children().is_empty() {
words.push(inner_words);
continue;
}

for word in get_leaf_nodes(cell) {
let word_type = WordType::from(word.kind());
let mut content = word.content().to_owned();

Expand Down Expand Up @@ -508,7 +514,6 @@ pub enum MdParseEnum {
StrikethroughStr,
Table,
TableCell,
TableRow,
TableSeperator,
Task,
TaskClosed,
Expand Down Expand Up @@ -544,7 +549,7 @@ impl From<Rule> for MdParseEnum {
Rule::task_complete => Self::TaskClosed,
Rule::code_line => Self::CodeBlockStr,
Rule::sentence | Rule::t_sentence => Self::Sentence,
Rule::table_cell => Self::TableRow,
Rule::table_cell => Self::TableCell,
Rule::table_seperator => Self::TableSeperator,
Rule::u_list => Self::UnorderedList,
Rule::o_list => Self::OrderedList,
Expand Down

0 comments on commit af5bcb9

Please sign in to comment.