azul_css/props/style/
exclusion.rs1use std::num::ParseFloatError;
7
8#[cfg(feature = "parser")]
9use crate::macros::*;
10use crate::{
11 corety::AzString,
12 codegen::format::FormatAsRustCode,
13 props::{
14 basic::{length::parse_float_value, FloatValue},
15 formatter::{FormatAsCssValue, PrintAsCssValue},
16 },
17};
18
19#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
31#[repr(C)]
32pub struct StyleExclusionMargin {
33 pub inner: FloatValue,
34}
35
36impl Default for StyleExclusionMargin {
37 fn default() -> Self {
38 Self {
39 inner: FloatValue::const_new(0),
40 }
41 }
42}
43
44impl StyleExclusionMargin {
45 #[must_use] pub const fn is_initial(&self) -> bool {
46 self.inner.number == 0
47 }
48}
49
50impl PrintAsCssValue for StyleExclusionMargin {
51 fn print_as_css_value(&self) -> String {
52 format!("{}", self.inner.get())
53 }
54}
55
56impl FormatAsCssValue for StyleExclusionMargin {
57 fn format_as_css_value(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 write!(f, "{}", self.inner.get())
59 }
60}
61
62impl FormatAsRustCode for StyleExclusionMargin {
63 fn format_as_rust_code(&self, _tabs: usize) -> String {
64 format!(
65 "StyleExclusionMargin {{ inner: FloatValue::const_new({}) }}",
66 self.inner.get()
67 )
68 }
69}
70
71#[cfg(feature = "parser")]
72#[derive(Clone, PartialEq, Eq)]
73pub enum StyleExclusionMarginParseError {
74 FloatValue(ParseFloatError),
75}
76
77#[cfg(feature = "parser")]
78impl_debug_as_display!(StyleExclusionMarginParseError);
79
80#[cfg(feature = "parser")]
81impl_display! { StyleExclusionMarginParseError, {
82 FloatValue(e) => format!("Invalid -azul-exclusion-margin value: {}", e),
83}}
84
85#[cfg(feature = "parser")]
86impl_from!(ParseFloatError, StyleExclusionMarginParseError::FloatValue);
87
88#[cfg(feature = "parser")]
89#[derive(Debug, Clone, PartialEq, Eq)]
90#[repr(C, u8)]
91pub enum StyleExclusionMarginParseErrorOwned {
92 FloatValue(AzString),
93}
94
95#[cfg(feature = "parser")]
96impl StyleExclusionMarginParseError {
97 #[must_use] pub fn to_contained(&self) -> StyleExclusionMarginParseErrorOwned {
98 match self {
99 Self::FloatValue(e) => {
100 StyleExclusionMarginParseErrorOwned::FloatValue(format!("{e}").into())
101 }
102 }
103 }
104}
105
106#[cfg(feature = "parser")]
107impl StyleExclusionMarginParseErrorOwned {
108 #[must_use] pub fn to_shared(&self) -> StyleExclusionMarginParseError {
109 match self {
110 Self::FloatValue(_) => {
111 StyleExclusionMarginParseError::FloatValue("".parse::<f32>().unwrap_err())
114 }
115 }
116 }
117}
118
119#[cfg(feature = "parser")]
120pub fn parse_style_exclusion_margin(
124 input: &str,
125) -> Result<StyleExclusionMargin, StyleExclusionMarginParseError> {
126 parse_float_value(input)
127 .map(|inner| StyleExclusionMargin { inner })
128 .map_err(StyleExclusionMarginParseError::FloatValue)
129}
130
131#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
143#[repr(C)]
144pub struct StyleHyphenationLanguage {
145 pub inner: AzString,
146}
147
148impl Default for StyleHyphenationLanguage {
149 fn default() -> Self {
150 Self {
151 inner: AzString::from_const_str("en-US"),
152 }
153 }
154}
155
156impl StyleHyphenationLanguage {
157 #[must_use] pub fn is_initial(&self) -> bool {
158 self.inner.as_str() == "en-US"
159 }
160}
161
162impl PrintAsCssValue for StyleHyphenationLanguage {
163 fn print_as_css_value(&self) -> String {
164 format!("\"{}\"", self.inner.as_str())
165 }
166}
167
168impl FormatAsCssValue for StyleHyphenationLanguage {
169 fn format_as_css_value(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
170 write!(f, "\"{}\"", self.inner.as_str())
171 }
172}
173
174impl FormatAsRustCode for StyleHyphenationLanguage {
175 fn format_as_rust_code(&self, _tabs: usize) -> String {
176 format!(
177 "StyleHyphenationLanguage {{ inner: AzString::from_const_str(\"{}\") }}",
178 self.inner.as_str()
179 )
180 }
181}
182
183#[cfg(feature = "parser")]
184#[derive(Clone, PartialEq, Eq)]
185pub enum StyleHyphenationLanguageParseError {
186 InvalidString(String),
187}
188
189#[cfg(feature = "parser")]
190impl_debug_as_display!(StyleHyphenationLanguageParseError);
191
192#[cfg(feature = "parser")]
193impl_display! { StyleHyphenationLanguageParseError, {
194 InvalidString(e) => format!("Invalid -azul-hyphenation-language value: {}", e),
195}}
196
197#[cfg(feature = "parser")]
198#[derive(Debug, Clone, PartialEq, Eq)]
199#[repr(C, u8)]
200pub enum StyleHyphenationLanguageParseErrorOwned {
201 InvalidString(AzString),
202}
203
204#[cfg(feature = "parser")]
205impl StyleHyphenationLanguageParseError {
206 #[must_use] pub fn to_contained(&self) -> StyleHyphenationLanguageParseErrorOwned {
207 match self {
208 Self::InvalidString(e) => {
209 StyleHyphenationLanguageParseErrorOwned::InvalidString(e.clone().into())
210 }
211 }
212 }
213}
214
215#[cfg(feature = "parser")]
216impl StyleHyphenationLanguageParseErrorOwned {
217 #[must_use] pub fn to_shared(&self) -> StyleHyphenationLanguageParseError {
218 match self {
219 Self::InvalidString(e) => StyleHyphenationLanguageParseError::InvalidString(e.to_string()),
220 }
221 }
222}
223
224#[cfg(feature = "parser")]
225pub fn parse_style_hyphenation_language(
229 input: &str,
230) -> Result<StyleHyphenationLanguage, StyleHyphenationLanguageParseError> {
231 let trimmed = input.trim();
233 let unquoted = if (trimmed.starts_with('"') && trimmed.ends_with('"'))
234 || (trimmed.starts_with('\'') && trimmed.ends_with('\''))
235 {
236 &trimmed[1..trimmed.len() - 1]
237 } else {
238 trimmed
239 };
240
241 if unquoted.is_empty()
243 || !unquoted.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'-')
244 || unquoted.starts_with('-')
245 || unquoted.ends_with('-')
246 {
247 return Err(StyleHyphenationLanguageParseError::InvalidString(
248 unquoted.to_string(),
249 ));
250 }
251
252 Ok(StyleHyphenationLanguage {
253 inner: AzString::from_string(unquoted.to_string()),
254 })
255}
256
257#[cfg(test)]
258mod tests {
259 #![allow(clippy::float_cmp)]
261 use super::*;
262
263 #[test]
264 fn test_parse_exclusion_margin() {
265 let margin = parse_style_exclusion_margin("10.5").unwrap();
266 assert_eq!(margin.inner.get(), 10.5);
267
268 let margin = parse_style_exclusion_margin("0").unwrap();
269 assert_eq!(margin.inner.get(), 0.0);
270 }
271
272 #[test]
273 fn test_parse_hyphenation_language() {
274 let lang = parse_style_hyphenation_language("\"en-US\"").unwrap();
275 assert_eq!(lang.inner.as_str(), "en-US");
276
277 let lang = parse_style_hyphenation_language("'de-DE'").unwrap();
278 assert_eq!(lang.inner.as_str(), "de-DE");
279
280 let lang = parse_style_hyphenation_language("fr-FR").unwrap();
281 assert_eq!(lang.inner.as_str(), "fr-FR");
282
283 let lang = parse_style_hyphenation_language("zh").unwrap();
284 assert_eq!(lang.inner.as_str(), "zh");
285
286 let lang = parse_style_hyphenation_language("sr-Latn-RS").unwrap();
287 assert_eq!(lang.inner.as_str(), "sr-Latn-RS");
288
289 let lang = parse_style_hyphenation_language("en--US").unwrap();
291 assert_eq!(lang.inner.as_str(), "en--US");
292 }
293
294 #[test]
295 fn test_parse_hyphenation_language_invalid() {
296 assert!(matches!(
297 parse_style_hyphenation_language(""),
298 Err(StyleHyphenationLanguageParseError::InvalidString(_))
299 ));
300 assert!(matches!(
301 parse_style_hyphenation_language("-en"),
302 Err(StyleHyphenationLanguageParseError::InvalidString(_))
303 ));
304 assert!(matches!(
305 parse_style_hyphenation_language("en-"),
306 Err(StyleHyphenationLanguageParseError::InvalidString(_))
307 ));
308 assert!(matches!(
309 parse_style_hyphenation_language("en_US"),
310 Err(StyleHyphenationLanguageParseError::InvalidString(_))
311 ));
312 assert!(matches!(
313 parse_style_hyphenation_language("日本語"),
314 Err(StyleHyphenationLanguageParseError::InvalidString(_))
315 ));
316 }
317
318 #[test]
319 fn test_exclusion_margin_default() {
320 let margin = StyleExclusionMargin::default();
321 assert_eq!(margin.inner.get(), 0.0);
322 assert!(margin.is_initial());
323 }
324
325 #[test]
326 fn test_hyphenation_language_default() {
327 let lang = StyleHyphenationLanguage::default();
328 assert_eq!(lang.inner.as_str(), "en-US");
329 }
330}