1pub fn distribute_grow(items: &mut [(f64, f64)], remaining: f64) {
9 let total_grow: f64 = items.iter().map(|(_, g)| g).sum();
11 if total_grow <= 0.0 || remaining <= 0.0 {
12 return;
13 }
14 for (width, grow) in items.iter_mut() {
15 *width += remaining * (*grow / total_grow);
16 }
17}
18
19#[derive(Debug, Clone)]
21pub struct WrapLine {
22 pub start: usize,
24 pub end: usize,
26}
27
28pub fn partition_into_lines(
31 base_widths: &[f64],
32 column_gap: f64,
33 available_width: f64,
34) -> Vec<WrapLine> {
35 if base_widths.is_empty() {
36 return vec![];
37 }
38
39 let mut lines = Vec::new();
40 let mut line_start = 0;
41 let mut line_width = 0.0;
42
43 for (i, &w) in base_widths.iter().enumerate() {
44 let needed = if i == line_start { w } else { column_gap + w };
45 if i > line_start && line_width + needed > available_width + 0.01 {
50 lines.push(WrapLine {
51 start: line_start,
52 end: i,
53 });
54 line_start = i;
55 line_width = w;
56 } else {
57 line_width += needed;
58 }
59 }
60
61 if line_start < base_widths.len() {
63 lines.push(WrapLine {
64 start: line_start,
65 end: base_widths.len(),
66 });
67 }
68
69 lines
70}
71
72pub fn distribute_shrink(items: &mut [(f64, f64)], overflow: f64) {
74 let total_shrink_weighted: f64 = items.iter().map(|(w, s)| w * s).sum();
76 if total_shrink_weighted <= 0.0 || overflow >= 0.0 {
77 return;
78 }
79 let overflow = overflow.abs();
80 for (width, shrink) in items.iter_mut() {
81 let factor = (*width * *shrink) / total_shrink_weighted;
82 *width -= overflow * factor;
83 *width = width.max(0.0);
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn test_grow_distribution() {
93 let mut items = vec![(100.0, 1.0), (100.0, 2.0)];
94 distribute_grow(&mut items, 90.0);
95 assert!((items[0].0 - 130.0).abs() < 0.01);
96 assert!((items[1].0 - 160.0).abs() < 0.01);
97 }
98
99 #[test]
100 fn test_shrink_distribution() {
101 let mut items = vec![(200.0, 1.0), (100.0, 1.0)];
102 distribute_shrink(&mut items, -60.0);
103 assert!(items[0].0 < 200.0);
105 assert!(items[1].0 < 100.0);
106 assert!((items[0].0 + items[1].0 - 240.0).abs() < 0.01);
107 }
108
109 #[test]
110 fn test_partition_single_line_fits() {
111 let widths = vec![100.0, 100.0, 100.0];
112 let lines = partition_into_lines(&widths, 10.0, 400.0);
113 assert_eq!(lines.len(), 1);
114 assert_eq!(lines[0].start, 0);
115 assert_eq!(lines[0].end, 3);
116 }
117
118 #[test]
119 fn test_partition_two_line_split() {
120 let widths = vec![100.0, 100.0, 100.0];
122 let lines = partition_into_lines(&widths, 10.0, 250.0);
123 assert_eq!(lines.len(), 2);
124 assert_eq!(lines[0].start, 0);
125 assert_eq!(lines[0].end, 2); assert_eq!(lines[1].start, 2);
127 assert_eq!(lines[1].end, 3);
128 }
129
130 #[test]
131 fn test_partition_oversized_item() {
132 let widths = vec![500.0];
134 let lines = partition_into_lines(&widths, 10.0, 200.0);
135 assert_eq!(lines.len(), 1);
136 assert_eq!(lines[0].start, 0);
137 assert_eq!(lines[0].end, 1);
138 }
139
140 #[test]
141 fn test_partition_empty_input() {
142 let lines = partition_into_lines(&[], 10.0, 200.0);
143 assert!(lines.is_empty());
144 }
145
146 #[test]
147 fn test_partition_exact_fit() {
148 let widths = vec![100.0, 100.0];
150 let lines = partition_into_lines(&widths, 10.0, 210.0);
151 assert_eq!(lines.len(), 1);
152 }
153}
154
155#[cfg(test)]
156mod epsilon_probe {
157 #[test]
158 fn exact_percent_sum_shares_a_line() {
159 let w = 487.28_f64;
162 let widths = [w, f64::from(0.6_f32) * w, f64::from(0.4_f32) * w];
163 let lines = super::partition_into_lines(&widths, 0.0, w);
164 assert_eq!(lines.len(), 2, "{lines:?}");
165 assert_eq!((lines[1].start, lines[1].end), (1, 3));
166 }
167}