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    #[must_use] 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            Self::HorizontalTb => "horizontal-tb".to_string(),
63            Self::VerticalRl => "vertical-rl".to_string(),
64            Self::VerticalLr => "vertical-lr".to_string(),
65        }
66    }
67}
68
69#[cfg(feature = "parser")]
70#[derive(Clone, PartialEq, Eq)]
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, Eq)]
84#[repr(C, u8)]
85pub enum LayoutWritingModeParseErrorOwned {
86    InvalidValue(AzString),
87}
88
89#[cfg(feature = "parser")]
90impl LayoutWritingModeParseError<'_> {
91    #[must_use] 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    #[must_use] pub fn to_shared(&self) -> LayoutWritingModeParseError<'_> {
103        match self {
104            Self::InvalidValue(s) => {
105                LayoutWritingModeParseError::InvalidValue(s.as_str())
106            }
107        }
108    }
109}
110
111#[cfg(feature = "parser")]
112/// # Errors
113///
114/// Returns an error if `input` is not a valid CSS `writing-mode` value.
115pub fn parse_layout_writing_mode(
116    input: &str,
117) -> Result<LayoutWritingMode, LayoutWritingModeParseError<'_>> {
118    let input = input.trim();
119    match input {
120        "horizontal-tb" => Ok(LayoutWritingMode::HorizontalTb),
121        "vertical-rl" => Ok(LayoutWritingMode::VerticalRl),
122        // +spec:writing-modes:23147f - SVG1.1 tb-lr maps to vertical-lr
123        "vertical-lr" | "tb-lr" => Ok(LayoutWritingMode::VerticalLr),
124        _ => Err(LayoutWritingModeParseError::InvalidValue(input)),
125    }
126}
127
128// --- clear (LayoutClear) ---
129
130/// Represents a `clear` attribute
131#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
132#[repr(C)]
133#[derive(Default)]
134pub enum LayoutClear {
135    /// No clearing; element is not moved below preceding floats.
136    #[default]
137    None,
138    /// Element is moved below preceding left floats.
139    Left,
140    /// Element is moved below preceding right floats.
141    Right,
142    /// Element is moved below all preceding floats.
143    Both,
144}
145
146
147impl core::fmt::Debug for LayoutClear {
148    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
149        write!(f, "{}", self.print_as_css_value())
150    }
151}
152
153impl core::fmt::Display for LayoutClear {
154    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
155        write!(f, "{}", self.print_as_css_value())
156    }
157}
158
159impl PrintAsCssValue for LayoutClear {
160    fn print_as_css_value(&self) -> String {
161        match self {
162            Self::None => "none".to_string(),
163            Self::Left => "left".to_string(),
164            Self::Right => "right".to_string(),
165            Self::Both => "both".to_string(),
166        }
167    }
168}
169
170#[cfg(feature = "parser")]
171#[derive(Clone, PartialEq, Eq)]
172pub enum LayoutClearParseError<'a> {
173    InvalidValue(&'a str),
174}
175
176#[cfg(feature = "parser")]
177impl_debug_as_display!(LayoutClearParseError<'a>);
178#[cfg(feature = "parser")]
179impl_display! { LayoutClearParseError<'a>, {
180    InvalidValue(e) => format!("Invalid clear value: \"{}\"", e),
181}}
182
183#[cfg(feature = "parser")]
184#[derive(Debug, Clone, PartialEq, Eq)]
185#[repr(C, u8)]
186pub enum LayoutClearParseErrorOwned {
187    InvalidValue(AzString),
188}
189
190#[cfg(feature = "parser")]
191impl LayoutClearParseError<'_> {
192    #[must_use] pub fn to_contained(&self) -> LayoutClearParseErrorOwned {
193        match self {
194            LayoutClearParseError::InvalidValue(s) => {
195                LayoutClearParseErrorOwned::InvalidValue((*s).to_string().into())
196            }
197        }
198    }
199}
200
201#[cfg(feature = "parser")]
202impl LayoutClearParseErrorOwned {
203    #[must_use] pub fn to_shared(&self) -> LayoutClearParseError<'_> {
204        match self {
205            Self::InvalidValue(s) => {
206                LayoutClearParseError::InvalidValue(s.as_str())
207            }
208        }
209    }
210}
211
212#[cfg(feature = "parser")]
213/// # Errors
214///
215/// Returns an error if `input` is not a valid CSS `clear` value.
216pub fn parse_layout_clear(input: &str) -> Result<LayoutClear, LayoutClearParseError<'_>> {
217    let input = input.trim();
218    match input {
219        "none" => Ok(LayoutClear::None),
220        "left" => Ok(LayoutClear::Left),
221        "right" => Ok(LayoutClear::Right),
222        "both" => Ok(LayoutClear::Both),
223        _ => Err(LayoutClearParseError::InvalidValue(input)),
224    }
225}
226
227#[cfg(all(test, feature = "parser"))]
228mod tests {
229    use super::*;
230
231    // LayoutWritingMode tests
232    #[test]
233    fn test_parse_writing_mode_horizontal_tb() {
234        assert_eq!(
235            parse_layout_writing_mode("horizontal-tb").unwrap(),
236            LayoutWritingMode::HorizontalTb
237        );
238    }
239
240    #[test]
241    fn test_parse_writing_mode_vertical_rl() {
242        assert_eq!(
243            parse_layout_writing_mode("vertical-rl").unwrap(),
244            LayoutWritingMode::VerticalRl
245        );
246    }
247
248    #[test]
249    fn test_parse_writing_mode_vertical_lr() {
250        assert_eq!(
251            parse_layout_writing_mode("vertical-lr").unwrap(),
252            LayoutWritingMode::VerticalLr
253        );
254    }
255
256    #[test]
257    fn test_parse_writing_mode_invalid() {
258        assert!(parse_layout_writing_mode("invalid").is_err());
259        assert!(parse_layout_writing_mode("horizontal").is_err());
260    }
261
262    #[test]
263    fn test_parse_writing_mode_whitespace() {
264        assert_eq!(
265            parse_layout_writing_mode("  vertical-rl  ").unwrap(),
266            LayoutWritingMode::VerticalRl
267        );
268    }
269
270    // LayoutClear tests
271    #[test]
272    fn test_parse_layout_clear_none() {
273        assert_eq!(parse_layout_clear("none").unwrap(), LayoutClear::None);
274    }
275
276    #[test]
277    fn test_parse_layout_clear_left() {
278        assert_eq!(parse_layout_clear("left").unwrap(), LayoutClear::Left);
279    }
280
281    #[test]
282    fn test_parse_layout_clear_right() {
283        assert_eq!(parse_layout_clear("right").unwrap(), LayoutClear::Right);
284    }
285
286    #[test]
287    fn test_parse_layout_clear_both() {
288        assert_eq!(parse_layout_clear("both").unwrap(), LayoutClear::Both);
289    }
290
291    #[test]
292    fn test_parse_layout_clear_invalid() {
293        assert!(parse_layout_clear("invalid").is_err());
294        assert!(parse_layout_clear("all").is_err());
295    }
296
297    #[test]
298    fn test_parse_layout_clear_whitespace() {
299        assert_eq!(parse_layout_clear("  both  ").unwrap(), LayoutClear::Both);
300    }
301
302    // Print tests
303    #[test]
304    fn test_print_writing_mode() {
305        assert_eq!(
306            LayoutWritingMode::HorizontalTb.print_as_css_value(),
307            "horizontal-tb"
308        );
309        assert_eq!(
310            LayoutWritingMode::VerticalRl.print_as_css_value(),
311            "vertical-rl"
312        );
313        assert_eq!(
314            LayoutWritingMode::VerticalLr.print_as_css_value(),
315            "vertical-lr"
316        );
317    }
318
319    #[test]
320    fn test_print_layout_clear() {
321        assert_eq!(LayoutClear::None.print_as_css_value(), "none");
322        assert_eq!(LayoutClear::Left.print_as_css_value(), "left");
323        assert_eq!(LayoutClear::Right.print_as_css_value(), "right");
324        assert_eq!(LayoutClear::Both.print_as_css_value(), "both");
325    }
326}