use crate::render::dimension::Pt;
use crate::render::layout::draw_command::DrawCommand;
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,
shift: Pt,
}
impl CellCut {
fn keep_all() -> Self {
Self {
content_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;
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);
}
None => cells.push(CellCut::keep_all()),
}
}
if !any_fits {
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 mut baselines: Vec<Pt> = entry
.layout
.commands
.iter()
.filter_map(|c| match c {
DrawCommand::Text { position, .. } => Some(position.y),
_ => None,
})
.collect();
baselines.sort_by(|a, b| {
a.raw()
.partial_cmp(&b.raw())
.unwrap_or(std::cmp::Ordering::Equal)
});
baselines.dedup_by(|a, b| (a.raw() - b.raw()).abs() < 0.01);
if baselines.len() < 2 {
return None;
}
let budget = available - margin_bottom;
if budget <= Pt::ZERO {
return None;
}
let first = baselines[0];
let mut best_k: Option<usize> = None;
for k in 0..baselines.len() - 1 {
let midpoint = Pt::new((baselines[k].raw() + baselines[k + 1].raw()) * 0.5);
if midpoint <= budget {
best_k = Some(k);
} else {
break;
}
}
let k = best_k?;
let midpoint = Pt::new((baselines[k].raw() + baselines[k + 1].raw()) * 0.5);
let shift = baselines[k + 1] - first;
let half_h = midpoint + margin_bottom;
Some((
CellCut {
content_cut_y: baselines[k + 1],
shift,
},
half_h,
))
}
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);
first_entries.push(CellLayoutEntry {
layout: crate::render::layout::cell::CellLayout {
commands: first_cmds,
content_height: entry.layout.content_height.min(first_h),
},
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),
},
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 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::Rect { rect, .. }
| DrawCommand::LinkAnnotation { rect, .. }
| DrawCommand::InternalLink { rect, .. } => rect.origin.y,
DrawCommand::Path { origin, .. } => origin.y,
}
}