ailloli_ui_text/params.rs
1//! Value types that describe layout requests and results.
2
3use core::ops::Range;
4
5use ailloli_ui_core::style::TextStyle;
6
7/// Line breaking strategy for layout.
8///
9/// # Examples
10///
11/// ```
12/// use ailloli_ui_text::WrapMode;
13/// assert_ne!(WrapMode::NoWrap, WrapMode::WordOrAnywhere);
14/// ```
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub enum WrapMode {
17 /// Do not wrap; [`TextLayoutParams::max_width`] is ignored.
18 NoWrap,
19 /// Prefer word boundaries when wrapping to the requested maximum width.
20 Word,
21 /// Prefer words, but split an otherwise overflowing word at shaped clusters.
22 WordOrAnywhere,
23}
24
25/// Inputs for text layout (content, style, width, wrap).
26///
27/// # Examples
28///
29/// ```
30/// use ailloli_ui_core::{Color, FontId, TextStyle};
31/// use ailloli_ui_text::{TextLayoutParams, WrapMode};
32///
33/// let params = TextLayoutParams::new("hello", TextStyle::new(FontId::Ui, 16, Color::WHITE));
34/// assert_eq!(params.max_width, None);
35/// assert_eq!(params.wrap_mode, WrapMode::Word);
36/// ```
37#[derive(Debug, Clone, Copy)]
38pub struct TextLayoutParams<'a> {
39 /// UTF-8 text to shape; an empty string is valid.
40 pub text: &'a str,
41 /// Base font, logical-pixel size, color, and paint decoration.
42 pub style: TextStyle,
43 /// Optional wrap width in logical pixels; `None` means unconstrained.
44 ///
45 /// This value is ignored when [`Self::wrap_mode`] is [`WrapMode::NoWrap`].
46 pub max_width: Option<f32>,
47 /// Line-breaking policy.
48 pub wrap_mode: WrapMode,
49}
50
51impl<'a> TextLayoutParams<'a> {
52 /// Creates an unconstrained request using [`WrapMode::Word`].
53 ///
54 /// # Examples
55 ///
56 /// ```
57 /// use ailloli_ui_core::{Color, FontId, TextStyle};
58 /// use ailloli_ui_text::{TextLayoutParams, WrapMode};
59 ///
60 /// let params = TextLayoutParams::new("Ailloli", TextStyle::new(FontId::Mono, 13, Color::BLACK));
61 /// assert_eq!(params.text, "Ailloli");
62 /// assert_eq!(params.wrap_mode, WrapMode::Word);
63 /// ```
64 pub fn new(text: &'a str, style: TextStyle) -> Self {
65 Self {
66 text,
67 style,
68 max_width: None,
69 wrap_mode: WrapMode::Word,
70 }
71 }
72}
73
74/// Style override for a UTF-8 byte range in a text layout.
75///
76/// The range must be ordered, lie within the associated text, and use UTF-8
77/// boundaries when submitted to the layout engine. This value type does not
78/// validate those invariants itself.
79///
80/// # Examples
81///
82/// ```
83/// use ailloli_ui_core::{Color, FontId, TextStyle};
84/// use ailloli_ui_text::StyledTextSpan;
85///
86/// let span = StyledTextSpan {
87/// range: 0..4,
88/// style: TextStyle::new(FontId::Mono, 14, Color::WHITE),
89/// };
90/// assert_eq!(span.range, 0..4);
91/// ```
92#[derive(Debug, Clone, PartialEq)]
93pub struct StyledTextSpan {
94 /// Half-open UTF-8 byte range receiving the override.
95 pub range: Range<usize>,
96 /// Complete replacement style for the range.
97 pub style: TextStyle,
98}
99
100/// Inputs for styled text layout. Spans affect cache invalidation and prepare
101/// the pipeline for syntax-colored runs while preserving a single layout pass.
102///
103/// Overlapping spans are forwarded in slice order to Parley. Every span range
104/// must be a valid UTF-8 range in `text`; callers should not rely on this data
105/// structure to clip or repair invalid ranges.
106///
107/// # Examples
108///
109/// ```
110/// use ailloli_ui_core::{Color, FontId, TextStyle};
111/// use ailloli_ui_text::{StyledTextLayoutParams, StyledTextSpan, WrapMode};
112///
113/// let base = TextStyle::new(FontId::Mono, 14, Color::WHITE);
114/// let spans = [StyledTextSpan { range: 0..2, style: base.underline() }];
115/// let params = StyledTextLayoutParams {
116/// text: "fn main",
117/// base_style: base,
118/// spans: &spans,
119/// max_width: Some(120.0),
120/// wrap_mode: WrapMode::Word,
121/// };
122/// assert_eq!(params.spans.len(), 1);
123/// ```
124#[derive(Debug, Clone, Copy)]
125pub struct StyledTextLayoutParams<'a> {
126 /// UTF-8 text to shape; an empty string is valid.
127 pub text: &'a str,
128 /// Style used outside overridden ranges.
129 pub base_style: TextStyle,
130 /// Ordered range overrides, borrowed for the duration of the request.
131 pub spans: &'a [StyledTextSpan],
132 /// Optional wrap width in logical pixels; `None` means unconstrained.
133 pub max_width: Option<f32>,
134 /// Line-breaking policy.
135 pub wrap_mode: WrapMode,
136}
137
138/// Metrics for one laid-out line.
139///
140/// Distances are logical pixels. `text_range` is a half-open UTF-8 byte range
141/// in the original string. `descent` follows Parley's metric convention and is
142/// normally non-negative.
143///
144/// # Examples
145///
146/// ```
147/// use ailloli_ui_text::LaidOutLine;
148/// let line = LaidOutLine {
149/// text_range: 0..5,
150/// width: 40.0,
151/// ascent: 12.0,
152/// descent: 4.0,
153/// baseline_y: 12.0,
154/// };
155/// assert_eq!(line.text_range, 0..5);
156/// ```
157#[derive(Debug, Clone, PartialEq)]
158pub struct LaidOutLine {
159 /// Half-open UTF-8 byte range covered by the line.
160 pub text_range: Range<usize>,
161 /// Advance excluding trailing whitespace, in logical pixels.
162 pub width: f32,
163 /// Distance above the baseline, in logical pixels.
164 pub ascent: f32,
165 /// Distance below the baseline, in logical pixels.
166 pub descent: f32,
167 /// Baseline Y coordinate relative to the layout origin, in logical pixels.
168 pub baseline_y: f32,
169}
170
171/// Overall width and height of laid-out text.
172///
173/// Both dimensions are logical pixels and come directly from Parley.
174///
175/// # Examples
176///
177/// ```
178/// use ailloli_ui_text::TextMetrics;
179/// let metrics = TextMetrics { width: 80.0, height: 24.0 };
180/// assert_eq!(metrics.width, 80.0);
181/// ```
182#[derive(Debug, Clone, Copy, PartialEq)]
183pub struct TextMetrics {
184 /// Layout width in logical pixels, including Parley's layout extent.
185 pub width: f32,
186 /// Layout height in logical pixels.
187 pub height: f32,
188}