forme/layout/
page_break.rs1#[derive(Debug, Clone, PartialEq)]
9pub enum BreakDecision {
10 Place,
12 MoveToNextPage,
14 Split {
16 items_on_current_page: usize,
18 },
19}
20
21pub fn decide_break(
24 remaining_height: f64,
25 child_heights: &[f64],
26 is_breakable: bool,
27 min_orphan_lines: usize,
28 min_widow_lines: usize,
29) -> BreakDecision {
30 let total: f64 = child_heights.iter().sum();
32
33 if total <= remaining_height {
35 return BreakDecision::Place;
36 }
37
38 if !is_breakable {
40 return BreakDecision::MoveToNextPage;
41 }
42
43 let mut running = 0.0;
45 let mut fit_count = 0;
46 for &h in child_heights {
47 if running + h > remaining_height {
48 break;
49 }
50 running += h;
51 fit_count += 1;
52 }
53
54 let total_items = child_heights.len();
56
57 if fit_count < min_orphan_lines && fit_count < total_items {
59 return BreakDecision::MoveToNextPage;
60 }
61
62 let remaining_items = total_items - fit_count;
64 if remaining_items < min_widow_lines && remaining_items > 0 {
65 let adjusted = fit_count.saturating_sub(min_widow_lines - remaining_items);
67 if adjusted == 0 {
68 return BreakDecision::MoveToNextPage;
69 }
70 return BreakDecision::Split {
71 items_on_current_page: adjusted,
72 };
73 }
74
75 if fit_count == 0 {
76 return BreakDecision::MoveToNextPage;
77 }
78
79 BreakDecision::Split {
80 items_on_current_page: fit_count,
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 #[test]
89 fn everything_fits() {
90 let decision = decide_break(100.0, &[20.0, 30.0, 40.0], true, 2, 2);
91 assert_eq!(decision, BreakDecision::Place);
92 }
93
94 #[test]
95 fn unbreakable_moves() {
96 let decision = decide_break(50.0, &[20.0, 30.0, 40.0], false, 2, 2);
97 assert_eq!(decision, BreakDecision::MoveToNextPage);
98 }
99
100 #[test]
101 fn split_at_right_point() {
102 let decision = decide_break(55.0, &[20.0, 30.0, 40.0], true, 1, 1);
103 assert_eq!(
104 decision,
105 BreakDecision::Split {
106 items_on_current_page: 2,
107 }
108 );
109 }
110
111 #[test]
112 fn orphan_control() {
113 let decision = decide_break(25.0, &[20.0, 30.0, 40.0], true, 2, 2);
115 assert_eq!(decision, BreakDecision::MoveToNextPage);
116 }
117
118 #[test]
119 fn widow_control() {
120 let decision = decide_break(70.0, &[20.0, 20.0, 20.0, 20.0], true, 2, 2);
122 assert_eq!(
123 decision,
124 BreakDecision::Split {
125 items_on_current_page: 2,
126 }
127 );
128 }
129}