Skip to main content

cranpose_ui/text/
paragraph.rs

1use crate::text::unit::TextUnit;
2
3#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
4pub enum TextAlign {
5    #[default]
6    Unspecified,
7    Left,
8    Right,
9    Center,
10    Justify,
11    Start,
12    End,
13}
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
16pub enum TextDirection {
17    #[default]
18    Unspecified,
19    Ltr,
20    Rtl,
21    Content,
22    ContentOrLtr,
23    ContentOrRtl,
24}
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
27pub enum LineBreak {
28    #[default]
29    Unspecified,
30    Simple,
31    Paragraph,
32    Heading,
33}
34
35#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
36pub enum Hyphens {
37    #[default]
38    Unspecified,
39    None,
40    Auto,
41}
42
43#[derive(Clone, Copy, Debug, PartialEq)]
44pub struct TextIndent {
45    pub first_line: TextUnit,
46    pub rest_line: TextUnit,
47}
48
49impl Default for TextIndent {
50    fn default() -> Self {
51        Self {
52            first_line: TextUnit::Unspecified,
53            rest_line: TextUnit::Unspecified,
54        }
55    }
56}
57
58#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
59pub enum ResolvedTextDirection {
60    #[default]
61    Ltr,
62    Rtl,
63}
64
65impl TextDirection {
66    pub fn resolve(self, text: &str) -> ResolvedTextDirection {
67        match self {
68            TextDirection::Ltr => ResolvedTextDirection::Ltr,
69            TextDirection::Rtl => ResolvedTextDirection::Rtl,
70            TextDirection::Content | TextDirection::ContentOrLtr => {
71                resolve_content_direction(text, ResolvedTextDirection::Ltr)
72            }
73            TextDirection::ContentOrRtl => {
74                resolve_content_direction(text, ResolvedTextDirection::Rtl)
75            }
76            TextDirection::Unspecified => {
77                resolve_content_direction(text, ResolvedTextDirection::Ltr)
78            }
79        }
80    }
81}
82
83impl LineBreak {
84    pub fn is_specified(self) -> bool {
85        !matches!(self, Self::Unspecified)
86    }
87
88    pub fn take_or_else(self, fallback: impl FnOnce() -> LineBreak) -> LineBreak {
89        if self.is_specified() {
90            self
91        } else {
92            fallback()
93        }
94    }
95}
96
97impl Hyphens {
98    pub fn is_specified(self) -> bool {
99        !matches!(self, Self::Unspecified)
100    }
101
102    pub fn take_or_else(self, fallback: impl FnOnce() -> Hyphens) -> Hyphens {
103        if self.is_specified() {
104            self
105        } else {
106            fallback()
107        }
108    }
109}
110
111pub fn resolve_text_direction(
112    text: &str,
113    text_direction: Option<TextDirection>,
114) -> ResolvedTextDirection {
115    text_direction.unwrap_or_default().resolve(text)
116}
117
118fn resolve_content_direction(text: &str, fallback: ResolvedTextDirection) -> ResolvedTextDirection {
119    for ch in text.chars() {
120        if is_rtl_char(ch) {
121            return ResolvedTextDirection::Rtl;
122        }
123        if ch.is_alphabetic() {
124            return ResolvedTextDirection::Ltr;
125        }
126    }
127    fallback
128}
129
130fn is_rtl_char(ch: char) -> bool {
131    matches!(
132        ch as u32,
133        0x0590..=0x08FF |
134        0xFB1D..=0xFDFF |
135        0xFE70..=0xFEFF |
136        0x10800..=0x10FFF |
137        0x1E800..=0x1EEFF
138    )
139}
140
141#[cfg(test)]
142#[path = "tests/paragraph_tests.rs"]
143mod tests;