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