Skip to main content

telar_renderer_core/style/
mod.rs

1mod gradient;
2mod paint;
3mod scale;
4mod shape;
5
6pub use gradient::{Gradient, GradientKind, GradientStop, GradientStops};
7pub use paint::{FillRule, LineCap, LineJoin, Paint, Shadow, Stroke};
8pub use shape::{PathStyle, RectStyle, ShapeStyle};
9
10/// Horizontal alignment of text within its box. `Start` is the writing-direction start (left in LTR).
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
12pub enum TextAlign {
13    #[default]
14    Start,
15    Center,
16    End,
17    Justify,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq)]
21pub struct TextStyle {
22    pub font_size: f32,
23    pub paint: Paint,
24    pub shadow: Option<Shadow>,
25    /// OpenType weight axis: 400 is normal, 700 is bold. Selects the matching font face.
26    pub weight: u16,
27    pub italic: bool,
28    pub align: TextAlign,
29    /// Clamp the text to at most this many lines (`None` = unlimited). Lines beyond it are dropped.
30    pub max_lines: Option<u16>,
31    /// When clamped by `max_lines`, replace the overflowing tail with an ellipsis (`…`).
32    pub ellipsis: bool,
33    /// Line height as a multiple of `font_size` (e.g. `1.5`). `None` keeps the shaper's natural line height, so the default renders byte-for-byte as before.
34    pub line_height: Option<f32>,
35    /// Extra advance in logical pixels added after each glyph. `0.0` uses the font's natural advances.
36    pub letter_spacing: f32,
37}
38
39impl TextStyle {
40    pub fn new(font_size: f32, paint: impl Into<Paint>) -> Self {
41        Self {
42            font_size,
43            paint: paint.into(),
44            shadow: None,
45            weight: 400,
46            italic: false,
47            align: TextAlign::Start,
48            max_lines: None,
49            ellipsis: false,
50            line_height: None,
51            letter_spacing: 0.0,
52        }
53    }
54
55    pub fn with_weight(mut self, weight: u16) -> Self {
56        self.weight = weight;
57        self
58    }
59
60    /// Overrides the size a style was built at, so a style carrying theme-resolved weight and slant can be
61    /// re-sized without being rebuilt from scratch (and losing them).
62    pub fn with_size(mut self, font_size: f32) -> Self {
63        self.font_size = font_size;
64        self
65    }
66
67    /// Drops a shadow behind the glyphs — what keeps text legible over an image the style knows nothing about.
68    pub fn with_shadow(mut self, shadow: Shadow) -> Self {
69        self.shadow = Some(shadow);
70        self
71    }
72
73    pub fn with_italic(mut self, italic: bool) -> Self {
74        self.italic = italic;
75        self
76    }
77
78    pub fn with_align(mut self, align: TextAlign) -> Self {
79        self.align = align;
80        self
81    }
82
83    pub fn with_max_lines(mut self, max_lines: u16) -> Self {
84        self.max_lines = Some(max_lines);
85        self
86    }
87
88    pub fn with_ellipsis(mut self, ellipsis: bool) -> Self {
89        self.ellipsis = ellipsis;
90        self
91    }
92
93    pub fn with_line_height(mut self, line_height: f32) -> Self {
94        self.line_height = Some(line_height);
95        self
96    }
97
98    pub fn with_letter_spacing(mut self, letter_spacing: f32) -> Self {
99        self.letter_spacing = letter_spacing;
100        self
101    }
102}
103
104pub trait Scale: Sized {
105    fn scale(self, sf: f32) -> Self;
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111    use crate::Color;
112
113    #[test]
114    fn text_style_new_stores_font_size() {
115        let style = TextStyle::new(16.0, Color::BLACK);
116        assert_eq!(style.font_size, 16.0);
117    }
118
119    #[test]
120    fn text_style_new_stores_color() {
121        let style = TextStyle::new(12.0, Color::WHITE);
122        assert_eq!(style.paint, Paint::Solid(Color::WHITE));
123    }
124
125    #[test]
126    fn text_style_defaults_to_natural_spacing() {
127        let style = TextStyle::new(16.0, Color::BLACK);
128        assert_eq!(style.line_height, None);
129        assert_eq!(style.letter_spacing, 0.0);
130    }
131
132    #[test]
133    fn text_style_builders_set_spacing() {
134        let style = TextStyle::new(16.0, Color::BLACK)
135            .with_line_height(1.5)
136            .with_letter_spacing(2.0);
137        assert_eq!(style.line_height, Some(1.5));
138        assert_eq!(style.letter_spacing, 2.0);
139    }
140}