1use crate::segment::Segments;
4use crate::{Console, ConsoleOptions, Renderable};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
8pub struct Measurement {
9 pub minimum: usize,
11 pub maximum: usize,
13}
14
15impl Measurement {
16 pub fn new(minimum: usize, maximum: usize) -> Self {
18 Measurement { minimum, maximum }
19 }
20
21 pub fn exact(width: usize) -> Self {
23 Measurement {
24 minimum: width,
25 maximum: width,
26 }
27 }
28
29 pub fn span(&self) -> usize {
31 self.maximum.saturating_sub(self.minimum)
32 }
33
34 pub fn normalize(&self) -> Self {
51 let minimum = self.minimum.min(self.maximum);
52 Measurement {
53 minimum,
54 maximum: self.maximum,
55 }
56 }
57
58 pub fn with_maximum(&self, width: usize) -> Self {
77 Measurement {
78 minimum: self.minimum.min(width),
79 maximum: self.maximum.min(width),
80 }
81 }
82
83 pub fn with_minimum(&self, width: usize) -> Self {
102 Measurement {
103 minimum: self.minimum.max(width),
104 maximum: self.maximum.max(width),
105 }
106 }
107
108 pub fn clamp_bounds(&self, min_width: Option<usize>, max_width: Option<usize>) -> Self {
136 let mut result = *self;
137 if let Some(min_w) = min_width {
138 result = result.with_minimum(min_w);
139 }
140 if let Some(max_w) = max_width {
141 result = result.with_maximum(max_w);
142 }
143 result
144 }
145
146 #[track_caller]
168 pub fn clamp_width(&self, width: usize) -> usize {
169 debug_assert!(
170 self.minimum <= self.maximum,
171 "Measurement invariant violated: minimum ({}) > maximum ({})",
172 self.minimum,
173 self.maximum
174 );
175 width.clamp(self.minimum, self.maximum)
176 }
177
178 pub fn union(&self, other: &Measurement) -> Self {
180 Measurement {
181 minimum: self.minimum.max(other.minimum),
182 maximum: self.maximum.max(other.maximum),
183 }
184 }
185
186 pub fn from_segments(segments: &Segments) -> Self {
196 let mut total_width = 0;
197 let mut max_word_width = 0;
198 let mut current_word_width = 0;
199
200 for segment in segments.iter() {
201 for c in segment.text.chars() {
202 let char_width = unicode_width::UnicodeWidthChar::width(c).unwrap_or(0);
203 total_width += char_width;
204
205 if c.is_whitespace() || c == '\n' {
206 max_word_width = max_word_width.max(current_word_width);
207 current_word_width = 0;
208 } else {
209 current_word_width += char_width;
210 }
211 }
212 }
213 max_word_width = max_word_width.max(current_word_width);
215
216 Measurement {
217 minimum: max_word_width,
218 maximum: total_width,
219 }
220 }
221}
222
223pub fn measure_renderables(
239 console: &Console,
240 options: &ConsoleOptions,
241 renderables: &[&dyn Renderable],
242) -> Measurement {
243 if renderables.is_empty() {
244 return Measurement::new(0, 0);
245 }
246
247 let mut max_minimum = 0;
248 let mut max_maximum = 0;
249
250 for renderable in renderables {
251 let measurement = renderable.measure(console, options);
252 max_minimum = max_minimum.max(measurement.minimum);
253 max_maximum = max_maximum.max(measurement.maximum);
254 }
255
256 Measurement {
257 minimum: max_minimum,
258 maximum: max_maximum,
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265
266 #[test]
267 fn test_measurement_basic() {
268 let m = Measurement::new(10, 50);
269 assert_eq!(m.minimum, 10);
270 assert_eq!(m.maximum, 50);
271 }
272
273 #[test]
274 fn test_measurement_exact() {
275 let m = Measurement::exact(25);
276 assert_eq!(m.minimum, 25);
277 assert_eq!(m.maximum, 25);
278 assert_eq!(m.span(), 0);
279 }
280
281 #[test]
282 fn test_span() {
283 let m = Measurement::new(10, 50);
284 assert_eq!(m.span(), 40);
285
286 let inverted = Measurement::new(50, 10);
288 assert_eq!(inverted.span(), 0);
289 }
290
291 #[test]
292 fn test_normalize() {
293 let m = Measurement::new(10, 50);
295 let normalized = m.normalize();
296 assert_eq!(normalized.minimum, 10);
297 assert_eq!(normalized.maximum, 50);
298
299 let inverted = Measurement::new(50, 10);
301 let normalized = inverted.normalize();
302 assert_eq!(normalized.minimum, 10);
303 assert_eq!(normalized.maximum, 10);
304
305 let equal = Measurement::new(25, 25);
307 let normalized = equal.normalize();
308 assert_eq!(normalized.minimum, 25);
309 assert_eq!(normalized.maximum, 25);
310 }
311
312 #[test]
313 fn test_with_maximum() {
314 let m = Measurement::new(10, 50);
315
316 let result = m.with_maximum(100);
318 assert_eq!(result.minimum, 10);
319 assert_eq!(result.maximum, 50);
320
321 let result = m.with_maximum(30);
323 assert_eq!(result.minimum, 10);
324 assert_eq!(result.maximum, 30);
325
326 let result = m.with_maximum(5);
328 assert_eq!(result.minimum, 5);
329 assert_eq!(result.maximum, 5);
330
331 let result = m.with_maximum(10);
333 assert_eq!(result.minimum, 10);
334 assert_eq!(result.maximum, 10);
335 }
336
337 #[test]
338 fn test_with_minimum() {
339 let m = Measurement::new(10, 50);
340
341 let result = m.with_minimum(5);
343 assert_eq!(result.minimum, 10);
344 assert_eq!(result.maximum, 50);
345
346 let result = m.with_minimum(30);
348 assert_eq!(result.minimum, 30);
349 assert_eq!(result.maximum, 50);
350
351 let result = m.with_minimum(60);
353 assert_eq!(result.minimum, 60);
354 assert_eq!(result.maximum, 60);
355
356 let result = m.with_minimum(50);
358 assert_eq!(result.minimum, 50);
359 assert_eq!(result.maximum, 50);
360 }
361
362 #[test]
363 fn test_clamp_bounds() {
364 let m = Measurement::new(10, 50);
365
366 let clamped = m.clamp_bounds(Some(15), Some(40));
368 assert_eq!(clamped.minimum, 15);
369 assert_eq!(clamped.maximum, 40);
370
371 let clamped = m.clamp_bounds(Some(20), None);
373 assert_eq!(clamped.minimum, 20);
374 assert_eq!(clamped.maximum, 50);
375
376 let clamped = m.clamp_bounds(None, Some(30));
378 assert_eq!(clamped.minimum, 10);
379 assert_eq!(clamped.maximum, 30);
380
381 let clamped = m.clamp_bounds(None, None);
383 assert_eq!(clamped.minimum, 10);
384 assert_eq!(clamped.maximum, 50);
385
386 let clamped = m.clamp_bounds(Some(40), Some(30));
389 assert_eq!(clamped.minimum, 30);
390 assert_eq!(clamped.maximum, 30);
391 }
392
393 #[test]
394 fn test_clamp_width() {
395 let m = Measurement::new(10, 50);
396 assert_eq!(m.clamp_width(5), 10); assert_eq!(m.clamp_width(10), 10); assert_eq!(m.clamp_width(30), 30); assert_eq!(m.clamp_width(50), 50); assert_eq!(m.clamp_width(100), 50); }
402
403 #[test]
404 fn test_union() {
405 let m1 = Measurement::new(10, 50);
406 let m2 = Measurement::new(15, 40);
407 let combined = m1.union(&m2);
408 assert_eq!(combined.minimum, 15);
409 assert_eq!(combined.maximum, 50);
410
411 let m3 = Measurement::new(5, 60);
412 let combined = m1.union(&m3);
413 assert_eq!(combined.minimum, 10);
414 assert_eq!(combined.maximum, 60);
415 }
416
417 #[test]
418 fn test_default() {
419 let m = Measurement::default();
420 assert_eq!(m.minimum, 0);
421 assert_eq!(m.maximum, 0);
422 }
423
424 #[test]
425 fn test_measure_renderables_empty() {
426 let console = Console::new();
427 let options = ConsoleOptions::default();
428 let renderables: Vec<&dyn Renderable> = vec![];
429 let measurement = measure_renderables(&console, &options, &renderables);
430 assert_eq!(measurement.minimum, 0);
431 assert_eq!(measurement.maximum, 0);
432 }
433
434 #[test]
435 fn test_measure_renderables_single() {
436 let console = Console::new();
437 let options = ConsoleOptions::default();
438 let text = String::from("Hello");
439 let renderables: Vec<&dyn Renderable> = vec![&text];
440 let measurement = measure_renderables(&console, &options, &renderables);
441 assert_eq!(measurement.minimum, 5);
443 assert_eq!(measurement.maximum, 5);
444 }
445
446 #[test]
447 fn test_measure_renderables_multiple() {
448 let console = Console::new();
449 let options = ConsoleOptions::default();
450 let short = String::from("Hi");
451 let long = String::from("Hello World");
452 let renderables: Vec<&dyn Renderable> = vec![&short, &long];
453 let measurement = measure_renderables(&console, &options, &renderables);
454 assert_eq!(measurement.minimum, 5);
458 assert_eq!(measurement.maximum, 11);
459 }
460
461 #[test]
462 fn test_measure_renderables_takes_max_of_measurements() {
463 let console = Console::new();
464 let options = ConsoleOptions::default();
465 let a = String::from("ABCDEFGHIJ"); let b = String::from("XY Z"); let c = String::from("12345 67"); let renderables: Vec<&dyn Renderable> = vec![&a, &b, &c];
469 let measurement = measure_renderables(&console, &options, &renderables);
470 assert_eq!(measurement.minimum, 10);
473 assert_eq!(measurement.maximum, 10);
474 }
475}