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 {
191 let mut max_line_width = 0;
192 let mut current_line_width = 0;
193 let mut max_word_width = 0;
194 let mut current_word_width = 0;
195
196 for segment in segments.iter() {
197 for c in segment.text.chars() {
198 if c == '\n' {
199 max_line_width = max_line_width.max(current_line_width);
200 max_word_width = max_word_width.max(current_word_width);
201 current_line_width = 0;
202 current_word_width = 0;
203 continue;
204 }
205
206 let char_width = unicode_width::UnicodeWidthChar::width(c).unwrap_or(0);
207 current_line_width += char_width;
208
209 if c.is_whitespace() {
210 max_word_width = max_word_width.max(current_word_width);
211 current_word_width = 0;
212 } else {
213 current_word_width += char_width;
214 }
215 }
216 }
217
218 max_line_width = max_line_width.max(current_line_width);
220 max_word_width = max_word_width.max(current_word_width);
221
222 Measurement {
223 minimum: max_word_width,
224 maximum: max_line_width,
225 }
226 }
227}
228
229pub fn measure_renderables(
245 console: &Console,
246 options: &ConsoleOptions,
247 renderables: &[&dyn Renderable],
248) -> Measurement {
249 if renderables.is_empty() {
250 return Measurement::new(0, 0);
251 }
252
253 let mut max_minimum = 0;
254 let mut max_maximum = 0;
255
256 for renderable in renderables {
257 let measurement = renderable.measure(console, options);
258 max_minimum = max_minimum.max(measurement.minimum);
259 max_maximum = max_maximum.max(measurement.maximum);
260 }
261
262 Measurement {
263 minimum: max_minimum,
264 maximum: max_maximum,
265 }
266}
267
268#[cfg(test)]
269mod tests {
270 use super::*;
271 use crate::segment::Segment;
272
273 #[test]
274 fn test_measurement_basic() {
275 let m = Measurement::new(10, 50);
276 assert_eq!(m.minimum, 10);
277 assert_eq!(m.maximum, 50);
278 }
279
280 #[test]
281 fn test_measurement_exact() {
282 let m = Measurement::exact(25);
283 assert_eq!(m.minimum, 25);
284 assert_eq!(m.maximum, 25);
285 assert_eq!(m.span(), 0);
286 }
287
288 #[test]
289 fn test_span() {
290 let m = Measurement::new(10, 50);
291 assert_eq!(m.span(), 40);
292
293 let inverted = Measurement::new(50, 10);
295 assert_eq!(inverted.span(), 0);
296 }
297
298 #[test]
299 fn test_normalize() {
300 let m = Measurement::new(10, 50);
302 let normalized = m.normalize();
303 assert_eq!(normalized.minimum, 10);
304 assert_eq!(normalized.maximum, 50);
305
306 let inverted = Measurement::new(50, 10);
308 let normalized = inverted.normalize();
309 assert_eq!(normalized.minimum, 10);
310 assert_eq!(normalized.maximum, 10);
311
312 let equal = Measurement::new(25, 25);
314 let normalized = equal.normalize();
315 assert_eq!(normalized.minimum, 25);
316 assert_eq!(normalized.maximum, 25);
317 }
318
319 #[test]
320 fn test_with_maximum() {
321 let m = Measurement::new(10, 50);
322
323 let result = m.with_maximum(100);
325 assert_eq!(result.minimum, 10);
326 assert_eq!(result.maximum, 50);
327
328 let result = m.with_maximum(30);
330 assert_eq!(result.minimum, 10);
331 assert_eq!(result.maximum, 30);
332
333 let result = m.with_maximum(5);
335 assert_eq!(result.minimum, 5);
336 assert_eq!(result.maximum, 5);
337
338 let result = m.with_maximum(10);
340 assert_eq!(result.minimum, 10);
341 assert_eq!(result.maximum, 10);
342 }
343
344 #[test]
345 fn test_with_minimum() {
346 let m = Measurement::new(10, 50);
347
348 let result = m.with_minimum(5);
350 assert_eq!(result.minimum, 10);
351 assert_eq!(result.maximum, 50);
352
353 let result = m.with_minimum(30);
355 assert_eq!(result.minimum, 30);
356 assert_eq!(result.maximum, 50);
357
358 let result = m.with_minimum(60);
360 assert_eq!(result.minimum, 60);
361 assert_eq!(result.maximum, 60);
362
363 let result = m.with_minimum(50);
365 assert_eq!(result.minimum, 50);
366 assert_eq!(result.maximum, 50);
367 }
368
369 #[test]
370 fn test_clamp_bounds() {
371 let m = Measurement::new(10, 50);
372
373 let clamped = m.clamp_bounds(Some(15), Some(40));
375 assert_eq!(clamped.minimum, 15);
376 assert_eq!(clamped.maximum, 40);
377
378 let clamped = m.clamp_bounds(Some(20), None);
380 assert_eq!(clamped.minimum, 20);
381 assert_eq!(clamped.maximum, 50);
382
383 let clamped = m.clamp_bounds(None, Some(30));
385 assert_eq!(clamped.minimum, 10);
386 assert_eq!(clamped.maximum, 30);
387
388 let clamped = m.clamp_bounds(None, None);
390 assert_eq!(clamped.minimum, 10);
391 assert_eq!(clamped.maximum, 50);
392
393 let clamped = m.clamp_bounds(Some(40), Some(30));
396 assert_eq!(clamped.minimum, 30);
397 assert_eq!(clamped.maximum, 30);
398 }
399
400 #[test]
401 fn test_clamp_width() {
402 let m = Measurement::new(10, 50);
403 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); }
409
410 #[test]
411 fn test_union() {
412 let m1 = Measurement::new(10, 50);
413 let m2 = Measurement::new(15, 40);
414 let combined = m1.union(&m2);
415 assert_eq!(combined.minimum, 15);
416 assert_eq!(combined.maximum, 50);
417
418 let m3 = Measurement::new(5, 60);
419 let combined = m1.union(&m3);
420 assert_eq!(combined.minimum, 10);
421 assert_eq!(combined.maximum, 60);
422 }
423
424 #[test]
425 fn test_default() {
426 let m = Measurement::default();
427 assert_eq!(m.minimum, 0);
428 assert_eq!(m.maximum, 0);
429 }
430
431 #[test]
432 fn test_measure_renderables_empty() {
433 let console = Console::new();
434 let options = ConsoleOptions::default();
435 let renderables: Vec<&dyn Renderable> = vec![];
436 let measurement = measure_renderables(&console, &options, &renderables);
437 assert_eq!(measurement.minimum, 0);
438 assert_eq!(measurement.maximum, 0);
439 }
440
441 #[test]
442 fn test_measure_renderables_single() {
443 let console = Console::new();
444 let options = ConsoleOptions::default();
445 let text = String::from("Hello");
446 let renderables: Vec<&dyn Renderable> = vec![&text];
447 let measurement = measure_renderables(&console, &options, &renderables);
448 assert_eq!(measurement.minimum, 5);
450 assert_eq!(measurement.maximum, 5);
451 }
452
453 #[test]
454 fn test_measure_renderables_multiple() {
455 let console = Console::new();
456 let options = ConsoleOptions::default();
457 let short = String::from("Hi");
458 let long = String::from("Hello World");
459 let renderables: Vec<&dyn Renderable> = vec![&short, &long];
460 let measurement = measure_renderables(&console, &options, &renderables);
461 assert_eq!(measurement.minimum, 5);
465 assert_eq!(measurement.maximum, 11);
466 }
467
468 #[test]
469 fn test_measure_renderables_takes_max_of_measurements() {
470 let console = Console::new();
471 let options = ConsoleOptions::default();
472 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];
476 let measurement = measure_renderables(&console, &options, &renderables);
477 assert_eq!(measurement.minimum, 10);
480 assert_eq!(measurement.maximum, 10);
481 }
482
483 #[test]
484 fn test_from_segments_multiline_uses_widest_line() {
485 let segments: Segments = vec![Segment::new("abcd\nef")].into();
486 let measurement = Measurement::from_segments(&segments);
487
488 assert_eq!(measurement.minimum, 4);
489 assert_eq!(measurement.maximum, 4);
490 }
491}