Skip to main content

ferroui_pretext_layout/
prepared_text_metrics.rs

1use ferroui_pretext::{LayoutCursor, PreparedTextWithSegments, PretextLayout};
2
3use crate::{LayoutError, WrapMetrics};
4
5/// Sizing helpers that reuse preparation without constructing line strings.
6pub struct PreparedTextMetrics;
7
8impl PreparedTextMetrics {
9    pub fn measure_max_line_width(prepared: &PreparedTextWithSegments) -> f64 {
10        PretextLayout::measure_natural_width(prepared)
11    }
12
13    pub fn collect_wrap_metrics(
14        prepared: &PreparedTextWithSegments,
15        max_width: f64,
16        line_height: f64,
17    ) -> WrapMetrics {
18        let stats = PretextLayout::measure_line_stats(prepared, max_width);
19        WrapMetrics {
20            line_count: stats.line_count,
21            height: stats.line_count as f64 * line_height,
22            max_line_width: stats.max_line_width,
23        }
24    }
25
26    /// Find the smallest integral width with no more lines than `max_width`.
27    /// Width is bounded to the exactly representable integer range of `f64` so
28    /// binary search makes strict progress, including very large user inputs.
29    pub fn find_tight_wrap_metrics(
30        prepared: &PreparedTextWithSegments,
31        max_width: f64,
32        line_height: f64,
33    ) -> Result<WrapMetrics, LayoutError> {
34        if !max_width.is_finite() || max_width <= 0.0 || max_width > (1u64 << 53) as f64 {
35            return Err(LayoutError::InvalidMaximumWidth);
36        }
37        if !line_height.is_finite() || line_height <= 0.0 {
38            return Err(LayoutError::InvalidLineHeight);
39        }
40        let initial = Self::collect_wrap_metrics(prepared, max_width, line_height);
41        let mut lo = 1.0;
42        let mut hi = max_width.ceil().max(1.0);
43        while lo < hi {
44            let mid = lo + ((hi - lo) * 0.5).floor();
45            let result = PretextLayout::layout(prepared, mid, line_height);
46            if result.line_count <= initial.line_count {
47                hi = mid;
48            } else {
49                lo = mid + 1.0;
50            }
51        }
52        Ok(Self::collect_wrap_metrics(prepared, lo, line_height))
53    }
54
55    pub fn is_end(prepared: &PreparedTextWithSegments, cursor: LayoutCursor) -> bool {
56        cursor.segment_index >= prepared.segments().len()
57    }
58}