fast_rich/
measure.rs

1//! Measure API for calculating renderable dimensions.
2//!
3//! Allows measuring how a renderable will be rendered without actually rendering it.
4
5use crate::console::RenderContext;
6use crate::renderable::Renderable;
7
8/// Measurement of a renderable's dimensions.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub struct Measurement {
11    /// Minimum width required
12    pub minimum: usize,
13    /// Maximum width that will be used
14    pub maximum: usize,
15    /// Number of lines when rendered
16    pub lines: usize,
17}
18
19impl Measurement {
20    /// Create a new measurement.
21    pub fn new(minimum: usize, maximum: usize, lines: usize) -> Self {
22        Measurement {
23            minimum,
24            maximum,
25            lines,
26        }
27    }
28
29    /// Measure a renderable at a given width.
30    pub fn measure(renderable: &impl Renderable, width: usize) -> Self {
31        let context = RenderContext { width, height: None };
32        let segments = renderable.render(&context);
33
34        let lines = segments.len();
35        let minimum = renderable.min_width();
36        let maximum = renderable.max_width().min(width);
37
38        Measurement {
39            minimum,
40            maximum,
41            lines,
42        }
43    }
44
45    /// Get the aspect ratio (width / height).
46    pub fn aspect_ratio(&self) -> f64 {
47        if self.lines == 0 {
48            0.0
49        } else {
50            self.maximum as f64 / self.lines as f64
51        }
52    }
53
54    /// Check if the measurement fits within given dimensions.
55    pub fn fits(&self, width: usize, height: usize) -> bool {
56        self.minimum <= width && self.lines <= height
57    }
58
59    /// Get the total area (width * height).
60    pub fn area(&self) -> usize {
61        self.maximum * self.lines
62    }
63}
64
65/// Helper trait for measuring renderables.
66pub trait Measurable {
67    /// Measure this renderable at the given width.
68    fn measure(&self, width: usize) -> Measurement;
69}
70
71impl<T: Renderable> Measurable for T {
72    fn measure(&self, width: usize) -> Measurement {
73        Measurement::measure(self, width)
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80    use crate::text::Text;
81
82    #[test]
83    fn test_measurement_creation() {
84        let m = Measurement::new(10, 50, 3);
85        assert_eq!(m.minimum, 10);
86        assert_eq!(m.maximum, 50);
87        assert_eq!(m.lines, 3);
88    }
89
90    #[test]
91    fn test_measure_text() {
92        let text = Text::plain("Hello, World!");
93        let m = Measurement::measure(&text, 80);
94
95        assert!(m.minimum > 0);
96        assert!(m.lines > 0);
97    }
98
99    #[test]
100    fn test_aspect_ratio() {
101        let m = Measurement::new(20, 40, 5);
102        assert_eq!(m.aspect_ratio(), 8.0);
103    }
104
105    #[test]
106    fn test_fits() {
107        let m = Measurement::new(10, 40, 3);
108        assert!(m.fits(50, 5));
109        assert!(!m.fits(5, 5)); // Too narrow
110        assert!(!m.fits(50, 2)); // Too short
111    }
112
113    #[test]
114    fn test_area() {
115        let m = Measurement::new(10, 20, 5);
116        assert_eq!(m.area(), 100); // 20 * 5
117    }
118}