Skip to main content

azul_css/props/layout/
wrapping.rs

1//! CSS properties for text wrapping and writing modes.
2
3use alloc::string::{String, ToString};
4
5use crate::props::formatter::PrintAsCssValue;
6
7// --- flex-wrap (LayoutWrap) ---
8
9/// Represents a `flex-wrap` attribute
10#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
11#[repr(C)]
12pub enum LayoutWrap {
13    NoWrap,
14    Wrap,
15    WrapReverse,
16}
17
18impl Default for LayoutWrap {
19    fn default() -> Self {
20        LayoutWrap::NoWrap
21    }
22}
23
24impl core::fmt::Debug for LayoutWrap {
25    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
26        write!(f, "{}", self.print_as_css_value())
27    }
28}
29
30impl core::fmt::Display for LayoutWrap {
31    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
32        write!(f, "{}", self.print_as_css_value())
33    }
34}
35
36impl PrintAsCssValue for LayoutWrap {
37    fn print_as_css_value(&self) -> String {
38        match self {
39            LayoutWrap::NoWrap => "nowrap".to_string(),
40            LayoutWrap::Wrap => "wrap".to_string(),
41            LayoutWrap::WrapReverse => "wrap-reverse".to_string(),
42        }
43    }
44}
45
46#[cfg(feature = "parser")]
47#[derive(Clone, PartialEq)]
48pub enum LayoutWrapParseError<'a> {
49    InvalidValue(&'a str),
50}
51
52#[cfg(feature = "parser")]
53impl_debug_as_display!(LayoutWrapParseError<'a>);
54#[cfg(feature = "parser")]
55impl_display! { LayoutWrapParseError<'a>, {
56    InvalidValue(e) => format!("Invalid flex-wrap value: \"{}\"", e),
57}}
58
59#[cfg(feature = "parser")]
60#[derive(Debug, Clone, PartialEq)]
61pub enum LayoutWrapParseErrorOwned {
62    InvalidValue(String),
63}
64
65#[cfg(feature = "parser")]
66impl<'a> LayoutWrapParseError<'a> {
67    pub fn to_contained(&self) -> LayoutWrapParseErrorOwned {
68        match self {
69            LayoutWrapParseError::InvalidValue(s) => {
70                LayoutWrapParseErrorOwned::InvalidValue(s.to_string())
71            }
72        }
73    }
74}
75
76#[cfg(feature = "parser")]
77impl LayoutWrapParseErrorOwned {
78    pub fn to_shared<'a>(&'a self) -> LayoutWrapParseError<'a> {
79        match self {
80            LayoutWrapParseErrorOwned::InvalidValue(s) => {
81                LayoutWrapParseError::InvalidValue(s.as_str())
82            }
83        }
84    }
85}
86
87#[cfg(feature = "parser")]
88pub fn parse_layout_wrap<'a>(input: &'a str) -> Result<LayoutWrap, LayoutWrapParseError<'a>> {
89    let input = input.trim();
90    match input {
91        "nowrap" => Ok(LayoutWrap::NoWrap),
92        "wrap" => Ok(LayoutWrap::Wrap),
93        "wrap-reverse" => Ok(LayoutWrap::WrapReverse),
94        _ => Err(LayoutWrapParseError::InvalidValue(input)),
95    }
96}
97
98// --- writing-mode (LayoutWritingMode) ---
99
100/// Represents a `writing-mode` attribute
101#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
102#[repr(C)]
103pub enum LayoutWritingMode {
104    HorizontalTb,
105    VerticalRl,
106    VerticalLr,
107}
108
109impl Default for LayoutWritingMode {
110    fn default() -> Self {
111        LayoutWritingMode::HorizontalTb
112    }
113}
114
115impl LayoutWritingMode {
116    /// Returns true if the writing mode is vertical (VerticalRl or VerticalLr)
117    pub const fn is_vertical(self) -> bool {
118        matches!(self, Self::VerticalRl | Self::VerticalLr)
119    }
120}
121
122impl core::fmt::Debug for LayoutWritingMode {
123    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
124        write!(f, "{}", self.print_as_css_value())
125    }
126}
127
128impl core::fmt::Display for LayoutWritingMode {
129    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
130        write!(f, "{}", self.print_as_css_value())
131    }
132}
133
134impl PrintAsCssValue for LayoutWritingMode {
135    fn print_as_css_value(&self) -> String {
136        match self {
137            LayoutWritingMode::HorizontalTb => "horizontal-tb".to_string(),
138            LayoutWritingMode::VerticalRl => "vertical-rl".to_string(),
139            LayoutWritingMode::VerticalLr => "vertical-lr".to_string(),
140        }
141    }
142}
143
144#[cfg(feature = "parser")]
145#[derive(Clone, PartialEq)]
146pub enum LayoutWritingModeParseError<'a> {
147    InvalidValue(&'a str),
148}
149
150#[cfg(feature = "parser")]
151impl_debug_as_display!(LayoutWritingModeParseError<'a>);
152#[cfg(feature = "parser")]
153impl_display! { LayoutWritingModeParseError<'a>, {
154    InvalidValue(e) => format!("Invalid writing-mode value: \"{}\"", e),
155}}
156
157#[cfg(feature = "parser")]
158#[derive(Debug, Clone, PartialEq)]
159pub enum LayoutWritingModeParseErrorOwned {
160    InvalidValue(String),
161}
162
163#[cfg(feature = "parser")]
164impl<'a> LayoutWritingModeParseError<'a> {
165    pub fn to_contained(&self) -> LayoutWritingModeParseErrorOwned {
166        match self {
167            LayoutWritingModeParseError::InvalidValue(s) => {
168                LayoutWritingModeParseErrorOwned::InvalidValue(s.to_string())
169            }
170        }
171    }
172}
173
174#[cfg(feature = "parser")]
175impl LayoutWritingModeParseErrorOwned {
176    pub fn to_shared<'a>(&'a self) -> LayoutWritingModeParseError<'a> {
177        match self {
178            LayoutWritingModeParseErrorOwned::InvalidValue(s) => {
179                LayoutWritingModeParseError::InvalidValue(s.as_str())
180            }
181        }
182    }
183}
184
185#[cfg(feature = "parser")]
186pub fn parse_layout_writing_mode<'a>(
187    input: &'a str,
188) -> Result<LayoutWritingMode, LayoutWritingModeParseError<'a>> {
189    let input = input.trim();
190    match input {
191        "horizontal-tb" => Ok(LayoutWritingMode::HorizontalTb),
192        "vertical-rl" => Ok(LayoutWritingMode::VerticalRl),
193        "vertical-lr" => Ok(LayoutWritingMode::VerticalLr),
194        _ => Err(LayoutWritingModeParseError::InvalidValue(input)),
195    }
196}
197
198// --- clear (LayoutClear) ---
199
200/// Represents a `clear` attribute
201#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
202#[repr(C)]
203pub enum LayoutClear {
204    None,
205    Left,
206    Right,
207    Both,
208}
209
210impl Default for LayoutClear {
211    fn default() -> Self {
212        LayoutClear::None
213    }
214}
215
216impl core::fmt::Debug for LayoutClear {
217    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
218        write!(f, "{}", self.print_as_css_value())
219    }
220}
221
222impl core::fmt::Display for LayoutClear {
223    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
224        write!(f, "{}", self.print_as_css_value())
225    }
226}
227
228impl PrintAsCssValue for LayoutClear {
229    fn print_as_css_value(&self) -> String {
230        match self {
231            LayoutClear::None => "none".to_string(),
232            LayoutClear::Left => "left".to_string(),
233            LayoutClear::Right => "right".to_string(),
234            LayoutClear::Both => "both".to_string(),
235        }
236    }
237}
238
239#[cfg(feature = "parser")]
240#[derive(Clone, PartialEq)]
241pub enum LayoutClearParseError<'a> {
242    InvalidValue(&'a str),
243}
244
245#[cfg(feature = "parser")]
246impl_debug_as_display!(LayoutClearParseError<'a>);
247#[cfg(feature = "parser")]
248impl_display! { LayoutClearParseError<'a>, {
249    InvalidValue(e) => format!("Invalid clear value: \"{}\"", e),
250}}
251
252#[cfg(feature = "parser")]
253#[derive(Debug, Clone, PartialEq)]
254pub enum LayoutClearParseErrorOwned {
255    InvalidValue(String),
256}
257
258#[cfg(feature = "parser")]
259impl<'a> LayoutClearParseError<'a> {
260    pub fn to_contained(&self) -> LayoutClearParseErrorOwned {
261        match self {
262            LayoutClearParseError::InvalidValue(s) => {
263                LayoutClearParseErrorOwned::InvalidValue(s.to_string())
264            }
265        }
266    }
267}
268
269#[cfg(feature = "parser")]
270impl LayoutClearParseErrorOwned {
271    pub fn to_shared<'a>(&'a self) -> LayoutClearParseError<'a> {
272        match self {
273            LayoutClearParseErrorOwned::InvalidValue(s) => {
274                LayoutClearParseError::InvalidValue(s.as_str())
275            }
276        }
277    }
278}
279
280#[cfg(feature = "parser")]
281pub fn parse_layout_clear<'a>(input: &'a str) -> Result<LayoutClear, LayoutClearParseError<'a>> {
282    let input = input.trim();
283    match input {
284        "none" => Ok(LayoutClear::None),
285        "left" => Ok(LayoutClear::Left),
286        "right" => Ok(LayoutClear::Right),
287        "both" => Ok(LayoutClear::Both),
288        _ => Err(LayoutClearParseError::InvalidValue(input)),
289    }
290}
291
292#[cfg(all(test, feature = "parser"))]
293mod tests {
294    use super::*;
295
296    // LayoutWrap tests
297    #[test]
298    fn test_parse_layout_wrap_nowrap() {
299        assert_eq!(parse_layout_wrap("nowrap").unwrap(), LayoutWrap::NoWrap);
300    }
301
302    #[test]
303    fn test_parse_layout_wrap_wrap() {
304        assert_eq!(parse_layout_wrap("wrap").unwrap(), LayoutWrap::Wrap);
305    }
306
307    #[test]
308    fn test_parse_layout_wrap_wrap_reverse() {
309        assert_eq!(
310            parse_layout_wrap("wrap-reverse").unwrap(),
311            LayoutWrap::WrapReverse
312        );
313    }
314
315    #[test]
316    fn test_parse_layout_wrap_invalid() {
317        assert!(parse_layout_wrap("invalid").is_err());
318    }
319
320    #[test]
321    fn test_parse_layout_wrap_whitespace() {
322        assert_eq!(parse_layout_wrap("  wrap  ").unwrap(), LayoutWrap::Wrap);
323    }
324
325    #[test]
326    fn test_parse_layout_wrap_case_sensitive() {
327        assert!(parse_layout_wrap("Wrap").is_err());
328        assert!(parse_layout_wrap("WRAP").is_err());
329    }
330
331    // LayoutWritingMode tests
332    #[test]
333    fn test_parse_writing_mode_horizontal_tb() {
334        assert_eq!(
335            parse_layout_writing_mode("horizontal-tb").unwrap(),
336            LayoutWritingMode::HorizontalTb
337        );
338    }
339
340    #[test]
341    fn test_parse_writing_mode_vertical_rl() {
342        assert_eq!(
343            parse_layout_writing_mode("vertical-rl").unwrap(),
344            LayoutWritingMode::VerticalRl
345        );
346    }
347
348    #[test]
349    fn test_parse_writing_mode_vertical_lr() {
350        assert_eq!(
351            parse_layout_writing_mode("vertical-lr").unwrap(),
352            LayoutWritingMode::VerticalLr
353        );
354    }
355
356    #[test]
357    fn test_parse_writing_mode_invalid() {
358        assert!(parse_layout_writing_mode("invalid").is_err());
359        assert!(parse_layout_writing_mode("horizontal").is_err());
360    }
361
362    #[test]
363    fn test_parse_writing_mode_whitespace() {
364        assert_eq!(
365            parse_layout_writing_mode("  vertical-rl  ").unwrap(),
366            LayoutWritingMode::VerticalRl
367        );
368    }
369
370    // LayoutClear tests
371    #[test]
372    fn test_parse_layout_clear_none() {
373        assert_eq!(parse_layout_clear("none").unwrap(), LayoutClear::None);
374    }
375
376    #[test]
377    fn test_parse_layout_clear_left() {
378        assert_eq!(parse_layout_clear("left").unwrap(), LayoutClear::Left);
379    }
380
381    #[test]
382    fn test_parse_layout_clear_right() {
383        assert_eq!(parse_layout_clear("right").unwrap(), LayoutClear::Right);
384    }
385
386    #[test]
387    fn test_parse_layout_clear_both() {
388        assert_eq!(parse_layout_clear("both").unwrap(), LayoutClear::Both);
389    }
390
391    #[test]
392    fn test_parse_layout_clear_invalid() {
393        assert!(parse_layout_clear("invalid").is_err());
394        assert!(parse_layout_clear("all").is_err());
395    }
396
397    #[test]
398    fn test_parse_layout_clear_whitespace() {
399        assert_eq!(parse_layout_clear("  both  ").unwrap(), LayoutClear::Both);
400    }
401
402    // Print tests
403    #[test]
404    fn test_print_layout_wrap() {
405        assert_eq!(LayoutWrap::NoWrap.print_as_css_value(), "nowrap");
406        assert_eq!(LayoutWrap::Wrap.print_as_css_value(), "wrap");
407        assert_eq!(LayoutWrap::WrapReverse.print_as_css_value(), "wrap-reverse");
408    }
409
410    #[test]
411    fn test_print_writing_mode() {
412        assert_eq!(
413            LayoutWritingMode::HorizontalTb.print_as_css_value(),
414            "horizontal-tb"
415        );
416        assert_eq!(
417            LayoutWritingMode::VerticalRl.print_as_css_value(),
418            "vertical-rl"
419        );
420        assert_eq!(
421            LayoutWritingMode::VerticalLr.print_as_css_value(),
422            "vertical-lr"
423        );
424    }
425
426    #[test]
427    fn test_print_layout_clear() {
428        assert_eq!(LayoutClear::None.print_as_css_value(), "none");
429        assert_eq!(LayoutClear::Left.print_as_css_value(), "left");
430        assert_eq!(LayoutClear::Right.print_as_css_value(), "right");
431        assert_eq!(LayoutClear::Both.print_as_css_value(), "both");
432    }
433}