Skip to main content

i_slint_core/
styled_text.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4/// Styled text that has been parsed and separated into paragraphs
5#[repr(transparent)]
6#[derive(Debug, PartialEq, Clone, Default)]
7pub struct StyledText {
8    /// Paragraphs of styled text
9    pub(crate) paragraphs: crate::SharedVector<i_slint_common::styled_text::StyledTextParagraph>,
10}
11
12/// Error returned when [`StyledText::from_markdown`] cannot parse the provided markdown input.
13#[cfg(feature = "std")]
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct StyledTextFromMarkdownError {
16    message: alloc::string::String,
17}
18
19#[cfg(feature = "std")]
20impl StyledTextFromMarkdownError {
21    fn new(errors: alloc::vec::Vec<i_slint_common::styled_text::StyledTextParseError>) -> Self {
22        Self {
23            message: errors
24                .iter()
25                .map(alloc::string::ToString::to_string)
26                .collect::<alloc::vec::Vec<_>>()
27                .join("\n"),
28        }
29    }
30}
31
32#[cfg(feature = "std")]
33impl core::fmt::Display for StyledTextFromMarkdownError {
34    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
35        f.write_str(&self.message)
36    }
37}
38
39#[cfg(feature = "std")]
40impl std::error::Error for StyledTextFromMarkdownError {}
41
42#[cfg(feature = "std")]
43impl StyledText {
44    /// Creates styled text from plain text without applying markdown parsing.
45    pub fn from_plain_text(text: &str) -> Self {
46        Self {
47            paragraphs: [i_slint_common::styled_text::paragraph_from_plain_text(text.into())]
48                .into(),
49        }
50    }
51
52    /// Parses markdown into styled text.
53    pub fn from_markdown(markdown: &str) -> Result<Self, StyledTextFromMarkdownError> {
54        let (paragraphs, errors) = i_slint_common::styled_text::parse_interpolated::<
55            &[i_slint_common::styled_text::StyledTextParagraph],
56        >(markdown, &[]);
57
58        if errors.is_empty() {
59            Ok(Self { paragraphs: paragraphs.as_slice().into() })
60        } else {
61            Err(StyledTextFromMarkdownError::new(errors))
62        }
63    }
64}
65
66pub fn get_raw_text(styled_text: &StyledText) -> alloc::borrow::Cow<'_, str> {
67    match styled_text.paragraphs.as_slice() {
68        [] => "".into(),
69        [paragraph] => paragraph.text.as_str().into(),
70        _ => {
71            let mut result = alloc::string::String::new();
72            for paragraph in styled_text.paragraphs.iter() {
73                if !result.is_empty() {
74                    result.push('\n');
75                }
76                result.push_str(paragraph.text.as_str());
77            }
78            result.into()
79        }
80    }
81}
82
83/// Bindings for cbindgen
84#[cfg(feature = "ffi")]
85pub mod ffi {
86    #![allow(unsafe_code)]
87
88    use super::*;
89
90    #[unsafe(no_mangle)]
91    /// Create a new default styled text
92    pub unsafe extern "C" fn slint_styled_text_new(out: *mut StyledText) {
93        unsafe {
94            core::ptr::write(out, Default::default());
95        }
96    }
97
98    #[unsafe(no_mangle)]
99    /// Destroy the shared string
100    pub unsafe extern "C" fn slint_styled_text_drop(text: *const StyledText) {
101        unsafe {
102            core::ptr::read(text);
103        }
104    }
105
106    #[unsafe(no_mangle)]
107    /// Returns true if \a a is equal to \a b; otherwise returns false.
108    pub extern "C" fn slint_styled_text_eq(a: &StyledText, b: &StyledText) -> bool {
109        a == b
110    }
111
112    #[unsafe(no_mangle)]
113    /// Clone the styled text
114    pub unsafe extern "C" fn slint_styled_text_clone(out: *mut StyledText, ss: &StyledText) {
115        unsafe { core::ptr::write(out, ss.clone()) }
116    }
117
118    #[cfg(feature = "std")]
119    #[unsafe(no_mangle)]
120    /// Create a styled text from plain text
121    pub extern "C" fn slint_styled_text_from_plain_text(
122        text: crate::slice::Slice<u8>,
123        out: &mut StyledText,
124    ) {
125        let text = unsafe { core::str::from_utf8_unchecked(text.as_slice()) };
126        *out = StyledText::from_plain_text(text);
127    }
128
129    #[cfg(feature = "std")]
130    #[unsafe(no_mangle)]
131    /// Parse markdown into styled text. Returns true on success.
132    /// On failure, resets `out` to default.
133    pub extern "C" fn slint_styled_text_from_markdown(
134        markdown: crate::slice::Slice<u8>,
135        out: &mut StyledText,
136    ) -> bool {
137        let markdown = unsafe { core::str::from_utf8_unchecked(markdown.as_slice()) };
138        match StyledText::from_markdown(markdown) {
139            Ok(styled) => {
140                *out = styled;
141                true
142            }
143            Err(_) => false,
144        }
145    }
146}
147
148pub fn parse_markdown(_format_string: &str, _args: &[StyledText]) -> StyledText {
149    #[cfg(feature = "std")]
150    {
151        let paragraph_slices = _args
152            .iter()
153            .map(|styled_text| styled_text.paragraphs.as_slice())
154            .collect::<alloc::vec::Vec<_>>();
155
156        let (paragraphs, errors) =
157            i_slint_common::styled_text::parse_interpolated(_format_string, &paragraph_slices);
158
159        for e in &errors {
160            crate::debug_log!("@markdown: {e}");
161        }
162
163        StyledText { paragraphs: paragraphs.as_slice().into() }
164    }
165    #[cfg(not(feature = "std"))]
166    Default::default()
167}
168
169pub fn color_to_styled_text(_color: crate::Color) -> StyledText {
170    #[cfg(feature = "std")]
171    {
172        let hex = alloc::format!(
173            "#{:02x}{:02x}{:02x}{:02x}",
174            _color.red(),
175            _color.green(),
176            _color.blue(),
177            _color.alpha()
178        );
179        StyledText::from_plain_text(&hex)
180    }
181    #[cfg(not(feature = "std"))]
182    Default::default()
183}
184
185pub fn string_to_styled_text(_string: alloc::string::String) -> StyledText {
186    #[cfg(feature = "std")]
187    {
188        if _string.is_empty() {
189            return Default::default();
190        }
191        StyledText::from_plain_text(&_string)
192    }
193    #[cfg(not(feature = "std"))]
194    Default::default()
195}
196
197#[cfg(all(test, feature = "std"))]
198mod tests {
199    use super::*;
200
201    #[test]
202    fn string_to_styled_text_returns_default_for_empty_string() {
203        assert_eq!(super::string_to_styled_text(Default::default()), StyledText::default());
204    }
205}