css_style/
text.rs

1use crate::{color::Color, unit::*, Style, StyleUpdater};
2use derive_rich::Rich;
3use std::{borrow::Cow, fmt};
4
5/// ```
6/// use css_style::{prelude::*, color, unit::{em, px}, text::{TextAlign, TextTransform}};
7/// use palette::rgb::Rgb;
8///
9/// style()
10///     .and_text(|conf| {
11///         conf.line_height(1.7)
12///             // rgb value f32 based, we can pass u32 hex value too e.g. 0xFFFFFFFF
13///             .color((0.5, 0.1, 0.1))
14///             // or we can use HTML colors
15///             .color(color::named::BLUEVIOLET)
16///             .align(TextAlign::Center)
17///             .transform(TextTransform::Capitalize)
18///             .indent(em(2.))
19///             // for single text shadow
20///             .and_shadow(|conf| {
21///                 conf.x(px(3))
22///                     .y(px(4))
23///                     .color(color::named::BLUE)
24///                     .blur(px(2))
25///             })
26///             // for multiple text shadows
27///             .and_shadow(|conf| {
28///                 conf.push(|conf| conf.x(px(2))).y(px(-4))
29///                     .push(|conf| conf.x(px(9)))
30///             })
31///     });
32/// ```
33#[derive(Rich, Clone, Debug, PartialEq, Default)]
34pub struct Text {
35    #[rich(write, write(option))]
36    pub color: Option<Color>,
37    #[rich(write, write(option))]
38    pub direction: Option<Direction>,
39    #[rich(write, write(option))]
40    pub letter_spacing: Option<LetterSpacing>,
41    #[rich(write, write(option))]
42    pub word_spacing: Option<WordSpacing>,
43    #[rich(write, write(option))]
44    pub line_height: Option<LineHeight>,
45    #[rich(write, write(option))]
46    pub align: Option<TextAlign>,
47    #[rich(write, write(option))]
48    pub align_last: Option<TextAlignLast>,
49    #[rich(write, write(option))]
50    pub justify: Option<TextJustify>,
51    #[rich(write, write(option), write(style = compose))]
52    pub shadow: Option<TextShadow>,
53    #[rich(write, write(option))]
54    pub indent: Option<TextIndent>,
55    #[rich(write, write(option), write(style = compose))]
56    pub decoration: Option<TextDecoration>,
57    #[rich(write, write(option))]
58    pub white_space: Option<WhiteSpace>,
59    #[rich(write, write(option))]
60    pub unicode_bidi: Option<UnicodeBidi>,
61    #[rich(write, write(option))]
62    pub transform: Option<TextTransform>,
63    #[rich(write, write(option))]
64    pub overflow: Option<TextOverflow>,
65    #[rich(write, write(option))]
66    // FIXME: this doesn't belong to text properties
67    pub vertical_align: Option<VerticalAlign>,
68    #[rich(write, write(option))]
69    pub writing_mode: Option<WritingMode>,
70    #[rich(write, write(option))]
71    pub word_wrap: Option<WordWrap>,
72    #[rich(write, write(option))]
73    pub word_break: Option<WordBreak>,
74}
75
76impl StyleUpdater for Text {
77    fn update_style(self, style: Style) -> Style {
78        style
79            .try_insert("color", self.color)
80            .try_insert("direction", self.direction)
81            .try_insert("letter-spacing", self.letter_spacing)
82            .try_insert("word-spacing", self.word_spacing)
83            .try_insert("line-height", self.line_height)
84            .try_insert("text-align", self.align)
85            .try_insert("text-align-last", self.align_last)
86            .try_insert("text-justify", self.justify)
87            .try_merge(self.shadow)
88            .try_insert("text-indent", self.indent)
89            .try_insert("text-decoration", self.decoration)
90            .try_insert("white-space", self.white_space)
91            .try_insert("unicode-bidi", self.unicode_bidi)
92            .try_insert("text-transform", self.transform)
93            .try_insert("text-overflow", self.overflow.clone())
94            .try_insert("vertical-align", self.vertical_align)
95            .try_insert("writing-mode", self.writing_mode)
96            .try_insert("word-wrap", self.word_wrap)
97            .try_insert("word-break", self.word_break)
98    }
99}
100
101impl<T: Into<Color>> From<T> for Text {
102    fn from(source: T) -> Self {
103        Self::default().color(source.into())
104    }
105}
106
107#[derive(Clone, Debug, PartialEq, From, Display)]
108pub enum TextShadow {
109    One(Shadow),
110    #[display(
111        fmt = "{}",
112        "_0.iter().map(|s| s.to_string()).collect::<Vec<_>>().join(\", \")"
113    )]
114    Multiple(Vec<Shadow>),
115    #[display(fmt = "initial")]
116    Initial,
117    #[display(fmt = "inherit")]
118    Inherit,
119    #[display(fmt = "none")]
120    None,
121    #[display(fmt = "unset")]
122    Unset,
123}
124
125impl StyleUpdater for TextShadow {
126    fn update_style(self, style: Style) -> Style {
127        style.insert("text-shadow", self)
128    }
129}
130
131impl Default for TextShadow {
132    fn default() -> Self {
133        TextShadow::Multiple(vec![])
134    }
135}
136
137impl TextShadow {
138    fn shadow(mut self, conf: impl FnOnce(Shadow) -> Shadow) -> Self {
139        self = match self {
140            Self::One(shadow) => Self::One(conf(shadow)),
141            Self::Multiple(shadows) => {
142                Self::One(conf(shadows.into_iter().next().unwrap_or_default()))
143            }
144            _ => Self::One(conf(Shadow::default())),
145        };
146        self
147    }
148
149    pub fn new() -> Self {
150        TextShadow::default()
151    }
152
153    pub fn x(self, val: impl Into<Length>) -> Self {
154        self.shadow(|sh| sh.x(val))
155    }
156
157    pub fn y(self, val: impl Into<Length>) -> Self {
158        self.shadow(|sh| sh.y(val))
159    }
160
161    pub fn blur(self, val: impl Into<Length>) -> Self {
162        self.shadow(|sh| sh.blur(val))
163    }
164
165    pub fn try_blur(self, val: Option<impl Into<Length>>) -> Self {
166        self.shadow(|sh| sh.try_blur(val))
167    }
168
169    pub fn color(self, val: impl Into<Color>) -> Self {
170        self.shadow(|sh| sh.color(val))
171    }
172
173    pub fn try_color(self, val: Option<impl Into<Color>>) -> Self {
174        self.shadow(|sh| sh.try_color(val))
175    }
176
177    pub fn push(mut self, get_val: impl FnOnce(Shadow) -> Shadow) -> Self {
178        let val = get_val(Shadow::default());
179        self = match self {
180            Self::Multiple(mut vec) => {
181                vec.push(val);
182                Self::Multiple(vec)
183            }
184            _ => Self::Multiple(vec![val]),
185        };
186        self
187    }
188}
189
190#[derive(Rich, Clone, Debug, PartialEq)]
191pub struct Shadow {
192    #[rich(write)]
193    x: Length,
194    #[rich(write)]
195    y: Length,
196    #[rich(write, write(option))]
197    blur: Option<Length>,
198    #[rich(write, write(option))]
199    color: Option<Color>,
200}
201
202impl fmt::Display for Shadow {
203    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
204        write!(f, "{} {}", self.x, self.y)?;
205        if let Some(blur) = self.blur.as_ref() {
206            write!(f, " {}", blur)?;
207        }
208        if let Some(color) = self.color.as_ref() {
209            write!(f, " {}", color)?;
210        }
211        Ok(())
212    }
213}
214
215impl Default for Shadow {
216    fn default() -> Self {
217        Self {
218            x: px(0),
219            y: px(0),
220            blur: None,
221            color: None,
222        }
223    }
224}
225
226#[derive(Clone, Copy, Debug, PartialEq, Display, From)]
227pub enum Direction {
228    #[display(fmt = "ltr")]
229    Ltr,
230    #[display(fmt = "rtl")]
231    Rtl,
232    #[display(fmt = "initial")]
233    Initial,
234    #[display(fmt = "inherit")]
235    Inherit,
236}
237
238#[derive(Clone, Debug, PartialEq, Display, From)]
239pub enum Spacing {
240    #[display(fmt = "normal")]
241    Normal,
242    Length(Length),
243    #[display(fmt = "initial")]
244    Initial,
245    #[display(fmt = "inherit")]
246    Inherit,
247}
248
249pub type LetterSpacing = Spacing;
250pub type WordSpacing = Spacing;
251
252#[derive(Clone, Debug, PartialEq, Display, From)]
253pub enum LineHeight {
254    #[display(fmt = "normal")]
255    Normal,
256    Number(f32),
257    Length(Length),
258    Percent(Percent),
259    #[display(fmt = "initial")]
260    Initial,
261    #[display(fmt = "inherit")]
262    Inherit,
263}
264
265#[derive(Clone, Copy, Debug, PartialEq, Display, From)]
266pub enum TextAlign {
267    #[display(fmt = "start")]
268    Start,
269    #[display(fmt = "end")]
270    End,
271    #[display(fmt = "left")]
272    Left,
273    #[display(fmt = "right")]
274    Right,
275    #[display(fmt = "center")]
276    Center,
277    #[display(fmt = "justify")]
278    Justify,
279    #[display(fmt = "initial")]
280    Initial,
281    #[display(fmt = "inherit")]
282    Inherit,
283}
284
285fn display_helper(value: &Option<impl ToString>) -> String {
286    value
287        .as_ref()
288        .map(|v| v.to_string() + " ")
289        .unwrap_or_else(|| "".into())
290}
291
292#[derive(Clone, Copy, Debug, PartialEq, Display, From)]
293pub enum TextDecoration {
294    #[display(
295        fmt = "{}{}{}",
296        "display_helper(line)",
297        "display_helper(color)",
298        "display_helper(style).trim()"
299    )]
300    Decoration {
301        // TODO: add support for multiple unique values
302        line: Option<TextDecorationLine>,
303        color: Option<TextDecorationColor>,
304        style: Option<TextDecorationStyle>,
305    },
306    #[display(fmt = "initial")]
307    Initial,
308    #[display(fmt = "inherit")]
309    Inherit,
310}
311
312impl Default for TextDecoration {
313    fn default() -> Self {
314        TextDecoration::Initial
315    }
316}
317
318impl TextDecoration {
319    pub fn line(mut self, value: impl Into<TextDecorationLine>) -> Self {
320        match self {
321            Self::Decoration { ref mut line, .. } => *line = Some(value.into()),
322            _ => {
323                self = Self::Decoration {
324                    line: Some(value.into()),
325                    color: None,
326                    style: None,
327                }
328            }
329        };
330        self
331    }
332
333    pub fn line_none(self) -> Self {
334        self.line(TextDecorationLine::None)
335    }
336
337    pub fn line_underline(self) -> Self {
338        self.line(TextDecorationLine::Underline)
339    }
340
341    pub fn line_overline(self) -> Self {
342        self.line(TextDecorationLine::Overline)
343    }
344
345    pub fn line_line_through(self) -> Self {
346        self.line(TextDecorationLine::LineThrough)
347    }
348
349    pub fn color(mut self, value: impl Into<TextDecorationColor>) -> Self {
350        match self {
351            Self::Decoration { ref mut color, .. } => *color = Some(value.into()),
352            _ => {
353                self = Self::Decoration {
354                    line: Some(TextDecorationLine::None),
355                    color: Some(value.into()),
356                    style: None,
357                }
358            }
359        };
360        self
361    }
362
363    pub fn style(mut self, value: impl Into<TextDecorationStyle>) -> Self {
364        match self {
365            Self::Decoration { ref mut style, .. } => *style = Some(value.into()),
366            _ => {
367                self = Self::Decoration {
368                    line: Some(TextDecorationLine::None),
369                    color: None,
370                    style: Some(value.into()),
371                }
372            }
373        };
374        self
375    }
376
377    pub fn style_solid(self) -> Self {
378        self.style(TextDecorationStyle::Solid)
379    }
380
381    pub fn style_dashed(self) -> Self {
382        self.style(TextDecorationStyle::Dashed)
383    }
384
385    // TODO: add shortcute functions none(), solid() ..etc
386}
387
388#[derive(Clone, Copy, Debug, PartialEq, Display, From)]
389pub enum TextDecorationLine {
390    #[display(fmt = "none")]
391    None,
392    #[display(fmt = "underline")]
393    Underline,
394    #[display(fmt = "overline")]
395    Overline,
396    #[display(fmt = "line-through")]
397    LineThrough,
398    #[display(fmt = "initial")]
399    Initial,
400    #[display(fmt = "inherit")]
401    Inherit,
402}
403
404#[derive(Clone, Copy, Debug, PartialEq, Display, From)]
405pub enum TextDecorationColor {
406    Color(Color),
407    #[display(fmt = "initial")]
408    Initial,
409    #[display(fmt = "inherit")]
410    Inherit,
411}
412
413#[derive(Clone, Copy, Debug, PartialEq, Display, From)]
414pub enum TextDecorationStyle {
415    #[display(fmt = "solid")]
416    Solid,
417    #[display(fmt = "double")]
418    Double,
419    #[display(fmt = "dotted")]
420    Dotted,
421    #[display(fmt = "dashed")]
422    Dashed,
423    #[display(fmt = "wavy")]
424    Wavy,
425    #[display(fmt = "initial")]
426    Initial,
427    #[display(fmt = "inherit")]
428    Inherit,
429}
430
431#[derive(Clone, Debug, PartialEq, Display, From)]
432pub enum TextIndent {
433    Length(Length),
434    Percent(Percent),
435    #[display(fmt = "initial")]
436    Initial,
437    #[display(fmt = "inherit")]
438    Inherit,
439}
440
441#[derive(Clone, Copy, Debug, PartialEq, Display, From)]
442pub enum TextTransform {
443    #[display(fmt = "none")]
444    None,
445    #[display(fmt = "capitalize")]
446    Capitalize,
447    #[display(fmt = "uppercase")]
448    Uppercase,
449    #[display(fmt = "lowercase")]
450    Lowercase,
451    #[display(fmt = "initial")]
452    Initial,
453    #[display(fmt = "inherit")]
454    Inherit,
455}
456
457#[derive(Clone, Debug, PartialEq, Display, From)]
458pub enum TextOverflow {
459    #[display(fmt = "clip")]
460    Clip,
461    #[display(fmt = "ellipsis")]
462    Ellipsis,
463    String(Cow<'static, str>),
464    #[display(fmt = "initial")]
465    Initial,
466    #[display(fmt = "inherit")]
467    Inherit,
468}
469
470#[derive(Clone, Copy, Debug, PartialEq, Display, From)]
471pub enum UnicodeBidi {
472    #[display(fmt = "normal")]
473    Normal,
474    #[display(fmt = "embed")]
475    Embed,
476    #[display(fmt = "bidi-override")]
477    BidiOverride,
478    #[display(fmt = "isolate")]
479    Isolate,
480    #[display(fmt = "isolate-override")]
481    IsolateOverride,
482    #[display(fmt = "plaintext")]
483    Plaintext,
484    #[display(fmt = "initial")]
485    Initial,
486    #[display(fmt = "inherit")]
487    Inherit,
488}
489
490#[derive(Clone, Debug, PartialEq, Display, From)]
491pub enum VerticalAlign {
492    #[display(fmt = "baseline")]
493    Baseline,
494    #[display(fmt = "sub")]
495    Sub,
496    #[display(fmt = "super")]
497    Super,
498    #[display(fmt = "top")]
499    Top,
500    #[display(fmt = "text-top")]
501    TextTop,
502    #[display(fmt = "middle")]
503    Middle,
504    #[display(fmt = "bottom")]
505    Bottom,
506    #[display(fmt = "text-bottom")]
507    TextBottom,
508    Length(Length),
509    Percent(Percent),
510    #[display(fmt = "initial")]
511    Initial,
512    #[display(fmt = "inherit")]
513    Inherit,
514}
515
516#[derive(Clone, Copy, Debug, PartialEq, Display, From)]
517pub enum WhiteSpace {
518    #[display(fmt = "normal")]
519    Normal,
520    #[display(fmt = "nowrap")]
521    Nowrap,
522    #[display(fmt = "pre")]
523    Pre,
524    #[display(fmt = "pre-line")]
525    PreLine,
526    #[display(fmt = "pre-wrap")]
527    PreWrap,
528    #[display(fmt = "initial")]
529    Initial,
530    #[display(fmt = "inherit")]
531    Inherit,
532}
533
534#[derive(Clone, Copy, Debug, PartialEq, Display, From)]
535pub enum TextAlignLast {
536    #[display(fmt = "auto")]
537    Auto,
538    #[display(fmt = "left")]
539    Left,
540    #[display(fmt = "right")]
541    Right,
542    #[display(fmt = "center")]
543    Center,
544    #[display(fmt = "justify")]
545    Justify,
546    #[display(fmt = "start")]
547    Start,
548    #[display(fmt = "end")]
549    End,
550    #[display(fmt = "initial")]
551    Initial,
552    #[display(fmt = "inherit")]
553    Inherit,
554}
555
556#[derive(Clone, Copy, Debug, PartialEq, Display, From)]
557pub enum TextJustify {
558    #[display(fmt = "auto")]
559    Auto,
560    #[display(fmt = "inter-word")]
561    InterWord,
562    #[display(fmt = "inter-character")]
563    InterCharacter,
564    #[display(fmt = "none")]
565    None,
566    #[display(fmt = "initial")]
567    Initial,
568    #[display(fmt = "inherit")]
569    Inherit,
570}
571
572#[derive(Clone, Copy, Debug, PartialEq, Display, From)]
573pub enum WordBreak {
574    #[display(fmt = "normal")]
575    Normal,
576    #[display(fmt = "break-all")]
577    BreakAll,
578    #[display(fmt = "keep-all")]
579    KeepAll,
580    #[display(fmt = "break-word")]
581    BreakWord,
582    #[display(fmt = "initial")]
583    Initial,
584    #[display(fmt = "inherit")]
585    Inherit,
586}
587
588#[derive(Clone, Copy, Debug, PartialEq, Display, From)]
589pub enum WordWrap {
590    #[display(fmt = "normal")]
591    Normal,
592    #[display(fmt = "break-word")]
593    BreakWord,
594    #[display(fmt = "initial")]
595    Initial,
596    #[display(fmt = "inherit")]
597    Inherit,
598}
599
600#[derive(Clone, Copy, Debug, PartialEq, Display, From)]
601pub enum WritingMode {
602    #[display(fmt = "horizontal-tb")]
603    HorizontalTb,
604    #[display(fmt = "vertical-rl")]
605    VerticalRl,
606    #[display(fmt = "vertical-lr")]
607    VerticalLr,
608}