Skip to main content

ferroui_base/media/
text_decoration_collection.rs

1use crate::media::media_collection::media_collection_type;
2use crate::media::{MediaCollection, TextDecoration, TextDecorationLocation};
3use crate::utilities::{FormatError, SpanStringTokenizer};
4use crate::Ref;
5
6media_collection_type!(
7    /// A collection that holds `TextDecoration` objects.
8    ///
9    /// A shared handle to an observable list; equality is reference equality.
10    TextDecorationCollection,
11    Ref<TextDecoration>
12);
13
14impl TextDecorationCollection {
15    /// Creates an empty collection.
16    pub fn new() -> Self {
17        Self(MediaCollection::new())
18    }
19
20    /// Creates a collection from text decorations.
21    pub fn from_items(text_decorations: impl IntoIterator<Item = Ref<TextDecoration>>) -> Self {
22        Self(MediaCollection::from_items(text_decorations))
23    }
24
25    /// Parses a `TextDecorationCollection` string: a comma separated list of
26    /// decoration locations (`Underline`, `Overline`, `Strikethrough`, `Baseline`).
27    pub fn parse(s: &str) -> Result<TextDecorationCollection, FormatError> {
28        let mut locations = Vec::new();
29
30        SpanStringTokenizer::with_message(s, "Invalid text decoration.").scope(|tokenizer| {
31            while let Some(name) = tokenizer.try_read_span()? {
32                let location = Self::get_text_decoration_location(name)?;
33
34                if locations.contains(&location) {
35                    return Err(FormatError::new("Text decoration already specified."));
36                }
37
38                locations.push(location);
39            }
40            Ok(())
41        })?;
42
43        let text_decorations = TextDecorationCollection::new();
44
45        for text_decoration_location in locations {
46            let text_decoration = TextDecoration::new();
47            text_decoration.set_location(text_decoration_location);
48            text_decorations.add(text_decoration);
49        }
50
51        Ok(text_decorations)
52    }
53
54    /// Parses a text decoration enum value.
55    fn get_text_decoration_location(s: &str) -> Result<TextDecorationLocation, FormatError> {
56        const NAMES: [(&str, TextDecorationLocation); 4] = [
57            ("Underline", TextDecorationLocation::Underline),
58            ("Overline", TextDecorationLocation::Overline),
59            ("Strikethrough", TextDecorationLocation::Strikethrough),
60            ("Baseline", TextDecorationLocation::Baseline),
61        ];
62
63        NAMES
64            .iter()
65            .find(|(name, _)| name.eq_ignore_ascii_case(s))
66            .map(|(_, location)| *location)
67            .ok_or(FormatError::new("Could not parse text decoration."))
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn should_parse_text_decorations() {
77        let baseline = TextDecorationCollection::parse("baseline").unwrap();
78        assert_eq!(TextDecorationLocation::Baseline, baseline.get(0).location());
79
80        let underline = TextDecorationCollection::parse("underline").unwrap();
81        assert_eq!(TextDecorationLocation::Underline, underline.get(0).location());
82
83        let overline = TextDecorationCollection::parse("overline").unwrap();
84        assert_eq!(TextDecorationLocation::Overline, overline.get(0).location());
85
86        let strikethrough = TextDecorationCollection::parse("strikethrough").unwrap();
87        assert_eq!(TextDecorationLocation::Strikethrough, strikethrough.get(0).location());
88    }
89
90    #[test]
91    fn should_reject_duplicates_and_unknown_names() {
92        assert!(TextDecorationCollection::parse("underline, underline").is_err());
93        assert!(TextDecorationCollection::parse("wavy").is_err());
94        assert_eq!(TextDecorationCollection::parse("Underline, Strikethrough").unwrap().len(), 2);
95    }
96}