use crate::render::dimension::Pt;
use crate::render::layout::draw_command::DrawCommand;
use crate::render::layout::section::CellLine;
use super::borders::CellBorders;
use super::types::{CellLayoutEntry, MeasuredRow, TableRowInput};
pub(super) struct RowCutInput<'a> {
pub(super) mr: &'a MeasuredRow,
pub(super) row: &'a TableRowInput,
pub(super) available: Pt,
}
struct CellCut {
content_cut_y: Pt,
line_cut_y: Pt,
shift: Pt,
}
impl CellCut {
fn keep_all() -> Self {
Self {
content_cut_y: Pt::new(f32::INFINITY),
line_cut_y: Pt::new(f32::INFINITY),
shift: Pt::ZERO,
}
}
}
pub(super) struct SplitCut {
first_half_height: Pt,
cells: Vec<CellCut>,
}
pub(super) fn find_row_cut(input: &RowCutInput<'_>) -> Option<SplitCut> {
let mut cells: Vec<CellCut> = Vec::with_capacity(input.row.cells.len());
let mut first_half_height = Pt::ZERO;
let mut any_fits = false;
let mut non_splittable_heights: Vec<Pt> = Vec::with_capacity(input.row.cells.len());
for (entry, cell) in input.mr.entries.iter().zip(&input.row.cells) {
match cut_for_cell(
entry,
cell.margins.top,
cell.margins.bottom,
input.available,
) {
Some((cut, half_h)) => {
any_fits = true;
if half_h > first_half_height {
first_half_height = half_h;
}
cells.push(cut);
non_splittable_heights.push(Pt::ZERO);
}
None => {
cells.push(CellCut::keep_all());
let required = entry.layout.content_height + cell.margins.top + cell.margins.bottom;
non_splittable_heights.push(required);
}
}
}
if !any_fits {
return None;
}
let non_splittable_max = non_splittable_heights
.iter()
.copied()
.fold(Pt::ZERO, Pt::max);
if non_splittable_max > first_half_height {
first_half_height = non_splittable_max;
}
if first_half_height > input.available {
return None;
}
Some(SplitCut {
first_half_height,
cells,
})
}
fn cut_for_cell(
entry: &CellLayoutEntry,
margin_top: Pt,
margin_bottom: Pt,
available: Pt,
) -> Option<(CellCut, Pt)> {
let budget = available - margin_top - margin_bottom;
if budget <= Pt::ZERO {
return None;
}
let cont_top = largest_legal_cut(&entry.layout.lines, budget)?;
if cont_top <= Pt::ZERO {
return None;
}
let shift = cont_top;
let half_h = cont_top + margin_top + margin_bottom;
Some((
CellCut {
content_cut_y: margin_top + cont_top,
line_cut_y: cont_top,
shift,
},
half_h,
))
}
fn largest_legal_cut(lines: &[CellLine], budget: Pt) -> Option<Pt> {
let n = lines.len();
if n < 2 {
return None;
}
let mut best: Option<Pt> = None;
for l in 0..n - 1 {
let a = &lines[l];
let b = &lines[l + 1];
let cont_top = b.top_y;
if cont_top > budget {
break;
}
let legal = if a.para == b.para {
if a.interior_atomic {
false
} else if a.widow_control {
let first = lines.iter().position(|x| x.para == a.para).unwrap_or(l);
let last = lines.iter().rposition(|x| x.para == a.para).unwrap_or(l);
let head = l + 1 - first; let tail = last - l; head >= 2 && tail >= 2
} else {
true
}
} else {
!a.keep_next
};
if legal {
best = Some(best.map_or(cont_top, |x: Pt| x.max(cont_top)));
}
}
best
}
pub(super) struct SplitRow {
pub(super) first: MeasuredRow,
pub(super) second: MeasuredRow,
}
pub(super) fn split_row_at(mr: &MeasuredRow, cut: &SplitCut) -> SplitRow {
let first_h = cut.first_half_height;
let max_shift = cut.cells.iter().map(|c| c.shift).fold(Pt::ZERO, Pt::max);
let second_h = (mr.height - max_shift).max(Pt::ZERO);
let mut first_entries: Vec<CellLayoutEntry> = Vec::with_capacity(mr.entries.len());
let mut second_entries: Vec<CellLayoutEntry> = Vec::with_capacity(mr.entries.len());
for (entry, cc) in mr.entries.iter().zip(cut.cells.iter()) {
let (first_cmds, second_cmds) =
partition_commands(&entry.layout.commands, cc.content_cut_y, cc.shift);
let (first_lines, second_lines) =
partition_lines(&entry.layout.lines, cc.line_cut_y, cc.shift);
first_entries.push(CellLayoutEntry {
layout: crate::render::layout::cell::CellLayout {
commands: first_cmds,
content_height: entry.layout.content_height.min(first_h),
lines: first_lines,
},
cell_x: entry.cell_x,
cell_w: entry.cell_w,
grid_col: entry.grid_col,
});
second_entries.push(CellLayoutEntry {
layout: crate::render::layout::cell::CellLayout {
commands: second_cmds,
content_height: (entry.layout.content_height - cc.shift).max(Pt::ZERO),
lines: second_lines,
},
cell_x: entry.cell_x,
cell_w: entry.cell_w,
grid_col: entry.grid_col,
});
}
let first_borders: Vec<CellBorders> = mr.borders.to_vec();
let second_borders: Vec<CellBorders> = mr
.borders
.iter()
.map(|b| CellBorders {
top: b.top.or(b.bottom),
bottom: b.bottom,
left: b.left,
right: b.right,
})
.collect();
SplitRow {
first: MeasuredRow {
entries: first_entries,
borders: first_borders,
height: first_h,
border_gap_below: Pt::ZERO,
},
second: MeasuredRow {
entries: second_entries,
borders: second_borders,
height: second_h,
border_gap_below: mr.border_gap_below,
},
}
}
fn partition_commands(
commands: &[DrawCommand],
cut_y: Pt,
shift: Pt,
) -> (Vec<DrawCommand>, Vec<DrawCommand>) {
let mut first = Vec::new();
let mut second = Vec::new();
for cmd in commands {
if command_primary_y(cmd) < cut_y {
first.push(cmd.clone());
} else {
let mut c = cmd.clone();
c.shift_y(-shift);
second.push(c);
}
}
(first, second)
}
fn partition_lines(lines: &[CellLine], cut_y: Pt, shift: Pt) -> (Vec<CellLine>, Vec<CellLine>) {
let mut first = Vec::new();
let mut second = Vec::new();
for line in lines {
if line.top_y < cut_y {
first.push(line.clone());
} else {
let mut l = line.clone();
l.top_y -= shift;
second.push(l);
}
}
(first, second)
}
fn command_primary_y(cmd: &DrawCommand) -> Pt {
match cmd {
DrawCommand::Text { position, .. } | DrawCommand::NamedDestination { position, .. } => {
position.y
}
DrawCommand::Underline { line, .. } | DrawCommand::Line { line, .. } => line.start.y,
DrawCommand::Image { rect, .. }
| DrawCommand::EmojiCluster { rect, .. }
| DrawCommand::Rect { rect, .. }
| DrawCommand::LinkAnnotation { rect, .. }
| DrawCommand::InternalLink { rect, .. } => rect.origin.y,
DrawCommand::Path { origin, .. } => origin.y,
}
}