Skip to main content

azul_css/props/layout/
wrapping.rs

1//! CSS properties for writing modes and clearing.
2//!
3//! Key types:
4//! - [`LayoutWritingMode`] — `writing-mode` (`horizontal-tb`, `vertical-rl`, `vertical-lr`)
5//! - [`LayoutClear`] — `clear` (`none`, `left`, `right`, `both`)
6//!
7//! Parse functions are gated behind the `parser` feature and are consumed
8//! by the CSS property system in `property.rs`.
9
10use alloc::string::{String, ToString};
11use crate::corety::AzString;
12
13use crate::props::formatter::PrintAsCssValue;
14
15// --- writing-mode (LayoutWritingMode) ---
16
17// +spec:writing-modes:ec496c - writing-mode property: horizontal-tb, vertical-rl, vertical-lr block flow directions
18// +spec:writing-modes:fdc4cc - writing-mode property: horizontal-tb | vertical-rl | vertical-lr
19// +spec:writing-modes:aeb9bb - writing-mode property determines block flow direction
20/// Represents a `writing-mode` attribute
21// +spec:writing-modes:a7f174 - line orientation: in vertical-lr the line-over (ascender) side is block-end, not block-start
22#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
23#[repr(C)]
24// +spec:block-formatting-context:387117 - writing-mode specifies horizontal/vertical line layout and block progression direction
25// +spec:block-formatting-context:3815e7 - vertical-rl writing mode supported via VerticalRl variant
26// +spec:block-formatting-context:9d7cd4 - vertical writing mode support (VerticalRl, VerticalLr)
27#[derive(Default)]
28pub enum LayoutWritingMode {
29    /// Top-to-bottom block flow, left-to-right inline direction (Latin, etc.).
30    #[default]
31    HorizontalTb,
32    /// Right-to-left block flow, top-to-bottom inline direction (CJK vertical).
33    VerticalRl,
34    // +spec:writing-modes:f35728 - vertical-lr writing mode for left-to-right block flow (Manchu, Mongolian)
35    /// Left-to-right block flow, top-to-bottom inline direction (Mongolian).
36    VerticalLr,
37}
38
39
40impl LayoutWritingMode {
41    /// Returns true if the writing mode is vertical (VerticalRl or VerticalLr)
42    pub const fn is_vertical(self) -> bool {
43        matches!(self, Self::VerticalRl | Self::VerticalLr)
44    }
45}
46
47impl core::fmt::Debug for LayoutWritingMode {
48    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
49        write!(f, "{}", self.print_as_css_value())
50    }
51}
52
53impl core::fmt::Display for LayoutWritingMode {
54    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
55        write!(f, "{}", self.print_as_css_value())
56    }
57}
58
59impl PrintAsCssValue for LayoutWritingMode {
60    fn print_as_css_value(&self) -> String {
61        match self {
62            LayoutWritingMode::HorizontalTb => "horizontal-tb".to_string(),
63            LayoutWritingMode::VerticalRl => "vertical-rl".to_string(),
64            LayoutWritingMode::VerticalLr => "vertical-lr".to_string(),
65        }
66    }
67}
68
69#[cfg(feature = "parser")]
70#[derive(Clone, PartialEq)]
71pub enum LayoutWritingModeParseError<'a> {
72    InvalidValue(&'a str),
73}
74
75#[cfg(feature = "parser")]
76impl_debug_as_display!(LayoutWritingModeParseError<'a>);
77#[cfg(feature = "parser")]
78impl_display! { LayoutWritingModeParseError<'a>, {
79    InvalidValue(e) => format!("Invalid writing-mode value: \"{}\"", e),
80}}
81
82#[cfg(feature = "parser")]
83#[derive(Debug, Clone, PartialEq)]
84#[repr(C, u8)]
85pub enum LayoutWritingModeParseErrorOwned {
86    InvalidValue(AzString),
87}
88
89#[cfg(feature = "parser")]
90impl<'a> LayoutWritingModeParseError<'a> {
91    pub fn to_contained(&self) -> LayoutWritingModeParseErrorOwned {
92        match self {
93            LayoutWritingModeParseError::InvalidValue(s) => {
94                LayoutWritingModeParseErrorOwned::InvalidValue(s.to_string().into())
95            }
96        }
97    }
98}
99
100#[cfg(feature = "parser")]
101impl LayoutWritingModeParseErrorOwned {
102    pub fn to_shared<'a>(&'a self) -> LayoutWritingModeParseError<'a> {
103        match self {
104            LayoutWritingModeParseErrorOwned::InvalidValue(s) => {
105                LayoutWritingModeParseError::InvalidValue(s.as_str())
106            }
107        }
108    }
109}
110
111#[cfg(feature = "parser")]
112pub fn parse_layout_writing_mode<'a>(
113    input: &'a str,
114) -> Result<LayoutWritingMode, LayoutWritingModeParseError<'a>> {
115    let input = input.trim();
116    match input {
117        "horizontal-tb" => Ok(LayoutWritingMode::HorizontalTb),
118        "vertical-rl" => Ok(LayoutWritingMode::VerticalRl),
119        "vertical-lr" => Ok(LayoutWritingMode::VerticalLr),
120        "tb-lr" => Ok(LayoutWritingMode::VerticalLr), // +spec:writing-modes:23147f - SVG1.1 tb-lr maps to vertical-lr
121        _ => Err(LayoutWritingModeParseError::InvalidValue(input)),
122    }
123}
124
125// --- clear (LayoutClear) ---
126
127/// Represents a `clear` attribute
128#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
129#[repr(C)]
130#[derive(Default)]
131pub enum LayoutClear {
132    /// No clearing; element is not moved below preceding floats.
133    #[default]
134    None,
135    /// Element is moved below preceding left floats.
136    Left,
137    /// Element is moved below preceding right floats.
138    Right,
139    /// Element is moved below all preceding floats.
140    Both,
141}
142
143
144impl core::fmt::Debug for LayoutClear {
145    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
146        write!(f, "{}", self.print_as_css_value())
147    }
148}
149
150impl core::fmt::Display for LayoutClear {
151    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
152        write!(f, "{}", self.print_as_css_value())
153    }
154}
155
156impl PrintAsCssValue for LayoutClear {
157    fn print_as_css_value(&self) -> String {
158        match self {
159            LayoutClear::None => "none".to_string(),
160            LayoutClear::Left => "left".to_string(),
161            LayoutClear::Right => "right".to_string(),
162            LayoutClear::Both => "both".to_string(),
163        }
164    }
165}
166
167#[cfg(feature = "parser")]
168#[derive(Clone, PartialEq)]
169pub enum LayoutClearParseError<'a> {
170    InvalidValue(&'a str),
171}
172
173#[cfg(feature = "parser")]
174impl_debug_as_display!(LayoutClearParseError<'a>);
175#[cfg(feature = "parser")]
176impl_display! { LayoutClearParseError<'a>, {
177    InvalidValue(e) => format!("Invalid clear value: \"{}\"", e),
178}}
179
180#[cfg(feature = "parser")]
181#[derive(Debug, Clone, PartialEq)]
182#[repr(C, u8)]
183pub enum LayoutClearParseErrorOwned {
184    InvalidValue(AzString),
185}
186
187#[cfg(feature = "parser")]
188impl<'a> LayoutClearParseError<'a> {
189    pub fn to_contained(&self) -> LayoutClearParseErrorOwned {
190        match self {
191            LayoutClearParseError::InvalidValue(s) => {
192                LayoutClearParseErrorOwned::InvalidValue(s.to_string().into())
193            }
194        }
195    }
196}
197
198#[cfg(feature = "parser")]
199impl LayoutClearParseErrorOwned {
200    pub fn to_shared<'a>(&'a self) -> LayoutClearParseError<'a> {
201        match self {
202            LayoutClearParseErrorOwned::InvalidValue(s) => {
203                LayoutClearParseError::InvalidValue(s.as_str())
204            }
205        }
206    }
207}
208
209#[cfg(feature = "parser")]
210pub fn parse_layout_clear<'a>(input: &'a str) -> Result<LayoutClear, LayoutClearParseError<'a>> {
211    let input = input.trim();
212    match input {
213        "none" => Ok(LayoutClear::None),
214        "left" => Ok(LayoutClear::Left),
215        "right" => Ok(LayoutClear::Right),
216        "both" => Ok(LayoutClear::Both),
217        _ => Err(LayoutClearParseError::InvalidValue(input)),
218    }
219}
220
221#[cfg(all(test, feature = "parser"))]
222mod tests {
223    use super::*;
224
225    // LayoutWritingMode tests
226    #[test]
227    fn test_parse_writing_mode_horizontal_tb() {
228        assert_eq!(
229            parse_layout_writing_mode("horizontal-tb").unwrap(),
230            LayoutWritingMode::HorizontalTb
231        );
232    }
233
234    #[test]
235    fn test_parse_writing_mode_vertical_rl() {
236        assert_eq!(
237            parse_layout_writing_mode("vertical-rl").unwrap(),
238            LayoutWritingMode::VerticalRl
239        );
240    }
241
242    #[test]
243    fn test_parse_writing_mode_vertical_lr() {
244        assert_eq!(
245            parse_layout_writing_mode("vertical-lr").unwrap(),
246            LayoutWritingMode::VerticalLr
247        );
248    }
249
250    #[test]
251    fn test_parse_writing_mode_invalid() {
252        assert!(parse_layout_writing_mode("invalid").is_err());
253        assert!(parse_layout_writing_mode("horizontal").is_err());
254    }
255
256    #[test]
257    fn test_parse_writing_mode_whitespace() {
258        assert_eq!(
259            parse_layout_writing_mode("  vertical-rl  ").unwrap(),
260            LayoutWritingMode::VerticalRl
261        );
262    }
263
264    // LayoutClear tests
265    #[test]
266    fn test_parse_layout_clear_none() {
267        assert_eq!(parse_layout_clear("none").unwrap(), LayoutClear::None);
268    }
269
270    #[test]
271    fn test_parse_layout_clear_left() {
272        assert_eq!(parse_layout_clear("left").unwrap(), LayoutClear::Left);
273    }
274
275    #[test]
276    fn test_parse_layout_clear_right() {
277        assert_eq!(parse_layout_clear("right").unwrap(), LayoutClear::Right);
278    }
279
280    #[test]
281    fn test_parse_layout_clear_both() {
282        assert_eq!(parse_layout_clear("both").unwrap(), LayoutClear::Both);
283    }
284
285    #[test]
286    fn test_parse_layout_clear_invalid() {
287        assert!(parse_layout_clear("invalid").is_err());
288        assert!(parse_layout_clear("all").is_err());
289    }
290
291    #[test]
292    fn test_parse_layout_clear_whitespace() {
293        assert_eq!(parse_layout_clear("  both  ").unwrap(), LayoutClear::Both);
294    }
295
296    // Print tests
297    #[test]
298    fn test_print_writing_mode() {
299        assert_eq!(
300            LayoutWritingMode::HorizontalTb.print_as_css_value(),
301            "horizontal-tb"
302        );
303        assert_eq!(
304            LayoutWritingMode::VerticalRl.print_as_css_value(),
305            "vertical-rl"
306        );
307        assert_eq!(
308            LayoutWritingMode::VerticalLr.print_as_css_value(),
309            "vertical-lr"
310        );
311    }
312
313    #[test]
314    fn test_print_layout_clear() {
315        assert_eq!(LayoutClear::None.print_as_css_value(), "none");
316        assert_eq!(LayoutClear::Left.print_as_css_value(), "left");
317        assert_eq!(LayoutClear::Right.print_as_css_value(), "right");
318        assert_eq!(LayoutClear::Both.print_as_css_value(), "both");
319    }
320}