Skip to main content

azul_css/props/style/
azul_exclusion.rs

1// Azul-specific CSS properties for advanced layout features
2
3use std::num::ParseFloatError;
4
5#[cfg(feature = "parser")]
6use crate::macros::*;
7use crate::{
8    corety::AzString,
9    format_rust_code::FormatAsRustCode,
10    props::{
11        basic::{length::parse_float_value, FloatValue},
12        formatter::{FormatAsCssValue, PrintAsCssValue},
13    },
14};
15
16/// `-azul-exclusion-margin` property: defines margin around shape exclusions
17///
18/// This property controls the spacing between text and shapes that text flows around.
19/// It's similar to `shape-margin` but specifically for exclusions (text wrapping).
20///
21/// # Example
22/// ```css
23/// .element {
24///     -azul-exclusion-margin: 10.5;
25/// }
26/// ```
27#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
28#[repr(C)]
29pub struct StyleExclusionMargin {
30    pub inner: FloatValue,
31}
32
33impl Default for StyleExclusionMargin {
34    fn default() -> Self {
35        Self {
36            inner: FloatValue::const_new(0),
37        }
38    }
39}
40
41impl StyleExclusionMargin {
42    pub fn is_initial(&self) -> bool {
43        self.inner.number == 0
44    }
45}
46
47impl PrintAsCssValue for StyleExclusionMargin {
48    fn print_as_css_value(&self) -> String {
49        format!("{}", self.inner.get())
50    }
51}
52
53impl FormatAsCssValue for StyleExclusionMargin {
54    fn format_as_css_value(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
55        write!(f, "{}", self.inner.get())
56    }
57}
58
59impl FormatAsRustCode for StyleExclusionMargin {
60    fn format_as_rust_code(&self, _tabs: usize) -> String {
61        format!(
62            "StyleExclusionMargin {{ inner: FloatValue::const_new({}) }}",
63            self.inner.get()
64        )
65    }
66}
67
68#[cfg(feature = "parser")]
69#[derive(Clone, PartialEq)]
70pub enum StyleExclusionMarginParseError {
71    FloatValue(ParseFloatError),
72}
73
74#[cfg(feature = "parser")]
75impl_debug_as_display!(StyleExclusionMarginParseError);
76
77#[cfg(feature = "parser")]
78impl_display! { StyleExclusionMarginParseError, {
79    FloatValue(e) => format!("Invalid -azul-exclusion-margin value: {}", e),
80}}
81
82#[cfg(feature = "parser")]
83impl_from!(ParseFloatError, StyleExclusionMarginParseError::FloatValue);
84
85#[cfg(feature = "parser")]
86#[derive(Debug, Clone, PartialEq)]
87pub enum StyleExclusionMarginParseErrorOwned {
88    FloatValue(String),
89}
90
91#[cfg(feature = "parser")]
92impl StyleExclusionMarginParseError {
93    pub fn to_contained(&self) -> StyleExclusionMarginParseErrorOwned {
94        match self {
95            Self::FloatValue(e) => {
96                StyleExclusionMarginParseErrorOwned::FloatValue(format!("{}", e))
97            }
98        }
99    }
100}
101
102#[cfg(feature = "parser")]
103impl StyleExclusionMarginParseErrorOwned {
104    pub fn to_shared<'a>(&'a self) -> StyleExclusionMarginParseError {
105        match self {
106            Self::FloatValue(e) => {
107                // ParseFloatError doesn't have to_shared, so we recreate it from string
108                StyleExclusionMarginParseError::FloatValue(e.parse::<f32>().unwrap_err())
109            }
110        }
111    }
112}
113
114#[cfg(feature = "parser")]
115pub fn parse_style_exclusion_margin(
116    input: &str,
117) -> Result<StyleExclusionMargin, StyleExclusionMarginParseError> {
118    parse_float_value(input)
119        .map(|inner| StyleExclusionMargin { inner })
120        .map_err(|e| StyleExclusionMarginParseError::FloatValue(e))
121}
122
123/// `-azul-hyphenation-language` property: specifies language for hyphenation
124///
125/// This property defines the language code (BCP 47 format) used for automatic
126/// hyphenation. Examples: "en-US", "de-DE", "fr-FR"
127///
128/// # Example
129/// ```css
130/// .element {
131///     -azul-hyphenation-language: "en-US";
132/// }
133/// ```
134#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
135#[repr(C)]
136pub struct StyleHyphenationLanguage {
137    pub inner: AzString,
138}
139
140impl Default for StyleHyphenationLanguage {
141    fn default() -> Self {
142        Self {
143            inner: AzString::from_const_str("en-US"),
144        }
145    }
146}
147
148impl StyleHyphenationLanguage {
149    pub const fn is_initial(&self) -> bool {
150        // Cannot compare AzString in const context, so always return false
151        false
152    }
153}
154
155impl PrintAsCssValue for StyleHyphenationLanguage {
156    fn print_as_css_value(&self) -> String {
157        format!("\"{}\"", self.inner.as_str())
158    }
159}
160
161impl FormatAsCssValue for StyleHyphenationLanguage {
162    fn format_as_css_value(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
163        write!(f, "\"{}\"", self.inner.as_str())
164    }
165}
166
167impl FormatAsRustCode for StyleHyphenationLanguage {
168    fn format_as_rust_code(&self, _tabs: usize) -> String {
169        format!(
170            "StyleHyphenationLanguage {{ inner: AzString::from_const_str(\"{}\") }}",
171            self.inner.as_str()
172        )
173    }
174}
175
176#[cfg(feature = "parser")]
177#[derive(Clone, PartialEq)]
178pub enum StyleHyphenationLanguageParseError {
179    InvalidString(String),
180}
181
182#[cfg(feature = "parser")]
183impl_debug_as_display!(StyleHyphenationLanguageParseError);
184
185#[cfg(feature = "parser")]
186impl_display! { StyleHyphenationLanguageParseError, {
187    InvalidString(e) => format!("Invalid -azul-hyphenation-language value: {}", e),
188}}
189
190#[cfg(feature = "parser")]
191#[derive(Debug, Clone, PartialEq)]
192pub enum StyleHyphenationLanguageParseErrorOwned {
193    InvalidString(String),
194}
195
196#[cfg(feature = "parser")]
197impl StyleHyphenationLanguageParseError {
198    pub fn to_contained(&self) -> StyleHyphenationLanguageParseErrorOwned {
199        match self {
200            Self::InvalidString(e) => {
201                StyleHyphenationLanguageParseErrorOwned::InvalidString(e.clone())
202            }
203        }
204    }
205}
206
207#[cfg(feature = "parser")]
208impl StyleHyphenationLanguageParseErrorOwned {
209    pub fn to_shared<'a>(&'a self) -> StyleHyphenationLanguageParseError {
210        match self {
211            Self::InvalidString(e) => StyleHyphenationLanguageParseError::InvalidString(e.clone()),
212        }
213    }
214}
215
216#[cfg(feature = "parser")]
217pub fn parse_style_hyphenation_language(
218    input: &str,
219) -> Result<StyleHyphenationLanguage, StyleHyphenationLanguageParseError> {
220    // Remove quotes if present
221    let trimmed = input.trim();
222    let unquoted = if (trimmed.starts_with('"') && trimmed.ends_with('"'))
223        || (trimmed.starts_with('\'') && trimmed.ends_with('\''))
224    {
225        &trimmed[1..trimmed.len() - 1]
226    } else {
227        trimmed
228    };
229
230    Ok(StyleHyphenationLanguage {
231        inner: AzString::from_string(unquoted.to_string()),
232    })
233}
234
235#[cfg(test)]
236mod tests {
237    use super::*;
238
239    #[test]
240    fn test_parse_exclusion_margin() {
241        let margin = parse_style_exclusion_margin("10.5").unwrap();
242        assert_eq!(margin.inner.get(), 10.5);
243
244        let margin = parse_style_exclusion_margin("0").unwrap();
245        assert_eq!(margin.inner.get(), 0.0);
246    }
247
248    #[test]
249    fn test_parse_hyphenation_language() {
250        let lang = parse_style_hyphenation_language("\"en-US\"").unwrap();
251        assert_eq!(lang.inner.as_str(), "en-US");
252
253        let lang = parse_style_hyphenation_language("'de-DE'").unwrap();
254        assert_eq!(lang.inner.as_str(), "de-DE");
255
256        let lang = parse_style_hyphenation_language("fr-FR").unwrap();
257        assert_eq!(lang.inner.as_str(), "fr-FR");
258    }
259
260    #[test]
261    fn test_exclusion_margin_default() {
262        let margin = StyleExclusionMargin::default();
263        assert_eq!(margin.inner.get(), 0.0);
264        assert!(margin.is_initial());
265    }
266
267    #[test]
268    fn test_hyphenation_language_default() {
269        let lang = StyleHyphenationLanguage::default();
270        assert_eq!(lang.inner.as_str(), "en-US");
271    }
272}