Skip to main content

incular_text/style/
text.rs

1use super::fonts::{FontFamily, FontWeight};
2use incular_rendering::Shadow;
3use std::sync::Arc;
4
5#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
6pub enum FontStyle {
7    #[default]
8    Normal,
9    Italic,
10}
11
12#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
13pub enum TextAlign {
14    /// Aligns to the start edge of the paragraph's text direction (`left` for
15    /// LTR, `right` for RTL) - Flutter's `TextAlign.start`, which is also the
16    /// default of `Text`.
17    #[default]
18    Start,
19    Center,
20    /// Aligns to the end edge of the paragraph's text direction.
21    End,
22    Justify,
23    /// Aligns to the physical left edge regardless of text direction.
24    Left,
25    /// Aligns to the physical right edge regardless of text direction.
26    Right,
27}
28
29/// The baseline alignment policy.
30#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
31pub enum TextBaseline {
32    #[default]
33    Alphabetic,
34    Ideographic,
35}
36
37/// The leading distribution policy between line boxes.
38#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
39pub enum TextLeadingDistribution {
40    #[default]
41    Proportional,
42    Even,
43}
44
45/// Strategy for measuring paragraph width bounds.
46#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
47pub enum TextWidthBasis {
48    #[default]
49    Parent,
50    LongestLine,
51}
52
53/// Controls line-box height calculations for first ascent and last descent.
54#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
55pub struct TextHeightBehavior {
56    pub apply_height_to_first_ascent: bool,
57    pub apply_height_to_last_descent: bool,
58    pub leading_distribution: TextLeadingDistribution,
59}
60
61/// Matches Flutter's `TextHeightBehavior()`: height modifiers apply to both
62/// the first ascent and the last descent, with proportional leading.
63impl Default for TextHeightBehavior {
64    fn default() -> Self {
65        Self {
66            apply_height_to_first_ascent: true,
67            apply_height_to_last_descent: true,
68            leading_distribution: TextLeadingDistribution::Proportional,
69        }
70    }
71}
72
73impl TextHeightBehavior {
74    #[must_use]
75    pub const fn new(
76        apply_height_to_first_ascent: bool,
77        apply_height_to_last_descent: bool,
78        leading_distribution: TextLeadingDistribution,
79    ) -> Self {
80        Self {
81            apply_height_to_first_ascent,
82            apply_height_to_last_descent,
83            leading_distribution,
84        }
85    }
86}
87
88bitflags::bitflags! {
89    /// Visual line decorations for rendered text.
90    #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
91    pub struct TextDecoration: u8 {
92        const NONE = 0;
93        const UNDERLINE = 1;
94        const OVERLINE = 2;
95        const LINE_THROUGH = 4;
96    }
97}
98
99impl TextDecoration {
100    #[must_use]
101    pub const fn combine(decorations: &[Self]) -> Self {
102        let mut bits = 0;
103        let mut i = 0;
104        while i < decorations.len() {
105            bits |= decorations[i].bits();
106            i += 1;
107        }
108        Self::from_bits_retain(bits)
109    }
110}
111
112/// Visual stroke pattern for text decorations.
113#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
114pub enum TextDecorationStyle {
115    #[default]
116    Solid,
117    Double,
118    Dotted,
119    Dashed,
120    Wavy,
121}
122
123/// Compatibility spelling for the renderer-owned canonical shadow value.
124/// Text must not maintain a second color/offset/blur representation.
125pub type TextShadow = Shadow;
126
127/// Baseline strut configuration defining minimum vertical line spacing.
128#[derive(Clone, Debug, Default, PartialEq)]
129pub struct StrutStyle {
130    pub font_family: Option<FontFamily>,
131    pub font_family_fallback: Arc<[FontFamily]>,
132    pub font_size: Option<f32>,
133    pub font_weight: Option<FontWeight>,
134    pub font_style: Option<FontStyle>,
135    pub height: Option<f32>,
136    pub leading_distribution: Option<TextLeadingDistribution>,
137    pub force_strut_height: Option<bool>,
138}
139
140impl StrutStyle {
141    #[must_use]
142    pub fn new() -> Self {
143        Self::default()
144    }
145
146    #[must_use]
147    pub fn font_family(mut self, family: impl Into<String>) -> Self {
148        self.font_family = Some(FontFamily::named(family));
149        self
150    }
151
152    #[must_use]
153    pub fn font_size(mut self, size: f32) -> Self {
154        self.font_size = Some(size.max(0.0));
155        self
156    }
157
158    #[must_use]
159    pub fn font_weight(mut self, weight: FontWeight) -> Self {
160        self.font_weight = Some(weight);
161        self
162    }
163
164    #[must_use]
165    pub fn font_style(mut self, style: FontStyle) -> Self {
166        self.font_style = Some(style);
167        self
168    }
169
170    #[must_use]
171    pub fn height(mut self, height: f32) -> Self {
172        self.height = Some(height.max(0.0));
173        self
174    }
175
176    #[must_use]
177    pub fn leading_distribution(mut self, distribution: TextLeadingDistribution) -> Self {
178        self.leading_distribution = Some(distribution);
179        self
180    }
181
182    #[must_use]
183    pub fn force_strut_height(mut self, force: bool) -> Self {
184        self.force_strut_height = Some(force);
185        self
186    }
187}
188
189/// What to do when a paragraph cannot fit its configured line or width limit.
190#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
191pub enum TextOverflow {
192    #[default]
193    Clip,
194    Ellipsis,
195    Visible,
196    Fade,
197}
198
199/// Text scaling policy applied after a style's logical font size is chosen.
200#[derive(Clone, Copy, Debug, PartialEq)]
201pub struct TextScaler {
202    kind: TextScalerKind,
203    factor: f32,
204}
205
206#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
207pub enum TextScalerKind {
208    Linear,
209    NoScaling,
210}
211
212impl TextScaler {
213    pub const NO_SCALING: Self = Self {
214        kind: TextScalerKind::NoScaling,
215        factor: 1.0,
216    };
217
218    #[must_use]
219    pub const fn linear(factor: f32) -> Self {
220        Self {
221            kind: TextScalerKind::Linear,
222            factor,
223        }
224    }
225
226    #[must_use]
227    pub const fn no_scaling() -> Self {
228        Self::NO_SCALING
229    }
230
231    #[must_use]
232    pub const fn factor(self) -> f32 {
233        self.factor
234    }
235
236    #[must_use]
237    pub const fn kind(self) -> TextScalerKind {
238        self.kind
239    }
240
241    #[must_use]
242    pub fn scale(self, font_size: f32) -> f32 {
243        let factor = if self.factor.is_finite() && self.factor >= 0.0 {
244            self.factor
245        } else {
246            1.0
247        };
248        (font_size.max(0.0) * factor).max(0.0)
249    }
250
251    #[must_use]
252    pub fn is_no_scaling(self) -> bool {
253        self.kind == TextScalerKind::NoScaling
254    }
255}
256
257impl Default for TextScaler {
258    fn default() -> Self {
259        Self::NO_SCALING
260    }
261}
262
263/// Explicit text line height representation.
264#[derive(Clone, Copy, Debug, PartialEq)]
265pub enum LineHeight {
266    /// Natural line height derived from font metrics.
267    Normal,
268    /// Line height as a proportional multiplier of font size (e.g. `1.4` = 1.4 * font_size).
269    Multiplier(f32),
270    /// Line height in absolute logical pixels.
271    Absolute(f32),
272}
273
274impl LineHeight {
275    #[must_use]
276    pub const fn multiplier(factor: f32) -> Self {
277        Self::Multiplier(factor)
278    }
279
280    #[must_use]
281    pub const fn absolute(pixels: f32) -> Self {
282        Self::Absolute(pixels)
283    }
284
285    #[must_use]
286    pub fn to_parley(self, font_size: f32) -> Option<parley::style::LineHeight> {
287        match self {
288            Self::Normal => None,
289            Self::Multiplier(factor) => Some(parley::style::LineHeight::Absolute(
290                (factor * font_size).max(0.0),
291            )),
292            Self::Absolute(pixels) => Some(parley::style::LineHeight::Absolute(pixels.max(0.0))),
293        }
294    }
295}