Skip to main content

sequoia_layout/
ctx.rs

1//! Text measurement context used during layout.
2
3use sequoia_core::Size;
4use sequoia_text::{
5    ElideMode, FontStyle, FontWeight, LetterSpacing, LineHeight, RichAlign, TextSpan, TextSystem,
6    WrapMode,
7};
8
9/// Borrowed text system used by layout code to shape and measure text.
10///
11/// Measurements are not cached here; the underlying [`TextSystem`] owns any
12/// caching that may occur.
13pub struct LayoutCtx<'a> {
14    text: &'a mut TextSystem,
15    default_font_size: f32,
16    available_width: f32,
17}
18
19impl<'a> LayoutCtx<'a> {
20    /// Creates a context that borrows `text` and uses `default_font_size` for
21    /// callers that do not specify a size.
22    pub fn new(text: &'a mut TextSystem, default_font_size: f32) -> Self {
23        Self {
24            text,
25            default_font_size,
26            available_width: f32::INFINITY,
27        }
28    }
29
30    /// Returns the default font size for this layout pass.
31    pub fn default_font_size(&self) -> f32 {
32        self.default_font_size
33    }
34
35    /// Sets the width available to the view currently being measured.
36    pub fn set_available_width(&mut self, width: f32) {
37        self.available_width = if width.is_finite() {
38            width.max(0.0)
39        } else {
40            f32::INFINITY
41        };
42    }
43
44    /// Returns the width available to the view currently being measured.
45    pub fn available_width(&self) -> f32 {
46        self.available_width
47    }
48
49    /// Measures the width of a single line of text.
50    pub fn text_width(&mut self, text: &str, size: f32) -> f32 {
51        self.text.text_width(text, size)
52    }
53
54    /// Measures the width of a single line using the given font weight.
55    pub fn text_width_weighted(&mut self, text: &str, size: f32, weight: FontWeight) -> f32 {
56        self.text.text_width_weighted(text, size, weight)
57    }
58
59    /// Measures the width of a single line with letter spacing.
60    pub fn text_width_spaced(&mut self, text: &str, size: f32, spacing: LetterSpacing) -> f32 {
61        self.text.text_width_spaced(text, size, spacing)
62    }
63
64    /// Measures the width of a single line with weight and letter spacing.
65    pub fn text_width_weighted_spaced(
66        &mut self,
67        text: &str,
68        size: f32,
69        weight: FontWeight,
70        spacing: LetterSpacing,
71    ) -> f32 {
72        self.text
73            .text_width_weighted_spaced(text, size, weight, spacing)
74    }
75
76    /// Measures the width of a single line with weight, spacing, family, and
77    /// style.
78    pub fn text_width_styled_spaced(
79        &mut self,
80        text: &str,
81        size: f32,
82        weight: FontWeight,
83        spacing: LetterSpacing,
84        family: Option<&str>,
85        style: FontStyle,
86    ) -> f32 {
87        self.text
88            .text_width_styled_spaced(text, size, weight, spacing, family, style)
89    }
90
91    /// Returns caret positions along a single line of text.
92    pub fn caret_positions(&mut self, text: &str, size: f32, weight: FontWeight) -> Vec<f32> {
93        self.text.caret_positions(text, size, weight)
94    }
95
96    /// Returns caret positions along a single line with letter spacing.
97    pub fn caret_positions_spaced(
98        &mut self,
99        text: &str,
100        size: f32,
101        weight: FontWeight,
102        spacing: LetterSpacing,
103    ) -> Vec<f32> {
104        self.text
105            .caret_positions_spaced(text, size, weight, spacing)
106    }
107
108    /// Measures wrapped text within `width`, returning its size.
109    pub fn text_wrapped_size(
110        &mut self,
111        text: &str,
112        size: f32,
113        weight: FontWeight,
114        width: f32,
115        mode: WrapMode,
116    ) -> Size {
117        self.text_wrapped_size_spaced(text, size, weight, width, mode, 0.0)
118    }
119
120    /// Measures wrapped text within `width` with letter spacing.
121    pub fn text_wrapped_size_spaced(
122        &mut self,
123        text: &str,
124        size: f32,
125        weight: FontWeight,
126        width: f32,
127        mode: WrapMode,
128        spacing: LetterSpacing,
129    ) -> Size {
130        let shaped = self
131            .text
132            .shape_wrapped_spaced(text, size, weight, width, mode, spacing);
133        Size::new(shaped.width(), shaped.height())
134    }
135
136    /// Measures wrapped text with weight, spacing, family, and style.
137    pub fn text_wrapped_size_styled_spaced(
138        &mut self,
139        text: &str,
140        size: f32,
141        weight: FontWeight,
142        width: f32,
143        mode: WrapMode,
144        spacing: LetterSpacing,
145        family: Option<&str>,
146        style: FontStyle,
147    ) -> Size {
148        let shaped = self
149            .text
150            .shape_wrapped_styled_spaced(text, size, weight, width, mode, spacing, family, style);
151        Size::new(shaped.width(), shaped.height())
152    }
153
154    /// Measures wrapped text with a custom line height.
155    #[allow(clippy::too_many_arguments)]
156    pub fn text_wrapped_size_styled_spaced_lh(
157        &mut self,
158        text: &str,
159        size: f32,
160        weight: FontWeight,
161        width: f32,
162        mode: WrapMode,
163        spacing: LetterSpacing,
164        family: Option<&str>,
165        style: FontStyle,
166        line_height: LineHeight,
167    ) -> Size {
168        let shaped = self.text.shape_wrapped_styled_spaced_lh(
169            text,
170            size,
171            weight,
172            width,
173            mode,
174            spacing,
175            family,
176            style,
177            line_height,
178        );
179        Size::new(shaped.width(), shaped.height())
180    }
181
182    /// Measures the longest unbreakable unit of `text` for the given wrap mode.
183    #[allow(clippy::too_many_arguments)]
184    pub fn text_min_content_width_styled_spaced(
185        &mut self,
186        text: &str,
187        size: f32,
188        weight: FontWeight,
189        spacing: LetterSpacing,
190        family: Option<&str>,
191        style: FontStyle,
192        mode: WrapMode,
193    ) -> f32 {
194        self.text
195            .min_content_width_styled_spaced(text, size, weight, spacing, family, style, mode)
196    }
197
198    /// Elides wrapped text to `max_lines`, returning the visible string.
199    #[allow(clippy::too_many_arguments)]
200    pub fn text_elide_lines_styled_spaced(
201        &mut self,
202        text: &str,
203        size: f32,
204        weight: FontWeight,
205        width: f32,
206        mode: WrapMode,
207        spacing: LetterSpacing,
208        family: Option<&str>,
209        style: FontStyle,
210        line_height: LineHeight,
211        max_lines: usize,
212    ) -> String {
213        self.text.elide_lines_styled_spaced(
214            text,
215            size,
216            weight,
217            width,
218            mode,
219            spacing,
220            family,
221            style,
222            line_height,
223            max_lines,
224        )
225    }
226
227    /// Measures text elided to `max_width` with the given elision mode.
228    pub fn text_elided_size(
229        &mut self,
230        text: &str,
231        size: f32,
232        weight: FontWeight,
233        max_width: f32,
234        mode: ElideMode,
235    ) -> Size {
236        self.text_elided_size_spaced(text, size, weight, max_width, mode, 0.0)
237    }
238
239    /// Measures elided text with letter spacing.
240    pub fn text_elided_size_spaced(
241        &mut self,
242        text: &str,
243        size: f32,
244        weight: FontWeight,
245        max_width: f32,
246        mode: ElideMode,
247        spacing: LetterSpacing,
248    ) -> Size {
249        let shown = self
250            .text
251            .elide_spaced(text, size, weight, max_width, mode, spacing);
252        let w = self
253            .text
254            .text_width_weighted_spaced(&shown, size, weight, spacing)
255            .min(max_width.max(0.0));
256        Size::new(w, Self::text_line_height(size))
257    }
258
259    /// Measures elided text with weight, spacing, family, and style.
260    pub fn text_elided_size_styled_spaced(
261        &mut self,
262        text: &str,
263        size: f32,
264        weight: FontWeight,
265        max_width: f32,
266        mode: ElideMode,
267        spacing: LetterSpacing,
268        family: Option<&str>,
269        style: FontStyle,
270    ) -> Size {
271        let shown = self
272            .text
273            .elide_styled_spaced(text, size, weight, max_width, mode, spacing, family, style);
274        let w = self
275            .text
276            .text_width_styled_spaced(&shown, size, weight, spacing, family, style)
277            .min(max_width.max(0.0));
278        Size::new(w, Self::text_line_height(size))
279    }
280
281    /// Measures elided text with a custom line height.
282    #[allow(clippy::too_many_arguments)]
283    pub fn text_elided_size_styled_spaced_lh(
284        &mut self,
285        text: &str,
286        size: f32,
287        weight: FontWeight,
288        max_width: f32,
289        mode: ElideMode,
290        spacing: LetterSpacing,
291        family: Option<&str>,
292        style: FontStyle,
293        line_height: LineHeight,
294    ) -> Size {
295        let shown = self
296            .text
297            .elide_styled_spaced(text, size, weight, max_width, mode, spacing, family, style);
298        let w = self
299            .text
300            .text_width_styled_spaced(&shown, size, weight, spacing, family, style)
301            .min(max_width.max(0.0));
302        Size::new(w, Self::text_line_height_with(size, line_height))
303    }
304
305    /// Returns the line height used for a font size with the default factor.
306    pub fn text_line_height(size: f32) -> f32 {
307        Self::text_line_height_with(size, LineHeight::default())
308    }
309
310    /// Returns the line height used for a font size.
311    pub fn text_line_height_with(size: f32, line_height: LineHeight) -> f32 {
312        line_height.resolve(size).max(1.0)
313    }
314
315    /// Returns the first baseline offset of a shaped single/wrapped line.
316    #[allow(clippy::too_many_arguments)]
317    pub fn text_first_baseline_styled_spaced_lh(
318        &mut self,
319        text: &str,
320        size: f32,
321        weight: FontWeight,
322        width: f32,
323        mode: WrapMode,
324        spacing: LetterSpacing,
325        family: Option<&str>,
326        style: FontStyle,
327        line_height: LineHeight,
328    ) -> f32 {
329        self.text
330            .shape_wrapped_styled_spaced_lh(
331                text,
332                size,
333                weight,
334                width,
335                mode,
336                spacing,
337                family,
338                style,
339                line_height,
340            )
341            .first_baseline()
342    }
343
344    /// Measures rich text spans, returning their shaped size.
345    pub fn rich_size(
346        &mut self,
347        spans: &[TextSpan],
348        size: f32,
349        width: f32,
350        mode: WrapMode,
351        spacing: LetterSpacing,
352        align: RichAlign,
353    ) -> Size {
354        self.rich_size_lh(
355            spans,
356            size,
357            width,
358            mode,
359            spacing,
360            align,
361            LineHeight::default(),
362        )
363    }
364
365    /// Measures rich text spans with a custom line height.
366    #[allow(clippy::too_many_arguments)]
367    pub fn rich_size_lh(
368        &mut self,
369        spans: &[TextSpan],
370        size: f32,
371        width: f32,
372        mode: WrapMode,
373        spacing: LetterSpacing,
374        align: RichAlign,
375        line_height: LineHeight,
376    ) -> Size {
377        self.text
378            .shape_rich_full_lh(spans, size, width, mode, spacing, align, line_height)
379            .size()
380    }
381
382    /// Elides rich text to `max_lines`.
383    #[allow(clippy::too_many_arguments)]
384    pub fn rich_elide_lines(
385        &mut self,
386        spans: &[TextSpan],
387        size: f32,
388        width: f32,
389        mode: WrapMode,
390        spacing: LetterSpacing,
391        align: RichAlign,
392        line_height: LineHeight,
393        max_lines: usize,
394    ) -> Vec<TextSpan> {
395        self.text.elide_rich_lines(
396            spans,
397            size,
398            width,
399            mode,
400            spacing,
401            align,
402            line_height,
403            max_lines,
404        )
405    }
406
407    /// Returns the first baseline of shaped rich text.
408    #[allow(clippy::too_many_arguments)]
409    pub fn rich_first_baseline(
410        &mut self,
411        spans: &[TextSpan],
412        size: f32,
413        width: f32,
414        mode: WrapMode,
415        spacing: LetterSpacing,
416        align: RichAlign,
417        line_height: LineHeight,
418    ) -> f32 {
419        self.text
420            .shape_rich_full_lh(spans, size, width, mode, spacing, align, line_height)
421            .line(0)
422            .map(|line| line.baseline)
423            .unwrap_or_else(|| Self::text_line_height_with(size, line_height) * 0.5)
424    }
425}