Skip to main content

valo_text/
style.rs

1use valo_geometry::{Color, Point};
2
3/// `TextStyle` controls font selection and painting for a span of text.
4///
5/// Families are tried in order for each character before the collection's
6/// fallback fonts.
7#[derive(Clone, Debug, PartialEq)]
8pub struct TextStyle {
9    /// `families` lists preferred font families in fallback order.
10    pub families: Vec<String>,
11    /// `weight` selects a CSS font weight, conventionally from 100 to 900.
12    pub weight: u16,
13    /// `italic` selects an italic face when available.
14    pub italic: bool,
15    /// `stretch` selects CSS font width as a percentage, where 100 is normal.
16    ///
17    /// Valo selects a registered width and does not synthesize one.
18    pub stretch: f32,
19    /// `kerning` enables the font's kerning adjustments.
20    pub kerning: bool,
21    /// `variant_caps` selects OpenType capital-letter forms.
22    pub variant_caps: VariantCaps,
23    /// `size` is the font size in logical pixels.
24    pub size: f32,
25    /// `color` is the text fill color.
26    pub color: Color,
27    /// `letter_spacing` adds logical pixels after each grapheme cluster.
28    pub letter_spacing: f32,
29    /// `word_spacing` adds logical pixels after each space, in addition to letter spacing.
30    pub word_spacing: f32,
31    /// `height` overrides line height as a multiple of `size`.
32    ///
33    /// `None` uses the font's metrics.
34    pub height: Option<f32>,
35    /// `decoration` optionally adds an underline, overline, or strike-through.
36    pub decoration: Option<Decoration>,
37    /// `shadows` are painted back-to-front beneath the text.
38    pub shadows: Vec<Shadow>,
39}
40
41impl Default for TextStyle {
42    fn default() -> Self {
43        Self {
44            families: Vec::new(),
45            weight: 400,
46            italic: false,
47            stretch: crate::font::NORMAL_STRETCH,
48            kerning: true,
49            variant_caps: VariantCaps::Normal,
50            size: 14.0,
51            color: Color::BLACK,
52            letter_spacing: 0.0,
53            word_spacing: 0.0,
54            height: None,
55            decoration: None,
56            shadows: Vec::new(),
57        }
58    }
59}
60
61impl TextStyle {
62    /// `new` creates a style with one preferred family, size, and color.
63    pub fn new(family: &str, size: f32, color: Color) -> Self {
64        Self {
65            families: vec![family.to_owned()],
66            size,
67            color,
68            ..Default::default()
69        }
70    }
71
72    /// `font_attrs` returns the attributes used to select a face within a family.
73    pub fn font_attrs(&self) -> crate::font::FontAttrs {
74        crate::font::FontAttrs {
75            weight: self.weight,
76            italic: self.italic,
77            stretch: self.stretch,
78        }
79    }
80}
81
82/// `VariantCaps` selects an OpenType capitalization variant.
83///
84/// If a font lacks the requested feature, text remains unchanged; Valo does not
85/// synthesize capital forms.
86#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
87pub enum VariantCaps {
88    /// `Normal` leaves capitalization features disabled.
89    #[default]
90    Normal,
91    /// `SmallCaps` renders lowercase letters as small capitals.
92    SmallCaps,
93    /// `AllSmallCaps` renders both lowercase and uppercase letters as small capitals.
94    AllSmallCaps,
95    /// `PetiteCaps` renders lowercase letters as petite capitals.
96    PetiteCaps,
97    /// `AllPetiteCaps` renders both lowercase and uppercase letters as petite capitals.
98    AllPetiteCaps,
99    /// `Unicase` uses a mixture of uppercase and lowercase-sized capitals.
100    Unicase,
101    /// `TitlingCaps` uses capitals designed for display text.
102    TitlingCaps,
103}
104
105impl VariantCaps {
106    /// `feature_tags` returns the enabled OpenType tags in application order.
107    pub fn feature_tags(self) -> &'static [&'static [u8; 4]] {
108        match self {
109            Self::Normal => &[],
110            Self::SmallCaps => &[b"smcp"],
111            Self::AllSmallCaps => &[b"c2sc", b"smcp"],
112            Self::PetiteCaps => &[b"pcap"],
113            Self::AllPetiteCaps => &[b"c2pc", b"pcap"],
114            Self::Unicase => &[b"unic"],
115            Self::TitlingCaps => &[b"titl"],
116        }
117    }
118}
119
120/// `Decoration` describes a line drawn relative to styled text.
121#[derive(Clone, Copy, Debug, PartialEq)]
122pub struct Decoration {
123    /// `kind` selects the decoration's position.
124    pub kind: DecorationKind,
125    /// `color` overrides the text color when set.
126    pub color: Option<Color>,
127    /// `thickness` multiplies the font's suggested decoration thickness.
128    pub thickness: f32,
129}
130
131impl Decoration {
132    /// `new` creates a text-colored decoration at the font's suggested thickness.
133    pub fn new(kind: DecorationKind) -> Self {
134        Self {
135            kind,
136            color: None,
137            thickness: 1.0,
138        }
139    }
140}
141
142/// `DecorationKind` selects where a text decoration is drawn.
143#[derive(Clone, Copy, Debug, PartialEq, Eq)]
144pub enum DecorationKind {
145    /// `Underline` draws below the baseline using the font's underline metrics.
146    Underline,
147    /// `LineThrough` draws through the text using the font's strikeout metrics.
148    LineThrough,
149    /// `Overline` draws above the text.
150    Overline,
151}
152
153/// `Shadow` describes a colored, offset copy painted beneath text.
154#[derive(Clone, Copy, Debug, PartialEq)]
155pub struct Shadow {
156    /// `color` is the shadow color.
157    pub color: Color,
158    /// `offset` moves the shadow in logical pixels.
159    pub offset: Point,
160    /// `blur` is the Gaussian sigma; zero produces a sharp copy.
161    pub blur: f32,
162}
163
164/// `TextAlign` controls horizontal line placement within the layout width.
165#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
166pub enum TextAlign {
167    /// `Left` aligns lines with the left edge.
168    #[default]
169    Left,
170    /// `Center` centers each line.
171    Center,
172    /// `Right` aligns lines with the right edge.
173    Right,
174    /// `Justify` expands word spacing to fill eligible lines.
175    ///
176    /// Final lines and lines ending in a hard break remain unexpanded.
177    Justify,
178}
179
180impl From<TextAlign> for ParagraphStyle {
181    fn from(align: TextAlign) -> Self {
182        Self {
183            align,
184            ..Default::default()
185        }
186    }
187}
188
189/// `TextDirection` selects a paragraph's base writing direction.
190#[derive(Clone, Copy, Debug, PartialEq, Eq)]
191pub enum TextDirection {
192    /// `Ltr` sets a left-to-right base direction.
193    Ltr,
194    /// `Rtl` sets a right-to-left base direction.
195    Rtl,
196}
197
198/// `ParagraphStyle` controls layout behavior for a complete paragraph.
199#[derive(Clone, Debug, Default)]
200pub struct ParagraphStyle {
201    /// `align` controls horizontal line alignment.
202    pub align: TextAlign,
203    /// `direction` sets the bidirectional base direction.
204    ///
205    /// `None` infers it from the first strong character. Set it explicitly for
206    /// neutral text such as digits and punctuation.
207    pub direction: Option<TextDirection>,
208    /// `preserve_trailing_whitespace` includes trailing spaces in line widths.
209    pub preserve_trailing_whitespace: bool,
210    /// `max_lines` limits the number of laid-out lines.
211    pub max_lines: Option<u32>,
212    /// `ellipsis` replaces omitted content at the visual end of a truncated paragraph.
213    pub ellipsis: Option<String>,
214}