#[derive(Debug, Clone, PartialEq)]
pub enum BreakDecision {
Place,
MoveToNextPage,
Split {
items_on_current_page: usize,
},
}
pub fn decide_break(
remaining_height: f64,
child_heights: &[f64],
is_breakable: bool,
min_orphan_lines: usize,
min_widow_lines: usize,
) -> BreakDecision {
let total: f64 = child_heights.iter().sum();
if total <= remaining_height {
return BreakDecision::Place;
}
if !is_breakable {
return BreakDecision::MoveToNextPage;
}
let mut running = 0.0;
let mut fit_count = 0;
for &h in child_heights {
if running + h > remaining_height {
break;
}
running += h;
fit_count += 1;
}
let total_items = child_heights.len();
if fit_count < min_orphan_lines && fit_count < total_items {
return BreakDecision::MoveToNextPage;
}
let remaining_items = total_items - fit_count;
if remaining_items < min_widow_lines && remaining_items > 0 {
let adjusted = fit_count.saturating_sub(min_widow_lines - remaining_items);
if adjusted == 0 {
return BreakDecision::MoveToNextPage;
}
return BreakDecision::Split {
items_on_current_page: adjusted,
};
}
if fit_count == 0 {
return BreakDecision::MoveToNextPage;
}
BreakDecision::Split {
items_on_current_page: fit_count,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn everything_fits() {
let decision = decide_break(100.0, &[20.0, 30.0, 40.0], true, 2, 2);
assert_eq!(decision, BreakDecision::Place);
}
#[test]
fn unbreakable_moves() {
let decision = decide_break(50.0, &[20.0, 30.0, 40.0], false, 2, 2);
assert_eq!(decision, BreakDecision::MoveToNextPage);
}
#[test]
fn split_at_right_point() {
let decision = decide_break(55.0, &[20.0, 30.0, 40.0], true, 1, 1);
assert_eq!(
decision,
BreakDecision::Split {
items_on_current_page: 2,
}
);
}
#[test]
fn orphan_control() {
let decision = decide_break(25.0, &[20.0, 30.0, 40.0], true, 2, 2);
assert_eq!(decision, BreakDecision::MoveToNextPage);
}
#[test]
fn widow_control() {
let decision = decide_break(70.0, &[20.0, 20.0, 20.0, 20.0], true, 2, 2);
assert_eq!(
decision,
BreakDecision::Split {
items_on_current_page: 2,
}
);
}
}